From 203a2fe117070964a5bf7cc940a742cad7a19fca Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 7 Feb 2008 11:39:48 -0500 Subject: [PATCH 01/21] Allow callers of unpack_trees() to handle failure Return an error from unpack_trees() instead of calling die(), and exit with an error in read-tree, builtin-commit, and diff-lib. merge-recursive already expected an error return from unpack_trees, so it doesn't need to be changed. The merge function can return negative to abort. This will be used in builtin-checkout -m. Signed-off-by: Daniel Barkalow --- builtin-commit.c | 3 +- builtin-read-tree.c | 3 +- diff-lib.c | 6 ++-- unpack-trees.c | 85 +++++++++++++++++++++++++-------------------- 4 files changed, 56 insertions(+), 41 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index c63ff826fc..5b5b7c0f4d 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -200,7 +200,8 @@ static void create_base_index(void) die("failed to unpack HEAD tree object"); parse_tree(tree); init_tree_desc(&t, tree->buffer, tree->size); - unpack_trees(1, &t, &opts); + if (unpack_trees(1, &t, &opts)) + exit(128); /* We've already reported the error, finish dying */ } static char *prepare_index(int argc, const char **argv, const char *prefix) diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 5785401753..1d9d125b91 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -268,7 +268,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) parse_tree(tree); init_tree_desc(t+i, tree->buffer, tree->size); } - unpack_trees(nr_trees, t, &opts); + if (unpack_trees(nr_trees, t, &opts)) + return 128; /* * When reading only one tree (either the most basic form, diff --git a/diff-lib.c b/diff-lib.c index 03eaa7cef3..94b150e830 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -737,7 +737,8 @@ int run_diff_index(struct rev_info *revs, int cached) opts.unpack_data = revs; init_tree_desc(&t, tree->buffer, tree->size); - unpack_trees(1, &t, &opts); + if (unpack_trees(1, &t, &opts)) + exit(128); diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); @@ -789,6 +790,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) opts.unpack_data = &revs; init_tree_desc(&t, tree->buffer, tree->size); - unpack_trees(1, &t, &opts); + if (unpack_trees(1, &t, &opts)) + exit(128); return 0; } diff --git a/unpack-trees.c b/unpack-trees.c index ff46fd62fd..9e6587f661 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -219,6 +219,8 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, } #endif ret = o->fn(src, o, remove); + if (ret < 0) + return ret; #if DBRT_DEBUG > 1 printf("Added %d entries\n", ret); @@ -359,7 +361,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options } if (o->trivial_merges_only && o->nontrivial_merge) - die("Merge requires file-level merging"); + return error("Merge requires file-level merging"); check_updates(active_cache, active_nr, o); return 0; @@ -367,10 +369,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options /* Here come the merge functions */ -static void reject_merge(struct cache_entry *ce) +static int reject_merge(struct cache_entry *ce) { - die("Entry '%s' would be overwritten by merge. Cannot merge.", - ce->name); + return error("Entry '%s' would be overwritten by merge. Cannot merge.", + ce->name); } static int same(struct cache_entry *a, struct cache_entry *b) @@ -388,18 +390,18 @@ static int same(struct cache_entry *a, struct cache_entry *b) * When a CE gets turned into an unmerged entry, we * want it to be up-to-date */ -static void verify_uptodate(struct cache_entry *ce, +static int verify_uptodate(struct cache_entry *ce, struct unpack_trees_options *o) { struct stat st; if (o->index_only || o->reset) - return; + return 0; if (!lstat(ce->name, &st)) { unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID); if (!changed) - return; + return 0; /* * NEEDSWORK: the current default policy is to allow * submodule to be out of sync wrt the supermodule @@ -408,12 +410,12 @@ static void verify_uptodate(struct cache_entry *ce, * checked out. */ if (S_ISGITLINK(ce->ce_mode)) - return; + return 0; errno = 0; } if (errno == ENOENT) - return; - die("Entry '%s' not uptodate. Cannot merge.", ce->name); + return 0; + return error("Entry '%s' not uptodate. Cannot merge.", ce->name); } static void invalidate_ce_path(struct cache_entry *ce) @@ -479,7 +481,8 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, * ce->name is an entry in the subdirectory. */ if (!ce_stage(ce)) { - verify_uptodate(ce, o); + if (verify_uptodate(ce, o)) + return -1; ce->ce_flags |= CE_REMOVE; } cnt++; @@ -498,8 +501,8 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, d.exclude_per_dir = o->dir->exclude_per_dir; i = read_directory(&d, ce->name, pathbuf, namelen+1, NULL); if (i) - die("Updating '%s' would lose untracked files in it", - ce->name); + return error("Updating '%s' would lose untracked files in it", + ce->name); free(pathbuf); return cnt; } @@ -508,16 +511,16 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, * We do not want to remove or overwrite a working tree file that * is not tracked, unless it is ignored. */ -static void verify_absent(struct cache_entry *ce, const char *action, - struct unpack_trees_options *o) +static int verify_absent(struct cache_entry *ce, const char *action, + struct unpack_trees_options *o) { struct stat st; if (o->index_only || o->reset || !o->update) - return; + return 0; if (has_symlink_leading_path(ce->name, NULL)) - return; + return 0; if (!lstat(ce->name, &st)) { int cnt; @@ -527,7 +530,7 @@ static void verify_absent(struct cache_entry *ce, const char *action, * ce->name is explicitly excluded, so it is Ok to * overwrite it. */ - return; + return 0; if (S_ISDIR(st.st_mode)) { /* * We are checking out path "foo" and @@ -556,7 +559,7 @@ static void verify_absent(struct cache_entry *ce, const char *action, * deleted entries here. */ o->pos += cnt; - return; + return 0; } /* @@ -568,12 +571,13 @@ static void verify_absent(struct cache_entry *ce, const char *action, if (0 <= cnt) { struct cache_entry *ce = active_cache[cnt]; if (ce->ce_flags & CE_REMOVE) - return; + return 0; } - die("Untracked working tree file '%s' " - "would be %s by merge.", ce->name, action); + return error("Untracked working tree file '%s' " + "would be %s by merge.", ce->name, action); } + return 0; } static int merged_entry(struct cache_entry *merge, struct cache_entry *old, @@ -591,12 +595,14 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old, if (same(old, merge)) { memcpy(merge, old, offsetof(struct cache_entry, name)); } else { - verify_uptodate(old, o); + if (verify_uptodate(old, o)) + return -1; invalidate_ce_path(old); } } else { - verify_absent(merge, "overwritten", o); + if (verify_absent(merge, "overwritten", o)) + return -1; invalidate_ce_path(merge); } @@ -608,10 +614,12 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old, static int deleted_entry(struct cache_entry *ce, struct cache_entry *old, struct unpack_trees_options *o) { - if (old) - verify_uptodate(old, o); - else - verify_absent(ce, "removed", o); + if (old) { + if (verify_uptodate(old, o)) + return -1; + } else + if (verify_absent(ce, "removed", o)) + return -1; ce->ce_flags |= CE_REMOVE; add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); invalidate_ce_path(ce); @@ -699,7 +707,7 @@ int threeway_merge(struct cache_entry **stages, /* #14, #14ALT, #2ALT */ if (remote && !df_conflict_head && head_match && !remote_match) { if (index && !same(index, remote) && !same(index, head)) - reject_merge(index); + return reject_merge(index); return merged_entry(remote, index, o); } /* @@ -707,7 +715,7 @@ int threeway_merge(struct cache_entry **stages, * make sure that it matches head. */ if (index && !same(index, head)) { - reject_merge(index); + return reject_merge(index); } if (head) { @@ -758,8 +766,10 @@ int threeway_merge(struct cache_entry **stages, remove_entry(remove); if (index) return deleted_entry(index, index, o); - else if (ce && !head_deleted) - verify_absent(ce, "removed", o); + else if (ce && !head_deleted) { + if (verify_absent(ce, "removed", o)) + return -1; + } return 0; } /* @@ -775,7 +785,8 @@ int threeway_merge(struct cache_entry **stages, * conflict resolution files. */ if (index) { - verify_uptodate(index, o); + if (verify_uptodate(index, o)) + return -1; } remove_entry(remove); @@ -855,11 +866,11 @@ int twoway_merge(struct cache_entry **src, /* all other failures */ remove_entry(remove); if (oldtree) - reject_merge(oldtree); + return reject_merge(oldtree); if (current) - reject_merge(current); + return reject_merge(current); if (newtree) - reject_merge(newtree); + return reject_merge(newtree); return -1; } } @@ -886,7 +897,7 @@ int bind_merge(struct cache_entry **src, return error("Cannot do a bind merge of %d trees\n", o->merge_size); if (a && old) - die("Entry '%s' overlaps. Cannot bind.", a->name); + return error("Entry '%s' overlaps. Cannot bind.", a->name); if (!a) return keep_entry(old, o); else From 17e464266701bc1453f60a80cd71d8ba55b528e6 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 7 Feb 2008 11:39:52 -0500 Subject: [PATCH 02/21] Add flag to make unpack_trees() not print errors. (This applies only to errors where a plausible operation is impossible due to the particular data, not to errors resulting from misuse of the merge functions.) This will allow builtin-checkout to suppress merge errors if it's going to try more merging methods. Additionally, if unpack_trees() returns with an error, but without printing anything, it will roll back any changes to the index (by rereading the index, currently). This obviously could be done by the caller, but chances are that the caller would forget and debugging this is difficult. Also, future implementations may give unpack_trees() a more efficient way of undoing its changes than the caller could. Signed-off-by: Daniel Barkalow --- unpack-trees.c | 43 +++++++++++++++++++++++++++++-------------- unpack-trees.h | 1 + 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/unpack-trees.c b/unpack-trees.c index 9e6587f661..45f40c2775 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -356,12 +356,23 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options posns[i] = create_tree_entry_list(t+i); if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "", - o, &df_conflict_list)) + o, &df_conflict_list)) { + if (o->gently) { + discard_cache(); + read_cache(); + } return -1; + } } - if (o->trivial_merges_only && o->nontrivial_merge) - return error("Merge requires file-level merging"); + if (o->trivial_merges_only && o->nontrivial_merge) { + if (o->gently) { + discard_cache(); + read_cache(); + } + return o->gently ? -1 : + error("Merge requires file-level merging"); + } check_updates(active_cache, active_nr, o); return 0; @@ -415,7 +426,8 @@ static int verify_uptodate(struct cache_entry *ce, } if (errno == ENOENT) return 0; - return error("Entry '%s' not uptodate. Cannot merge.", ce->name); + return o->gently ? -1 : + error("Entry '%s' not uptodate. Cannot merge.", ce->name); } static void invalidate_ce_path(struct cache_entry *ce) @@ -501,8 +513,9 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, d.exclude_per_dir = o->dir->exclude_per_dir; i = read_directory(&d, ce->name, pathbuf, namelen+1, NULL); if (i) - return error("Updating '%s' would lose untracked files in it", - ce->name); + return o->gently ? -1 : + error("Updating '%s' would lose untracked files in it", + ce->name); free(pathbuf); return cnt; } @@ -574,8 +587,9 @@ static int verify_absent(struct cache_entry *ce, const char *action, return 0; } - return error("Untracked working tree file '%s' " - "would be %s by merge.", ce->name, action); + return o->gently ? -1 : + error("Untracked working tree file '%s' " + "would be %s by merge.", ce->name, action); } return 0; } @@ -707,7 +721,7 @@ int threeway_merge(struct cache_entry **stages, /* #14, #14ALT, #2ALT */ if (remote && !df_conflict_head && head_match && !remote_match) { if (index && !same(index, remote) && !same(index, head)) - return reject_merge(index); + return o->gently ? -1 : reject_merge(index); return merged_entry(remote, index, o); } /* @@ -715,7 +729,7 @@ int threeway_merge(struct cache_entry **stages, * make sure that it matches head. */ if (index && !same(index, head)) { - return reject_merge(index); + return o->gently ? -1 : reject_merge(index); } if (head) { @@ -866,11 +880,11 @@ int twoway_merge(struct cache_entry **src, /* all other failures */ remove_entry(remove); if (oldtree) - return reject_merge(oldtree); + return o->gently ? -1 : reject_merge(oldtree); if (current) - return reject_merge(current); + return o->gently ? -1 : reject_merge(current); if (newtree) - return reject_merge(newtree); + return o->gently ? -1 : reject_merge(newtree); return -1; } } @@ -897,7 +911,8 @@ int bind_merge(struct cache_entry **src, return error("Cannot do a bind merge of %d trees\n", o->merge_size); if (a && old) - return error("Entry '%s' overlaps. Cannot bind.", a->name); + return o->gently ? -1 : + error("Entry '%s' overlaps. Cannot bind.", a->name); if (!a) return keep_entry(old, o); else diff --git a/unpack-trees.h b/unpack-trees.h index 197a0044aa..83d1229532 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -16,6 +16,7 @@ struct unpack_trees_options { int trivial_merges_only; int verbose_update; int aggressive; + int gently; const char *prefix; int pos; struct dir_struct *dir; From b05c6dff8aa8af5a03717e8d863b911cede213a0 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 7 Feb 2008 11:39:56 -0500 Subject: [PATCH 03/21] Send unpack-trees debugging output to stderr This is to keep git-stash from getting confused if you're debugging unpack-trees. Signed-off-by: Daniel Barkalow --- unpack-trees.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/unpack-trees.c b/unpack-trees.c index 45f40c2775..f462a56ad2 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -122,13 +122,13 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, #if DBRT_DEBUG > 1 if (first) - printf("index %s\n", first); + fprintf(stderr, "index %s\n", first); #endif for (i = 0; i < len; i++) { if (!posns[i] || posns[i] == df_conflict_list) continue; #if DBRT_DEBUG > 1 - printf("%d %s\n", i + 1, posns[i]->name); + fprintf(stderr, "%d %s\n", i + 1, posns[i]->name); #endif if (!first || entcmp(first, firstdir, posns[i]->name, @@ -209,13 +209,13 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, int ret; #if DBRT_DEBUG > 1 - printf("%s:\n", first); + fprintf(stderr, "%s:\n", first); for (i = 0; i < src_size; i++) { - printf(" %d ", i); + fprintf(stderr, " %d ", i); if (src[i]) - printf("%s\n", sha1_to_hex(src[i]->sha1)); + fprintf(stderr, "%06x %s\n", src[i]->ce_mode, sha1_to_hex(src[i]->sha1)); else - printf("\n"); + fprintf(stderr, "\n"); } #endif ret = o->fn(src, o, remove); @@ -223,7 +223,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, return ret; #if DBRT_DEBUG > 1 - printf("Added %d entries\n", ret); + fprintf(stderr, "Added %d entries\n", ret); #endif o->pos += ret; } else { From 33ecf7eb6143143711ccaf828134beb2dacbe5c9 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 7 Feb 2008 11:39:59 -0500 Subject: [PATCH 04/21] Discard "deleted" cache entries after using them to update the working tree Way back in read-tree.c, we used a mode 0 cache entry to indicate that an entry had been deleted, so that the update code would remove the working tree file, and we would just skip it when writing out the index file afterward. These days, unpack_trees is a library function, and it is still leaving these entries in the active cache. Furthermore, unpack_trees doesn't correctly ignore those entries, and who knows what other code wouldn't expect them to be there, but just isn't yet called after a call to unpack_trees. To avoid having other code trip over these entries, have check_updates() remove them after it removes the working tree files. While we're at it, simplify the loop in check_updates(), and avoid passing global variables as parameters to check_updates(): there is only one call site anyway. Signed-off-by: Daniel Barkalow --- unpack-trees.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/unpack-trees.c b/unpack-trees.c index f462a56ad2..40d4130758 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -288,16 +288,16 @@ static void unlink_entry(char *name, char *last_symlink) } static struct checkout state; -static void check_updates(struct cache_entry **src, int nr, - struct unpack_trees_options *o) +static void check_updates(struct unpack_trees_options *o) { unsigned cnt = 0, total = 0; struct progress *progress = NULL; char last_symlink[PATH_MAX]; + int i; if (o->update && o->verbose_update) { - for (total = cnt = 0; cnt < nr; cnt++) { - struct cache_entry *ce = src[cnt]; + for (total = cnt = 0; cnt < active_nr; cnt++) { + struct cache_entry *ce = active_cache[cnt]; if (ce->ce_flags & (CE_UPDATE | CE_REMOVE)) total++; } @@ -308,14 +308,16 @@ static void check_updates(struct cache_entry **src, int nr, } *last_symlink = '\0'; - while (nr--) { - struct cache_entry *ce = *src++; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; if (ce->ce_flags & (CE_UPDATE | CE_REMOVE)) display_progress(progress, ++cnt); if (ce->ce_flags & CE_REMOVE) { if (o->update) unlink_entry(ce->name, last_symlink); + remove_cache_entry_at(i); + i--; continue; } if (ce->ce_flags & CE_UPDATE) { @@ -374,7 +376,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options error("Merge requires file-level merging"); } - check_updates(active_cache, active_nr, o); + check_updates(o); return 0; } From 4e7c4571b8b31d6a09de2826361540caa76d3526 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 7 Feb 2008 11:40:02 -0500 Subject: [PATCH 05/21] Add "skip_unmerged" option to unpack_trees. This option allows the caller to reset everything that isn't unmerged, leaving the unmerged things to be resolved. If, after a merge of "working" and "HEAD", this is used with "HEAD" (reset, !update), the result will be that all of the changes from "local" are in the working tree but not added to the index (either with the index clean but unchanged, or with the index unmerged, depending on whether there are conflicts). This will be used in checkout -m. Signed-off-by: Daniel Barkalow --- unpack-trees.c | 20 +++++++++++++++++--- unpack-trees.h | 1 + 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/unpack-trees.c b/unpack-trees.c index 40d4130758..470fa02e08 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -85,6 +85,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, int any_dirs = 0; char *cache_name; int ce_stage; + int skip_entry = 0; /* Find the first name in the input. */ @@ -153,6 +154,8 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, any_files = 1; src[0] = active_cache[o->pos]; remove = o->pos; + if (o->skip_unmerged && ce_stage(src[0])) + skip_entry = 1; } for (i = 0; i < len; i++) { @@ -181,6 +184,12 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, continue; } + if (skip_entry) { + subposns[i] = df_conflict_list; + posns[i] = posns[i]->next; + continue; + } + if (!o->merge) ce_stage = 0; else if (i + 1 < o->head_idx) @@ -205,7 +214,13 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, posns[i] = posns[i]->next; } if (any_files) { - if (o->merge) { + if (skip_entry) { + o->pos++; + while (o->pos < active_nr && + !strcmp(active_cache[o->pos]->name, + src[0]->name)) + o->pos++; + } else if (o->merge) { int ret; #if DBRT_DEBUG > 1 @@ -730,9 +745,8 @@ int threeway_merge(struct cache_entry **stages, * If we have an entry in the index cache, then we want to * make sure that it matches head. */ - if (index && !same(index, head)) { + if (index && !same(index, head)) return o->gently ? -1 : reject_merge(index); - } if (head) { /* #5ALT, #15 */ diff --git a/unpack-trees.h b/unpack-trees.h index 83d1229532..a2df544d04 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -16,6 +16,7 @@ struct unpack_trees_options { int trivial_merges_only; int verbose_update; int aggressive; + int skip_unmerged; int gently; const char *prefix; int pos; From e1b3a2cad79a8138d18593c6eb3c46906ad2ee42 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 7 Feb 2008 11:40:05 -0500 Subject: [PATCH 06/21] Build-in merge-recursive This makes write_tree_from_memory(), which writes the active cache as a tree and returns the struct tree for it, available to other code. It also makes available merge_trees(), which does the internal merge of two trees with a known base, and merge_recursive(), which does the recursive internal merge of two commits with a list of common ancestors. The first two of these will be used by checkout -m, and the third is presumably useful in general, although the implementation of checkout -m which entirely matches the behavior of the shell version does not use it (since it ignores the difference of ancestry between the old branch and the new branch). Signed-off-by: Daniel Barkalow --- Makefile | 2 +- ...e-recursive.c => builtin-merge-recursive.c | 44 ++++++++++--------- builtin.h | 1 + git.c | 1 + merge-recursive.h | 20 +++++++++ 5 files changed, 46 insertions(+), 22 deletions(-) rename merge-recursive.c => builtin-merge-recursive.c (98%) create mode 100644 merge-recursive.h diff --git a/Makefile b/Makefile index 5aac0c0c87..6f08bccbcf 100644 --- a/Makefile +++ b/Makefile @@ -256,7 +256,6 @@ PROGRAMS = \ git-upload-pack$X \ git-pack-redundant$X git-var$X \ git-merge-tree$X git-imap-send$X \ - git-merge-recursive$X \ $(EXTRA_PROGRAMS) # Empty... @@ -360,6 +359,7 @@ BUILTIN_OBJS = \ builtin-merge-base.o \ builtin-merge-file.o \ builtin-merge-ours.o \ + builtin-merge-recursive.o \ builtin-mv.o \ builtin-name-rev.o \ builtin-pack-objects.o \ diff --git a/merge-recursive.c b/builtin-merge-recursive.c similarity index 98% rename from merge-recursive.c rename to builtin-merge-recursive.c index bdf03b1f1f..45d4601533 100644 --- a/merge-recursive.c +++ b/builtin-merge-recursive.c @@ -7,6 +7,7 @@ #include "cache-tree.h" #include "commit.h" #include "blob.h" +#include "builtin.h" #include "tree-walk.h" #include "diff.h" #include "diffcore.h" @@ -17,6 +18,7 @@ #include "xdiff-interface.h" #include "interpolate.h" #include "attr.h" +#include "merge-recursive.h" static int subtree_merge; @@ -232,7 +234,7 @@ static int unmerged_index(void) return 0; } -static struct tree *git_write_tree(void) +struct tree *write_tree_from_memory(void) { struct tree *result = NULL; @@ -1495,12 +1497,12 @@ static int process_entry(const char *path, struct stage_data *entry, return clean_merge; } -static int merge_trees(struct tree *head, - struct tree *merge, - struct tree *common, - const char *branch1, - const char *branch2, - struct tree **result) +int merge_trees(struct tree *head, + struct tree *merge, + struct tree *common, + const char *branch1, + const char *branch2, + struct tree **result) { int code, clean; @@ -1552,7 +1554,7 @@ static int merge_trees(struct tree *head, clean = 1; if (index_only) - *result = git_write_tree(); + *result = write_tree_from_memory(); return clean; } @@ -1572,12 +1574,12 @@ static struct commit_list *reverse_commit_list(struct commit_list *list) * Merge the commits h1 and h2, return the resulting virtual * commit object and a flag indicating the cleanness of the merge. */ -static int merge(struct commit *h1, - struct commit *h2, - const char *branch1, - const char *branch2, - struct commit_list *ca, - struct commit **result) +int merge_recursive(struct commit *h1, + struct commit *h2, + const char *branch1, + const char *branch2, + struct commit_list *ca, + struct commit **result) { struct commit_list *iter; struct commit *merged_common_ancestors; @@ -1622,11 +1624,11 @@ static int merge(struct commit *h1, * "conflicts" were already resolved. */ discard_cache(); - merge(merged_common_ancestors, iter->item, - "Temporary merge branch 1", - "Temporary merge branch 2", - NULL, - &merged_common_ancestors); + merge_recursive(merged_common_ancestors, iter->item, + "Temporary merge branch 1", + "Temporary merge branch 2", + NULL, + &merged_common_ancestors); call_depth--; if (!merged_common_ancestors) @@ -1695,7 +1697,7 @@ static int merge_config(const char *var, const char *value) return git_default_config(var, value); } -int main(int argc, char *argv[]) +int cmd_merge_recursive(int argc, const char **argv, const char *prefix) { static const char *bases[20]; static unsigned bases_count = 0; @@ -1749,7 +1751,7 @@ int main(int argc, char *argv[]) struct commit *ancestor = get_ref(bases[i]); ca = commit_list_insert(ancestor, &ca); } - clean = merge(h1, h2, branch1, branch2, ca, &result); + clean = merge_recursive(h1, h2, branch1, branch2, ca, &result); if (active_cache_changed && (write_cache(index_fd, active_cache, active_nr) || diff --git a/builtin.h b/builtin.h index cb675c4d7a..428160d0e4 100644 --- a/builtin.h +++ b/builtin.h @@ -57,6 +57,7 @@ extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); extern int cmd_merge_base(int argc, const char **argv, const char *prefix); extern int cmd_merge_ours(int argc, const char **argv, const char *prefix); extern int cmd_merge_file(int argc, const char **argv, const char *prefix); +extern int cmd_merge_recursive(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); diff --git a/git.c b/git.c index 15fec8974a..114ea75eef 100644 --- a/git.c +++ b/git.c @@ -330,6 +330,7 @@ static void handle_internal_command(int argc, const char **argv) { "merge-base", cmd_merge_base, RUN_SETUP }, { "merge-file", cmd_merge_file }, { "merge-ours", cmd_merge_ours, RUN_SETUP }, + { "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE }, { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, diff --git a/merge-recursive.h b/merge-recursive.h new file mode 100644 index 0000000000..f37630a8ad --- /dev/null +++ b/merge-recursive.h @@ -0,0 +1,20 @@ +#ifndef MERGE_RECURSIVE_H +#define MERGE_RECURSIVE_H + +int merge_recursive(struct commit *h1, + struct commit *h2, + const char *branch1, + const char *branch2, + struct commit_list *ancestors, + struct commit **result); + +int merge_trees(struct tree *head, + struct tree *merge, + struct tree *common, + const char *branch1, + const char *branch2, + struct tree **result); + +struct tree *write_tree_from_memory(void); + +#endif From e496c00348140e73bdd202443df52192f6928541 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 7 Feb 2008 11:40:08 -0500 Subject: [PATCH 07/21] Move create_branch into a library file You can also create branches, in exactly the same way, with checkout -b. This introduces branch.{c,h} library files for doing porcelain-level operations on branches (such as creating them with their appropriate default configuration). Signed-off-by: Daniel Barkalow --- Makefile | 2 +- branch.c | 140 +++++++++++++++++++++++++++++++++++++++++++++++ branch.h | 8 +++ builtin-branch.c | 138 +--------------------------------------------- 4 files changed, 151 insertions(+), 137 deletions(-) create mode 100644 branch.c create mode 100644 branch.h diff --git a/Makefile b/Makefile index 6f08bccbcf..5cfadfd307 100644 --- a/Makefile +++ b/Makefile @@ -317,7 +317,7 @@ LIB_OBJS = \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \ convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \ - transport.o bundle.o walker.o parse-options.o ws.o archive.o + transport.o bundle.o walker.o parse-options.o ws.o archive.o branch.o BUILTIN_OBJS = \ builtin-add.o \ diff --git a/branch.c b/branch.c new file mode 100644 index 0000000000..45ab820c4a --- /dev/null +++ b/branch.c @@ -0,0 +1,140 @@ +#include "cache.h" +#include "branch.h" +#include "refs.h" +#include "remote.h" +#include "commit.h" + +struct tracking { + struct refspec spec; + char *src; + const char *remote; + int matches; +}; + +static int find_tracked_branch(struct remote *remote, void *priv) +{ + struct tracking *tracking = priv; + + if (!remote_find_tracking(remote, &tracking->spec)) { + if (++tracking->matches == 1) { + tracking->src = tracking->spec.src; + tracking->remote = remote->name; + } else { + free(tracking->spec.src); + if (tracking->src) { + free(tracking->src); + tracking->src = NULL; + } + } + tracking->spec.src = NULL; + } + + return 0; +} + +/* + * This is called when new_ref is branched off of orig_ref, and tries + * to infer the settings for branch..{remote,merge} from the + * config. + */ +static int setup_tracking(const char *new_ref, const char *orig_ref) +{ + char key[1024]; + struct tracking tracking; + + if (strlen(new_ref) > 1024 - 7 - 7 - 1) + return error("Tracking not set up: name too long: %s", + new_ref); + + memset(&tracking, 0, sizeof(tracking)); + tracking.spec.dst = (char *)orig_ref; + if (for_each_remote(find_tracked_branch, &tracking) || + !tracking.matches) + return 1; + + if (tracking.matches > 1) + return error("Not tracking: ambiguous information for ref %s", + orig_ref); + + if (tracking.matches == 1) { + sprintf(key, "branch.%s.remote", new_ref); + git_config_set(key, tracking.remote ? tracking.remote : "."); + sprintf(key, "branch.%s.merge", new_ref); + git_config_set(key, tracking.src); + free(tracking.src); + printf("Branch %s set up to track remote branch %s.\n", + new_ref, orig_ref); + } + + return 0; +} + +void create_branch(const char *head, + const char *name, const char *start_name, + int force, int reflog, int track) +{ + struct ref_lock *lock; + struct commit *commit; + unsigned char sha1[20]; + char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20]; + int forcing = 0; + + snprintf(ref, sizeof ref, "refs/heads/%s", name); + if (check_ref_format(ref)) + die("'%s' is not a valid branch name.", name); + + if (resolve_ref(ref, sha1, 1, NULL)) { + if (!force) + die("A branch named '%s' already exists.", name); + else if (!is_bare_repository() && !strcmp(head, name)) + die("Cannot force update the current branch."); + forcing = 1; + } + + real_ref = NULL; + if (get_sha1(start_name, sha1)) + die("Not a valid object name: '%s'.", start_name); + + switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) { + case 0: + /* Not branching from any existing branch */ + real_ref = NULL; + break; + case 1: + /* Unique completion -- good */ + break; + default: + die("Ambiguous object name: '%s'.", start_name); + break; + } + + if ((commit = lookup_commit_reference(sha1)) == NULL) + die("Not a valid branch point: '%s'.", start_name); + hashcpy(sha1, commit->object.sha1); + + lock = lock_any_ref_for_update(ref, NULL, 0); + if (!lock) + die("Failed to lock ref for update: %s.", strerror(errno)); + + if (reflog) + log_all_ref_updates = 1; + + if (forcing) + snprintf(msg, sizeof msg, "branch: Reset from %s", + start_name); + else + snprintf(msg, sizeof msg, "branch: Created from %s", + start_name); + + /* When branching off a remote branch, set up so that git-pull + automatically merges from there. So far, this is only done for + remotes registered via .git/config. */ + if (real_ref && track) + setup_tracking(name, real_ref); + + if (write_ref_sha1(lock, sha1, msg) < 0) + die("Failed to write ref: %s.", strerror(errno)); + + if (real_ref) + free(real_ref); +} diff --git a/branch.h b/branch.h new file mode 100644 index 0000000000..8bcd9dc217 --- /dev/null +++ b/branch.h @@ -0,0 +1,8 @@ +#ifndef BRANCH_H +#define BRANCH_H + +void create_branch(const char *head, + const char *name, const char *start_name, + int force, int reflog, int track); + +#endif diff --git a/builtin-branch.c b/builtin-branch.c index 089cae5929..1e0c9dea3f 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -12,6 +12,7 @@ #include "builtin.h" #include "remote.h" #include "parse-options.h" +#include "branch.h" static const char * const builtin_branch_usage[] = { "git-branch [options] [-r | -a]", @@ -356,141 +357,6 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str free_ref_list(&ref_list); } -struct tracking { - struct refspec spec; - char *src; - const char *remote; - int matches; -}; - -static int find_tracked_branch(struct remote *remote, void *priv) -{ - struct tracking *tracking = priv; - - if (!remote_find_tracking(remote, &tracking->spec)) { - if (++tracking->matches == 1) { - tracking->src = tracking->spec.src; - tracking->remote = remote->name; - } else { - free(tracking->spec.src); - if (tracking->src) { - free(tracking->src); - tracking->src = NULL; - } - } - tracking->spec.src = NULL; - } - - return 0; -} - - -/* - * This is called when new_ref is branched off of orig_ref, and tries - * to infer the settings for branch..{remote,merge} from the - * config. - */ -static int setup_tracking(const char *new_ref, const char *orig_ref) -{ - char key[1024]; - struct tracking tracking; - - if (strlen(new_ref) > 1024 - 7 - 7 - 1) - return error("Tracking not set up: name too long: %s", - new_ref); - - memset(&tracking, 0, sizeof(tracking)); - tracking.spec.dst = (char *)orig_ref; - if (for_each_remote(find_tracked_branch, &tracking) || - !tracking.matches) - return 1; - - if (tracking.matches > 1) - return error("Not tracking: ambiguous information for ref %s", - orig_ref); - - if (tracking.matches == 1) { - sprintf(key, "branch.%s.remote", new_ref); - git_config_set(key, tracking.remote ? tracking.remote : "."); - sprintf(key, "branch.%s.merge", new_ref); - git_config_set(key, tracking.src); - free(tracking.src); - printf("Branch %s set up to track remote branch %s.\n", - new_ref, orig_ref); - } - - return 0; -} - -static void create_branch(const char *name, const char *start_name, - int force, int reflog, int track) -{ - struct ref_lock *lock; - struct commit *commit; - unsigned char sha1[20]; - char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20]; - int forcing = 0; - - snprintf(ref, sizeof ref, "refs/heads/%s", name); - if (check_ref_format(ref)) - die("'%s' is not a valid branch name.", name); - - if (resolve_ref(ref, sha1, 1, NULL)) { - if (!force) - die("A branch named '%s' already exists.", name); - else if (!is_bare_repository() && !strcmp(head, name)) - die("Cannot force update the current branch."); - forcing = 1; - } - - real_ref = NULL; - if (get_sha1(start_name, sha1)) - die("Not a valid object name: '%s'.", start_name); - - switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) { - case 0: - /* Not branching from any existing branch */ - real_ref = NULL; - break; - case 1: - /* Unique completion -- good */ - break; - default: - die("Ambiguous object name: '%s'.", start_name); - break; - } - - if ((commit = lookup_commit_reference(sha1)) == NULL) - die("Not a valid branch point: '%s'.", start_name); - hashcpy(sha1, commit->object.sha1); - - lock = lock_any_ref_for_update(ref, NULL, 0); - if (!lock) - die("Failed to lock ref for update: %s.", strerror(errno)); - - if (reflog) - log_all_ref_updates = 1; - - if (forcing) - snprintf(msg, sizeof msg, "branch: Reset from %s", - start_name); - else - snprintf(msg, sizeof msg, "branch: Created from %s", - start_name); - - /* When branching off a remote branch, set up so that git-pull - automatically merges from there. So far, this is only done for - remotes registered via .git/config. */ - if (real_ref && track) - setup_tracking(name, real_ref); - - if (write_ref_sha1(lock, sha1, msg) < 0) - die("Failed to write ref: %s.", strerror(errno)); - - if (real_ref) - free(real_ref); -} - static void rename_branch(const char *oldname, const char *newname, int force) { char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100]; @@ -611,7 +477,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) else if (rename && (argc == 2)) rename_branch(argv[0], argv[1], rename > 1); else if (argc <= 2) - create_branch(argv[0], (argc == 2) ? argv[1] : head, + create_branch(head, argv[0], (argc == 2) ? argv[1] : head, force_create, reflog, track); else usage_with_options(builtin_branch_usage, options); From 922d87f92f79d76ee1d9027d4a0f8a0cf36d5340 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 7 Feb 2008 11:40:11 -0500 Subject: [PATCH 08/21] Use diff -u instead of diff in t7201 If the test failed, it was giving really unclear ed script output. Instead, give a diff that sort of suggests the problem. Also replaces the use of "git diff" for this purpose with "diff -u". Signed-off-by: Daniel Barkalow --- t/t7201-co.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 73d8a00e2c..3d8e01c030 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -83,13 +83,13 @@ test_expect_success "checkout with unrelated dirty tree without -m" ' fill 0 1 2 3 4 5 6 7 8 >same && cp same kept git checkout side >messages && - git diff same kept + diff -u same kept (cat > messages.expect <expect.master && git diff --name-status master >current.master && - diff expect.master current.master && + diff -u expect.master current.master && fill "M one" >expect.side && git diff --name-status side >current.side && - diff expect.side current.side && + diff -u expect.side current.side && : >expect.index && git diff --cached >current.index && - diff expect.index current.index + diff -u expect.index current.index ' test_expect_success "checkout -m with dirty tree, renamed" ' @@ -143,7 +143,7 @@ test_expect_success "checkout -m with dirty tree, renamed" ' git checkout -m renamer && fill 1 3 4 5 7 8 >expect && - diff expect uno && + diff -u expect uno && ! test -f one && git diff --cached >current && ! test -s current @@ -168,7 +168,7 @@ test_expect_success 'checkout -m with merge conflict' ' git diff master:one :3:uno | sed -e "1,/^@@/d" -e "/^ /d" -e "s/^-/d/" -e "s/^+/a/" >current && fill d2 aT d7 aS >expect && - diff current expect && + diff -u current expect && git diff --cached two >current && ! test -s current ' @@ -185,7 +185,7 @@ If you want to create a new branch from this checkout, you may do so HEAD is now at 7329388... Initial A one, A two EOF ) && - git diff messages.expect messages && + diff -u messages.expect messages && H=$(git rev-parse --verify HEAD) && M=$(git show-ref -s --verify refs/heads/master) && test "z$H" = "z$M" && From 94a5728cfb593d80164620f8fa7e1ef322ad0025 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 7 Feb 2008 11:40:13 -0500 Subject: [PATCH 09/21] Library function to check for unmerged index entries It's small, but it was in three places already, so it should be in the library. Signed-off-by: Daniel Barkalow --- builtin-merge-recursive.c | 15 ++------------- builtin-reset.c | 14 +------------- cache.h | 2 ++ read-cache.c | 10 ++++++++++ 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 45d4601533..558a58e4d3 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -223,22 +223,11 @@ static int git_merge_trees(int index_only, return rc; } -static int unmerged_index(void) -{ - int i; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (ce_stage(ce)) - return 1; - } - return 0; -} - struct tree *write_tree_from_memory(void) { struct tree *result = NULL; - if (unmerged_index()) { + if (unmerged_cache()) { int i; output(0, "There are unmerged index entries:"); for (i = 0; i < active_nr; i++) { @@ -1524,7 +1513,7 @@ int merge_trees(struct tree *head, sha1_to_hex(head->object.sha1), sha1_to_hex(merge->object.sha1)); - if (unmerged_index()) { + if (unmerged_cache()) { struct path_list *entries, *re_head, *re_merge; int i; path_list_clear(¤t_file_set, 1); diff --git a/builtin-reset.c b/builtin-reset.c index 7ee811f0b8..3bec06bc8e 100644 --- a/builtin-reset.c +++ b/builtin-reset.c @@ -44,18 +44,6 @@ static inline int is_merge(void) return !access(git_path("MERGE_HEAD"), F_OK); } -static int unmerged_files(void) -{ - int i; - read_cache(); - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (ce_stage(ce)) - return 1; - } - return 0; -} - static int reset_index_file(const unsigned char *sha1, int is_hard_reset) { int i = 0; @@ -250,7 +238,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) * at all, but requires them in a good order. Other resets reset * the index file to the tree object we are switching to. */ if (reset_type == SOFT) { - if (is_merge() || unmerged_files()) + if (is_merge() || read_cache() < 0 || unmerged_cache()) die("Cannot do a soft reset in the middle of a merge."); } else if (reset_index_file(sha1, (reset_type == HARD))) diff --git a/cache.h b/cache.h index e4aeff07d1..888895a969 100644 --- a/cache.h +++ b/cache.h @@ -208,6 +208,7 @@ extern struct index_state the_index; #define read_cache_from(path) read_index_from(&the_index, (path)) #define write_cache(newfd, cache, entries) write_index(&the_index, (newfd)) #define discard_cache() discard_index(&the_index) +#define unmerged_cache() unmerged_index(&the_index) #define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen)) #define add_cache_entry(ce, option) add_index_entry(&the_index, (ce), (option)) #define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos)) @@ -302,6 +303,7 @@ extern int read_index(struct index_state *); extern int read_index_from(struct index_state *, const char *path); extern int write_index(struct index_state *, int newfd); extern int discard_index(struct index_state *); +extern int unmerged_index(struct index_state *); extern int verify_path(const char *path); extern int index_name_exists(struct index_state *istate, const char *name, int namelen); extern int index_name_pos(struct index_state *, const char *name, int namelen); diff --git a/read-cache.c b/read-cache.c index e45f4b3d61..22d7b46245 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1176,6 +1176,16 @@ int discard_index(struct index_state *istate) return 0; } +int unmerged_index(struct index_state *istate) +{ + int i; + for (i = 0; i < istate->cache_nr; i++) { + if (ce_stage(istate->cache[i])) + return 1; + } + return 0; +} + #define WRITE_BUFFER_SIZE 8192 static unsigned char write_buffer[WRITE_BUFFER_SIZE]; static unsigned long write_buffer_len; From c369e7b805f927bb87fcf345dd19a55c8b9e6b8e Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 7 Feb 2008 11:40:16 -0500 Subject: [PATCH 10/21] Move code to clean up after a branch change to branch.c Signed-off-by: Daniel Barkalow --- branch.c | 8 ++++++++ branch.h | 20 ++++++++++++++++++-- builtin-reset.c | 6 ++---- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/branch.c b/branch.c index 45ab820c4a..1fc8788897 100644 --- a/branch.c +++ b/branch.c @@ -138,3 +138,11 @@ void create_branch(const char *head, if (real_ref) free(real_ref); } + +void remove_branch_state(void) +{ + unlink(git_path("MERGE_HEAD")); + unlink(git_path("rr-cache/MERGE_RR")); + unlink(git_path("MERGE_MSG")); + unlink(git_path("SQUASH_MSG")); +} diff --git a/branch.h b/branch.h index 8bcd9dc217..d30abe0369 100644 --- a/branch.h +++ b/branch.h @@ -1,8 +1,24 @@ #ifndef BRANCH_H #define BRANCH_H -void create_branch(const char *head, - const char *name, const char *start_name, +/* Functions for acting on the information about branches. */ + +/* + * Creates a new branch, where head is the branch currently checked + * out, name is the new branch name, start_name is the name of the + * existing branch that the new branch should start from, force + * enables overwriting an existing (non-head) branch, reflog creates a + * reflog for the branch, and track causes the new branch to be + * configured to merge the remote branch that start_name is a tracking + * branch for (if any). + */ +void create_branch(const char *head, const char *name, const char *start_name, int force, int reflog, int track); +/* + * Remove information about the state of working on the current + * branch. (E.g., MERGE_HEAD) + */ +void remove_branch_state(void); + #endif diff --git a/builtin-reset.c b/builtin-reset.c index 3bec06bc8e..af0037ec6e 100644 --- a/builtin-reset.c +++ b/builtin-reset.c @@ -16,6 +16,7 @@ #include "diff.h" #include "diffcore.h" #include "tree.h" +#include "branch.h" static const char builtin_reset_usage[] = "git-reset [--mixed | --soft | --hard] [-q] [] [ [--] ...]"; @@ -270,10 +271,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) break; } - unlink(git_path("MERGE_HEAD")); - unlink(git_path("rr-cache/MERGE_RR")); - unlink(git_path("MERGE_MSG")); - unlink(git_path("SQUASH_MSG")); + remove_branch_state(); free(reflog_action); From 782c2d65c24066a5d83453efb52763bc34c10f81 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 7 Feb 2008 11:40:23 -0500 Subject: [PATCH 11/21] Build in checkout The only differences in behavior should be: - git checkout -m with non-trivial merging won't print out merge-recursive messages (see the change in t7201-co.sh) - git checkout -- paths... will give a sensible error message if HEAD is invalid as a commit. - some intermediate states which were written to disk in the shell version (in particular, index states) are only kept in memory in this version, and therefore these can no longer be revealed by later write operations becoming impossible. - when we change branches, we discard MERGE_MSG, SQUASH_MSG, and rr-cache/MERGE_RR, like reset always has. I'm not 100% sure I got the merge recursive setup exactly right; the base for a non-trivial merge in the shell code doesn't seem theoretically justified to me, but I tried to match it anyway, and the tests all pass this way. Other than these items, the results should be identical to the shell version, so far as I can tell. [jc: squashed lock-file fix from Dscho in] Signed-off-by: Daniel Barkalow Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Makefile | 3 +- builtin-checkout.c | 477 ++++++++++++++++++ builtin.h | 1 + .../examples/git-checkout.sh | 0 git.c | 1 + t/t7201-co.sh | 7 - 6 files changed, 481 insertions(+), 8 deletions(-) create mode 100644 builtin-checkout.c rename git-checkout.sh => contrib/examples/git-checkout.sh (100%) diff --git a/Makefile b/Makefile index 5cfadfd307..90c0dd8c43 100644 --- a/Makefile +++ b/Makefile @@ -217,7 +217,7 @@ BASIC_CFLAGS = BASIC_LDFLAGS = SCRIPT_SH = \ - git-bisect.sh git-checkout.sh \ + git-bisect.sh \ git-clone.sh \ git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \ git-pull.sh git-rebase.sh git-rebase--interactive.sh \ @@ -329,6 +329,7 @@ BUILTIN_OBJS = \ builtin-bundle.o \ builtin-cat-file.o \ builtin-check-attr.o \ + builtin-checkout.o \ builtin-checkout-index.o \ builtin-check-ref-format.o \ builtin-clean.o \ diff --git a/builtin-checkout.c b/builtin-checkout.c new file mode 100644 index 0000000000..59a0ef4ec9 --- /dev/null +++ b/builtin-checkout.c @@ -0,0 +1,477 @@ +#include "cache.h" +#include "builtin.h" +#include "parse-options.h" +#include "refs.h" +#include "commit.h" +#include "tree.h" +#include "tree-walk.h" +#include "unpack-trees.h" +#include "dir.h" +#include "run-command.h" +#include "merge-recursive.h" +#include "branch.h" +#include "diff.h" +#include "revision.h" + +static const char * const checkout_usage[] = { + "git checkout [options] ", + "git checkout [options] [] -- ...", + NULL, +}; + +static int post_checkout_hook(struct commit *old, struct commit *new, + int changed) +{ + struct child_process proc; + const char *name = git_path("hooks/post-checkout"); + const char *argv[5]; + + if (access(name, X_OK) < 0) + return 0; + + memset(&proc, 0, sizeof(proc)); + argv[0] = name; + argv[1] = xstrdup(sha1_to_hex(old->object.sha1)); + argv[2] = xstrdup(sha1_to_hex(new->object.sha1)); + argv[3] = changed ? "1" : "0"; + argv[4] = NULL; + proc.argv = argv; + proc.no_stdin = 1; + proc.stdout_to_stderr = 1; + return run_command(&proc); +} + +static int update_some(const unsigned char *sha1, const char *base, int baselen, + const char *pathname, unsigned mode, int stage) +{ + int len; + struct cache_entry *ce; + + if (S_ISGITLINK(mode)) + return 0; + + if (S_ISDIR(mode)) + return READ_TREE_RECURSIVE; + + len = baselen + strlen(pathname); + ce = xcalloc(1, cache_entry_size(len)); + hashcpy(ce->sha1, sha1); + memcpy(ce->name, base, baselen); + memcpy(ce->name + baselen, pathname, len - baselen); + ce->ce_flags = create_ce_flags(len, 0); + ce->ce_mode = create_ce_mode(mode); + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); + return 0; +} + +static int read_tree_some(struct tree *tree, const char **pathspec) +{ + int newfd; + struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); + newfd = hold_locked_index(lock_file, 1); + read_cache(); + + read_tree_recursive(tree, "", 0, 0, pathspec, update_some); + + if (write_cache(newfd, active_cache, active_nr) || + commit_locked_index(lock_file)) + die("unable to write new index file"); + + /* update the index with the given tree's info + * for all args, expanding wildcards, and exit + * with any non-zero return code. + */ + return 0; +} + +static int checkout_paths(const char **pathspec) +{ + int pos; + struct checkout state; + static char *ps_matched; + unsigned char rev[20]; + int flag; + struct commit *head; + + for (pos = 0; pathspec[pos]; pos++) + ; + ps_matched = xcalloc(1, pos); + + for (pos = 0; pos < active_nr; pos++) { + struct cache_entry *ce = active_cache[pos]; + pathspec_match(pathspec, ps_matched, ce->name, 0); + } + + if (report_path_error(ps_matched, pathspec, 0)) + return 1; + + memset(&state, 0, sizeof(state)); + state.force = 1; + state.refresh_cache = 1; + for (pos = 0; pos < active_nr; pos++) { + struct cache_entry *ce = active_cache[pos]; + if (pathspec_match(pathspec, NULL, ce->name, 0)) { + checkout_entry(ce, &state, NULL); + } + } + + resolve_ref("HEAD", rev, 0, &flag); + head = lookup_commit_reference_gently(rev, 1); + + return post_checkout_hook(head, head, 0); +} + +static void show_local_changes(struct object *head) +{ + struct rev_info rev; + /* I think we want full paths, even if we're in a subdirectory. */ + init_revisions(&rev, NULL); + rev.abbrev = 0; + rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS; + add_pending_object(&rev, head, NULL); + run_diff_index(&rev, 0); +} + +static void describe_detached_head(char *msg, struct commit *commit) +{ + struct strbuf sb; + strbuf_init(&sb, 0); + parse_commit(commit); + pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, 0, "", "", 0, 0); + fprintf(stderr, "%s %s... %s\n", msg, + find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf); + strbuf_release(&sb); +} + +static int reset_to_new(struct tree *tree, int quiet) +{ + struct unpack_trees_options opts; + struct tree_desc tree_desc; + memset(&opts, 0, sizeof(opts)); + opts.head_idx = -1; + opts.update = 1; + opts.reset = 1; + opts.merge = 1; + opts.fn = oneway_merge; + opts.verbose_update = !quiet; + parse_tree(tree); + init_tree_desc(&tree_desc, tree->buffer, tree->size); + if (unpack_trees(1, &tree_desc, &opts)) + return 128; + return 0; +} + +static void reset_clean_to_new(struct tree *tree, int quiet) +{ + struct unpack_trees_options opts; + struct tree_desc tree_desc; + memset(&opts, 0, sizeof(opts)); + opts.head_idx = -1; + opts.skip_unmerged = 1; + opts.reset = 1; + opts.merge = 1; + opts.fn = oneway_merge; + opts.verbose_update = !quiet; + parse_tree(tree); + init_tree_desc(&tree_desc, tree->buffer, tree->size); + if (unpack_trees(1, &tree_desc, &opts)) + exit(128); +} + +struct checkout_opts { + int quiet; + int merge; + int force; + + char *new_branch; + int new_branch_log; + int track; +}; + +struct branch_info { + const char *name; /* The short name used */ + const char *path; /* The full name of a real branch */ + struct commit *commit; /* The named commit */ +}; + +static void setup_branch_path(struct branch_info *branch) +{ + struct strbuf buf; + strbuf_init(&buf, 0); + strbuf_addstr(&buf, "refs/heads/"); + strbuf_addstr(&buf, branch->name); + branch->path = strbuf_detach(&buf, NULL); +} + +static int merge_working_tree(struct checkout_opts *opts, + struct branch_info *old, struct branch_info *new, + const char *prefix) +{ + int ret; + struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); + int newfd = hold_locked_index(lock_file, 1); + read_cache(); + + if (opts->force) { + ret = reset_to_new(new->commit->tree, opts->quiet); + if (ret) + return ret; + } else { + struct tree_desc trees[2]; + struct tree *tree; + struct unpack_trees_options topts; + memset(&topts, 0, sizeof(topts)); + topts.head_idx = -1; + + refresh_cache(REFRESH_QUIET); + + if (unmerged_cache()) { + ret = opts->merge ? -1 : + error("you need to resolve your current index first"); + } else { + topts.update = 1; + topts.merge = 1; + topts.gently = opts->merge; + topts.fn = twoway_merge; + topts.dir = xcalloc(1, sizeof(*topts.dir)); + topts.dir->show_ignored = 1; + topts.dir->exclude_per_dir = ".gitignore"; + topts.prefix = prefix; + tree = parse_tree_indirect(old->commit->object.sha1); + init_tree_desc(&trees[0], tree->buffer, tree->size); + tree = parse_tree_indirect(new->commit->object.sha1); + init_tree_desc(&trees[1], tree->buffer, tree->size); + ret = unpack_trees(2, trees, &topts); + } + if (ret) { + /* + * Unpack couldn't do a trivial merge; either + * give up or do a real merge, depending on + * whether the merge flag was used. + */ + struct tree *result; + struct tree *work; + if (!opts->merge) + return 1; + parse_commit(old->commit); + + /* Do more real merge */ + + /* + * We update the index fully, then write the + * tree from the index, then merge the new + * branch with the current tree, with the old + * branch as the base. Then we reset the index + * (but not the working tree) to the new + * branch, leaving the working tree as the + * merged version, but skipping unmerged + * entries in the index. + */ + + add_files_to_cache(0, NULL, NULL); + work = write_tree_from_memory(); + + ret = reset_to_new(new->commit->tree, opts->quiet); + if (ret) + return ret; + merge_trees(new->commit->tree, work, old->commit->tree, + new->name, "local", &result); + reset_clean_to_new(new->commit->tree, opts->quiet); + } + } + + if (write_cache(newfd, active_cache, active_nr) || + commit_locked_index(lock_file)) + die("unable to write new index file"); + + if (!opts->force) + show_local_changes(&new->commit->object); + + return 0; +} + +static void update_refs_for_switch(struct checkout_opts *opts, + struct branch_info *old, + struct branch_info *new) +{ + struct strbuf msg; + const char *old_desc; + if (opts->new_branch) { + create_branch(old->name, opts->new_branch, new->name, 0, + opts->new_branch_log, opts->track); + new->name = opts->new_branch; + setup_branch_path(new); + } + + strbuf_init(&msg, 0); + old_desc = old->name; + if (!old_desc) + old_desc = sha1_to_hex(old->commit->object.sha1); + strbuf_addf(&msg, "checkout: moving from %s to %s", + old_desc, new->name); + + if (new->path) { + create_symref("HEAD", new->path, msg.buf); + if (!opts->quiet) { + if (old->path && !strcmp(new->path, old->path)) + fprintf(stderr, "Already on \"%s\"\n", + new->name); + else + fprintf(stderr, "Switched to%s branch \"%s\"\n", + opts->new_branch ? " a new" : "", + new->name); + } + } else if (strcmp(new->name, "HEAD")) { + update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, + REF_NODEREF, DIE_ON_ERR); + if (!opts->quiet) { + if (old->path) + fprintf(stderr, "Note: moving to \"%s\" which isn't a local branch\nIf you want to create a new branch from this checkout, you may do so\n(now or later) by using -b with the checkout command again. Example:\n git checkout -b \n", new->name); + describe_detached_head("HEAD is now at", new->commit); + } + } + remove_branch_state(); + strbuf_release(&msg); +} + +static int switch_branches(struct checkout_opts *opts, + struct branch_info *new, const char *prefix) +{ + int ret = 0; + struct branch_info old; + unsigned char rev[20]; + int flag; + memset(&old, 0, sizeof(old)); + old.path = resolve_ref("HEAD", rev, 0, &flag); + old.commit = lookup_commit_reference_gently(rev, 1); + if (!(flag & REF_ISSYMREF)) + old.path = NULL; + + if (old.path && !prefixcmp(old.path, "refs/heads/")) + old.name = old.path + strlen("refs/heads/"); + + if (!new->name) { + new->name = "HEAD"; + new->commit = old.commit; + if (!new->commit) + die("You are on a branch yet to be born"); + parse_commit(new->commit); + } + + /* + * If the new thing isn't a branch and isn't HEAD and we're + * not starting a new branch, and we want messages, and we + * weren't on a branch, and we're moving to a new commit, + * describe the old commit. + */ + if (!new->path && strcmp(new->name, "HEAD") && !opts->new_branch && + !opts->quiet && !old.path && new->commit != old.commit) + describe_detached_head("Previous HEAD position was", old.commit); + + if (!old.commit) { + if (!opts->quiet) { + fprintf(stderr, "warning: You appear to be on a branch yet to be born.\n"); + fprintf(stderr, "warning: Forcing checkout of %s.\n", new->name); + } + opts->force = 1; + } + + ret = merge_working_tree(opts, &old, new, prefix); + if (ret) + return ret; + + update_refs_for_switch(opts, &old, new); + + return post_checkout_hook(old.commit, new->commit, 1); +} + +static int branch_track = 0; + +static int git_checkout_config(const char *var, const char *value) +{ + if (!strcmp(var, "branch.autosetupmerge")) + branch_track = git_config_bool(var, value); + + return git_default_config(var, value); +} + +int cmd_checkout(int argc, const char **argv, const char *prefix) +{ + struct checkout_opts opts; + unsigned char rev[20]; + const char *arg; + struct branch_info new; + struct tree *source_tree = NULL; + struct option options[] = { + OPT__QUIET(&opts.quiet), + OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"), + OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"), + OPT_BOOLEAN( 0 , "track", &opts.track, "track"), + OPT_BOOLEAN('f', NULL, &opts.force, "force"), + OPT_BOOLEAN('m', NULL, &opts.merge, "merge"), + }; + + memset(&opts, 0, sizeof(opts)); + memset(&new, 0, sizeof(new)); + + git_config(git_checkout_config); + + opts.track = branch_track; + + argc = parse_options(argc, argv, options, checkout_usage, 0); + if (argc) { + arg = argv[0]; + if (get_sha1(arg, rev)) + ; + else if ((new.commit = lookup_commit_reference_gently(rev, 1))) { + new.name = arg; + setup_branch_path(&new); + if (resolve_ref(new.path, rev, 1, NULL)) + new.commit = lookup_commit_reference(rev); + else + new.path = NULL; + parse_commit(new.commit); + source_tree = new.commit->tree; + argv++; + argc--; + } else if ((source_tree = parse_tree_indirect(rev))) { + argv++; + argc--; + } + } + + if (argc && !strcmp(argv[0], "--")) { + argv++; + argc--; + } + + if (!opts.new_branch && (opts.track != branch_track)) + die("git checkout: --track and --no-track require -b"); + + if (opts.force && opts.merge) + die("git checkout: -f and -m are incompatible"); + + if (argc) { + const char **pathspec = get_pathspec(prefix, argv); + /* Checkout paths */ + if (opts.new_branch || opts.force || opts.merge) { + if (argc == 1) { + die("git checkout: updating paths is incompatible with switching branches/forcing\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]); + } else { + die("git checkout: updating paths is incompatible with switching branches/forcing"); + } + } + + if (source_tree) + read_tree_some(source_tree, pathspec); + else + read_cache(); + return checkout_paths(pathspec); + } + + if (new.name && !new.commit) { + die("Cannot switch branch to a non-commit."); + } + + return switch_branches(&opts, &new, prefix); +} diff --git a/builtin.h b/builtin.h index 428160d0e4..25d91bbfb2 100644 --- a/builtin.h +++ b/builtin.h @@ -19,6 +19,7 @@ extern int cmd_blame(int argc, const char **argv, const char *prefix); extern int cmd_branch(int argc, const char **argv, const char *prefix); extern int cmd_bundle(int argc, const char **argv, const char *prefix); extern int cmd_cat_file(int argc, const char **argv, const char *prefix); +extern int cmd_checkout(int argc, const char **argv, const char *prefix); extern int cmd_checkout_index(int argc, const char **argv, const char *prefix); extern int cmd_check_attr(int argc, const char **argv, const char *prefix); extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix); diff --git a/git-checkout.sh b/contrib/examples/git-checkout.sh similarity index 100% rename from git-checkout.sh rename to contrib/examples/git-checkout.sh diff --git a/git.c b/git.c index 114ea75eef..fc156863b0 100644 --- a/git.c +++ b/git.c @@ -287,6 +287,7 @@ static void handle_internal_command(int argc, const char **argv) { "branch", cmd_branch, RUN_SETUP }, { "bundle", cmd_bundle }, { "cat-file", cmd_cat_file, RUN_SETUP }, + { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE }, { "checkout-index", cmd_checkout_index, RUN_SETUP | NEED_WORK_TREE}, { "check-ref-format", cmd_check_ref_format }, diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 3d8e01c030..5492f21c7e 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -103,13 +103,6 @@ test_expect_success "checkout -m with dirty tree" ' test "$(git symbolic-ref HEAD)" = "refs/heads/side" && (cat >expect.messages < Date: Sat, 16 Feb 2008 17:17:09 -0800 Subject: [PATCH 12/21] checkout: notice when the switched branch is behind or forked When you are switching to a branch that is marked to merge from somewhere else, e.g. when you have: [branch "next"] remote = upstream merge = refs/heads/next [remote "upstream"] url = ... fetch = refs/heads/*:refs/remotes/linus/* and you say "git checkout next", the branch you checked out may be behind, and you may want to update from the upstream before continuing to work. This patch makes the command to check the upstream (in this example, "refs/remotes/linus/next") and our branch "next", and: (1) if they match, nothing happens; (2) if you are ahead (i.e. the upstream is a strict ancestor of you), one line message tells you so; (3) otherwise, you are either behind or you and the upstream have forked. One line message will tell you which and then you will see a "log --pretty=oneline --left-right". We could enhance this with an option that tells the command to check if there is no local change, and automatically fast forward when you are truly behind. But I ripped out that change because I was unsure what the right way should be to allow users to control it (issues include that checkout should not become automatically interactive). Signed-off-by: Junio C Hamano --- builtin-checkout.c | 136 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/builtin-checkout.c b/builtin-checkout.c index 59a0ef4ec9..9370ba07b4 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -12,6 +12,7 @@ #include "branch.h" #include "diff.h" #include "revision.h" +#include "remote.h" static const char * const checkout_usage[] = { "git checkout [options] ", @@ -290,6 +291,139 @@ static int merge_working_tree(struct checkout_opts *opts, return 0; } +/* + * We really should allow cb_data... Yuck + */ +static const char *branch_name; +static int branch_name_len; +static char *found_remote; +static char *found_merge; +static int read_branch_config(const char *var, const char *value) +{ + const char *name; + if (prefixcmp(var, "branch.")) + return 0; /* not ours */ + name = var + strlen("branch."); + if (strncmp(name, branch_name, branch_name_len) || + name[branch_name_len] != '.') + return 0; /* not ours either */ + if (!strcmp(name + branch_name_len, ".remote")) { + /* + * Yeah, I know Christian's clean-up should + * be used here, but the topic is based on an + * older fork point. + */ + if (!value) + return error("'%s' not string", var); + found_remote = xstrdup(value); + return 0; + } + if (!strcmp(name + branch_name_len, ".merge")) { + if (!value) + return error("'%s' not string", var); + found_merge = xstrdup(value); + return 0; + } + return 0; /* not ours */ +} + +static int find_build_base(const char *ours, char **base) +{ + struct remote *remote; + struct refspec spec; + + *base = NULL; + + branch_name = ours + strlen("refs/heads/"); + branch_name_len = strlen(branch_name); + found_remote = NULL; + found_merge = NULL; + git_config(read_branch_config); + + if (!found_remote || !found_merge) { + cleanup: + free(found_remote); + free(found_merge); + return 0; + } + + remote = remote_get(found_remote); + memset(&spec, 0, sizeof(spec)); + spec.src = found_merge; + if (remote_find_tracking(remote, &spec)) + goto cleanup; + *base = spec.dst; + return 1; +} + +static void adjust_to_tracking(struct branch_info *new, struct checkout_opts *opts) +{ + /* + * We have switched to a new branch; is it building on + * top of another branch, and if so does that other branch + * have changes we do not have yet? + */ + char *base; + unsigned char sha1[20]; + struct commit *ours, *theirs; + const char *msgfmt; + char symmetric[84]; + int show_log; + + if (!resolve_ref(new->path, sha1, 1, NULL)) + return; + ours = lookup_commit(sha1); + + if (!find_build_base(new->path, &base)) + return; + + sprintf(symmetric, "%s", sha1_to_hex(sha1)); + + /* + * Ok, it is tracking base; is it ahead of us? + */ + if (!resolve_ref(base, sha1, 1, NULL)) + return; + theirs = lookup_commit(sha1); + + sprintf(symmetric + 40, "...%s", sha1_to_hex(sha1)); + + if (!hashcmp(sha1, ours->object.sha1)) + return; /* we are the same */ + + show_log = 1; + if (in_merge_bases(theirs, &ours, 1)) { + msgfmt = "You are ahead of the tracked branch '%s'\n"; + show_log = 0; + } + else if (in_merge_bases(ours, &theirs, 1)) + msgfmt = "Your branch can be fast-forwarded to the tracked branch '%s'\n"; + else + msgfmt = "Both your branch and the tracked branch '%s' have own changes, you would eventually need to merge\n"; + + if (!prefixcmp(base, "refs/remotes/")) + base += strlen("refs/remotes/"); + fprintf(stderr, msgfmt, base); + + if (show_log) { + const char *args[32]; + int ac; + + ac = 0; + args[ac++] = "log"; + args[ac++] = "--pretty=oneline"; + args[ac++] = "--abbrev-commit"; + args[ac++] = "--left-right"; + args[ac++] = "--boundary"; + args[ac++] = symmetric; + args[ac++] = "--"; + args[ac] = NULL; + + run_command_v_opt(args, RUN_GIT_CMD); + } +} + + static void update_refs_for_switch(struct checkout_opts *opts, struct branch_info *old, struct branch_info *new) @@ -332,6 +466,8 @@ static void update_refs_for_switch(struct checkout_opts *opts, } remove_branch_state(); strbuf_release(&msg); + if (new->path) + adjust_to_tracking(new, opts); } static int switch_branches(struct checkout_opts *opts, From b249b552e012824f1bd5026187bf9b895c2132c6 Mon Sep 17 00:00:00 2001 From: Jay Soffian Date: Mon, 18 Feb 2008 05:20:20 -0500 Subject: [PATCH 13/21] builtin-checkout.c: fix possible usage segfault Not terminating the options[] array with OPT_END can cause usage ("git checkout -h") output to segfault. Signed-off-by: Jay Soffian Signed-off-by: Junio C Hamano --- builtin-checkout.c | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin-checkout.c b/builtin-checkout.c index 9370ba07b4..0d19835a6b 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -545,6 +545,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) OPT_BOOLEAN( 0 , "track", &opts.track, "track"), OPT_BOOLEAN('f', NULL, &opts.force, "force"), OPT_BOOLEAN('m', NULL, &opts.merge, "merge"), + OPT_END(), }; memset(&opts, 0, sizeof(opts)); From 569012bf91ddb25220483e8912e079ce8a501525 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Tue, 19 Feb 2008 02:52:14 -0500 Subject: [PATCH 14/21] Clean up reporting differences on branch switch This also changes it such that: $ git checkout will give the same information without changing branches. This is good for finding out if the fetch you did recently had anything to say about the branch you've been on, whose name you don't remember at the moment. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- builtin-checkout.c | 80 +++++----------------------------------------- 1 file changed, 8 insertions(+), 72 deletions(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index 0d19835a6b..5291f72a37 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -291,71 +291,6 @@ static int merge_working_tree(struct checkout_opts *opts, return 0; } -/* - * We really should allow cb_data... Yuck - */ -static const char *branch_name; -static int branch_name_len; -static char *found_remote; -static char *found_merge; -static int read_branch_config(const char *var, const char *value) -{ - const char *name; - if (prefixcmp(var, "branch.")) - return 0; /* not ours */ - name = var + strlen("branch."); - if (strncmp(name, branch_name, branch_name_len) || - name[branch_name_len] != '.') - return 0; /* not ours either */ - if (!strcmp(name + branch_name_len, ".remote")) { - /* - * Yeah, I know Christian's clean-up should - * be used here, but the topic is based on an - * older fork point. - */ - if (!value) - return error("'%s' not string", var); - found_remote = xstrdup(value); - return 0; - } - if (!strcmp(name + branch_name_len, ".merge")) { - if (!value) - return error("'%s' not string", var); - found_merge = xstrdup(value); - return 0; - } - return 0; /* not ours */ -} - -static int find_build_base(const char *ours, char **base) -{ - struct remote *remote; - struct refspec spec; - - *base = NULL; - - branch_name = ours + strlen("refs/heads/"); - branch_name_len = strlen(branch_name); - found_remote = NULL; - found_merge = NULL; - git_config(read_branch_config); - - if (!found_remote || !found_merge) { - cleanup: - free(found_remote); - free(found_merge); - return 0; - } - - remote = remote_get(found_remote); - memset(&spec, 0, sizeof(spec)); - spec.src = found_merge; - if (remote_find_tracking(remote, &spec)) - goto cleanup; - *base = spec.dst; - return 1; -} - static void adjust_to_tracking(struct branch_info *new, struct checkout_opts *opts) { /* @@ -369,15 +304,16 @@ static void adjust_to_tracking(struct branch_info *new, struct checkout_opts *op const char *msgfmt; char symmetric[84]; int show_log; + struct branch *branch = branch_get(NULL); - if (!resolve_ref(new->path, sha1, 1, NULL)) - return; - ours = lookup_commit(sha1); - - if (!find_build_base(new->path, &base)) + if (!branch || !branch->merge) return; - sprintf(symmetric, "%s", sha1_to_hex(sha1)); + base = branch->merge[0]->dst; + + ours = new->commit; + + sprintf(symmetric, "%s", sha1_to_hex(ours->object.sha1)); /* * Ok, it is tracking base; is it ahead of us? @@ -466,7 +402,7 @@ static void update_refs_for_switch(struct checkout_opts *opts, } remove_branch_state(); strbuf_release(&msg); - if (new->path) + if (new->path || !strcmp(new->name, "HEAD")) adjust_to_tracking(new, opts); } From b0030db331141bedfaf02f34a83f18712c0ae011 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Feb 2008 15:05:23 -0800 Subject: [PATCH 15/21] checkout: tone down the "forked status" diagnostic messages When checking out a branch that is behind or forked from a branch you are building on top of, we used to show full left-right log but if you already _know_ you have long history since you forked, it is a bit too much. This tones down the message quite a bit, by only showing the number of commits each side has since they diverged. Also the message is not shown at all under --quiet. Signed-off-by: Junio C Hamano --- builtin-checkout.c | 106 +++++++++++++++++++++++++++------------------ 1 file changed, 65 insertions(+), 41 deletions(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index 5291f72a37..1fc1e56bdf 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -301,64 +301,88 @@ static void adjust_to_tracking(struct branch_info *new, struct checkout_opts *op char *base; unsigned char sha1[20]; struct commit *ours, *theirs; - const char *msgfmt; char symmetric[84]; - int show_log; + struct rev_info revs; + const char *rev_argv[10]; + int rev_argc; + int num_ours, num_theirs; + const char *remote_msg; struct branch *branch = branch_get(NULL); + /* + * Nothing to report unless we are marked to build on top of + * somebody else. + */ if (!branch || !branch->merge) return; - base = branch->merge[0]->dst; - - ours = new->commit; - - sprintf(symmetric, "%s", sha1_to_hex(ours->object.sha1)); - /* - * Ok, it is tracking base; is it ahead of us? + * If what we used to build on no longer exists, there is + * nothing to report. */ + base = branch->merge[0]->dst; if (!resolve_ref(base, sha1, 1, NULL)) return; + theirs = lookup_commit(sha1); - - sprintf(symmetric + 40, "...%s", sha1_to_hex(sha1)); - + ours = new->commit; if (!hashcmp(sha1, ours->object.sha1)) return; /* we are the same */ - show_log = 1; - if (in_merge_bases(theirs, &ours, 1)) { - msgfmt = "You are ahead of the tracked branch '%s'\n"; - show_log = 0; - } - else if (in_merge_bases(ours, &theirs, 1)) - msgfmt = "Your branch can be fast-forwarded to the tracked branch '%s'\n"; - else - msgfmt = "Both your branch and the tracked branch '%s' have own changes, you would eventually need to merge\n"; + /* Run "rev-list --left-right ours...theirs" internally... */ + rev_argc = 0; + rev_argv[rev_argc++] = NULL; + rev_argv[rev_argc++] = "--left-right"; + rev_argv[rev_argc++] = symmetric; + rev_argv[rev_argc++] = "--"; + rev_argv[rev_argc] = NULL; - if (!prefixcmp(base, "refs/remotes/")) + strcpy(symmetric, sha1_to_hex(ours->object.sha1)); + strcpy(symmetric + 40, "..."); + strcpy(symmetric + 43, sha1_to_hex(theirs->object.sha1)); + + init_revisions(&revs, NULL); + setup_revisions(rev_argc, rev_argv, &revs, NULL); + prepare_revision_walk(&revs); + + /* ... and count the commits on each side. */ + num_ours = 0; + num_theirs = 0; + while (1) { + struct commit *c = get_revision(&revs); + if (!c) + break; + if (c->object.flags & SYMMETRIC_LEFT) + num_ours++; + else + num_theirs++; + } + + if (!prefixcmp(base, "refs/remotes/")) { + remote_msg = " remote"; base += strlen("refs/remotes/"); - fprintf(stderr, msgfmt, base); - - if (show_log) { - const char *args[32]; - int ac; - - ac = 0; - args[ac++] = "log"; - args[ac++] = "--pretty=oneline"; - args[ac++] = "--abbrev-commit"; - args[ac++] = "--left-right"; - args[ac++] = "--boundary"; - args[ac++] = symmetric; - args[ac++] = "--"; - args[ac] = NULL; - - run_command_v_opt(args, RUN_GIT_CMD); + } else { + remote_msg = ""; } -} + if (!num_theirs) + printf("Your branch is ahead of the tracked%s branch '%s' " + "by %d commit%s.\n", + remote_msg, base, + num_ours, (num_ours == 1) ? "" : "s"); + else if (!num_ours) + printf("Your branch is behind of the tracked%s branch '%s' " + "by %d commit%s,\n" + "and can be fast-forwarded.\n", + remote_msg, base, + num_theirs, (num_theirs == 1) ? "" : "s"); + else + printf("Your branch and the tracked%s branch '%s' " + "have diverged,\nand respectively " + "have %d and %d different commit(s) each.\n", + remote_msg, base, + num_ours, num_theirs); +} static void update_refs_for_switch(struct checkout_opts *opts, struct branch_info *old, @@ -402,7 +426,7 @@ static void update_refs_for_switch(struct checkout_opts *opts, } remove_branch_state(); strbuf_release(&msg); - if (new->path || !strcmp(new->name, "HEAD")) + if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD"))) adjust_to_tracking(new, opts); } From 6010d2d957fb05838cd3ab887ac261752ff8ff87 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Feb 2008 15:54:54 -0800 Subject: [PATCH 16/21] checkout: work from a subdirectory When switching branches from a subdirectory, checkout rewritten in C extracted the toplevel of the tree in there. This should fix it. Signed-off-by: Junio C Hamano --- builtin-checkout.c | 1 - t/t7201-co.sh | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index 1fc1e56bdf..f51b77a6bc 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -237,7 +237,6 @@ static int merge_working_tree(struct checkout_opts *opts, topts.dir = xcalloc(1, sizeof(*topts.dir)); topts.dir->show_ignored = 1; topts.dir->exclude_per_dir = ".gitignore"; - topts.prefix = prefix; tree = parse_tree_indirect(old->commit->object.sha1); init_tree_desc(&trees[0], tree->buffer, tree->size); tree = parse_tree_indirect(new->commit->object.sha1); diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 5492f21c7e..0fa94678ad 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -263,4 +263,38 @@ test_expect_success 'checkout with ambiguous tag/branch names' ' ' +test_expect_success 'switch branches while in subdirectory' ' + + git reset --hard && + git checkout master && + + mkdir subs && + ( + cd subs && + git checkout side + ) && + ! test -f subs/one && + rm -fr subs + +' + +test_expect_success 'checkout specific path while in subdirectory' ' + + git reset --hard && + git checkout side && + mkdir subs && + >subs/bero && + git add subs/bero && + git commit -m "add subs/bero" && + + git checkout master && + mkdir -p subs && + ( + cd subs && + git checkout side -- bero + ) && + test -f subs/bero + +' + test_done From 75ea38df66910dcb9d09f1320ae2787b5bc8211e Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 21 Feb 2008 10:50:42 -0500 Subject: [PATCH 17/21] builtin-checkout.c: Remove unused prefix arguments in switch_branches path This path doesn't actually care where in the tree you started out, since it must change the whole thing anyway. With the gratuitous bug removed, the argument is unused. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- builtin-checkout.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index f51b77a6bc..e89b8f8ee0 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -205,8 +205,7 @@ static void setup_branch_path(struct branch_info *branch) } static int merge_working_tree(struct checkout_opts *opts, - struct branch_info *old, struct branch_info *new, - const char *prefix) + struct branch_info *old, struct branch_info *new) { int ret; struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); @@ -429,8 +428,7 @@ static void update_refs_for_switch(struct checkout_opts *opts, adjust_to_tracking(new, opts); } -static int switch_branches(struct checkout_opts *opts, - struct branch_info *new, const char *prefix) +static int switch_branches(struct checkout_opts *opts, struct branch_info *new) { int ret = 0; struct branch_info old; @@ -471,7 +469,7 @@ static int switch_branches(struct checkout_opts *opts, opts->force = 1; } - ret = merge_working_tree(opts, &old, new, prefix); + ret = merge_working_tree(opts, &old, new); if (ret) return ret; @@ -569,5 +567,5 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) die("Cannot switch branch to a non-commit."); } - return switch_branches(&opts, &new, prefix); + return switch_branches(&opts, &new); } From b56fca07d2bac20339d59218ab98de38a9363e77 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Feb 2008 19:42:53 -0800 Subject: [PATCH 18/21] checkout: updates to tracking report Ask branch_get() for the new branch explicitly instead of letting it return a potentially stale information. Tighten the logic to find the tracking branch to deal better with misconfigured repositories (i.e. branch.*.merge can exist but it may not have a refspec that fetches to .it) Also fixes grammar in a message, as pointed out by Jeff King. The function is about reporting and not automatically fast-forwarding to the upstream, so stop calling it "adjust-to". Signed-off-by: Junio C Hamano Acked-by: Daniel Barkalow --- builtin-checkout.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index e89b8f8ee0..5f176c6c38 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -289,7 +289,7 @@ static int merge_working_tree(struct checkout_opts *opts, return 0; } -static void adjust_to_tracking(struct branch_info *new, struct checkout_opts *opts) +static void report_tracking(struct branch_info *new, struct checkout_opts *opts) { /* * We have switched to a new branch; is it building on @@ -305,13 +305,13 @@ static void adjust_to_tracking(struct branch_info *new, struct checkout_opts *op int rev_argc; int num_ours, num_theirs; const char *remote_msg; - struct branch *branch = branch_get(NULL); + struct branch *branch = branch_get(new->name); /* * Nothing to report unless we are marked to build on top of * somebody else. */ - if (!branch || !branch->merge) + if (!branch || !branch->merge || !branch->merge[0] || !branch->merge[0]->dst) return; /* @@ -369,7 +369,7 @@ static void adjust_to_tracking(struct branch_info *new, struct checkout_opts *op remote_msg, base, num_ours, (num_ours == 1) ? "" : "s"); else if (!num_ours) - printf("Your branch is behind of the tracked%s branch '%s' " + printf("Your branch is behind the tracked%s branch '%s' " "by %d commit%s,\n" "and can be fast-forwarded.\n", remote_msg, base, @@ -425,7 +425,7 @@ static void update_refs_for_switch(struct checkout_opts *opts, remove_branch_state(); strbuf_release(&msg); if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD"))) - adjust_to_tracking(new, opts); + report_tracking(new, opts); } static int switch_branches(struct checkout_opts *opts, struct branch_info *new) From 1736855c9b59ac787af979a44840a58361cbaf66 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 23 Feb 2008 11:08:25 -0800 Subject: [PATCH 19/21] Add merge-subtree back An earlier commit e1b3a2c (Build-in merge-recursive) made the subtree merge strategy backend unavailable. This resurrects it. A new test t6029 currently only tests the strategy is available, but it should be enhanced to check the real "subtree" case. Signed-off-by: Junio C Hamano --- Makefile | 8 +++----- git.c | 1 + t/t6029-merge-subtree.sh | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100755 t/t6029-merge-subtree.sh diff --git a/Makefile b/Makefile index 90c0dd8c43..40fa41b5f3 100644 --- a/Makefile +++ b/Makefile @@ -261,17 +261,18 @@ PROGRAMS = \ # Empty... EXTRA_PROGRAMS = +# List built-in command $C whose implementation cmd_$C() is not in +# builtin-$C.o but is linked in as part of some other command. BUILT_INS = \ git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \ git-get-tar-commit-id$X git-init$X git-repo-config$X \ git-fsck-objects$X git-cherry-pick$X git-peek-remote$X git-status$X \ + git-merge-subtree$X \ $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) -ALL_PROGRAMS += git-merge-subtree$X - # what 'all' will build but not install in gitexecdir OTHER_PROGRAMS = git$X gitweb/gitweb.cgi @@ -807,9 +808,6 @@ help.o: help.c common-cmds.h GIT-CFLAGS '-DGIT_MAN_PATH="$(mandir_SQ)"' \ '-DGIT_INFO_PATH="$(infodir_SQ)"' $< -git-merge-subtree$X: git-merge-recursive$X - $(QUIET_BUILT_IN)$(RM) $@ && ln git-merge-recursive$X $@ - $(BUILT_INS): git$X $(QUIET_BUILT_IN)$(RM) $@ && ln git$X $@ diff --git a/git.c b/git.c index fc156863b0..bd424ea9bd 100644 --- a/git.c +++ b/git.c @@ -332,6 +332,7 @@ static void handle_internal_command(int argc, const char **argv) { "merge-file", cmd_merge_file }, { "merge-ours", cmd_merge_ours, RUN_SETUP }, { "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE }, + { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE }, { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, diff --git a/t/t6029-merge-subtree.sh b/t/t6029-merge-subtree.sh new file mode 100755 index 0000000000..3900a05082 --- /dev/null +++ b/t/t6029-merge-subtree.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +test_description='subtree merge strategy' + +. ./test-lib.sh + +test_expect_success setup ' + + s="1 2 3 4 5 6 7 8" + for i in $s; do echo $i; done >hello && + git add hello && + git commit -m initial && + git checkout -b side && + echo >>hello world && + git add hello && + git commit -m second && + git checkout master && + for i in mundo $s; do echo $i; done >hello && + git add hello && + git commit -m master + +' + +test_expect_success 'subtree available and works like recursive' ' + + git merge -s subtree side && + for i in mundo $s world; do echo $i; done >expect && + diff -u expect hello + +' + +test_done From 52229a29c78df2e48de23ed70ab1934667971d3c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 23 Feb 2008 15:37:48 -0800 Subject: [PATCH 20/21] checkout: show progress when checkout takes long time while switching branches Signed-off-by: Junio C Hamano --- builtin-checkout.c | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin-checkout.c b/builtin-checkout.c index 5f176c6c38..283831e8af 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -232,6 +232,7 @@ static int merge_working_tree(struct checkout_opts *opts, topts.update = 1; topts.merge = 1; topts.gently = opts->merge; + topts.verbose_update = !opts->quiet; topts.fn = twoway_merge; topts.dir = xcalloc(1, sizeof(*topts.dir)); topts.dir->show_ignored = 1; From 04c9e11f2cffaf84dd20602f811bf377f6033cb6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 23 Feb 2008 15:45:19 -0800 Subject: [PATCH 21/21] checkout: error out when index is unmerged even with -m Even when -m is given to allow fallilng back to 3-way merge while switching branches, we should refuse if the original index is unmerged. Signed-off-by: Junio C Hamano Acked-by: Daniel Barkalow --- builtin-checkout.c | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index 283831e8af..d5f093094c 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -226,24 +226,25 @@ static int merge_working_tree(struct checkout_opts *opts, refresh_cache(REFRESH_QUIET); if (unmerged_cache()) { - ret = opts->merge ? -1 : - error("you need to resolve your current index first"); - } else { - topts.update = 1; - topts.merge = 1; - topts.gently = opts->merge; - topts.verbose_update = !opts->quiet; - topts.fn = twoway_merge; - topts.dir = xcalloc(1, sizeof(*topts.dir)); - topts.dir->show_ignored = 1; - topts.dir->exclude_per_dir = ".gitignore"; - tree = parse_tree_indirect(old->commit->object.sha1); - init_tree_desc(&trees[0], tree->buffer, tree->size); - tree = parse_tree_indirect(new->commit->object.sha1); - init_tree_desc(&trees[1], tree->buffer, tree->size); - ret = unpack_trees(2, trees, &topts); + error("you need to resolve your current index first"); + return 1; } - if (ret) { + + /* 2-way merge to the new branch */ + topts.update = 1; + topts.merge = 1; + topts.gently = opts->merge; + topts.verbose_update = !opts->quiet; + topts.fn = twoway_merge; + topts.dir = xcalloc(1, sizeof(*topts.dir)); + topts.dir->show_ignored = 1; + topts.dir->exclude_per_dir = ".gitignore"; + tree = parse_tree_indirect(old->commit->object.sha1); + init_tree_desc(&trees[0], tree->buffer, tree->size); + tree = parse_tree_indirect(new->commit->object.sha1); + init_tree_desc(&trees[1], tree->buffer, tree->size); + + if (unpack_trees(2, trees, &topts)) { /* * Unpack couldn't do a trivial merge; either * give up or do a real merge, depending on