From 88cd621deedd2aab8f0a4c6ea3afed7269e66d0c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 30 Sep 2005 14:02:47 -0700 Subject: [PATCH 01/27] Consolidate null_sha1[]. Signed-off-by: Junio C Hamano --- cache.h | 1 + diff-files.c | 1 - diff.c | 3 +-- sha1_file.c | 2 ++ 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cache.h b/cache.h index 52a45f9c9a..b8e3d9bfac 100644 --- a/cache.h +++ b/cache.h @@ -189,6 +189,7 @@ extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2) extern char *sha1_file_name(const unsigned char *sha1); extern char *sha1_pack_name(const unsigned char *sha1); extern char *sha1_pack_index_name(const unsigned char *sha1); +extern const unsigned char null_sha1[20]; int git_mkstemp(char *path, size_t n, const char *template); diff --git a/diff-files.c b/diff-files.c index e8db3d2d69..5e598322ff 100644 --- a/diff-files.c +++ b/diff-files.c @@ -34,7 +34,6 @@ static void show_modified(int oldmode, int mode, int main(int argc, const char **argv) { - static const unsigned char null_sha1[20] = { 0, }; const char **pathspec; const char *prefix = setup_git_directory(); int entries, i; diff --git a/diff.c b/diff.c index 9bded28729..7d06b035ae 100644 --- a/diff.c +++ b/diff.c @@ -10,7 +10,6 @@ #include "diffcore.h" static const char *diff_opts = "-pu"; -static unsigned char null_sha1[20] = { 0, }; static int use_size_cache; @@ -414,7 +413,7 @@ void diff_free_filespec_data(struct diff_filespec *s) static void prep_temp_blob(struct diff_tempfile *temp, void *blob, unsigned long size, - unsigned char *sha1, + const unsigned char *sha1, int mode) { int fd; diff --git a/sha1_file.c b/sha1_file.c index 1e847a891a..895c1fab6f 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -20,6 +20,8 @@ #endif #endif +const unsigned char null_sha1[20] = { 0, }; + static unsigned int sha1_file_open_flag = O_NOATIME; static unsigned hexval(char c) From 1fea629f794cda57cc161979dab903ec7460cc7c Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 30 Sep 2005 23:25:23 -0700 Subject: [PATCH 02/27] [PATCH] Flag empty patches as errors A patch that contains no actual diff, and that doesn't change any meta-data is bad. It shouldn't be a patch at all, and git-apply shouldn't just accept it. This caused a corrupted patch to be silently applied as an empty change in the kernel, because the corruption ended up making the patch look empty. An example of such a patch is one that contains the patch header, but where the initial fragment header (the "@@ -nr,.." line) is missing, causing us to not parse any fragments. The real "patch" program will also flag such patches as bad, with the message patch: **** Only garbage was found in the patch input. and we should do likewise. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- apply.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apply.c b/apply.c index 964df2db10..f8862722fd 100644 --- a/apply.c +++ b/apply.c @@ -723,6 +723,16 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc return offset; } +static inline int metadata_changes(struct patch *patch) +{ + return patch->is_rename > 0 || + patch->is_copy > 0 || + patch->is_new > 0 || + patch->is_delete || + (patch->old_mode && patch->new_mode && + patch->old_mode != patch->new_mode); +} + static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) { int hdrsize, patchsize; @@ -733,6 +743,9 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) patchsize = parse_single_patch(buffer + offset + hdrsize, size - offset - hdrsize, patch); + if (!patchsize && !metadata_changes(patch)) + die("patch with only garbage at line %d", linenr); + return offset + hdrsize + patchsize; } From f8d839ad992d92e1e31d7a557c198371d1fb7692 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 1 Oct 2005 11:58:43 -0700 Subject: [PATCH 03/27] Honor user's umask. Fix the last two holdouts that forced mode bits stricter than the user's umask. Noticed by Wolfgang Denk and fixed by Linus. [jc: applied the same fix to mailsplit just for the sake of consistency.] Signed-off-by: Junio C Hamano --- index.c | 2 +- mailsplit.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/index.c b/index.c index 87fc7b0387..bdde65f75c 100644 --- a/index.c +++ b/index.c @@ -29,7 +29,7 @@ int hold_index_file_for_update(struct cache_file *cf, const char *path) signal(SIGINT, remove_lock_file_on_signal); atexit(remove_lock_file); } - return open(cf->lockfile, O_RDWR | O_CREAT | O_EXCL, 0600); + return open(cf->lockfile, O_RDWR | O_CREAT | O_EXCL, 0666); } int commit_index_file(struct cache_file *cf) diff --git a/mailsplit.c b/mailsplit.c index a3238c20da..7afea1aaca 100644 --- a/mailsplit.c +++ b/mailsplit.c @@ -128,7 +128,7 @@ int main(int argc, char **argv) unsigned long len = parse_email(map, size); assert(len <= size); sprintf(name, "%04d", ++nr); - fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0600); + fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666); if (fd < 0) { perror(name); exit(1); From 38ec15a973a1f075f0d94d130b0ef279562921cd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 1 Oct 2005 12:01:07 -0700 Subject: [PATCH 04/27] Honor extractor's umask in git-tar-tree. The archive generated with git-tar-tree had 0755 and 0644 mode bits. This inconvenienced the extractor with umask 002 by robbing g+w bit unconditionally. Just write it out with loose permissions bits and let the umask of the extractor do its job. Signed-off-by: Junio C Hamano --- tar-tree.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tar-tree.c b/tar-tree.c index 2716ae3eb1..970c4bb54e 100644 --- a/tar-tree.c +++ b/tar-tree.c @@ -353,6 +353,8 @@ static void traverse_tree(void *buffer, unsigned long size, if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1) die("corrupt 'tree' file"); + if (S_ISDIR(mode) || S_ISREG(mode)) + mode |= (mode & 0100) ? 0777 : 0666; buffer = sha1 + 20; size -= namelen + 20; From 37f15d50c93398eac90370cfe07315905501bdad Mon Sep 17 00:00:00 2001 From: Martin Langhoff Date: Fri, 30 Sep 2005 19:15:12 +1200 Subject: [PATCH 05/27] [PATCH] archimport: Actually cope with merges from "remote" repositories. Plus: Nicer messages. archimport was refusing to import commits that had merges from repositories that it didn't know about. Fixed. Also brings in nicer messages. Signed-off-by: Martin Langhoff Signed-off-by: Junio C Hamano --- git-archimport.perl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/git-archimport.perl b/git-archimport.perl index 3749b8b572..980e827b27 100755 --- a/git-archimport.perl +++ b/git-archimport.perl @@ -228,10 +228,12 @@ END # skip commits already in repo # if (ptag($ps->{id})) { - $opt_v && print "Skipping already imported: $ps->{id}\n"; + $opt_v && print " * Skipping already imported: $ps->{id}\n"; next; } + print " * Starting to work on $ps->{id}\n"; + # # create the branch if needed # @@ -675,6 +677,10 @@ sub find_parents { # that branch. # foreach my $branch (keys %branches) { + + # check that we actually know about the branch + next unless -e "$git_dir/refs/heads/$branch"; + my $mergebase = `git-merge-base $branch $ps->{branch}`; die "Cannot find merge base for $branch and $ps->{branch}" if $?; chomp $mergebase; From 94c23343dce0f556392fe8bbbba1b38cd37da481 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 30 Sep 2005 01:48:57 -0700 Subject: [PATCH 06/27] Pass CVSps generated A U Thor intact. Alexey Nezhdanov updated CVSps to generate author-name and author-email information in its output. If the input looks like it has that already properly formatted, use that without our own munging. Signed-off-by: Junio C Hamano --- git-cvsimport.perl | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 565f4f1b32..f35c0d045b 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -510,7 +510,7 @@ ($$) my $state = 0; -my($patchset,$date,$author,$branch,$ancestor,$tag,$logmsg); +my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg); my(@old,@new); my $commit = sub { my $pid; @@ -591,11 +591,11 @@ ($$) } exec("env", - "GIT_AUTHOR_NAME=$author", - "GIT_AUTHOR_EMAIL=$author", + "GIT_AUTHOR_NAME=$author_name", + "GIT_AUTHOR_EMAIL=$author_email", "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)), - "GIT_COMMITTER_NAME=$author", - "GIT_COMMITTER_EMAIL=$author", + "GIT_COMMITTER_NAME=$author_name", + "GIT_COMMITTER_EMAIL=$author_email", "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)), "git-commit-tree", $tree,@par); die "Cannot exec git-commit-tree: $!\n"; @@ -638,7 +638,7 @@ ($$) print $out "object $cid\n". "type commit\n". "tag $xtag\n". - "tagger $author <$author>\n" + "tagger $author_name <$author_email>\n" or die "Cannot create tag object $xtag: $!\n"; close($out) or die "Cannot create tag object $xtag: $!\n"; @@ -683,7 +683,11 @@ ($$) $state=3; } elsif($state == 3 and s/^Author:\s+//) { s/\s+$//; - $author = $_; + if (/^(.*?)\s+<(.*)>/) { + ($author_name, $author_email) = ($1, $2); + } else { + $author_name = $author_email = $_; + } $state = 4; } elsif($state == 4 and s/^Branch:\s+//) { s/\s+$//; From 49a0f240f7be05728f97903efd97ad7898ff6d08 Mon Sep 17 00:00:00 2001 From: Nick Hengeveld Date: Wed, 28 Sep 2005 10:14:04 -0700 Subject: [PATCH 07/27] [PATCH] HTTP partial transfer support for object, pack, and index transfers HTTP partial transfer support for object, pack, and index transfers [jc: this should not be placed in "master" -- it does not have any fixes requested on the list.] Signed-off-by: Nick Hengeveld Signed-off-by: Junio C Hamano --- http-fetch.c | 184 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 161 insertions(+), 23 deletions(-) diff --git a/http-fetch.c b/http-fetch.c index 0566a9125c..778d508243 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -13,8 +13,12 @@ #define curl_global_init(a) do { /* nothing */ } while(0) #endif +#define PREV_BUF_SIZE 4096 +#define RANGE_HEADER_SIZE 30 + static CURL *curl; static struct curl_slist *no_pragma_header; +static struct curl_slist *no_range_header; static char curl_errorstr[CURL_ERROR_SIZE]; static char *initial_base; @@ -87,12 +91,37 @@ void prefetch(unsigned char *sha1) { } +int relink_or_rename(char *old, char *new) { + int ret; + + ret = link(old, new); + if (ret < 0) { + /* Same Coda hack as in write_sha1_file(sha1_file.c) */ + ret = errno; + if (ret == EXDEV && !rename(old, new)) + return 0; + } + unlink(old); + if (ret) { + if (ret != EEXIST) + return ret; + } + + return 0; +} + static int got_alternates = 0; static int fetch_index(struct alt_base *repo, unsigned char *sha1) { char *filename; char *url; + char tmpfile[PATH_MAX]; + int ret; + long prev_posn = 0; + char range[RANGE_HEADER_SIZE]; + struct curl_slist *range_header = NULL; + CURLcode curl_result; FILE *indexfile; @@ -108,7 +137,8 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1) repo->base, sha1_to_hex(sha1)); filename = sha1_pack_index_name(sha1); - indexfile = fopen(filename, "w"); + snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename); + indexfile = fopen(tmpfile, "a"); if (!indexfile) return error("Unable to open local file %s for pack index", filename); @@ -119,13 +149,36 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1) curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr); - if (curl_easy_perform(curl)) { + /* If there is data present from a previous transfer attempt, + resume where it left off */ + prev_posn = ftell(indexfile); + if (prev_posn>0) { + if (get_verbosely) + fprintf(stderr, + "Resuming fetch of index for pack %s at byte %ld\n", + sha1_to_hex(sha1), prev_posn); + sprintf(range, "Range: bytes=%ld-", prev_posn); + range_header = curl_slist_append(range_header, range); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, range_header); + } + + /* Clear out the Range: header after performing the request, so + other curl requests don't inherit inappropriate header data */ + curl_result = curl_easy_perform(curl); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_range_header); + if (curl_result != 0) { fclose(indexfile); return error("Unable to get pack index %s\n%s", url, curl_errorstr); } fclose(indexfile); + + ret = relink_or_rename(tmpfile, filename); + if (ret) + return error("unable to write index filename %s: %s", + filename, strerror(ret)); + return 0; } @@ -306,6 +359,12 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1) struct packed_git **lst; FILE *packfile; char *filename; + char tmpfile[PATH_MAX]; + int ret; + long prev_posn = 0; + char range[RANGE_HEADER_SIZE]; + struct curl_slist *range_header = NULL; + CURLcode curl_result; if (fetch_indices(repo)) return -1; @@ -325,7 +384,8 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1) repo->base, sha1_to_hex(target->sha1)); filename = sha1_pack_name(target->sha1); - packfile = fopen(filename, "w"); + snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename); + packfile = fopen(tmpfile, "a"); if (!packfile) return error("Unable to open local file %s for pack", filename); @@ -336,7 +396,24 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1) curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr); - if (curl_easy_perform(curl)) { + /* If there is data present from a previous transfer attempt, + resume where it left off */ + prev_posn = ftell(packfile); + if (prev_posn>0) { + if (get_verbosely) + fprintf(stderr, + "Resuming fetch of pack %s at byte %ld\n", + sha1_to_hex(target->sha1), prev_posn); + sprintf(range, "Range: bytes=%ld-", prev_posn); + range_header = curl_slist_append(range_header, range); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, range_header); + } + + /* Clear out the Range: header after performing the request, so + other curl requests don't inherit inappropriate header data */ + curl_result = curl_easy_perform(curl); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_range_header); + if (curl_result != 0) { fclose(packfile); return error("Unable to get pack file %s\n%s", url, curl_errorstr); @@ -344,6 +421,11 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1) fclose(packfile); + ret = relink_or_rename(tmpfile, filename); + if (ret) + return error("unable to write pack filename %s: %s", + filename, strerror(ret)); + lst = &repo->packs; while (*lst != target) lst = &((*lst)->next); @@ -360,14 +442,29 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) char *filename = sha1_file_name(sha1); unsigned char real_sha1[20]; char tmpfile[PATH_MAX]; + char prevfile[PATH_MAX]; int ret; char *url; char *posn; + int prevlocal; + unsigned char prev_buf[PREV_BUF_SIZE]; + ssize_t prev_read = 0; + long prev_posn = 0; + char range[RANGE_HEADER_SIZE]; + struct curl_slist *range_header = NULL; + CURLcode curl_result; - snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", - get_object_directory()); + snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename); + snprintf(prevfile, sizeof(prevfile), "%s.prev", filename); + unlink(prevfile); + rename(tmpfile, prevfile); + unlink(tmpfile); + + local = open(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0666); + + /* Note: if another instance starts now, it will turn our new + tmpfile into its prevfile. */ - local = mkstemp(tmpfile); if (local < 0) return error("Couldn't create temporary file %s for %s: %s\n", tmpfile, filename, strerror(errno)); @@ -396,8 +493,57 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) curl_easy_setopt(curl, CURLOPT_URL, url); - if (curl_easy_perform(curl)) { - unlink(filename); + /* If a previous temp file is present, process what was already + fetched. */ + prevlocal = open(prevfile, O_RDONLY); + if (prevlocal != -1) { + do { + prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE); + if (prev_read>0) { + if (fwrite_sha1_file(prev_buf, + 1, + prev_read, + NULL) == prev_read) { + prev_posn += prev_read; + } else { + prev_read = -1; + } + } + } while (prev_read > 0); + close(prevlocal); + } + unlink(prevfile); + + /* Reset inflate/SHA1 if there was an error reading the previous temp + file; also rewind to the beginning of the local file. */ + if (prev_read == -1) { + memset(&stream, 0, sizeof(stream)); + inflateInit(&stream); + SHA1_Init(&c); + if (prev_posn>0) { + prev_posn = 0; + lseek(local, SEEK_SET, 0); + } + } + + /* If we have successfully processed data from a previous fetch + attempt, only fetch the data we don't already have. */ + if (prev_posn>0) { + if (get_verbosely) + fprintf(stderr, + "Resuming fetch of object %s at byte %ld\n", + hex, prev_posn); + sprintf(range, "Range: bytes=%ld-", prev_posn); + range_header = curl_slist_append(range_header, range); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, range_header); + } + + /* Clear out the Range: header after performing the request, so + other curl requests don't inherit inappropriate header data */ + curl_result = curl_easy_perform(curl); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_range_header); + if (curl_result != 0) { + unlink(tmpfile); return error("%s", curl_errorstr); } @@ -413,20 +559,11 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) unlink(tmpfile); return error("File %s has bad hash\n", hex); } - ret = link(tmpfile, filename); - if (ret < 0) { - /* Same Coda hack as in write_sha1_file(sha1_file.c) */ - ret = errno; - if (ret == EXDEV && !rename(tmpfile, filename)) - goto out; - } - unlink(tmpfile); - if (ret) { - if (ret != EEXIST) - return error("unable to write sha1 filename %s: %s", - filename, strerror(ret)); - } - out: + ret = relink_or_rename(tmpfile, filename); + if (ret) + return error("unable to write sha1 filename %s: %s", + filename, strerror(ret)); + pull_say("got %s\n", hex); return 0; } @@ -519,6 +656,7 @@ int main(int argc, char **argv) curl = curl_easy_init(); no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:"); + no_range_header = curl_slist_append(no_range_header, "Range:"); curl_ssl_verify = getenv("GIT_SSL_NO_VERIFY") ? 0 : 1; curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify); From 271421cd345a9b5e2fc7e30e672d2c998f50e1dc Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 30 Sep 2005 00:07:39 -0700 Subject: [PATCH 08/27] Update partial HTTP transfers. Add the sanity checks discussed on the list with Nick Hengeveld in <20050927000931.GA15615@reactrix.com>. * unlink of previous and rename from temp to previous can fail for reasons other than benign ones (missing previous and missing temp). Report these failures when we encounter them, to make diagnosing problems easier. * when rewinding the partially written result, make sure to truncate the file. Also verify the pack after downloading by calling verify_packfile(). Signed-off-by: Junio C Hamano --- http-fetch.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/http-fetch.c b/http-fetch.c index 778d508243..e8ac9959ff 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -1,6 +1,6 @@ #include "cache.h" #include "commit.h" - +#include "pack.h" #include "fetch.h" #include @@ -431,6 +431,8 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1) lst = &((*lst)->next); *lst = (*lst)->next; + if (verify_pack(target, 0)) + return -1; install_packed_git(target); return 0; @@ -456,9 +458,13 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename); snprintf(prevfile, sizeof(prevfile), "%s.prev", filename); - unlink(prevfile); - rename(tmpfile, prevfile); - unlink(tmpfile); + + if (unlink(prevfile) && (errno != ENOENT)) + return error("Failed to unlink %s (%s)", + prevfile, strerror(errno)); + if (rename(tmpfile, prevfile) && (errno != ENOENT)) + return error("Failed to rename %s to %s (%s)", + tmpfile, prevfile, strerror(errno)); local = open(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0666); @@ -523,6 +529,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) if (prev_posn>0) { prev_posn = 0; lseek(local, SEEK_SET, 0); + ftruncate(local, 0); } } From 4fa2197e614950279527b14825d1e9572454a48c Mon Sep 17 00:00:00 2001 From: Nick Hengeveld Date: Fri, 30 Sep 2005 16:27:47 -0700 Subject: [PATCH 09/27] [PATCH] HTTP partial transfer support fix. Don't unlink the temp file when an object transfer fails, so next attempt will pick up where the failed transfer left off Signed-off-by: Nick Hengeveld Signed-off-by: Junio C Hamano --- http-fetch.c | 1 - 1 file changed, 1 deletion(-) diff --git a/http-fetch.c b/http-fetch.c index e8ac9959ff..71a8c60b56 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -550,7 +550,6 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) curl_result = curl_easy_perform(curl); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_range_header); if (curl_result != 0) { - unlink(tmpfile); return error("%s", curl_errorstr); } From ed1aadf1b0cb4afc7cb0aaf4cec4f5ad84e3307d Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 29 Sep 2005 14:35:15 -0700 Subject: [PATCH 10/27] [PATCH] git fetch --tags You can do git fetch --tags and it should fetch all my tags automatically. [jc: The original by Linus fetched and overwrote branch heads with --all, which felt dangerous and wrong, so I removed it. Also this version does not use any refs that resulted as --tags for later merge. ] Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- git-fetch.sh | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/git-fetch.sh b/git-fetch.sh index 27407c1d35..61da6a9e31 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -5,6 +5,7 @@ _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" +tags= append= force= update_head_ok= @@ -17,6 +18,9 @@ do -f|--f|--fo|--for|--forc|--force) force=t ;; + -t|--t|--ta|--tag|--tags) + tags=t + ;; -u|--u|--up|--upd|--upda|--updat|--update|--update-|--update-h|\ --update-he|--update-hea|--update-head|--update-head-|\ --update-head-o|--update-head-ok) @@ -158,7 +162,26 @@ case "$update_head_ok" in ;; esac -for ref in $(get_remote_refs_for_fetch "$@") +# If --tags (and later --heads or --all) is specified, then we are +# not talking about defaults stored in Pull: line of remotes or +# branches file, and just fetch those and refspecs explicitly given. +# Otherwise we do what we always did. + +reflist=$(get_remote_refs_for_fetch "$@") +if test "$tags" +then + taglist=$(git-ls-remote --tags "$remote" | awk '{ print "."$2":"$2 }') + if test "$#" -gt 1 + then + # remote URL plus explicit refspecs; we need to merge them. + reflist="$reflist $taglist" + else + # No explicit refspecs; fetch tags only. + reflist=$taglist + fi +fi + +for ref in $reflist do refs="$refs $ref" From 9b143c6e155a8eead165b2a813b533e0f3e0018a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Sep 2005 19:30:24 -0700 Subject: [PATCH 11/27] Teach update-ref about a symbolic ref stored in a textfile. A symbolic ref is a regular file whose contents is "ref:", followed by optional leading whitespaces, followed by a GIT_DIR relative pathname, followed by optional trailing whitespaces (the optional whitespaces are unconditionally removed, so you cannot have leading nor trailing whitespaces). This can be used in place of a traditional symbolic link .git/HEAD that usually points at "refs/heads/master". You can instead have a regular file .git/HEAD whose contents is "ref: refs/heads/master". [jc: currently the code does not enforce the symbolic ref to begin with refs/, unlike the symbolic link case. It may be worthwhile to require either case to begin with refs/ and not have any /./ nor /../ in them.] Signed-off-by: Junio C Hamano --- update-ref.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/update-ref.c b/update-ref.c index 1863b82324..6919cead4b 100644 --- a/update-ref.c +++ b/update-ref.c @@ -13,6 +13,7 @@ static const char *resolve_ref(const char *path, unsigned char *sha1) for (;;) { struct stat st; + char *buf; int fd; if (--depth < 0) @@ -44,7 +45,19 @@ static const char *resolve_ref(const char *path, unsigned char *sha1) return NULL; len = read(fd, buffer, sizeof(buffer)-1); close(fd); - break; + + /* + * Is it a symbolic ref? + */ + if (len < 4 || memcmp("ref:", buffer, 4)) + break; + buf = buffer + 4; + len -= 4; + while (len && isspace(*buf)) + buf++, len--; + while (len && isspace(buf[len-1])) + buf[--len] = 0; + path = git_path("%.*s", len, buf); } if (len < 40 || get_sha1_hex(buffer, sha1)) return NULL; From ca8db1424d1808a1f78bc9905efd267f7c154d8e Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 25 Sep 2005 09:59:37 -0700 Subject: [PATCH 12/27] [PATCH] Allow reading "symbolic refs" that point to other refs This extends the ref reading to understand a "symbolic ref": a ref file that starts with "ref: " and points to another ref file, and thus introduces the notion of ref aliases. This is in preparation of allowing HEAD to eventually not be a symlink, but one of these symbolic refs instead. [jc: Linus originally required the prefix to be "ref: " five bytes and nothing else, but I changed it to allow and strip any number of leading whitespaces to match what update-ref.c does.] Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- cache.h | 1 + refs.c | 73 +++++++++++++++++++++++++++++------------------------ sha1_name.c | 17 +------------ 3 files changed, 42 insertions(+), 49 deletions(-) diff --git a/cache.h b/cache.h index b8e3d9bfac..958c96e14d 100644 --- a/cache.h +++ b/cache.h @@ -229,6 +229,7 @@ extern int has_pack_index(const unsigned char *sha1); extern int get_sha1(const char *str, unsigned char *sha1); extern int get_sha1_hex(const char *hex, unsigned char *sha1); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ +extern int read_ref(const char *filename, unsigned char *sha1); /* General helper functions */ extern void usage(const char *err) NORETURN; diff --git a/refs.c b/refs.c index 161018097d..d4f3612487 100644 --- a/refs.c +++ b/refs.c @@ -2,17 +2,43 @@ #include "cache.h" #include +#include -static int read_ref(const char *refname, unsigned char *sha1) +/* We allow "recursive" symbolic refs. Only within reason, though */ +#define MAXDEPTH 5 + +int read_ref(const char *filename, unsigned char *sha1) { - int ret = -1; - int fd = open(git_path("%s", refname), O_RDONLY); + int depth = 0; + int ret = -1, fd; + + while ((fd = open(filename, O_RDONLY)) >= 0) { + char buffer[256]; + int len = read(fd, buffer, sizeof(buffer)-1); - if (fd >= 0) { - char buffer[60]; - if (read(fd, buffer, sizeof(buffer)) >= 40) - ret = get_sha1_hex(buffer, sha1); close(fd); + if (len < 0) + break; + + buffer[len] = 0; + while (len && isspace(buffer[len-1])) + buffer[--len] = 0; + + if (!strncmp(buffer, "ref:", 4)) { + char *buf; + if (depth > MAXDEPTH) + break; + depth++; + buf = buffer + 4; + len -= 4; + while (len && isspace(*buf)) + buf++, len--; + filename = git_path("%.*s", len, buf); + continue; + } + if (len >= 40) + ret = get_sha1_hex(buffer, sha1); + break; } return ret; } @@ -54,7 +80,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u break; continue; } - if (read_ref(path, sha1) < 0) + if (read_ref(git_path("%s", path), sha1) < 0) continue; if (!has_sha1_file(sha1)) continue; @@ -71,7 +97,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u int head_ref(int (*fn)(const char *path, const unsigned char *sha1)) { unsigned char sha1[20]; - if (!read_ref("HEAD", sha1)) + if (!read_ref(git_path("HEAD"), sha1)) return fn("HEAD", sha1); return 0; } @@ -101,33 +127,14 @@ static char *ref_lock_file_name(const char *ref) return ret; } -static int read_ref_file(const char *filename, unsigned char *sha1) { - int fd = open(filename, O_RDONLY); - char hex[41]; - if (fd < 0) { - return error("Couldn't open %s\n", filename); - } - if ((read(fd, hex, 41) < 41) || - (hex[40] != '\n') || - get_sha1_hex(hex, sha1)) { - error("Couldn't read a hash from %s\n", filename); - close(fd); - return -1; - } - close(fd); - return 0; -} - int get_ref_sha1(const char *ref, unsigned char *sha1) { - char *filename; - int retval; + const char *filename; + if (check_ref_format(ref)) return -1; - filename = ref_file_name(ref); - retval = read_ref_file(filename, sha1); - free(filename); - return retval; + filename = git_path("refs/%s", ref); + return read_ref(filename, sha1); } static int lock_ref_file(const char *filename, const char *lock_filename, @@ -140,7 +147,7 @@ static int lock_ref_file(const char *filename, const char *lock_filename, return error("Couldn't open lock file for %s: %s", filename, strerror(errno)); } - retval = read_ref_file(filename, current_sha1); + retval = read_ref(filename, current_sha1); if (old_sha1) { if (retval) { close(fd); diff --git a/sha1_name.c b/sha1_name.c index b4fed924f7..57e6cd3bdf 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -119,21 +119,6 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1) return -1; } -static int get_sha1_file(const char *path, unsigned char *result) -{ - char buffer[60]; - int fd = open(path, O_RDONLY); - int len; - - if (fd < 0) - return -1; - len = read(fd, buffer, sizeof(buffer)); - close(fd); - if (len < 40) - return -1; - return get_sha1_hex(buffer, result); -} - static int get_sha1_basic(const char *str, int len, unsigned char *sha1) { static const char *prefix[] = { @@ -150,7 +135,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) for (p = prefix; *p; p++) { char *pathname = git_path("%s/%.*s", *p, len, str); - if (!get_sha1_file(pathname, sha1)) + if (!read_ref(pathname, sha1)) return 0; } From a876ed83be5467d6075da8a16306724cb1babc2a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 30 Sep 2005 14:08:25 -0700 Subject: [PATCH 13/27] Use resolve_ref() to implement read_ref(). Symbolic refs are understood by resolve_ref(), so existing read_ref() users will automatically understand them as well. Signed-off-by: Junio C Hamano --- cache.h | 1 + refs.c | 99 +++++++++++++++++++++++++++++++++++----------------- update-ref.c | 62 +------------------------------- 3 files changed, 69 insertions(+), 93 deletions(-) diff --git a/cache.h b/cache.h index 958c96e14d..63823c3529 100644 --- a/cache.h +++ b/cache.h @@ -230,6 +230,7 @@ extern int get_sha1(const char *str, unsigned char *sha1); extern int get_sha1_hex(const char *hex, unsigned char *sha1); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ extern int read_ref(const char *filename, unsigned char *sha1); +extern const char *resolve_ref(const char *path, unsigned char *sha1, int); /* General helper functions */ extern void usage(const char *err) NORETURN; diff --git a/refs.c b/refs.c index d4f3612487..6aa6aec82d 100644 --- a/refs.c +++ b/refs.c @@ -7,40 +7,75 @@ /* We allow "recursive" symbolic refs. Only within reason, though */ #define MAXDEPTH 5 +const char *resolve_ref(const char *path, unsigned char *sha1, int reading) +{ + int depth = MAXDEPTH, len; + char buffer[256]; + + for (;;) { + struct stat st; + char *buf; + int fd; + + if (--depth < 0) + return NULL; + + /* Special case: non-existing file. + * Not having the refs/heads/new-branch is OK + * if we are writing into it, so is .git/HEAD + * that points at refs/heads/master still to be + * born. It is NOT OK if we are resolving for + * reading. + */ + if (lstat(path, &st) < 0) { + if (reading || errno != ENOENT) + return NULL; + memset(sha1, 0, 20); + return path; + } + + /* Follow "normalized" - ie "refs/.." symlinks by hand */ + if (S_ISLNK(st.st_mode)) { + len = readlink(path, buffer, sizeof(buffer)-1); + if (len >= 5 && !memcmp("refs/", buffer, 5)) { + path = git_path("%.*s", len, buffer); + continue; + } + } + + /* + * Anything else, just open it and try to use it as + * a ref + */ + fd = open(path, O_RDONLY); + if (fd < 0) + return NULL; + len = read(fd, buffer, sizeof(buffer)-1); + close(fd); + + /* + * Is it a symbolic ref? + */ + if (len < 4 || memcmp("ref:", buffer, 4)) + break; + buf = buffer + 4; + len -= 4; + while (len && isspace(*buf)) + buf++, len--; + while (len && isspace(buf[len-1])) + buf[--len] = 0; + path = git_path("%.*s", len, buf); + } + if (len < 40 || get_sha1_hex(buffer, sha1)) + return NULL; + return path; +} + int read_ref(const char *filename, unsigned char *sha1) { - int depth = 0; - int ret = -1, fd; - - while ((fd = open(filename, O_RDONLY)) >= 0) { - char buffer[256]; - int len = read(fd, buffer, sizeof(buffer)-1); - - close(fd); - if (len < 0) - break; - - buffer[len] = 0; - while (len && isspace(buffer[len-1])) - buffer[--len] = 0; - - if (!strncmp(buffer, "ref:", 4)) { - char *buf; - if (depth > MAXDEPTH) - break; - depth++; - buf = buffer + 4; - len -= 4; - while (len && isspace(*buf)) - buf++, len--; - filename = git_path("%.*s", len, buf); - continue; - } - if (len >= 40) - ret = get_sha1_hex(buffer, sha1); - break; - } - return ret; + if (resolve_ref(filename, sha1, 1)) + return 0; + return -1; } static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1)) diff --git a/update-ref.c b/update-ref.c index 6919cead4b..4a1704c1a5 100644 --- a/update-ref.c +++ b/update-ref.c @@ -4,66 +4,6 @@ static const char git_update_ref_usage[] = "git-update-ref []"; -#define MAXDEPTH 5 - -static const char *resolve_ref(const char *path, unsigned char *sha1) -{ - int depth = MAXDEPTH, len; - char buffer[256]; - - for (;;) { - struct stat st; - char *buf; - int fd; - - if (--depth < 0) - return NULL; - - /* Special case: non-existing file */ - if (lstat(path, &st) < 0) { - if (errno != ENOENT) - return NULL; - memset(sha1, 0, 20); - return path; - } - - /* Follow "normalized" - ie "refs/.." symlinks by hand */ - if (S_ISLNK(st.st_mode)) { - len = readlink(path, buffer, sizeof(buffer)-1); - if (len >= 5 && !memcmp("refs/", buffer, 5)) { - path = git_path("%.*s", len, buffer); - continue; - } - } - - /* - * Anything else, just open it and try to use it as - * a ref - */ - fd = open(path, O_RDONLY); - if (fd < 0) - return NULL; - len = read(fd, buffer, sizeof(buffer)-1); - close(fd); - - /* - * Is it a symbolic ref? - */ - if (len < 4 || memcmp("ref:", buffer, 4)) - break; - buf = buffer + 4; - len -= 4; - while (len && isspace(*buf)) - buf++, len--; - while (len && isspace(buf[len-1])) - buf[--len] = 0; - path = git_path("%.*s", len, buf); - } - if (len < 40 || get_sha1_hex(buffer, sha1)) - return NULL; - return path; -} - static int re_verify(const char *path, unsigned char *oldsha1, unsigned char *currsha1) { char buf[40]; @@ -97,7 +37,7 @@ int main(int argc, char **argv) if (oldval && get_sha1(oldval, oldsha1) < 0) die("%s: not a valid old SHA1", oldval); - path = resolve_ref(git_path("%s", refname), currsha1); + path = resolve_ref(git_path("%s", refname), currsha1, !!oldval); if (!path) die("No such ref: %s", refname); From 8098a178b26dc7a158d129a092a5b78da6d12b72 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 30 Sep 2005 14:26:57 -0700 Subject: [PATCH 14/27] Add git-symbolic-ref This adds the counterpart of git-update-ref that lets you read and create "symbolic refs". By default it uses a symbolic link to represent ".git/HEAD -> refs/heads/master", but it can be compiled to use the textfile symbolic ref. The places that did 'readlink .git/HEAD' and 'ln -s refs/heads/blah .git/HEAD' have been converted to use new git-symbolic-ref command, so that they can deal with either implementation. Signed-off-by: Junio C Hamano --- .gitignore | 1 + Makefile | 2 +- cache.h | 2 ++ fsck-objects.c | 26 ++++++--------- git-bisect.sh | 7 ++-- git-branch.sh | 6 ++-- git-checkout.sh | 3 +- git-commit.sh | 17 +++++----- git-sh-setup.sh | 7 ++-- git-status.sh | 4 +-- init-db.c | 10 +++--- refs.c | 77 ++++++++++++++++++++++++++++++++++++++++++++ setup.c | 16 +++++---- show-branch.c | 13 +++++--- symbolic-ref.c | 34 +++++++++++++++++++ t/t5400-send-pack.sh | 6 ++-- 16 files changed, 175 insertions(+), 56 deletions(-) create mode 100644 symbolic-ref.c diff --git a/.gitignore b/.gitignore index c3eb9543ff..e90e2c3503 100644 --- a/.gitignore +++ b/.gitignore @@ -82,6 +82,7 @@ git-ssh-push git-ssh-upload git-status git-stripspace +git-symbolic-ref git-tag git-tar-tree git-unpack-file diff --git a/Makefile b/Makefile index e943954cf8..5648296a8d 100644 --- a/Makefile +++ b/Makefile @@ -116,7 +116,7 @@ PROGRAMS = \ git-ssh-upload git-tar-tree git-unpack-file \ git-unpack-objects git-update-index git-update-server-info \ git-upload-pack git-verify-pack git-write-tree \ - git-update-ref \ + git-update-ref git-symbolic-ref \ $(SIMPLE_PROGRAMS) # Backward compatibility -- to be removed after 1.0 diff --git a/cache.h b/cache.h index 63823c3529..ec2a1610b2 100644 --- a/cache.h +++ b/cache.h @@ -231,6 +231,8 @@ extern int get_sha1_hex(const char *hex, unsigned char *sha1); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ extern int read_ref(const char *filename, unsigned char *sha1); extern const char *resolve_ref(const char *path, unsigned char *sha1, int); +extern int create_symref(const char *git_HEAD, const char *refs_heads_master); +extern int validate_symref(const char *git_HEAD); /* General helper functions */ extern void usage(const char *err) NORETURN; diff --git a/fsck-objects.c b/fsck-objects.c index 247edf0529..65cec7d12b 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -402,25 +402,17 @@ static void fsck_object_dir(const char *path) static int fsck_head_link(void) { - int fd, count; - char hex[40]; unsigned char sha1[20]; - static char path[PATH_MAX], link[PATH_MAX]; - const char *git_dir = get_git_dir(); + const char *git_HEAD = strdup(git_path("HEAD")); + const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 1); + int pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */ - snprintf(path, sizeof(path), "%s/HEAD", git_dir); - if (readlink(path, link, sizeof(link)) < 0) - return error("HEAD is not a symlink"); - if (strncmp("refs/heads/", link, 11)) - return error("HEAD points to something strange (%s)", link); - fd = open(path, O_RDONLY); - if (fd < 0) - return error("HEAD: %s", strerror(errno)); - count = read(fd, hex, sizeof(hex)); - close(fd); - if (count < 0) - return error("HEAD: %s", strerror(errno)); - if (count < 40 || get_sha1_hex(hex, sha1)) + if (!git_refs_heads_master) + return error("HEAD is not a symbolic ref"); + if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11)) + return error("HEAD points to something strange (%s)", + git_refs_heads_master + pfxlen); + if (!memcmp(null_sha1, sha1, 20)) return error("HEAD: not a valid git pointer"); return 0; } diff --git a/git-bisect.sh b/git-bisect.sh index 8dc77c991c..1ab2f187dc 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -38,7 +38,8 @@ bisect_start() { # Verify HEAD. If we were bisecting before this, reset to the # top-of-line master first! # - head=$(readlink $GIT_DIR/HEAD) || die "Bad HEAD - I need a symlink" + head=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD) || + die "Bad HEAD - I need a symbolic ref" case "$head" in refs/heads/bisect*) git checkout master || exit @@ -46,7 +47,7 @@ bisect_start() { refs/heads/*) ;; *) - die "Bad HEAD - strange symlink" + die "Bad HEAD - strange symbolic ref" ;; esac @@ -135,7 +136,7 @@ bisect_next() { echo "$rev" > "$GIT_DIR/refs/heads/new-bisect" git checkout new-bisect || exit mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" && - ln -sf refs/heads/bisect "$GIT_DIR/HEAD" + GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect git-show-branch "$rev" } diff --git a/git-branch.sh b/git-branch.sh index dcec2a9f2f..074229c206 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -14,7 +14,8 @@ If two arguments, create a new branch based off of . delete_branch () { option="$1" branch_name="$2" - headref=$(readlink "$GIT_DIR/HEAD" | sed -e 's|^refs/heads/||') + headref=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD | + sed -e 's|^refs/heads/||') case ",$headref," in ",$branch_name,") die "Cannot delete the branch you are on." ;; @@ -67,7 +68,8 @@ done case "$#" in 0) - headref=$(readlink "$GIT_DIR/HEAD" | sed -e 's|^refs/heads/||') + headref=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD | + sed -e 's|^refs/heads/||') git-rev-parse --symbolic --all | sed -ne 's|^refs/heads/||p' | sort | diff --git a/git-checkout.sh b/git-checkout.sh index 37afcdda30..c3825904b6 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -71,7 +71,8 @@ if [ "$?" -eq 0 ]; then echo $new > "$GIT_DIR/refs/heads/$newbranch" branch="$newbranch" fi - [ "$branch" ] && ln -sf "refs/heads/$branch" "$GIT_DIR/HEAD" + [ "$branch" ] && + GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch" rm -f "$GIT_DIR/MERGE_HEAD" else exit 1 diff --git a/git-commit.sh b/git-commit.sh index 18b259c708..1206c20c2e 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -153,15 +153,8 @@ if [ -f "$GIT_DIR/MERGE_HEAD" ]; then fi >>.editmsg PARENTS="-p HEAD" -if [ ! -r "$GIT_DIR/HEAD" ]; then - if [ -z "$(git-ls-files)" ]; then - echo Nothing to commit 1>&2 - exit 1 - fi - PARENTS="" - current= -else - current=$(git-rev-parse --verify HEAD) +if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1 +then if [ -f "$GIT_DIR/MERGE_HEAD" ]; then PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"` fi @@ -194,6 +187,12 @@ else export GIT_AUTHOR_EMAIL export GIT_AUTHOR_DATE fi +else + if [ -z "$(git-ls-files)" ]; then + echo Nothing to commit 1>&2 + exit 1 + fi + PARENTS="" fi git-status >>.editmsg if [ "$?" != "0" -a ! -f $GIT_DIR/MERGE_HEAD ] diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 55db795843..a0172686a9 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -13,10 +13,13 @@ unset CDPATH die() { - echo "$@" >&2 + echo >&2 "$@" exit 1 } -[ -h "$GIT_DIR/HEAD" ] && +case "$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD 2>/dev/null)" in +refs/*) : ;; +*) false ;; +esac && [ -d "$GIT_DIR/refs" ] && [ -d "$GIT_OBJECT_DIRECTORY/00" ] diff --git a/git-status.sh b/git-status.sh index 621fa49d2b..ca9a15459f 100755 --- a/git-status.sh +++ b/git-status.sh @@ -31,7 +31,7 @@ report () { [ "$header" ] } -branch=`readlink "$GIT_DIR/HEAD"` +branch=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD) case "$branch" in refs/heads/master) ;; *) echo "# On branch $branch" ;; @@ -39,7 +39,7 @@ esac git-update-index --refresh >/dev/null 2>&1 -if test -f "$GIT_DIR/HEAD" +if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1 then git-diff-index -M --cached HEAD | sed 's/^://' | diff --git a/init-db.c b/init-db.c index da2bc8f42b..aabc09f4e1 100644 --- a/init-db.c +++ b/init-db.c @@ -166,6 +166,7 @@ static void create_default_files(const char *git_dir, { unsigned len = strlen(git_dir); static char path[PATH_MAX]; + unsigned char sha1[20]; if (len > sizeof(path)-50) die("insane git directory %s", git_dir); @@ -186,15 +187,14 @@ static void create_default_files(const char *git_dir, /* * Create the default symlink from ".git/HEAD" to the "master" - * branch + * branch, if it does not exist yet. */ strcpy(path + len, "HEAD"); - if (symlink("refs/heads/master", path) < 0) { - if (errno != EEXIST) { - perror(path); + if (read_ref(path, sha1) < 0) { + if (create_symref(path, "refs/heads/master") < 0) exit(1); - } } + path[len] = 0; copy_templates(path, len, template_path); } diff --git a/refs.c b/refs.c index 6aa6aec82d..2aac90ca54 100644 --- a/refs.c +++ b/refs.c @@ -7,6 +7,50 @@ /* We allow "recursive" symbolic refs. Only within reason, though */ #define MAXDEPTH 5 +#ifndef USE_SYMLINK_HEAD +#define USE_SYMLINK_HEAD 1 +#endif + +int validate_symref(const char *path) +{ + struct stat st; + char *buf, buffer[256]; + int len, fd; + + if (lstat(path, &st) < 0) + return -1; + + /* Make sure it is a "refs/.." symlink */ + if (S_ISLNK(st.st_mode)) { + len = readlink(path, buffer, sizeof(buffer)-1); + if (len >= 5 && !memcmp("refs/", buffer, 5)) + return 0; + return -1; + } + + /* + * Anything else, just open it and try to see if it is a symbolic ref. + */ + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + len = read(fd, buffer, sizeof(buffer)-1); + close(fd); + + /* + * Is it a symbolic ref? + */ + if (len < 4 || memcmp("ref:", buffer, 4)) + return -1; + buf = buffer + 4; + len -= 4; + while (len && isspace(*buf)) + buf++, len--; + if (len >= 5 && !memcmp("refs/", buffer, 5)) + return 0; + return -1; +} + const char *resolve_ref(const char *path, unsigned char *sha1, int reading) { int depth = MAXDEPTH, len; @@ -71,6 +115,39 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading) return path; } +int create_symref(const char *git_HEAD, const char *refs_heads_master) +{ +#if USE_SYMLINK_HEAD + unlink(git_HEAD); + return symlink(refs_heads_master, git_HEAD); +#else + const char *lockpath; + char ref[1000]; + int fd, len, written; + + len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master); + if (sizeof(ref) <= len) { + error("refname too long: %s", refs_heads_master); + return -1; + } + lockpath = mkpath("%s.lock", git_HEAD); + fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); + written = write(fd, ref, len); + close(fd); + if (written != len) { + unlink(lockpath); + error("Unable to write to %s", lockpath); + return -2; + } + if (rename(lockpath, git_HEAD) < 0) { + unlink(lockpath); + error("Unable to create %s", git_HEAD); + return -3; + } + return 0; +#endif +} + int read_ref(const char *filename, unsigned char *sha1) { if (resolve_ref(filename, sha1, 1)) diff --git a/setup.c b/setup.c index 9e20160d94..c487d7eb9d 100644 --- a/setup.c +++ b/setup.c @@ -76,18 +76,20 @@ const char **get_pathspec(const char *prefix, const char **pathspec) * Test it it looks like we're at the top * level git directory. We want to see a * - * - a HEAD symlink and a refs/ directory under ".git" * - either a .git/objects/ directory _or_ the proper * GIT_OBJECT_DIRECTORY environment variable + * - a refs/ directory under ".git" + * - either a HEAD symlink or a HEAD file that is formatted as + * a proper "ref:". */ static int is_toplevel_directory(void) { - struct stat st; - - return !lstat(".git/HEAD", &st) && - S_ISLNK(st.st_mode) && - !access(".git/refs/", X_OK) && - (getenv(DB_ENVIRONMENT) || !access(".git/objects/", X_OK)); + if (access(".git/refs/", X_OK) || + access(getenv(DB_ENVIRONMENT) ? + getenv(DB_ENVIRONMENT) : ".git/objects/", X_OK) || + validate_symref(".git/HEAD")) + return 0; + return 1; } const char *setup_git_directory(void) diff --git a/show-branch.c b/show-branch.c index 5778a594f4..8429c171cf 100644 --- a/show-branch.c +++ b/show-branch.c @@ -349,6 +349,7 @@ int main(int ac, char **av) int all_heads = 0, all_tags = 0; int all_mask, all_revs, shown_merge_point; char head_path[128]; + const char *head_path_p; int head_path_len; unsigned char head_sha1[20]; int merge_base = 0; @@ -430,11 +431,15 @@ int main(int ac, char **av) if (0 <= extra) join_revs(&list, &seen, num_rev, extra); - head_path_len = readlink(".git/HEAD", head_path, sizeof(head_path)-1); - if ((head_path_len < 0) || get_sha1("HEAD", head_sha1)) + head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1); + if (head_path_p) { + head_path_len = strlen(head_path_p); + memcpy(head_path, head_path_p, head_path_len + 1); + } + else { + head_path_len = 0; head_path[0] = 0; - else - head_path[head_path_len] = 0; + } if (merge_base) return show_merge_base(seen, num_rev); diff --git a/symbolic-ref.c b/symbolic-ref.c new file mode 100644 index 0000000000..af087d211d --- /dev/null +++ b/symbolic-ref.c @@ -0,0 +1,34 @@ +#include "cache.h" + +static const char git_symbolic_ref_usage[] = +"git-symbolic-ref name [ref]"; + +static int check_symref(const char *HEAD) +{ + unsigned char sha1[20]; + const char *git_HEAD = strdup(git_path("%s", HEAD)); + const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0); + if (git_refs_heads_master) { + /* we want to strip the .git/ part */ + int pfxlen = strlen(git_HEAD) - strlen(HEAD); + puts(git_refs_heads_master + pfxlen); + } + else + die("No such ref: %s", HEAD); +} + +int main(int argc, const char **argv) +{ + setup_git_directory(); + switch (argc) { + case 2: + check_symref(argv[1]); + break; + case 3: + create_symref(strdup(git_path("%s", argv[1])), argv[2]); + break; + default: + usage(git_symbolic_ref_usage); + } + return 0; +} diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 59ce77b6b4..1a4d2f2f13 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -20,12 +20,12 @@ test_expect_success setup ' commit=$(echo "Commit #$i" | git-commit-tree $tree -p $parent) && parent=$commit || return 1 done && - echo "$commit" >.git/HEAD && + git-update-ref HEAD "$commit" && git-clone -l ./. victim && cd victim && git-log && cd .. && - echo $zero >.git/HEAD && + git-update-ref HEAD "$zero" && parent=$zero && for i in $cnt do @@ -33,7 +33,7 @@ test_expect_success setup ' commit=$(echo "Rebase #$i" | git-commit-tree $tree -p $parent) && parent=$commit || return 1 done && - echo "$commit" >.git/HEAD && + git-update-ref HEAD "$commit" && echo Rebase && git-log' From 455a7f3275d264f6e66045b92c83747ec461dda5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 30 Sep 2005 13:31:16 -0700 Subject: [PATCH 15/27] More portability. - The location of openssl development files got customizable. - The location of iconv development files got customizable. - Pass $TAR down to t5000 test so that the user can override with 'gmake TAR=gtar'. - Solaris 'bc' does not seem to grok "define abs()". There is no reason to use bc there -- expr would do. Signed-off-by: Junio C Hamano --- Makefile | 20 +++++++++++++++++--- t/Makefile | 1 + t/t5000-tar-tree.sh | 6 +++--- t/t6002-rev-list-bisect.sh | 29 +++++++++++++---------------- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index 5648296a8d..e7f3d1ffca 100644 --- a/Makefile +++ b/Makefile @@ -200,18 +200,32 @@ endif ifndef NO_OPENSSL LIB_OBJS += epoch.o OPENSSL_LIBSSL = -lssl + ifdef OPENSSLDIR + # Again this may be problematic -- gcc does not always want -R. + CFLAGS += -I$(OPENSSLDIR)/include + OPENSSL_LINK = -L$(OPENSSLDIR)/lib -R$(OPENSSLDIR)/lib + else + OPENSSL_LINK = + endif else DEFINES += '-DNO_OPENSSL' MOZILLA_SHA1 = 1 OPENSSL_LIBSSL = endif ifdef NEEDS_SSL_WITH_CRYPTO - LIB_4_CRYPTO = -lcrypto -lssl + LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto -lssl else - LIB_4_CRYPTO = -lcrypto + LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto endif ifdef NEEDS_LIBICONV - LIB_4_ICONV = -liconv + ifdef ICONVDIR + # Again this may be problematic -- gcc does not always want -R. + CFLAGS += -I$(ICONVDIR)/include + ICONV_LINK = -L$(ICONVDIR)/lib -R$(ICONVDIR)/lib + else + ICONV_LINK = + endif + LIB_4_ICONV = $(ICONV_LINK) -liconv else LIB_4_ICONV = endif diff --git a/t/Makefile b/t/Makefile index a6b80882ce..e71da7782e 100644 --- a/t/Makefile +++ b/t/Makefile @@ -5,6 +5,7 @@ #GIT_TEST_OPTS=--verbose --debug SHELL_PATH ?= $(SHELL) +TAR ?= $(TAR) T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh) diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 6bf34066e1..5dffb8e1e5 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -50,7 +50,7 @@ test_expect_success \ test_expect_success \ 'validate file modification time' \ - 'TZ=GMT tar tvf b.tar a/a | + 'TZ=GMT $TAR tvf b.tar a/a | awk \{print\ \$4,\ \(length\(\$5\)\<7\)\ ?\ \$5\":00\"\ :\ \$5\} \ >b.mtime && echo "2005-05-27 22:00:00" >expected.mtime && @@ -63,7 +63,7 @@ test_expect_success \ test_expect_success \ 'extract tar archive' \ - '(cd b && tar xf -) =0) { return (x); } else { return (-x); } -} -define floor(x) { - save=scale; scale=0; result=x/1; scale=save; return (result); -} -$* -EOF -} - # usage: test_bisection max-diff bisect-option head ^prune... # # e.g. test_bisection 1 --bisect l1 ^l0 @@ -35,8 +21,19 @@ test_bisection_diff() _head=$1 shift 1 _bisection_size=$(git-rev-list $_bisection "$@" | wc -l) - [ -n "$_list_size" -a -n "$_bisection_size" ] || error "test_bisection_diff failed" - test_expect_success "bisection diff $_bisect_option $_head $* <= $_max_diff" "[ $(bc_expr "floor(abs($_list_size/2)-$_bisection_size)") -le $_max_diff ]" + [ -n "$_list_size" -a -n "$_bisection_size" ] || + error "test_bisection_diff failed" + + # Test if bisection size is close to half of list size within + # tolerance. + # + _bisect_err=`expr $_list_size - $_bisection_size \* 2` + test "$_bisect_err" -lt 0 && _bisect_err=`expr 0 - $_bisect_err` + _bisect_err=`expr $_bisect_err / 2` ; # floor + + test_expect_success \ + "bisection diff $_bisect_option $_head $* <= $_max_diff" \ + 'test $_bisect_err -le $_max_diff' } date >path0 From 5d1a5c02e8ac1c16688ea4a44512245f25a49f8a Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 1 Oct 2005 13:24:27 -0700 Subject: [PATCH 16/27] [PATCH] Better error reporting for "git status" Instead of "git status" ignoring (and hiding) potential errors from the "git-update-index" call, make it exit if it fails, and show the error. In order to do this, use the "-q" flag (to ignore not-up-to-date files) and add a new "--unmerged" flag that allows unmerged entries in the index without any errors. This also avoids marking the index "changed" if an entry isn't actually modified, and makes sure that we exit with an understandable error message if the index is corrupt or unreadable. "read_cache()" no longer returns an error for the caller to check. Finally, make die() and usage() exit with recognizable error codes, if we ever want to check the failure reason in scripts. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- git-status.sh | 2 +- read-cache.c | 14 +++++++++----- update-index.c | 16 ++++++++++++---- usage.c | 4 ++-- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/git-status.sh b/git-status.sh index ca9a15459f..44398d760c 100755 --- a/git-status.sh +++ b/git-status.sh @@ -37,7 +37,7 @@ refs/heads/master) ;; *) echo "# On branch $branch" ;; esac -git-update-index --refresh >/dev/null 2>&1 +git-update-index -q --unmerged --refresh || exit if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1 then diff --git a/read-cache.c b/read-cache.c index 0e345bdb2f..d2aebdd6bc 100644 --- a/read-cache.c +++ b/read-cache.c @@ -464,11 +464,15 @@ int read_cache(void) errno = EBUSY; if (active_cache) - return error("more than one cachefile"); + return active_nr; + errno = ENOENT; fd = open(get_index_file(), O_RDONLY); - if (fd < 0) - return (errno == ENOENT) ? 0 : error("open failed"); + if (fd < 0) { + if (errno == ENOENT) + return 0; + die("index file open failed (%s)", strerror(errno)); + } size = 0; // avoid gcc warning map = MAP_FAILED; @@ -480,7 +484,7 @@ int read_cache(void) } close(fd); if (map == MAP_FAILED) - return error("mmap failed"); + die("index file mmap failed (%s)", strerror(errno)); hdr = map; if (verify_hdr(hdr, size) < 0) @@ -501,7 +505,7 @@ int read_cache(void) unmap: munmap(map, size); errno = EINVAL; - return error("verify header failed"); + die("index file corrupt"); } #define WRITE_BUFFER_SIZE 8192 diff --git a/update-index.c b/update-index.c index 01eaa1a984..ade1ee72eb 100644 --- a/update-index.c +++ b/update-index.c @@ -13,7 +13,7 @@ * like "git-update-index *" and suddenly having all the object * files be revision controlled. */ -static int allow_add = 0, allow_remove = 0, allow_replace = 0, not_new = 0, quiet = 0, info_only = 0; +static int allow_add = 0, allow_remove = 0, allow_replace = 0, allow_unmerged = 0, not_new = 0, quiet = 0, info_only = 0; static int force_remove; /* Three functions to allow overloaded pointer return; see linux/err.h */ @@ -135,7 +135,7 @@ static struct cache_entry *refresh_entry(struct cache_entry *ce) changed = ce_match_stat(ce, &st); if (!changed) - return ce; + return NULL; if (ce_modified(ce, &st)) return ERR_PTR(-EINVAL); @@ -156,16 +156,20 @@ static int refresh_cache(void) struct cache_entry *ce, *new; ce = active_cache[i]; if (ce_stage(ce)) { - printf("%s: needs merge\n", ce->name); - has_errors = 1; while ((i < active_nr) && ! strcmp(active_cache[i]->name, ce->name)) i++; i--; + if (allow_unmerged) + continue; + printf("%s: needs merge\n", ce->name); + has_errors = 1; continue; } new = refresh_entry(ce); + if (!new) + continue; if (IS_ERR(new)) { if (not_new && PTR_ERR(new) == -ENOENT) continue; @@ -335,6 +339,10 @@ int main(int argc, const char **argv) allow_remove = 1; continue; } + if (!strcmp(path, "--unmerged")) { + allow_unmerged = 1; + continue; + } if (!strcmp(path, "--refresh")) { has_errors |= refresh_cache(); continue; diff --git a/usage.c b/usage.c index 86211c9141..dfa87fe119 100644 --- a/usage.c +++ b/usage.c @@ -15,7 +15,7 @@ static void report(const char *prefix, const char *err, va_list params) void usage(const char *err) { fprintf(stderr, "usage: %s\n", err); - exit(1); + exit(129); } void die(const char *err, ...) @@ -25,7 +25,7 @@ void die(const char *err, ...) va_start(params, err); report("fatal: ", err, params); va_end(params); - exit(1); + exit(128); } int error(const char *err, ...) From 5cd5ace72bf8944c920a20a7b98c82f7ed663d8a Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 1 Oct 2005 13:39:47 -0700 Subject: [PATCH 17/27] [PATCH] Re-instate index file write optimization This makes "git-update-index" avoid the new index file write if it didn't make any changes to the index. It still doesn't make things like "git status" be read-only operations in general, but if the index file doesn't need refreshing, it now will at least avoid making unnecessary changes. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- update-index.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/update-index.c b/update-index.c index ade1ee72eb..b825a11d2f 100644 --- a/update-index.c +++ b/update-index.c @@ -391,9 +391,11 @@ int main(int argc, const char **argv) update_one(buf.buf, prefix, prefix_length); } } - if (write_cache(newfd, active_cache, active_nr) || - commit_index_file(&cache_file)) - die("Unable to write new cachefile"); + if (active_cache_changed) { + if (write_cache(newfd, active_cache, active_nr) || + commit_index_file(&cache_file)) + die("Unable to write new cachefile"); + } return has_errors ? 1 : 0; } From 18c5a52537173b12bd78ce25c6dd524d147e87a7 Mon Sep 17 00:00:00 2001 From: Han Boetes Date: Sat, 1 Oct 2005 08:23:26 +0200 Subject: [PATCH 18/27] [PATCH] git on OpenBSD iconv is installed in /usr/local. Signed-off-by: Junio C Hamano --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index e7f3d1ffca..79cafd7171 100644 --- a/Makefile +++ b/Makefile @@ -175,6 +175,10 @@ endif ifneq (,$(findstring arm,$(shell uname -m))) ARM_SHA1 = YesPlease endif +ifeq ($(shell uname -s),OpenBSD) + NEEDS_LIBICONV = YesPlease + PLATFORM_DEFINES += -I/usr/local/include -L/usr/local/lib +endif ifndef NO_CURL ifdef CURLDIR From 0842acff57f386ba749c3ea6b5e034771f074f6b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 2 Oct 2005 00:20:45 -0700 Subject: [PATCH 19/27] Customize git command for installations that lack certain commands. When the platform lacks certain git subcommands, omit them from the list of subcommands that are available from "git" wrapper. Noticed by Geert Bosch. Signed-off-by: Junio C Hamano --- Makefile | 7 ++++++- git.sh | 55 ++++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 79cafd7171..4d721f2ace 100644 --- a/Makefile +++ b/Makefile @@ -122,6 +122,8 @@ PROGRAMS = \ # Backward compatibility -- to be removed after 1.0 PROGRAMS += git-ssh-pull git-ssh-push +GIT_LIST_TWEAK = + PYMODULES = \ gitMergeCommon.py @@ -131,6 +133,8 @@ endif ifdef WITH_SEND_EMAIL SCRIPT_PERL += git-send-email.perl +else + GIT_LIST_TWEAK += -e '/^send-email$$/d' endif LIB_FILE=libgit.a @@ -282,7 +286,8 @@ all: git: git.sh Makefile rm -f $@+ $@ sed -e '1s|#!.*/sh|#!$(SHELL_PATH)|' \ - -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' <$@.sh >$@+ + -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ + $(GIT_LIST_TWEAK) <$@.sh >$@+ chmod +x $@+ mv $@+ $@ diff --git a/git.sh b/git.sh index 178d0f0c09..dc383eddea 100755 --- a/git.sh +++ b/git.sh @@ -16,17 +16,50 @@ esac echo "Usage: git COMMAND [OPTIONS] [TARGET]" if [ -n "$cmd" ]; then - echo " git command '$cmd' not found: commands are:" -else - echo " git commands are:" + echo "git command '$cmd' not found." fi +echo "git commands are:" -cat <<\EOF - add apply archimport bisect branch checkout cherry clone - commit count-objects cvsimport diff fetch format-patch - fsck-cache get-tar-commit-id init-db log ls-remote octopus - pack-objects parse-remote patch-id prune pull push rebase - relink rename repack request-pull reset resolve revert - send-email shortlog show-branch status tag verify-tag - whatchanged +fmt <<\EOF | sed -e 's/^/ /' +add +apply +archimport +bisect +branch +checkout +cherry +clone +commit +count-objects +cvsimport +diff +fetch +format-patch +fsck-objects +get-tar-commit-id +init-db +log +ls-remote +octopus +pack-objects +parse-remote +patch-id +prune +pull +push +rebase +relink +rename +repack +request-pull +reset +resolve +revert +send-email +shortlog +show-branch +status +tag +verify-tag +whatchanged EOF From 7dd43575f8641c8a73b1e976715056ebdc773904 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 2 Oct 2005 00:50:16 -0700 Subject: [PATCH 20/27] read-tree: remove --head option. Initially it was to allow specifying more than one remote to allow creation of an Octopus, but it is not being used. Signed-off-by: Junio C Hamano --- read-tree.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/read-tree.c b/read-tree.c index ca808739db..390fe2f027 100644 --- a/read-tree.c +++ b/read-tree.c @@ -629,11 +629,6 @@ int main(int argc, char **argv) continue; } - if (!strcmp(arg, "--head")) { - head_idx = stage - 1; - fn = threeway_merge; - } - /* "-m" stands for "merge", meaning we start in stage 1 */ if (!strcmp(arg, "-m")) { if (stage || merge) @@ -657,7 +652,8 @@ int main(int argc, char **argv) } if ((update||index_only) && !merge) usage(read_tree_usage); - if (merge && !fn) { + + if (merge) { if (stage < 2) die("just how do you expect me to merge %d trees?", stage-1); switch (stage - 1) { @@ -674,9 +670,7 @@ int main(int argc, char **argv) fn = threeway_merge; break; } - } - if (head_idx < 0) { if (stage - 1 >= 3) head_idx = stage - 2; else From af215114f52af0f308ef19f67e544df8ea5e4ac2 Mon Sep 17 00:00:00 2001 From: Fredrik Kuivinen Date: Sun, 2 Oct 2005 17:43:07 +0200 Subject: [PATCH 21/27] [PATCH] Teach the recursive merge strategy about renames. It will now merge cases where a file was renamed in one branch and modified in the other branch cleanly. We also detect a couple of conflict cases now that wasn't detected before. Signed-off-by: Fredrik Kuivinen Signed-off-by: Junio C Hamano --- git-merge-recursive.py | 798 +++++++++++++++++++++++++++++++---------- 1 file changed, 607 insertions(+), 191 deletions(-) diff --git a/git-merge-recursive.py b/git-merge-recursive.py index 689f91430b..b80a860357 100755 --- a/git-merge-recursive.py +++ b/git-merge-recursive.py @@ -7,9 +7,6 @@ sys.path.append('@@GIT_PYTHON_PATH@@') from gitMergeCommon import * -# The actual merge code -# --------------------- - originalIndexFile = os.environ.get('GIT_INDEX_FILE', os.environ.get('GIT_DIR', '.git') + '/index') temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \ @@ -21,11 +18,23 @@ def setupIndex(temporary): pass if temporary: newIndex = temporaryIndexFile - os.environ else: newIndex = originalIndexFile os.environ['GIT_INDEX_FILE'] = newIndex +# This is a global variable which is used in a number of places but +# only written to in the 'merge' function. + +# cacheOnly == True => Don't leave any non-stage 0 entries in the cache and +# don't update the working directory. +# False => Leave unmerged entries in the cache and update +# the working directory. + +cacheOnly = False + +# The entry point to the merge code +# --------------------------------- + def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0): '''Merge the commits h1 and h2, return the resulting virtual commit object and a flag indicating the cleaness of the merge.''' @@ -35,6 +44,7 @@ def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0): def infoMsg(*args): sys.stdout.write(' '*callDepth) printList(args) + infoMsg('Merging:') infoMsg(h1) infoMsg(h2) @@ -46,27 +56,27 @@ def infoMsg(*args): infoMsg(x) sys.stdout.flush() - Ms = ca[0] + mergedCA = ca[0] for h in ca[1:]: - [Ms, ignore] = merge(Ms, h, - 'Temporary shared merge branch 1', - 'Temporary shared merge branch 2', - graph, callDepth+1) - assert(isinstance(Ms, Commit)) + [mergedCA, dummy] = merge(mergedCA, h, + 'Temporary shared merge branch 1', + 'Temporary shared merge branch 2', + graph, callDepth+1) + assert(isinstance(mergedCA, Commit)) + global cacheOnly if callDepth == 0: setupIndex(False) - cleanCache = False + cacheOnly = False else: setupIndex(True) runProgram(['git-read-tree', h1.tree()]) - cleanCache = True + cacheOnly = True - [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), Ms.tree(), - branch1Name, branch2Name, - cleanCache) + [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(), + branch1Name, branch2Name) - if clean or cleanCache: + if clean or cacheOnly: res = Commit(None, [h1, h2], tree=shaRes) graph.addNode(res) else: @@ -89,49 +99,14 @@ def getFilesAndDirs(tree): return [files, dirs] -class CacheEntry: - def __init__(self, path): - class Stage: - def __init__(self): - self.sha1 = None - self.mode = None - - self.stages = [Stage(), Stage(), Stage()] - self.path = path +# Those two global variables are used in a number of places but only +# written to in 'mergeTrees' and 'uniquePath'. They keep track of +# every file and directory in the two branches that are about to be +# merged. +currentFileSet = None +currentDirectorySet = None -unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S) -def unmergedCacheEntries(): - '''Create a dictionary mapping file names to CacheEntry - objects. The dictionary contains one entry for every path with a - non-zero stage entry.''' - - lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0') - lines.pop() - - res = {} - for l in lines: - m = unmergedRE.match(l) - if m: - mode = int(m.group(1), 8) - sha1 = m.group(2) - stage = int(m.group(3)) - 1 - path = m.group(4) - - if res.has_key(path): - e = res[path] - else: - e = CacheEntry(path) - res[path] = e - - e.stages[stage].mode = mode - e.stages[stage].sha1 = sha1 - else: - die('Error: Merge program failed: Unexpected output from', \ - 'git-ls-files:', l) - return res - -def mergeTrees(head, merge, common, branch1Name, branch2Name, - cleanCache): +def mergeTrees(head, merge, common, branch1Name, branch2Name): '''Merge the trees 'head' and 'merge' with the common ancestor 'common'. The name of the head branch is 'branch1Name' and the name of the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge) @@ -144,33 +119,38 @@ def mergeTrees(head, merge, common, branch1Name, branch2Name, print 'Already uptodate!' return [head, True] - if cleanCache: + if cacheOnly: updateArg = '-i' else: updateArg = '-u' - [out, code] = runProgram(['git-read-tree', updateArg, '-m', common, head, merge], returnCode = True) + [out, code] = runProgram(['git-read-tree', updateArg, '-m', + common, head, merge], returnCode = True) if code != 0: die('git-read-tree:', out) - cleanMerge = True - [tree, code] = runProgram('git-write-tree', returnCode=True) tree = tree.rstrip() if code != 0: - [files, dirs] = getFilesAndDirs(head) + global currentFileSet, currentDirectorySet + [currentFileSet, currentDirectorySet] = getFilesAndDirs(head) [filesM, dirsM] = getFilesAndDirs(merge) - files.union_update(filesM) - dirs.union_update(dirsM) - - cleanMerge = True + currentFileSet.union_update(filesM) + currentDirectorySet.union_update(dirsM) + entries = unmergedCacheEntries() - for name in entries: - if not processEntry(entries[name], branch1Name, branch2Name, - files, dirs, cleanCache): + renamesHead = getRenames(head, common, head, merge, entries) + renamesMerge = getRenames(merge, common, head, merge, entries) + + cleanMerge = processRenames(renamesHead, renamesMerge, + branch1Name, branch2Name) + for entry in entries: + if entry.processed: + continue + if not processEntry(entry, branch1Name, branch2Name): cleanMerge = False - if cleanMerge or cleanCache: + if cleanMerge or cacheOnly: tree = runProgram('git-write-tree').rstrip() else: tree = None @@ -179,84 +159,538 @@ def mergeTrees(head, merge, common, branch1Name, branch2Name, return [tree, cleanMerge] -def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache): - '''Merge one cache entry. 'files' is a Set with the files in both of - the heads that we are going to merge. 'dirs' contains the - corresponding data for directories. If 'cleanCache' is True no - non-zero stages will be left in the cache for the path - corresponding to the entry 'entry'.''' +# Low level file merging, update and removal +# ------------------------------------------ -# cleanCache == True => Don't leave any non-stage 0 entries in the cache and -# don't update the working directory -# False => Leave unmerged entries and update the working directory +def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode, + branch1Name, branch2Name): -# clean == True => non-conflict case -# False => conflict case + merge = False + clean = True -# If cleanCache == False then the cache shouldn't be updated if clean == False + if stat.S_IFMT(aMode) != stat.S_IFMT(bMode): + clean = False + if stat.S_ISREG(aMode): + mode = aMode + sha = aSha + else: + mode = bMode + sha = bSha + else: + if aSha != oSha and bSha != oSha: + merge = True - def updateFile(clean, sha, mode, path, onlyWd=False): - updateCache = not onlyWd and (cleanCache or (not cleanCache and clean)) - updateWd = onlyWd or (not cleanCache and clean) + if aMode == oMode: + mode = bMode + else: + mode = aMode - if updateWd: - prog = ['git-cat-file', 'blob', sha] - if stat.S_ISREG(mode): + if aSha == oSha: + sha = bSha + elif bSha == oSha: + sha = aSha + elif stat.S_ISREG(aMode): + assert(stat.S_ISREG(bMode)) + + orig = runProgram(['git-unpack-file', oSha]).rstrip() + src1 = runProgram(['git-unpack-file', aSha]).rstrip() + src2 = runProgram(['git-unpack-file', bSha]).rstrip() + [out, code] = runProgram(['merge', + '-L', branch1Name + '/' + aPath, + '-L', 'orig/' + oPath, + '-L', branch2Name + '/' + bPath, + src1, orig, src2], returnCode=True) + + sha = runProgram(['git-hash-object', '-t', 'blob', '-w', + src1]).rstrip() + + os.unlink(orig) + os.unlink(src1) + os.unlink(src2) + + clean = (code == 0) + else: + assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode)) + sha = aSha + + if aSha != bSha: + clean = False + + return [sha, mode, clean, merge] + +def updateFile(clean, sha, mode, path): + updateCache = cacheOnly or clean + updateWd = not cacheOnly + + return updateFileExt(sha, mode, path, updateCache, updateWd) + +def updateFileExt(sha, mode, path, updateCache, updateWd): + if cacheOnly: + updateWd = False + + if updateWd: + pathComponents = path.split('/') + for x in xrange(1, len(pathComponents)): + p = '/'.join(pathComponents[0:x]) + + try: + createDir = not stat.S_ISDIR(os.lstat(p).st_mode) + except: + createDir = True + + if createDir: try: - os.unlink(path) - except OSError: - pass - if mode & 0100: - mode = 0777 - else: - mode = 0666 - fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode) - proc = subprocess.Popen(prog, stdout=fd) - proc.wait() - os.close(fd) - elif stat.S_ISLNK(mode): - linkTarget = runProgram(prog) - os.symlink(linkTarget, path) - else: - assert(False) + os.mkdir(p) + except OSError, e: + die("Couldn't create directory", p, e.strerror) - if updateWd and updateCache: - runProgram(['git-update-index', '--add', '--', path]) - elif updateCache: - runProgram(['git-update-index', '--add', '--cacheinfo', - '0%o' % mode, sha, path]) - - def removeFile(clean, path): - if cleanCache or (not cleanCache and clean): - runProgram(['git-update-index', '--force-remove', '--', path]) - - if not cleanCache and clean: + prog = ['git-cat-file', 'blob', sha] + if stat.S_ISREG(mode): try: os.unlink(path) - except OSError, e: - if e.errno != errno.ENOENT and e.errno != errno.EISDIR: - raise + except OSError: + pass + if mode & 0100: + mode = 0777 + else: + mode = 0666 + fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode) + proc = subprocess.Popen(prog, stdout=fd) + proc.wait() + os.close(fd) + elif stat.S_ISLNK(mode): + linkTarget = runProgram(prog) + os.symlink(linkTarget, path) + else: + assert(False) - def uniquePath(path, branch): - newPath = path + '_' + branch - suffix = 0 - while newPath in files or newPath in dirs: - suffix += 1 - newPath = path + '_' + branch + '_' + str(suffix) - files.add(newPath) - return newPath + if updateWd and updateCache: + runProgram(['git-update-index', '--add', '--', path]) + elif updateCache: + runProgram(['git-update-index', '--add', '--cacheinfo', + '0%o' % mode, sha, path]) - debug('processing', entry.path, 'clean cache:', cleanCache) +def removeFile(clean, path): + updateCache = cacheOnly or clean + updateWd = not cacheOnly + + if updateCache: + runProgram(['git-update-index', '--force-remove', '--', path]) + + if updateWd: + try: + os.unlink(path) + except OSError, e: + if e.errno != errno.ENOENT and e.errno != errno.EISDIR: + raise + +def uniquePath(path, branch): + def fileExists(path): + try: + os.lstat(path) + return True + except OSError, e: + if e.errno == errno.ENOENT: + return False + else: + raise + + newPath = path + '_' + branch + suffix = 0 + while newPath in currentFileSet or \ + newPath in currentDirectorySet or \ + fileExists(newPath): + suffix += 1 + newPath = path + '_' + branch + '_' + str(suffix) + currentFileSet.add(newPath) + return newPath + +# Cache entry management +# ---------------------- + +class CacheEntry: + def __init__(self, path): + class Stage: + def __init__(self): + self.sha1 = None + self.mode = None + + # Used for debugging only + def __str__(self): + if self.mode != None: + m = '0%o' % self.mode + else: + m = 'None' + + if self.sha1: + sha1 = self.sha1 + else: + sha1 = 'None' + return 'sha1: ' + sha1 + ' mode: ' + m + + self.stages = [Stage(), Stage(), Stage(), Stage()] + self.path = path + self.processed = False + + def __str__(self): + return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages]) + +class CacheEntryContainer: + def __init__(self): + self.entries = {} + + def add(self, entry): + self.entries[entry.path] = entry + + def get(self, path): + return self.entries.get(path) + + def __iter__(self): + return self.entries.itervalues() + +unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S) +def unmergedCacheEntries(): + '''Create a dictionary mapping file names to CacheEntry + objects. The dictionary contains one entry for every path with a + non-zero stage entry.''' + + lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0') + lines.pop() + + res = CacheEntryContainer() + for l in lines: + m = unmergedRE.match(l) + if m: + mode = int(m.group(1), 8) + sha1 = m.group(2) + stage = int(m.group(3)) + path = m.group(4) + + e = res.get(path) + if not e: + e = CacheEntry(path) + res.add(e) + + e.stages[stage].mode = mode + e.stages[stage].sha1 = sha1 + else: + die('Error: Merge program failed: Unexpected output from', + 'git-ls-files:', l) + return res + +lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S) +def getCacheEntry(path, origTree, aTree, bTree): + '''Returns a CacheEntry object which doesn't have to correspond to + a real cache entry in Git's index.''' + + def parse(out): + if out == '': + return [None, None] + else: + m = lsTreeRE.match(out) + if not m: + die('Unexpected output from git-ls-tree:', out) + elif m.group(2) == 'blob': + return [m.group(3), int(m.group(1), 8)] + else: + return [None, None] + + res = CacheEntry(path) + + [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path])) + [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path])) + [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path])) + + res.stages[1].sha1 = oSha + res.stages[1].mode = oMode + res.stages[2].sha1 = aSha + res.stages[2].mode = aMode + res.stages[3].sha1 = bSha + res.stages[3].mode = bMode + + return res + +# Rename detection and handling +# ----------------------------- + +class RenameEntry: + def __init__(self, + src, srcSha, srcMode, srcCacheEntry, + dst, dstSha, dstMode, dstCacheEntry, + score): + self.srcName = src + self.srcSha = srcSha + self.srcMode = srcMode + self.srcCacheEntry = srcCacheEntry + self.dstName = dst + self.dstSha = dstSha + self.dstMode = dstMode + self.dstCacheEntry = dstCacheEntry + self.score = score + + self.processed = False + +class RenameEntryContainer: + def __init__(self): + self.entriesSrc = {} + self.entriesDst = {} + + def add(self, entry): + self.entriesSrc[entry.srcName] = entry + self.entriesDst[entry.dstName] = entry + + def getSrc(self, path): + return self.entriesSrc.get(path) + + def getDst(self, path): + return self.entriesDst.get(path) + + def __iter__(self): + return self.entriesSrc.itervalues() + +parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$') +def getRenames(tree, oTree, aTree, bTree, cacheEntries): + '''Get information of all renames which occured between 'oTree' and + 'tree'. We need the three trees in the merge ('oTree', 'aTree' and + 'bTree') to be able to associate the correct cache entries with + the rename information. 'tree' is always equal to either aTree or bTree.''' + + assert(tree == aTree or tree == bTree) + inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r', + '-z', oTree, tree]) + + ret = RenameEntryContainer() + try: + recs = inp.split("\0") + recs.pop() # remove last entry (which is '') + it = recs.__iter__() + while True: + rec = it.next() + m = parseDiffRenamesRE.match(rec) + + if not m: + die('Unexpected output from git-diff-tree:', rec) + + srcMode = int(m.group(1), 8) + dstMode = int(m.group(2), 8) + srcSha = m.group(3) + dstSha = m.group(4) + score = m.group(5) + src = it.next() + dst = it.next() + + srcCacheEntry = cacheEntries.get(src) + if not srcCacheEntry: + srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree) + cacheEntries.add(srcCacheEntry) + + dstCacheEntry = cacheEntries.get(dst) + if not dstCacheEntry: + dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree) + cacheEntries.add(dstCacheEntry) + + ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry, + dst, dstSha, dstMode, dstCacheEntry, + score)) + except StopIteration: + pass + return ret + +def fmtRename(src, dst): + srcPath = src.split('/') + dstPath = dst.split('/') + path = [] + endIndex = min(len(srcPath), len(dstPath)) - 1 + for x in range(0, endIndex): + if srcPath[x] == dstPath[x]: + path.append(srcPath[x]) + else: + endIndex = x + break + + if len(path) > 0: + return '/'.join(path) + \ + '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \ + '/'.join(dstPath[endIndex:]) + '}' + else: + return src + ' => ' + dst + +def processRenames(renamesA, renamesB, branchNameA, branchNameB): + srcNames = Set() + for x in renamesA: + srcNames.add(x.srcName) + for x in renamesB: + srcNames.add(x.srcName) + + cleanMerge = True + for path in srcNames: + if renamesA.getSrc(path): + renames1 = renamesA + renames2 = renamesB + branchName1 = branchNameA + branchName2 = branchNameB + else: + renames1 = renamesB + renames2 = renamesA + branchName1 = branchNameB + branchName2 = branchNameA + + ren1 = renames1.getSrc(path) + ren2 = renames2.getSrc(path) + + ren1.dstCacheEntry.processed = True + ren1.srcCacheEntry.processed = True + + if ren1.processed: + continue + + ren1.processed = True + removeFile(True, ren1.srcName) + if ren2: + # Renamed in 1 and renamed in 2 + assert(ren1.srcName == ren2.srcName) + ren2.dstCacheEntry.processed = True + ren2.processed = True + + if ren1.dstName != ren2.dstName: + print 'CONFLICT (rename/rename): Rename', \ + fmtRename(path, ren1.dstName), 'in branch', branchName1, \ + 'rename', fmtRename(path, ren2.dstName), 'in', branchName2 + cleanMerge = False + + if ren1.dstName in currentDirectorySet: + dstName1 = uniquePath(ren1.dstName, branchName1) + print ren1.dstName, 'is a directory in', branchName2, \ + 'adding as', dstName1, 'instead.' + removeFile(False, ren1.dstName) + else: + dstName1 = ren1.dstName + + if ren2.dstName in currentDirectorySet: + dstName2 = uniquePath(ren2.dstName, branchName2) + print ren2.dstName, 'is a directory in', branchName1, \ + 'adding as', dstName2, 'instead.' + removeFile(False, ren2.dstName) + else: + dstName2 = ren1.dstName + + updateFile(False, ren1.dstSha, ren1.dstMode, dstName1) + updateFile(False, ren2.dstSha, ren2.dstMode, dstName2) + else: + print 'Renaming', fmtRename(path, ren1.dstName) + [resSha, resMode, clean, merge] = \ + mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode, + ren1.dstName, ren1.dstSha, ren1.dstMode, + ren2.dstName, ren2.dstSha, ren2.dstMode, + branchName1, branchName2) + + if merge: + print 'Auto-merging', ren1.dstName + + if not clean: + print 'CONFLICT (content): merge conflict in', ren1.dstName + cleanMerge = False + + if not cacheOnly: + updateFileExt(ren1.dstSha, ren1.dstMode, ren1.dstName, + updateCache=True, updateWd=False) + updateFile(clean, resSha, resMode, ren1.dstName) + else: + # Renamed in 1, maybe changed in 2 + if renamesA == renames1: + stage = 3 + else: + stage = 2 + + srcShaOtherBranch = ren1.srcCacheEntry.stages[stage].sha1 + srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode + + dstShaOtherBranch = ren1.dstCacheEntry.stages[stage].sha1 + dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode + + tryMerge = False + + if ren1.dstName in currentDirectorySet: + newPath = uniquePath(ren1.dstName, branchName1) + print 'CONFLICT (rename/directory): Rename', \ + fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1,\ + 'directory', ren1.dstName, 'added in', branchName2 + print 'Renaming', ren1.srcName, 'to', newPath, 'instead' + cleanMerge = False + removeFile(False, ren1.dstName) + updateFile(False, ren1.dstSha, ren1.dstMode, newPath) + elif srcShaOtherBranch == None: + print 'CONFLICT (rename/delete): Rename', \ + fmtRename(ren1.srcName, ren1.dstName), 'in', \ + branchName1, 'and deleted in', branchName2 + cleanMerge = False + updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName) + elif dstShaOtherBranch: + newPath = uniquePath(ren1.dstName, branchName2) + print 'CONFLICT (rename/add): Rename', \ + fmtRename(ren1.srcName, ren1.dstName), 'in', \ + branchName1 + '.', ren1.dstName, 'added in', branchName2 + print 'Adding as', newPath, 'instead' + updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath) + cleanMerge = False + tryMerge = True + elif renames2.getDst(ren1.dstName): + dst2 = renames2.getDst(ren1.dstName) + newPath1 = uniquePath(ren1.dstName, branchName1) + newPath2 = uniquePath(dst2.dstName, branchName2) + print 'CONFLICT (rename/rename): Rename', \ + fmtRename(ren1.srcName, ren1.dstName), 'in', \ + branchName1+'. Rename', \ + fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2 + print 'Renaming', ren1.srcName, 'to', newPath1, 'and', \ + dst2.srcName, 'to', newPath2, 'instead' + removeFile(False, ren1.dstName) + updateFile(False, ren1.dstSha, ren1.dstMode, newPath1) + updateFile(False, dst2.dstSha, dst2.dstMode, newPath2) + dst2.processed = True + cleanMerge = False + else: + tryMerge = True + + if tryMerge: + print 'Renaming', fmtRename(ren1.srcName, ren1.dstName) + [resSha, resMode, clean, merge] = \ + mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode, + ren1.dstName, ren1.dstSha, ren1.dstMode, + ren1.srcName, srcShaOtherBranch, srcModeOtherBranch, + branchName1, branchName2) + + if merge: + print 'Auto-merging', ren1.dstName + + if not clean: + print 'CONFLICT (rename/modify): Merge conflict in', ren1.dstName + cleanMerge = False + + if not cacheOnly: + updateFileExt(ren1.dstSha, ren1.dstMode, ren1.dstName, + updateCache=True, updateWd=False) + updateFile(clean, resSha, resMode, ren1.dstName) + + return cleanMerge + +# Per entry merge function +# ------------------------ + +def processEntry(entry, branch1Name, branch2Name): + '''Merge one cache entry.''' + + debug('processing', entry.path, 'clean cache:', cacheOnly) cleanMerge = True path = entry.path - oSha = entry.stages[0].sha1 - oMode = entry.stages[0].mode - aSha = entry.stages[1].sha1 - aMode = entry.stages[1].mode - bSha = entry.stages[2].sha1 - bMode = entry.stages[2].mode + oSha = entry.stages[1].sha1 + oMode = entry.stages[1].mode + aSha = entry.stages[2].sha1 + aMode = entry.stages[2].mode + bSha = entry.stages[3].sha1 + bMode = entry.stages[3].mode assert(oSha == None or isSha(oSha)) assert(aSha == None or isSha(aSha)) @@ -275,28 +709,26 @@ def uniquePath(path, branch): (not aSha and bSha == oSha): # Deleted in both or deleted in one and unchanged in the other if aSha: - print 'Removing ' + path + print 'Removing', path removeFile(True, path) else: # Deleted in one and changed in the other cleanMerge = False if not aSha: - print 'CONFLICT (del/mod): "' + path + '" deleted in', \ - branch1Name, 'and modified in', branch2Name, \ - '. Version', branch2Name, ' of "' + path + \ - '" left in tree' + print 'CONFLICT (delete/modify):', path, 'deleted in', \ + branch1Name, 'and modified in', branch2Name + '.', \ + 'Version', branch2Name, 'of', path, 'left in tree.' mode = bMode sha = bSha else: - print 'CONFLICT (mod/del): "' + path + '" deleted in', \ - branch2Name, 'and modified in', branch1Name + \ - '. Version', branch1Name, 'of "' + path + \ - '" left in tree' + print 'CONFLICT (modify/delete):', path, 'deleted in', \ + branch2Name, 'and modified in', branch1Name + '.', \ + 'Version', branch1Name, 'of', path, 'left in tree.' mode = aMode sha = aSha updateFile(False, sha, mode, path) - + elif (not oSha and aSha and not bSha) or \ (not oSha and not aSha and bSha): # @@ -307,27 +739,26 @@ def uniquePath(path, branch): otherBranch = branch2Name mode = aMode sha = aSha - conf = 'file/dir' + conf = 'file/directory' else: addBranch = branch2Name otherBranch = branch1Name mode = bMode sha = bSha - conf = 'dir/file' + conf = 'directory/file' - if path in dirs: + if path in currentDirectorySet: cleanMerge = False newPath = uniquePath(path, addBranch) - print 'CONFLICT (' + conf + \ - '): There is a directory with name "' + path + '" in', \ - otherBranch + '. Adding "' + path + '" as "' + newPath + '"' + print 'CONFLICT (' + conf + '):', \ + 'There is a directory with name', path, 'in', \ + otherBranch + '. Adding', path, 'as', newPath removeFile(False, path) - path = newPath + updateFile(False, sha, mode, newPath) else: - print 'Adding "' + path + '"' - - updateFile(True, sha, mode, path) + print 'Adding', path + updateFile(True, sha, mode, path) elif not oSha and aSha and bSha: # @@ -336,10 +767,9 @@ def uniquePath(path, branch): if aSha == bSha: if aMode != bMode: cleanMerge = False - print 'CONFLICT: File "' + path + \ - '" added identically in both branches,', \ - 'but permissions conflict', '0%o' % aMode, '->', \ - '0%o' % bMode + print 'CONFLICT: File', path, \ + 'added identically in both branches, but permissions', \ + 'conflict', '0%o' % aMode, '->', '0%o' % bMode print 'CONFLICT: adding with permission:', '0%o' % aMode updateFile(False, aSha, aMode, path) @@ -350,8 +780,9 @@ def uniquePath(path, branch): cleanMerge = False newPath1 = uniquePath(path, branch1Name) newPath2 = uniquePath(path, branch2Name) - print 'CONFLICT (add/add): File "' + path + \ - '" added non-identically in both branches.' + print 'CONFLICT (add/add): File', path, \ + 'added non-identically in both branches. Adding as', \ + newPath1, 'and', newPath2, 'instead.' removeFile(False, path) updateFile(False, aSha, aMode, newPath1) updateFile(False, bSha, bMode, newPath2) @@ -360,39 +791,24 @@ def uniquePath(path, branch): # # case D: Modified in both, but differently. # - print 'Auto-merging', path - orig = runProgram(['git-unpack-file', oSha]).rstrip() - src1 = runProgram(['git-unpack-file', aSha]).rstrip() - src2 = runProgram(['git-unpack-file', bSha]).rstrip() - [out, ret] = runProgram(['merge', - '-L', branch1Name + '/' + path, - '-L', 'orig/' + path, - '-L', branch2Name + '/' + path, - src1, orig, src2], returnCode=True) - - if aMode == oMode: - mode = bMode + print 'Auto-merging', path + [sha, mode, clean, dummy] = \ + mergeFile(path, oSha, oMode, + path, aSha, aMode, + path, bSha, bMode, + branch1Name, branch2Name) + if clean: + updateFile(True, sha, mode, path) else: - mode = aMode - - sha = runProgram(['git-hash-object', '-t', 'blob', '-w', - src1]).rstrip() - - if ret != 0: cleanMerge = False - print 'CONFLICT (content): Merge conflict in "' + path + '".' + print 'CONFLICT (content): Merge conflict in', path - if cleanCache: + if cacheOnly: updateFile(False, sha, mode, path) else: - updateFile(True, aSha, aMode, path) - updateFile(False, sha, mode, path, True) - else: - updateFile(True, sha, mode, path) - - os.unlink(orig) - os.unlink(src1) - os.unlink(src2) + updateFileExt(aSha, aMode, path, + updateCache=True, updateWd=False) + updateFileExt(sha, mode, path, updateCache=False, updateWd=True) else: die("ERROR: Fatal merge failure, shouldn't happen.") @@ -416,7 +832,7 @@ def usage(): try: h1 = firstBranch = sys.argv[nextArg + 1] h2 = secondBranch = sys.argv[nextArg + 2] - except IndexError: + except IndexError: usage() break @@ -428,8 +844,8 @@ def usage(): graph = buildGraph([h1, h2]) - [res, clean] = merge(graph.shaMap[h1], graph.shaMap[h2], - firstBranch, secondBranch, graph) + [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2], + firstBranch, secondBranch, graph) print '' except: From 500b97e4bb184c954a52550843e6314ecf97a208 Mon Sep 17 00:00:00 2001 From: Fredrik Kuivinen Date: Sun, 2 Oct 2005 17:33:38 +0200 Subject: [PATCH 22/27] [PATCH] Teach git-ls-files about '--' to denote end of options. Useful if you have a file whose name starts with a dash. Signed-off-by: Fredrik Kuivinen Signed-off-by: Junio C Hamano --- Documentation/git-ls-files.txt | 9 ++++- ls-files.c | 6 ++- t/t3002-ls-files-dashpath.sh | 69 ++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100755 t/t3002-ls-files-dashpath.sh diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 591f4ed462..87cc362ad8 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -13,7 +13,7 @@ SYNOPSIS (-[c|d|o|i|s|u|k|m])\* [-x |--exclude=] [-X |--exclude-from=] - [--exclude-per-directory=] + [--exclude-per-directory=] [--] []\* DESCRIPTION ----------- @@ -77,6 +77,13 @@ OPTIONS K to be killed ? other +--:: + Do not interpret any more arguments as options. + +:: + Files to show. If no files are given all files which match the other + specified criteria are shown. + Output ------ show files just outputs the filename unless '--stage' is specified in diff --git a/ls-files.c b/ls-files.c index 956be09350..f47114a168 100644 --- a/ls-files.c +++ b/ls-files.c @@ -530,7 +530,7 @@ static void verify_pathspec(void) static const char ls_files_usage[] = "git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed|modified])* " "[ --ignored ] [--exclude=] [--exclude-from=] " - "[ --exclude-per-directory= ]"; + "[ --exclude-per-directory= ] [--] []*"; int main(int argc, const char **argv) { @@ -544,6 +544,10 @@ int main(int argc, const char **argv) for (i = 1; i < argc; i++) { const char *arg = argv[i]; + if (!strcmp(arg, "--")) { + i++; + break; + } if (!strcmp(arg, "-z")) { line_terminator = 0; continue; diff --git a/t/t3002-ls-files-dashpath.sh b/t/t3002-ls-files-dashpath.sh new file mode 100755 index 0000000000..b42f1382bc --- /dev/null +++ b/t/t3002-ls-files-dashpath.sh @@ -0,0 +1,69 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-ls-files test (-- to terminate the path list). + +This test runs git-ls-files --others with the following on the +filesystem. + + path0 - a file + -foo - a file with a funny name. + -- - another file with a funny name. +' +. ./test-lib.sh + +test_expect_success \ + setup \ + 'echo frotz >path0 && + echo frotz >./-foo && + echo frotz >./--' + +test_expect_success \ + 'git-ls-files without path restriction.' \ + 'git-ls-files --others >output && + diff -u output - <output && + diff -u output - <output && + diff -u output - <output && + diff -u output - <output && + diff -u output - < Date: Thu, 29 Sep 2005 08:16:12 -0700 Subject: [PATCH 23/27] read-tree: --trivial This adds an option --trivial to restrict 3-way 'read-tree -m -u' to happen only if there is no file-level merging required. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- read-tree.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/read-tree.c b/read-tree.c index 390fe2f027..5fdf58d240 100644 --- a/read-tree.c +++ b/read-tree.c @@ -13,6 +13,8 @@ static int merge = 0; static int update = 0; static int index_only = 0; +static int nontrivial_merge = 0; +static int trivial_merges_only = 0; static int head_idx = -1; static int merge_size = 0; @@ -275,6 +277,9 @@ static int unpack_trees(merge_fn_t fn) if (unpack_trees_rec(posns, len, "", fn, &indpos)) return -1; + if (trivial_merges_only && nontrivial_merge) + die("Merge requires file-level merging"); + check_updates(active_cache, active_nr); return 0; } @@ -460,6 +465,8 @@ static int threeway_merge(struct cache_entry **stages) verify_uptodate(index); } + nontrivial_merge = 1; + /* #2, #3, #4, #6, #7, #9, #11. */ count = 0; if (!head_match || !remote_match) { @@ -629,6 +636,11 @@ int main(int argc, char **argv) continue; } + if (!strcmp(arg, "--trivial")) { + trivial_merges_only = 1; + continue; + } + /* "-m" stands for "merge", meaning we start in stage 1 */ if (!strcmp(arg, "-m")) { if (stage || merge) From f9d72413bcbf33ced45f12e17ef156abd73963f6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 2 Oct 2005 11:13:44 -0700 Subject: [PATCH 24/27] Handle really trivial case inside git-merge. Using Linus' --trivial option, this handles really trivial case inside git-merge itself, without using any strategy modules. A 'really trivial case' is: - we are merging one branch into the current branch; - there is only one merge base between the branches; - there is no file-level merge required. Signed-off-by: Junio C Hamano --- git-merge.sh | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/git-merge.sh b/git-merge.sh index 29e86c6953..d12a2a93b1 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -123,10 +123,30 @@ case "$#,$common" in dropsave exit 0 ;; -1,*) +1,?*"$LF"?*) # We are not doing octopus and not fast forward. Need a # real merge. ;; +1,*) + # We are not doing octopus, not fast forward, and have only + # one common. See if it is really trivial. + echo "Trying really trivial in-index merge..." + git-update-index --refresh 2>/dev/null + if git-read-tree --trivial -m -u $common $head "$1" && + result_tree=$(git-write-tree) + then + echo "Wonderful." + result_commit=$( + echo "$merge_msg" | + git-commit-tree $result_tree -p HEAD -p "$1" + ) || exit + git-update-ref HEAD $result_commit $head + summary $result_commit + dropsave + exit 0 + fi + echo "Nope." + ;; *) # An octopus. If we can reach all the remote we are up to date. up_to_date=t From a2775c2a410be4bec1e29ae78bccd3247829e2be Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Sun, 2 Oct 2005 13:42:57 -0600 Subject: [PATCH 25/27] [PATCH] Update git-clone documentation The documentation for git-clone is behind the actual command. I have been getting tired of reading the shell script to see what the arguments are so here is an update of the actual documentation. Signed-off-by: Eric Biederman Signed-off-by: Junio C Hamano --- Documentation/git-clone.txt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index bd53ef430d..7d713c7385 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -9,7 +9,7 @@ git-clone - Clones a repository. SYNOPSIS -------- -'git clone' [-l] [-u ] [-q] +'git clone' [-l [-s]] [-q] [-n] [-u ] DESCRIPTION ----------- @@ -17,6 +17,7 @@ Clones a repository into a newly created directory. OPTIONS ------- +--local:: -l:: When the repository to clone from is on a local machine, this flag bypasses normal "git aware" transport @@ -25,10 +26,22 @@ OPTIONS The files under .git/objects/ directory are hardlinked to save space when possible. +--shared:: +-s:: + When the repository to clone is on the local machine, + instead of using hard links automatically setup + .git/objects/info/alternatives to share the objects + with the source repository + +--quiet:: -q:: Operate quietly. This flag is passed to "rsync" and "git-clone-pack" commands when given. +-n:: + No checkout of HEAD is performed after the clone is complete. + +--upload-pack :: -u :: When given, and the repository to clone from is handled by 'git-clone-pack', '--exec=' is passed to From 91dd674e30ba0298e89c9be2657024805170c2ac Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 2 Oct 2005 12:56:31 -0700 Subject: [PATCH 26/27] GIT 0.99.8 GIT already did everything I wanted it to do since mid 0.99.7, and it has almost everything I want it to have now, except a couple of minor tweaks and enhancements. Signed-off-by: Junio C Hamano --- Makefile | 2 +- debian/changelog | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4d721f2ace..92f0bda5e7 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ # DEFINES += -DUSE_STDEV -GIT_VERSION = 0.99.7.GIT +GIT_VERSION = 0.99.8 CFLAGS = -g -O2 -Wall ALL_CFLAGS = $(CFLAGS) $(PLATFORM_DEFINES) $(DEFINES) diff --git a/debian/changelog b/debian/changelog index 128513a455..bebc1919ba 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +git-core (0.99.8-0) unstable; urgency=low + + * GIT 0.99.8 + + -- Junio C Hamano Sun, 2 Oct 2005 12:54:26 -0700 + git-core (0.99.7-0) unstable; urgency=low * GIT 0.99.7 From baaac6a7143ad07de62926421f8740cd640c241c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 2 Oct 2005 16:37:27 -0700 Subject: [PATCH 27/27] Post 0.99.8 master branch Signed-off-by: Junio C Hamano --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 92f0bda5e7..25fd5cde86 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ # DEFINES += -DUSE_STDEV -GIT_VERSION = 0.99.8 +GIT_VERSION = 0.99.8.GIT CFLAGS = -g -O2 -Wall ALL_CFLAGS = $(CFLAGS) $(PLATFORM_DEFINES) $(DEFINES)