From 0989fe9623dc8d98033f6acdcc0c84ec28571b19 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Fri, 27 Jun 2008 18:21:54 +0200 Subject: [PATCH 01/20] Move split_cmdline() to alias.c split_cmdline() is currently used for aliases only, but later it can be useful for other builtins as well. Move it to alias.c for now, indicating that originally it's for aliases, but we'll have it in libgit this way. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- alias.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ cache.h | 1 + git.c | 53 ----------------------------------------------------- 3 files changed, 55 insertions(+), 53 deletions(-) diff --git a/alias.c b/alias.c index 995f3e6a0ac84f..ccb1108c944360 100644 --- a/alias.c +++ b/alias.c @@ -21,3 +21,57 @@ char *alias_lookup(const char *alias) git_config(alias_lookup_cb, NULL); return alias_val; } + +int split_cmdline(char *cmdline, const char ***argv) +{ + int src, dst, count = 0, size = 16; + char quoted = 0; + + *argv = xmalloc(sizeof(char*) * size); + + /* split alias_string */ + (*argv)[count++] = cmdline; + for (src = dst = 0; cmdline[src];) { + char c = cmdline[src]; + if (!quoted && isspace(c)) { + cmdline[dst++] = 0; + while (cmdline[++src] + && isspace(cmdline[src])) + ; /* skip */ + if (count >= size) { + size += 16; + *argv = xrealloc(*argv, sizeof(char*) * size); + } + (*argv)[count++] = cmdline + dst; + } else if (!quoted && (c == '\'' || c == '"')) { + quoted = c; + src++; + } else if (c == quoted) { + quoted = 0; + src++; + } else { + if (c == '\\' && quoted != '\'') { + src++; + c = cmdline[src]; + if (!c) { + free(*argv); + *argv = NULL; + return error("cmdline ends with \\"); + } + } + cmdline[dst++] = c; + src++; + } + } + + cmdline[dst] = 0; + + if (quoted) { + free(*argv); + *argv = NULL; + return error("unclosed quote"); + } + + return count; +} + diff --git a/cache.h b/cache.h index 64ef86e129337f..6913573d1a835a 100644 --- a/cache.h +++ b/cache.h @@ -831,5 +831,6 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_ void overlay_tree_on_cache(const char *tree_name, const char *prefix); char *alias_lookup(const char *alias); +int split_cmdline(char *cmdline, const char ***argv); #endif /* CACHE_H */ diff --git a/git.c b/git.c index 59f0fcc1f2278d..2fbe96b9bac352 100644 --- a/git.c +++ b/git.c @@ -90,59 +90,6 @@ static int handle_options(const char*** argv, int* argc, int* envchanged) return handled; } -static int split_cmdline(char *cmdline, const char ***argv) -{ - int src, dst, count = 0, size = 16; - char quoted = 0; - - *argv = xmalloc(sizeof(char*) * size); - - /* split alias_string */ - (*argv)[count++] = cmdline; - for (src = dst = 0; cmdline[src];) { - char c = cmdline[src]; - if (!quoted && isspace(c)) { - cmdline[dst++] = 0; - while (cmdline[++src] - && isspace(cmdline[src])) - ; /* skip */ - if (count >= size) { - size += 16; - *argv = xrealloc(*argv, sizeof(char*) * size); - } - (*argv)[count++] = cmdline + dst; - } else if(!quoted && (c == '\'' || c == '"')) { - quoted = c; - src++; - } else if (c == quoted) { - quoted = 0; - src++; - } else { - if (c == '\\' && quoted != '\'') { - src++; - c = cmdline[src]; - if (!c) { - free(*argv); - *argv = NULL; - return error("cmdline ends with \\"); - } - } - cmdline[dst++] = c; - src++; - } - } - - cmdline[dst] = 0; - - if (quoted) { - free(*argv); - *argv = NULL; - return error("unclosed quote"); - } - - return count; -} - static int handle_alias(int *argcp, const char ***argv) { int envchanged = 0, ret = 0, saved_errno = errno; From 653194758ee338b7c87d011007c532ed5cf68d45 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Fri, 27 Jun 2008 18:21:55 +0200 Subject: [PATCH 02/20] Move commit_list_count() to commit.c This function is useful outside builtin-merge-recursive, for example in builtin-merge. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- builtin-merge-recursive.c | 8 -------- commit.c | 8 ++++++++ commit.h | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 43bf6aa45eeacb..3731853f83be37 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -42,14 +42,6 @@ static struct tree *shift_tree_object(struct tree *one, struct tree *two) * - *(int *)commit->object.sha1 set to the virtual id. */ -static unsigned commit_list_count(const struct commit_list *l) -{ - unsigned c = 0; - for (; l; l = l->next ) - c++; - return c; -} - static struct commit *make_virtual_commit(struct tree *tree, const char *comment) { struct commit *commit = xcalloc(1, sizeof(struct commit)); diff --git a/commit.c b/commit.c index e2d8624d9c19ad..bbf9c75416537f 100644 --- a/commit.c +++ b/commit.c @@ -325,6 +325,14 @@ struct commit_list *commit_list_insert(struct commit *item, struct commit_list * return new_list; } +unsigned commit_list_count(const struct commit_list *l) +{ + unsigned c = 0; + for (; l; l = l->next ) + c++; + return c; +} + void free_commit_list(struct commit_list *list) { while (list) { diff --git a/commit.h b/commit.h index 2d94d4148ed404..7f8c5ee0fc15fd 100644 --- a/commit.h +++ b/commit.h @@ -41,6 +41,7 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size); int parse_commit(struct commit *item); struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p); +unsigned commit_list_count(const struct commit_list *l); struct commit_list * insert_by_date(struct commit *item, struct commit_list **list); void free_commit_list(struct commit_list *list); From fbca58373291afa07c5b30ab562d36ce0f6174e0 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Fri, 27 Jun 2008 18:21:56 +0200 Subject: [PATCH 03/20] Move parse-options's skip_prefix() to git-compat-util.h builtin-remote.c and parse-options.c both have a skip_prefix() function, for the same purpose. Move parse-options's one to git-compat-util.h and let builtin-remote use it as well. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- builtin-remote.c | 39 ++++++++++++++++++++++++++------------- git-compat-util.h | 6 ++++++ parse-options.c | 6 ------ 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/builtin-remote.c b/builtin-remote.c index 145dd8568c7a64..1491354a9d5d22 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -29,12 +29,6 @@ static inline int postfixcmp(const char *string, const char *postfix) return strcmp(string + len1 - len2, postfix); } -static inline const char *skip_prefix(const char *name, const char *prefix) -{ - return !name ? "" : - prefixcmp(name, prefix) ? name : name + strlen(prefix); -} - static int opt_parse_track(const struct option *opt, const char *arg, int not) { struct path_list *list = opt->value; @@ -182,12 +176,18 @@ static int config_read_branches(const char *key, const char *value, void *cb) info->remote = xstrdup(value); } else { char *space = strchr(value, ' '); - value = skip_prefix(value, "refs/heads/"); + const char *ptr = skip_prefix(value, "refs/heads/"); + if (ptr) + value = ptr; while (space) { char *merge; merge = xstrndup(value, space - value); path_list_append(merge, &info->merge); - value = skip_prefix(space + 1, "refs/heads/"); + ptr = skip_prefix(space + 1, "refs/heads/"); + if (ptr) + value = ptr; + else + value = space + 1; space = strchr(value, ' '); } path_list_append(xstrdup(value), &info->merge); @@ -219,7 +219,12 @@ static int handle_one_branch(const char *refname, refspec.dst = (char *)refname; if (!remote_find_tracking(states->remote, &refspec)) { struct path_list_item *item; - const char *name = skip_prefix(refspec.src, "refs/heads/"); + const char *name, *ptr; + ptr = skip_prefix(refspec.src, "refs/heads/"); + if (ptr) + name = ptr; + else + name = refspec.src; /* symbolic refs pointing nowhere were handled already */ if ((flags & REF_ISSYMREF) || unsorted_path_list_has_path(&states->tracked, @@ -248,6 +253,7 @@ static int get_ref_states(const struct ref *ref, struct ref_states *states) struct path_list *target = &states->tracked; unsigned char sha1[20]; void *util = NULL; + const char *ptr; if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1)) target = &states->new; @@ -256,8 +262,10 @@ static int get_ref_states(const struct ref *ref, struct ref_states *states) if (hashcmp(sha1, ref->new_sha1)) util = &states; } - path_list_append(skip_prefix(ref->name, "refs/heads/"), - target)->util = util; + ptr = skip_prefix(ref->name, "refs/heads/"); + if (!ptr) + ptr = ref->name; + path_list_append(ptr, target)->util = util; } free_refs(fetch_map); @@ -522,10 +530,15 @@ static int show(int argc, const char **argv) "es" : ""); for (i = 0; i < states.remote->push_refspec_nr; i++) { struct refspec *spec = states.remote->push + i; + const char *p = "", *q = ""; + if (spec->src) + p = skip_prefix(spec->src, "refs/heads/"); + if (spec->dst) + q = skip_prefix(spec->dst, "refs/heads/"); printf(" %s%s%s%s", spec->force ? "+" : "", - skip_prefix(spec->src, "refs/heads/"), + p ? p : spec->src, spec->dst ? ":" : "", - skip_prefix(spec->dst, "refs/heads/")); + q ? q : spec->dst); } printf("\n"); } diff --git a/git-compat-util.h b/git-compat-util.h index 6f94a8197f479f..31edc98f30e2b5 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -127,6 +127,12 @@ extern void set_warn_routine(void (*routine)(const char *warn, va_list params)); extern int prefixcmp(const char *str, const char *prefix); +static inline const char *skip_prefix(const char *str, const char *prefix) +{ + size_t len = strlen(prefix); + return strncmp(str, prefix, len) ? NULL : str + len; +} + #ifdef NO_MMAP #ifndef PROT_READ diff --git a/parse-options.c b/parse-options.c index b8bde2b04a1493..bbc3ca4a9ffc0c 100644 --- a/parse-options.c +++ b/parse-options.c @@ -22,12 +22,6 @@ static inline const char *get_arg(struct optparse_t *p) return *++p->argv; } -static inline const char *skip_prefix(const char *str, const char *prefix) -{ - size_t len = strlen(prefix); - return strncmp(str, prefix, len) ? NULL : str + len; -} - static int opterror(const struct option *opt, const char *reason, int flags) { if (flags & OPT_SHORT) From b2eabcc2539c021ea6cb2d1d2d28c8213986a186 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Sun, 29 Jun 2008 16:51:38 +0200 Subject: [PATCH 04/20] Add new test to ensure git-merge handles pull.twohead and pull.octopus Test if the given strategies are used and test the case when multiple strategies are configured using a space separated list. Also test if the best strategy is picked if none is specified. This is done by adding a simple test case where recursive detects a rename, but resolve does not, and verify that finally merge will pick up the previous. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- t/t7601-merge-pull-config.sh | 129 +++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100755 t/t7601-merge-pull-config.sh diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh new file mode 100755 index 00000000000000..32585f8e0dcedc --- /dev/null +++ b/t/t7601-merge-pull-config.sh @@ -0,0 +1,129 @@ +#!/bin/sh + +test_description='git-merge + +Testing pull.* configuration parsing.' + +. ./test-lib.sh + +test_expect_success 'setup' ' + echo c0 >c0.c && + git add c0.c && + git commit -m c0 && + git tag c0 && + echo c1 >c1.c && + git add c1.c && + git commit -m c1 && + git tag c1 && + git reset --hard c0 && + echo c2 >c2.c && + git add c2.c && + git commit -m c2 && + git tag c2 && + git reset --hard c0 && + echo c3 >c3.c && + git add c3.c && + git commit -m c3 && + git tag c3 +' + +test_expect_success 'merge c1 with c2' ' + git reset --hard c1 && + test -f c0.c && + test -f c1.c && + test ! -f c2.c && + test ! -f c3.c && + git merge c2 && + test -f c1.c && + test -f c2.c +' + +test_expect_success 'merge c1 with c2 (ours in pull.twohead)' ' + git reset --hard c1 && + git config pull.twohead ours && + git merge c2 && + test -f c1.c && + ! test -f c2.c +' + +test_expect_success 'merge c1 with c2 and c3 (recursive in pull.octopus)' ' + git reset --hard c1 && + git config pull.octopus "recursive" && + test_must_fail git merge c2 c3 && + test "$(git rev-parse c1)" = "$(git rev-parse HEAD)" +' + +test_expect_success 'merge c1 with c2 and c3 (recursive and octopus in pull.octopus)' ' + git reset --hard c1 && + git config pull.octopus "recursive octopus" && + git merge c2 c3 && + test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" && + test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" && + test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" && + test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" && + git diff --exit-code && + test -f c0.c && + test -f c1.c && + test -f c2.c && + test -f c3.c +' + +conflict_count() +{ + eval $1=`{ + git diff-files --name-only + git ls-files --unmerged + } | wc -l` +} + +# c4 - c5 +# \ c6 +# +# There are two conflicts here: +# +# 1) Because foo.c is renamed to bar.c, recursive will handle this, +# resolve won't. +# +# 2) One in conflict.c and that will always fail. + +test_expect_success 'setup conflicted merge' ' + git reset --hard c0 && + echo A >conflict.c && + git add conflict.c && + echo contents >foo.c && + git add foo.c && + git commit -m c4 && + git tag c4 && + echo B >conflict.c && + git add conflict.c && + git mv foo.c bar.c && + git commit -m c5 && + git tag c5 && + git reset --hard c4 && + echo C >conflict.c && + git add conflict.c && + echo secondline >> foo.c && + git add foo.c && + git commit -m c6 && + git tag c6 +' + +# First do the merge with resolve and recursive then verify that +# recusive is choosen. + +test_expect_success 'merge picks up the best result' ' + git config pull.twohead "recursive resolve" && + git reset --hard c5 && + git merge -s resolve c6 + conflict_count resolve_count && + git reset --hard c5 && + git merge -s recursive c6 + conflict_count recursive_count && + git reset --hard c5 && + git merge c6 + conflict_count auto_count && + test "$auto_count" = "$recursive_count" && + test "$auto_count" != "$resolve_count" +' + +test_done From e46bbcf6e89e4b1d3d8de1d20d836538ab0f0c85 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Fri, 27 Jun 2008 18:21:58 +0200 Subject: [PATCH 05/20] Move read_cache_unmerged() to read-cache.c builtin-read-tree has a read_cache_unmerged() which is useful for other builtins, for example builtin-merge uses it as well. Move it to read-cache.c to avoid code duplication. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- builtin-read-tree.c | 24 ------------------------ cache.h | 2 ++ read-cache.c | 31 +++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 5a09e17f1ad4fe..72a6de302f8872 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -29,30 +29,6 @@ static int list_tree(unsigned char *sha1) return 0; } -static int read_cache_unmerged(void) -{ - int i; - struct cache_entry **dst; - struct cache_entry *last = NULL; - - read_cache(); - dst = active_cache; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (ce_stage(ce)) { - remove_name_hash(ce); - if (last && !strcmp(ce->name, last->name)) - continue; - cache_tree_invalidate_path(active_cache_tree, ce->name); - last = ce; - continue; - } - *dst++ = ce; - } - active_nr = dst - active_cache; - return !!last; -} - static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree) { struct tree_desc desc; diff --git a/cache.h b/cache.h index 6913573d1a835a..d0d74a3ff880b8 100644 --- a/cache.h +++ b/cache.h @@ -254,6 +254,7 @@ static inline void remove_name_hash(struct cache_entry *ce) #define read_cache() read_index(&the_index) #define read_cache_from(path) read_index_from(&the_index, (path)) +#define read_cache_unmerged() read_index_unmerged(&the_index) #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) @@ -356,6 +357,7 @@ extern int init_db(const char *template_dir, unsigned int flags); /* Initialize and use the cache information */ extern int read_index(struct index_state *); extern int read_index_from(struct index_state *, const char *path); +extern int read_index_unmerged(struct index_state *); extern int write_index(const struct index_state *, int newfd); extern int discard_index(struct index_state *); extern int unmerged_index(const struct index_state *); diff --git a/read-cache.c b/read-cache.c index f83de8c4158e08..d801f9d1cf9eca 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1410,3 +1410,34 @@ int write_index(const struct index_state *istate, int newfd) } return ce_flush(&c, newfd); } + +/* + * Read the index file that is potentially unmerged into given + * index_state, dropping any unmerged entries. Returns true is + * the index is unmerged. Callers who want to refuse to work + * from an unmerged state can call this and check its return value, + * instead of calling read_cache(). + */ +int read_index_unmerged(struct index_state *istate) +{ + int i; + struct cache_entry **dst; + struct cache_entry *last = NULL; + + read_index(istate); + dst = istate->cache; + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; + if (ce_stage(ce)) { + remove_name_hash(ce); + if (last && !strcmp(ce->name, last->name)) + continue; + cache_tree_invalidate_path(istate->cache_tree, ce->name); + last = ce; + continue; + } + *dst++ = ce; + } + istate->cache_nr = dst - istate->cache; + return !!last; +} From 0b9a969e0fbb0a09e9de931cfe27005cbfd6cb7d Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Fri, 27 Jun 2008 18:21:59 +0200 Subject: [PATCH 06/20] git-fmt-merge-msg: make it usable from other builtins Move all functionality (except config and option parsing) from cmd_fmt_merge_msg() to fmt_merge_msg(), so that other builtins can use it without a child process. All functions have been changed to use strbufs, and now only cmd_fmt_merge_msg() reads directly from a file / writes anything to stdout. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- builtin-fmt-merge-msg.c | 155 +++++++++++++++++++++++----------------- builtin.h | 3 + 2 files changed, 92 insertions(+), 66 deletions(-) diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index b892621ab50346..dbd7d2ddae2d4b 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -159,23 +159,24 @@ static int handle_line(char *line) } static void print_joined(const char *singular, const char *plural, - struct list *list) + struct list *list, struct strbuf *out) { if (list->nr == 0) return; if (list->nr == 1) { - printf("%s%s", singular, list->list[0]); + strbuf_addf(out, "%s%s", singular, list->list[0]); } else { int i; - printf("%s", plural); + strbuf_addstr(out, plural); for (i = 0; i < list->nr - 1; i++) - printf("%s%s", i > 0 ? ", " : "", list->list[i]); - printf(" and %s", list->list[list->nr - 1]); + strbuf_addf(out, "%s%s", i > 0 ? ", " : "", list->list[i]); + strbuf_addf(out, " and %s", list->list[list->nr - 1]); } } static void shortlog(const char *name, unsigned char *sha1, - struct commit *head, struct rev_info *rev, int limit) + struct commit *head, struct rev_info *rev, int limit, + struct strbuf *out) { int i, count = 0; struct commit *commit; @@ -232,15 +233,15 @@ static void shortlog(const char *name, unsigned char *sha1, } if (count > limit) - printf("\n* %s: (%d commits)\n", name, count); + strbuf_addf(out, "\n* %s: (%d commits)\n", name, count); else - printf("\n* %s:\n", name); + strbuf_addf(out, "\n* %s:\n", name); for (i = 0; i < subjects.nr; i++) if (i >= limit) - printf(" ...\n"); + strbuf_addf(out, " ...\n"); else - printf(" %s\n", subjects.list[i]); + strbuf_addf(out, " %s\n", subjects.list[i]); clear_commit_marks((struct commit *)branch, flags); clear_commit_marks(head, flags); @@ -251,43 +252,13 @@ static void shortlog(const char *name, unsigned char *sha1, free_list(&subjects); } -int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) -{ - int limit = 20, i = 0; +int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { + int limit = 20, i = 0, pos = 0; char line[1024]; - FILE *in = stdin; - const char *sep = ""; + char *p = line, *sep = ""; unsigned char head_sha1[20]; const char *current_branch; - git_config(fmt_merge_msg_config, NULL); - - while (argc > 1) { - if (!strcmp(argv[1], "--log") || !strcmp(argv[1], "--summary")) - merge_summary = 1; - else if (!strcmp(argv[1], "--no-log") - || !strcmp(argv[1], "--no-summary")) - merge_summary = 0; - else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) { - if (argc < 3) - die ("Which file?"); - if (!strcmp(argv[2], "-")) - in = stdin; - else { - fclose(in); - in = fopen(argv[2], "r"); - if (!in) - die("cannot open %s", argv[2]); - } - argc--; argv++; - } else - break; - argc--; argv++; - } - - if (argc > 1) - usage(fmt_merge_msg_usage); - /* get current branch */ current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); if (!current_branch) @@ -295,75 +266,127 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) if (!prefixcmp(current_branch, "refs/heads/")) current_branch += 11; - while (fgets(line, sizeof(line), in)) { + /* get a line */ + while (pos < in->len) { + int len; + char *newline; + + p = in->buf + pos; + newline = strchr(p, '\n'); + len = newline ? newline - p : strlen(p); + pos += len + !!newline; i++; - if (line[0] == 0) - continue; - if (handle_line(line)) - die ("Error in line %d: %s", i, line); + p[len] = 0; + if (handle_line(p)) + die ("Error in line %d: %.*s", i, len, p); } - printf("Merge "); + strbuf_addstr(out, "Merge "); for (i = 0; i < srcs.nr; i++) { struct src_data *src_data = srcs.payload[i]; const char *subsep = ""; - printf(sep); + strbuf_addstr(out, sep); sep = "; "; if (src_data->head_status == 1) { - printf(srcs.list[i]); + strbuf_addstr(out, srcs.list[i]); continue; } if (src_data->head_status == 3) { subsep = ", "; - printf("HEAD"); + strbuf_addstr(out, "HEAD"); } if (src_data->branch.nr) { - printf(subsep); + strbuf_addstr(out, subsep); subsep = ", "; - print_joined("branch ", "branches ", &src_data->branch); + print_joined("branch ", "branches ", &src_data->branch, + out); } if (src_data->r_branch.nr) { - printf(subsep); + strbuf_addstr(out, subsep); subsep = ", "; print_joined("remote branch ", "remote branches ", - &src_data->r_branch); + &src_data->r_branch, out); } if (src_data->tag.nr) { - printf(subsep); + strbuf_addstr(out, subsep); subsep = ", "; - print_joined("tag ", "tags ", &src_data->tag); + print_joined("tag ", "tags ", &src_data->tag, out); } if (src_data->generic.nr) { - printf(subsep); - print_joined("commit ", "commits ", &src_data->generic); + strbuf_addstr(out, subsep); + print_joined("commit ", "commits ", &src_data->generic, + out); } if (strcmp(".", srcs.list[i])) - printf(" of %s", srcs.list[i]); + strbuf_addf(out, " of %s", srcs.list[i]); } if (!strcmp("master", current_branch)) - putchar('\n'); + strbuf_addch(out, '\n'); else - printf(" into %s\n", current_branch); + strbuf_addf(out, " into %s\n", current_branch); if (merge_summary) { struct commit *head; struct rev_info rev; head = lookup_commit(head_sha1); - init_revisions(&rev, prefix); + init_revisions(&rev, NULL); rev.commit_format = CMIT_FMT_ONELINE; rev.ignore_merges = 1; rev.limited = 1; for (i = 0; i < origins.nr; i++) shortlog(origins.list[i], origins.payload[i], - head, &rev, limit); + head, &rev, limit, out); } + return 0; +} + +int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) +{ + FILE *in = stdin; + struct strbuf input, output; + int ret; + + git_config(fmt_merge_msg_config, NULL); + + while (argc > 1) { + if (!strcmp(argv[1], "--log") || !strcmp(argv[1], "--summary")) + merge_summary = 1; + else if (!strcmp(argv[1], "--no-log") + || !strcmp(argv[1], "--no-summary")) + merge_summary = 0; + else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) { + if (argc < 3) + die ("Which file?"); + if (!strcmp(argv[2], "-")) + in = stdin; + else { + fclose(in); + in = fopen(argv[2], "r"); + if (!in) + die("cannot open %s", argv[2]); + } + argc--; argv++; + } else + break; + argc--; argv++; + } + + if (argc > 1) + usage(fmt_merge_msg_usage); - /* No cleanup yet; is standalone anyway */ + strbuf_init(&input, 0); + if (strbuf_read(&input, fileno(in), 0) < 0) + die("could not read input file %s", strerror(errno)); + strbuf_init(&output, 0); + ret = fmt_merge_msg(merge_summary, &input, &output); + if (ret) + return ret; + printf("%s", output.buf); return 0; } diff --git a/builtin.h b/builtin.h index b460b2da6f4179..2b01feabcb436c 100644 --- a/builtin.h +++ b/builtin.h @@ -2,6 +2,7 @@ #define BUILTIN_H #include "git-compat-util.h" +#include "strbuf.h" extern const char git_version_string[]; extern const char git_usage_string[]; @@ -11,6 +12,8 @@ extern void list_common_cmds_help(void); extern void help_unknown_cmd(const char *cmd); extern void prune_packed_objects(int); extern int read_line_with_nul(char *buf, int size, FILE *file); +extern int fmt_merge_msg(int merge_summary, struct strbuf *in, + struct strbuf *out); extern int cmd_add(int argc, const char **argv, const char *prefix); extern int cmd_annotate(int argc, const char **argv, const char *prefix); From 5240c9d75d8e1b747da427ba6320432d3201168a Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Fri, 27 Jun 2008 18:22:00 +0200 Subject: [PATCH 07/20] Introduce get_octopus_merge_bases() in commit.c This is like get_merge_bases() but it works for multiple heads, like show-branch --merge-base. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- commit.c | 27 +++++++++++++++++++++++++++ commit.h | 1 + 2 files changed, 28 insertions(+) diff --git a/commit.c b/commit.c index bbf9c75416537f..6052ca34c803e6 100644 --- a/commit.c +++ b/commit.c @@ -600,6 +600,33 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two) return result; } +struct commit_list *get_octopus_merge_bases(struct commit_list *in) +{ + struct commit_list *i, *j, *k, *ret = NULL; + struct commit_list **pptr = &ret; + + for (i = in; i; i = i->next) { + if (!ret) + pptr = &commit_list_insert(i->item, pptr)->next; + else { + struct commit_list *new = NULL, *end = NULL; + + for (j = ret; j; j = j->next) { + struct commit_list *bases; + bases = get_merge_bases(i->item, j->item, 1); + if (!new) + new = bases; + else + end->next = bases; + for (k = bases; k; k = k->next) + end = k; + } + ret = new; + } + } + return ret; +} + struct commit_list *get_merge_bases(struct commit *one, struct commit *two, int cleanup) { diff --git a/commit.h b/commit.h index 7f8c5ee0fc15fd..dcec7fb9a2264d 100644 --- a/commit.h +++ b/commit.h @@ -121,6 +121,7 @@ int read_graft_file(const char *graft_file); struct commit_graft *lookup_commit_graft(const unsigned char *sha1); extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup); +extern struct commit_list *get_octopus_merge_bases(struct commit_list *in); extern int register_shallow(const unsigned char *sha1); extern int unregister_shallow(const unsigned char *sha1); From 5948e2ae27d936f7737f4d7433ca6c0b879fe4c8 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Fri, 27 Jun 2008 19:06:26 +0200 Subject: [PATCH 08/20] Add new test to ensure git-merge handles more than 25 refs. The old shell version handled only 25 refs but we no longer have this limitation. Add a test to make sure this limitation will not be introduced again in the future. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- t/t7602-merge-octopus-many.sh | 52 +++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100755 t/t7602-merge-octopus-many.sh diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh new file mode 100755 index 00000000000000..f3a4bb2ea231f6 --- /dev/null +++ b/t/t7602-merge-octopus-many.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +test_description='git-merge + +Testing octopus merge with more than 25 refs.' + +. ./test-lib.sh + +test_expect_success 'setup' ' + echo c0 > c0.c && + git add c0.c && + git commit -m c0 && + git tag c0 && + i=1 && + while test $i -le 30 + do + git reset --hard c0 && + echo c$i > c$i.c && + git add c$i.c && + git commit -m c$i && + git tag c$i && + i=`expr $i + 1` || return 1 + done +' + +test_expect_failure 'merge c1 with c2, c3, c4, ... c29' ' + git reset --hard c1 && + i=2 && + refs="" && + while test $i -le 30 + do + refs="$refs c$i" + i=`expr $i + 1` + done + git merge $refs && + test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" && + i=1 && + while test $i -le 30 + do + test "$(git rev-parse c$i)" = "$(git rev-parse HEAD^$i)" && + i=`expr $i + 1` || return 1 + done && + git diff --exit-code && + i=1 && + while test $i -le 30 + do + test -f c$i.c && + i=`expr $i + 1` || return 1 + done +' + +test_done From 6a938648e1454842157b84408acbb6471ec6745f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 27 Jun 2008 18:22:02 +0200 Subject: [PATCH 09/20] Introduce get_merge_bases_many() This introduces a new function get_merge_bases_many() which is a natural extension of two commit merge base computation. It is given one commit (one) and a set of other commits (twos), and computes the merge base of one and a merge across other commits. This is mostly useful to figure out the common ancestor when iterating over heads during an octopus merge. When making an octopus between commits A, B, C and D, we first merge tree of A and B, and then try to merge C with it. If we were making pairwise merge, we would be recording the tree resulting from the merge between A and B as a commit, say M, and then the next round we will be computing the merge base between M and C. o---C...* / . o---B...M / . o---o---A But during an octopus merge, we actually do not create a commit M. In order to figure out that the common ancestor to use for this merge, instead of computing the merge base between C and M, we can call merge_bases_many() with one set to C and twos containing A and B. Signed-off-by: Junio C Hamano --- commit.c | 56 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/commit.c b/commit.c index 6052ca34c803e6..cafed26928cf6e 100644 --- a/commit.c +++ b/commit.c @@ -533,26 +533,34 @@ static struct commit *interesting(struct commit_list *list) return NULL; } -static struct commit_list *merge_bases(struct commit *one, struct commit *two) +static struct commit_list *merge_bases_many(struct commit *one, int n, struct commit **twos) { struct commit_list *list = NULL; struct commit_list *result = NULL; + int i; - if (one == two) - /* We do not mark this even with RESULT so we do not - * have to clean it up. - */ - return commit_list_insert(one, &result); + for (i = 0; i < n; i++) { + if (one == twos[i]) + /* + * We do not mark this even with RESULT so we do not + * have to clean it up. + */ + return commit_list_insert(one, &result); + } if (parse_commit(one)) return NULL; - if (parse_commit(two)) - return NULL; + for (i = 0; i < n; i++) { + if (parse_commit(twos[i])) + return NULL; + } one->object.flags |= PARENT1; - two->object.flags |= PARENT2; insert_by_date(one, &list); - insert_by_date(two, &list); + for (i = 0; i < n; i++) { + twos[i]->object.flags |= PARENT2; + insert_by_date(twos[i], &list); + } while (interesting(list)) { struct commit *commit; @@ -627,21 +635,26 @@ struct commit_list *get_octopus_merge_bases(struct commit_list *in) return ret; } -struct commit_list *get_merge_bases(struct commit *one, - struct commit *two, int cleanup) +struct commit_list *get_merge_bases_many(struct commit *one, + int n, + struct commit **twos, + int cleanup) { struct commit_list *list; struct commit **rslt; struct commit_list *result; int cnt, i, j; - result = merge_bases(one, two); - if (one == two) - return result; + result = merge_bases_many(one, n, twos); + for (i = 0; i < n; i++) { + if (one == twos[i]) + return result; + } if (!result || !result->next) { if (cleanup) { clear_commit_marks(one, all_flags); - clear_commit_marks(two, all_flags); + for (i = 0; i < n; i++) + clear_commit_marks(twos[i], all_flags); } return result; } @@ -659,12 +672,13 @@ struct commit_list *get_merge_bases(struct commit *one, free_commit_list(result); clear_commit_marks(one, all_flags); - clear_commit_marks(two, all_flags); + for (i = 0; i < n; i++) + clear_commit_marks(twos[i], all_flags); for (i = 0; i < cnt - 1; i++) { for (j = i+1; j < cnt; j++) { if (!rslt[i] || !rslt[j]) continue; - result = merge_bases(rslt[i], rslt[j]); + result = merge_bases_many(rslt[i], 1, &rslt[j]); clear_commit_marks(rslt[i], all_flags); clear_commit_marks(rslt[j], all_flags); for (list = result; list; list = list->next) { @@ -686,6 +700,12 @@ struct commit_list *get_merge_bases(struct commit *one, return result; } +struct commit_list *get_merge_bases(struct commit *one, struct commit *two, + int cleanup) +{ + return get_merge_bases_many(one, 1, &two, cleanup); +} + int in_merge_bases(struct commit *commit, struct commit **reference, int num) { struct commit_list *bases, *b; From 98cf9c3bd7504d36e6049939bf9cc624f2cf5b9f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 27 Jun 2008 18:22:03 +0200 Subject: [PATCH 10/20] Introduce reduce_heads() The new function reduce_heads() is given a list of commits, and removes ones that can be reached from other commits in the list. It is useful for reducing the commits randomly thrown at the git-merge command and remove redundant commits that the user shouldn't have given to it. The implementation uses the get_merge_bases_many() introduced in the previous commit. If the merge base between one commit taken from the list and the remaining commits is the commit itself, that means the commit is reachable from some of the other commits. Signed-off-by: Junio C Hamano --- commit.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ commit.h | 2 ++ 2 files changed, 47 insertions(+) diff --git a/commit.c b/commit.c index cafed26928cf6e..d20b14ee3efa71 100644 --- a/commit.c +++ b/commit.c @@ -725,3 +725,48 @@ int in_merge_bases(struct commit *commit, struct commit **reference, int num) free_commit_list(bases); return ret; } + +struct commit_list *reduce_heads(struct commit_list *heads) +{ + struct commit_list *p; + struct commit_list *result = NULL, **tail = &result; + struct commit **other; + size_t num_head, num_other; + + if (!heads) + return NULL; + + /* Avoid unnecessary reallocations */ + for (p = heads, num_head = 0; p; p = p->next) + num_head++; + other = xcalloc(sizeof(*other), num_head); + + /* For each commit, see if it can be reached by others */ + for (p = heads; p; p = p->next) { + struct commit_list *q, *base; + + num_other = 0; + for (q = heads; q; q = q->next) { + if (p == q) + continue; + other[num_other++] = q->item; + } + if (num_other) { + base = get_merge_bases_many(p->item, num_other, other, 1); + } else + base = NULL; + /* + * If p->item does not have anything common with other + * commits, there won't be any merge base. If it is + * reachable from some of the others, p->item will be + * the merge base. If its history is connected with + * others, but p->item is not reachable by others, we + * will get something other than p->item back. + */ + if (!base || (base->item != p->item)) + tail = &(commit_list_insert(p->item, tail)->next); + free_commit_list(base); + } + free(other); + return result; +} diff --git a/commit.h b/commit.h index dcec7fb9a2264d..2acfc79d3483b4 100644 --- a/commit.h +++ b/commit.h @@ -140,4 +140,6 @@ static inline int single_parent(struct commit *commit) return commit->parents && !commit->parents->next; } +struct commit_list *reduce_heads(struct commit_list *heads); + #endif /* COMMIT_H */ From f4022fa33f1b0a63029d1bc3748f01f720151218 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Fri, 27 Jun 2008 18:22:06 +0200 Subject: [PATCH 11/20] Add new test case to ensure git-merge reduces octopus parents when possible The old shell version used show-branch --independent to filter for the ones that cannot be reached from any other reference. The new C version uses reduce_heads() from commit.c for this, so add test to ensure it works as expected. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- t/t7603-merge-reduce-heads.sh | 63 +++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100755 t/t7603-merge-reduce-heads.sh diff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh new file mode 100755 index 00000000000000..17b19dc11f2b1a --- /dev/null +++ b/t/t7603-merge-reduce-heads.sh @@ -0,0 +1,63 @@ +#!/bin/sh + +test_description='git-merge + +Testing octopus merge when reducing parents to independent branches.' + +. ./test-lib.sh + +# 0 - 1 +# \ 2 +# \ 3 +# \ 4 - 5 +# +# So 1, 2, 3 and 5 should be kept, 4 should be avoided. + +test_expect_success 'setup' ' + echo c0 > c0.c && + git add c0.c && + git commit -m c0 && + git tag c0 && + echo c1 > c1.c && + git add c1.c && + git commit -m c1 && + git tag c1 && + git reset --hard c0 && + echo c2 > c2.c && + git add c2.c && + git commit -m c2 && + git tag c2 && + git reset --hard c0 && + echo c3 > c3.c && + git add c3.c && + git commit -m c3 && + git tag c3 && + git reset --hard c0 && + echo c4 > c4.c && + git add c4.c && + git commit -m c4 && + git tag c4 && + echo c5 > c5.c && + git add c5.c && + git commit -m c5 && + git tag c5 +' + +test_expect_success 'merge c1 with c2, c3, c4, c5' ' + git reset --hard c1 && + git merge c2 c3 c4 c5 && + test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" && + test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" && + test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" && + test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" && + test "$(git rev-parse c5)" = "$(git rev-parse HEAD^4)" && + git diff --exit-code && + test -f c0.c && + test -f c1.c && + test -f c2.c && + test -f c3.c && + test -f c4.c && + test -f c5.c +' + +test_done From c1fb35b98aa36ce789c60520028d95e5f9fee43c Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Fri, 27 Jun 2008 18:22:07 +0200 Subject: [PATCH 12/20] Add new test case to ensure git-merge prepends the custom merge message There was no test for this before, so the testsuite passed, even in case the merge summary was missing from the merge commit message. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- t/t7604-merge-custom-message.sh | 37 +++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100755 t/t7604-merge-custom-message.sh diff --git a/t/t7604-merge-custom-message.sh b/t/t7604-merge-custom-message.sh new file mode 100755 index 00000000000000..6081677d234f1f --- /dev/null +++ b/t/t7604-merge-custom-message.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +test_description='git-merge + +Testing merge when using a custom message for the merge commit.' + +. ./test-lib.sh + +test_expect_success 'setup' ' + echo c0 > c0.c && + git add c0.c && + git commit -m c0 && + git tag c0 && + echo c1 > c1.c && + git add c1.c && + git commit -m c1 && + git tag c1 && + git reset --hard c0 && + echo c2 > c2.c && + git add c2.c && + git commit -m c2 && + git tag c2 +' + +cat >expected <<\EOF +custom message + +Merge commit 'c2' +EOF +test_expect_success 'merge c2 with a custom message' ' + git reset --hard c1 && + git merge -m "custom message" c2 && + git cat-file commit HEAD | sed -e "1,/^$/d" > actual && + test_cmp expected actual +' + +test_done From 7b9c0a69a5f64fffb5de8b49a96f41ac35b4a84f Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Tue, 1 Jul 2008 04:37:49 +0200 Subject: [PATCH 13/20] git-commit-tree: make it usable from other builtins Move all functionality (except option parsing) from cmd_commit_tree() to commit_tree(), so that other builtins can use it without a child process. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- builtin-commit-tree.c | 71 +++++++++++++++++++++++++------------------ builtin.h | 4 +++ 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c index 3881f6c2f56f31..7a9a309be0543d 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -45,41 +45,19 @@ static const char commit_utf8_warn[] = "You may want to amend it after fixing the message, or set the config\n" "variable i18n.commitencoding to the encoding your project uses.\n"; -int cmd_commit_tree(int argc, const char **argv, const char *prefix) +int commit_tree(const char *msg, unsigned char *tree, + struct commit_list *parents, unsigned char *ret) { - int i; - struct commit_list *parents = NULL; - unsigned char tree_sha1[20]; - unsigned char commit_sha1[20]; - struct strbuf buffer; int encoding_is_utf8; + struct strbuf buffer; - git_config(git_default_config, NULL); - - if (argc < 2) - usage(commit_tree_usage); - if (get_sha1(argv[1], tree_sha1)) - die("Not a valid object name %s", argv[1]); - - check_valid(tree_sha1, OBJ_TREE); - for (i = 2; i < argc; i += 2) { - unsigned char sha1[20]; - const char *a, *b; - a = argv[i]; b = argv[i+1]; - if (!b || strcmp(a, "-p")) - usage(commit_tree_usage); - - if (get_sha1(b, sha1)) - die("Not a valid object name %s", b); - check_valid(sha1, OBJ_COMMIT); - new_parent(lookup_commit(sha1), &parents); - } + check_valid(tree, OBJ_TREE); /* Not having i18n.commitencoding is the same as having utf-8 */ encoding_is_utf8 = is_encoding_utf8(git_commit_encoding); strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */ - strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree_sha1)); + strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree)); /* * NOTE! This ordering means that the same exact tree merged with a @@ -102,14 +80,47 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) strbuf_addch(&buffer, '\n'); /* And add the comment */ - if (strbuf_read(&buffer, 0, 0) < 0) - die("git-commit-tree: read returned %s", strerror(errno)); + strbuf_addstr(&buffer, msg); /* And check the encoding */ if (encoding_is_utf8 && !is_utf8(buffer.buf)) fprintf(stderr, commit_utf8_warn); - if (!write_sha1_file(buffer.buf, buffer.len, commit_type, commit_sha1)) { + return write_sha1_file(buffer.buf, buffer.len, commit_type, ret); +} + +int cmd_commit_tree(int argc, const char **argv, const char *prefix) +{ + int i; + struct commit_list *parents = NULL; + unsigned char tree_sha1[20]; + unsigned char commit_sha1[20]; + struct strbuf buffer = STRBUF_INIT; + + git_config(git_default_config, NULL); + + if (argc < 2) + usage(commit_tree_usage); + if (get_sha1(argv[1], tree_sha1)) + die("Not a valid object name %s", argv[1]); + + for (i = 2; i < argc; i += 2) { + unsigned char sha1[20]; + const char *a, *b; + a = argv[i]; b = argv[i+1]; + if (!b || strcmp(a, "-p")) + usage(commit_tree_usage); + + if (get_sha1(b, sha1)) + die("Not a valid object name %s", b); + check_valid(sha1, OBJ_COMMIT); + new_parent(lookup_commit(sha1), &parents); + } + + if (strbuf_read(&buffer, 0, 0) < 0) + die("git-commit-tree: read returned %s", strerror(errno)); + + if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1)) { printf("%s\n", sha1_to_hex(commit_sha1)); return 0; } diff --git a/builtin.h b/builtin.h index 2b01feabcb436c..05ee56f21b1a54 100644 --- a/builtin.h +++ b/builtin.h @@ -3,6 +3,8 @@ #include "git-compat-util.h" #include "strbuf.h" +#include "cache.h" +#include "commit.h" extern const char git_version_string[]; extern const char git_usage_string[]; @@ -14,6 +16,8 @@ extern void prune_packed_objects(int); extern int read_line_with_nul(char *buf, int size, FILE *file); extern int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out); +extern int commit_tree(const char *msg, unsigned char *tree, + struct commit_list *parents, unsigned char *ret); extern int cmd_add(int argc, const char **argv, const char *prefix); extern int cmd_annotate(int argc, const char **argv, const char *prefix); From 1c6669351a47c834cceb75d6044a1aae259fc69f Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Sat, 5 Jul 2008 16:23:58 +0200 Subject: [PATCH 14/20] Fix t7601-merge-pull-config.sh on AIX The test failed on AIX (and likely other OS, such as apparently OSX) where wc -l outputs whitespace. Also, avoid unnecessary eval in conflict_count(). Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- t/t7601-merge-pull-config.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh index 32585f8e0dcedc..95b4d71c5154a6 100755 --- a/t/t7601-merge-pull-config.sh +++ b/t/t7601-merge-pull-config.sh @@ -70,10 +70,10 @@ test_expect_success 'merge c1 with c2 and c3 (recursive and octopus in pull.octo conflict_count() { - eval $1=`{ + { git diff-files --name-only git ls-files --unmerged - } | wc -l` + } | wc -l } # c4 - c5 @@ -115,15 +115,15 @@ test_expect_success 'merge picks up the best result' ' git config pull.twohead "recursive resolve" && git reset --hard c5 && git merge -s resolve c6 - conflict_count resolve_count && + resolve_count=$(conflict_count) && git reset --hard c5 && git merge -s recursive c6 - conflict_count recursive_count && + recursive_count=$(conflict_count) && git reset --hard c5 && git merge c6 - conflict_count auto_count && - test "$auto_count" = "$recursive_count" && - test "$auto_count" != "$resolve_count" + auto_count=$(conflict_count) && + test $auto_count = $recursive_count && + test $auto_count != $resolve_count ' test_done From 1c7b76be7d620bbaf2e6b8417f04012326bbb9df Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Mon, 7 Jul 2008 19:24:20 +0200 Subject: [PATCH 15/20] Build in merge Mentored-by: Johannes Schindelin Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- Makefile | 2 +- builtin-merge.c | 1153 +++++++++++++++++ builtin.h | 1 + git-merge.sh => contrib/examples/git-merge.sh | 0 git.c | 1 + t/t7602-merge-octopus-many.sh | 2 +- 6 files changed, 1157 insertions(+), 2 deletions(-) create mode 100644 builtin-merge.c rename git-merge.sh => contrib/examples/git-merge.sh (100%) diff --git a/Makefile b/Makefile index bf77292f1c04b0..fbc53e9cb35d26 100644 --- a/Makefile +++ b/Makefile @@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh SCRIPT_SH += git-merge-octopus.sh SCRIPT_SH += git-merge-one-file.sh SCRIPT_SH += git-merge-resolve.sh -SCRIPT_SH += git-merge.sh SCRIPT_SH += git-merge-stupid.sh SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-parse-remote.sh @@ -515,6 +514,7 @@ BUILTIN_OBJS += builtin-ls-remote.o BUILTIN_OBJS += builtin-ls-tree.o BUILTIN_OBJS += builtin-mailinfo.o BUILTIN_OBJS += builtin-mailsplit.o +BUILTIN_OBJS += builtin-merge.o BUILTIN_OBJS += builtin-merge-base.o BUILTIN_OBJS += builtin-merge-file.o BUILTIN_OBJS += builtin-merge-ours.o diff --git a/builtin-merge.c b/builtin-merge.c new file mode 100644 index 00000000000000..b2e702a11fe0c1 --- /dev/null +++ b/builtin-merge.c @@ -0,0 +1,1153 @@ +/* + * Builtin "git merge" + * + * Copyright (c) 2008 Miklos Vajna + * + * Based on git-merge.sh by Junio C Hamano. + */ + +#include "cache.h" +#include "parse-options.h" +#include "builtin.h" +#include "run-command.h" +#include "diff.h" +#include "refs.h" +#include "commit.h" +#include "diffcore.h" +#include "revision.h" +#include "unpack-trees.h" +#include "cache-tree.h" +#include "dir.h" +#include "utf8.h" +#include "log-tree.h" +#include "color.h" + +#define DEFAULT_TWOHEAD (1<<0) +#define DEFAULT_OCTOPUS (1<<1) +#define NO_FAST_FORWARD (1<<2) +#define NO_TRIVIAL (1<<3) + +struct strategy { + const char *name; + unsigned attr; +}; + +static const char * const builtin_merge_usage[] = { + "git-merge [options] ...", + "git-merge [options] HEAD ", + NULL +}; + +static int show_diffstat = 1, option_log, squash; +static int option_commit = 1, allow_fast_forward = 1; +static int allow_trivial = 1, have_message; +static struct strbuf merge_msg; +static struct commit_list *remoteheads; +static unsigned char head[20], stash[20]; +static struct strategy **use_strategies; +static size_t use_strategies_nr, use_strategies_alloc; +static const char *branch; + +static struct strategy all_strategy[] = { + { "recur", NO_TRIVIAL }, + { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, + { "octopus", DEFAULT_OCTOPUS }, + { "resolve", 0 }, + { "stupid", 0 }, + { "ours", NO_FAST_FORWARD | NO_TRIVIAL }, + { "subtree", NO_FAST_FORWARD | NO_TRIVIAL }, +}; + +static const char *pull_twohead, *pull_octopus; + +static int option_parse_message(const struct option *opt, + const char *arg, int unset) +{ + struct strbuf *buf = opt->value; + + if (unset) + strbuf_setlen(buf, 0); + else { + strbuf_addf(buf, "%s\n\n", arg); + have_message = 1; + } + return 0; +} + +static struct strategy *get_strategy(const char *name) +{ + int i; + + if (!name) + return NULL; + + for (i = 0; i < ARRAY_SIZE(all_strategy); i++) + if (!strcmp(name, all_strategy[i].name)) + return &all_strategy[i]; + return NULL; +} + +static void append_strategy(struct strategy *s) +{ + ALLOC_GROW(use_strategies, use_strategies_nr + 1, use_strategies_alloc); + use_strategies[use_strategies_nr++] = s; +} + +static int option_parse_strategy(const struct option *opt, + const char *name, int unset) +{ + int i; + struct strategy *s; + + if (unset) + return 0; + + s = get_strategy(name); + + if (s) + append_strategy(s); + else { + struct strbuf err; + strbuf_init(&err, 0); + for (i = 0; i < ARRAY_SIZE(all_strategy); i++) + strbuf_addf(&err, " %s", all_strategy[i].name); + fprintf(stderr, "Could not find merge strategy '%s'.\n", name); + fprintf(stderr, "Available strategies are:%s.\n", err.buf); + exit(1); + } + return 0; +} + +static int option_parse_n(const struct option *opt, + const char *arg, int unset) +{ + show_diffstat = unset; + return 0; +} + +static struct option builtin_merge_options[] = { + { OPTION_CALLBACK, 'n', NULL, NULL, NULL, + "do not show a diffstat at the end of the merge", + PARSE_OPT_NOARG, option_parse_n }, + OPT_BOOLEAN(0, "stat", &show_diffstat, + "show a diffstat at the end of the merge"), + OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"), + OPT_BOOLEAN(0, "log", &option_log, + "add list of one-line log to merge commit message"), + OPT_BOOLEAN(0, "squash", &squash, + "create a single commit instead of doing a merge"), + OPT_BOOLEAN(0, "commit", &option_commit, + "perform a commit if the merge succeeds (default)"), + OPT_BOOLEAN(0, "ff", &allow_fast_forward, + "allow fast forward (default)"), + OPT_CALLBACK('s', "strategy", &use_strategies, "strategy", + "merge strategy to use", option_parse_strategy), + OPT_CALLBACK('m', "message", &merge_msg, "message", + "message to be used for the merge commit (if any)", + option_parse_message), + OPT_END() +}; + +/* Cleans up metadata that is uninteresting after a succeeded merge. */ +static void drop_save(void) +{ + unlink(git_path("MERGE_HEAD")); + unlink(git_path("MERGE_MSG")); +} + +static void save_state(void) +{ + int len; + struct child_process cp; + struct strbuf buffer = STRBUF_INIT; + const char *argv[] = {"stash", "create", NULL}; + + memset(&cp, 0, sizeof(cp)); + cp.argv = argv; + cp.out = -1; + cp.git_cmd = 1; + + if (start_command(&cp)) + die("could not run stash."); + len = strbuf_read(&buffer, cp.out, 1024); + close(cp.out); + + if (finish_command(&cp) || len < 0) + die("stash failed"); + else if (!len) + return; + strbuf_setlen(&buffer, buffer.len-1); + if (get_sha1(buffer.buf, stash)) + die("not a valid object: %s", buffer.buf); +} + +static void reset_hard(unsigned const char *sha1, int verbose) +{ + int i = 0; + const char *args[6]; + + args[i++] = "read-tree"; + if (verbose) + args[i++] = "-v"; + args[i++] = "--reset"; + args[i++] = "-u"; + args[i++] = sha1_to_hex(sha1); + args[i] = NULL; + + if (run_command_v_opt(args, RUN_GIT_CMD)) + die("read-tree failed"); +} + +static void restore_state(void) +{ + struct strbuf sb; + const char *args[] = { "stash", "apply", NULL, NULL }; + + if (is_null_sha1(stash)) + return; + + reset_hard(head, 1); + + strbuf_init(&sb, 0); + args[2] = sha1_to_hex(stash); + + /* + * It is OK to ignore error here, for example when there was + * nothing to restore. + */ + run_command_v_opt(args, RUN_GIT_CMD); + + strbuf_release(&sb); + refresh_cache(REFRESH_QUIET); +} + +/* This is called when no merge was necessary. */ +static void finish_up_to_date(const char *msg) +{ + printf("%s%s\n", squash ? " (nothing to squash)" : "", msg); + drop_save(); +} + +static void squash_message(void) +{ + struct rev_info rev; + struct commit *commit; + struct strbuf out; + struct commit_list *j; + int fd; + + printf("Squash commit -- not updating HEAD\n"); + fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666); + if (fd < 0) + die("Could not write to %s", git_path("SQUASH_MSG")); + + init_revisions(&rev, NULL); + rev.ignore_merges = 1; + rev.commit_format = CMIT_FMT_MEDIUM; + + commit = lookup_commit(head); + commit->object.flags |= UNINTERESTING; + add_pending_object(&rev, &commit->object, NULL); + + for (j = remoteheads; j; j = j->next) + add_pending_object(&rev, &j->item->object, NULL); + + setup_revisions(0, NULL, &rev, NULL); + if (prepare_revision_walk(&rev)) + die("revision walk setup failed"); + + strbuf_init(&out, 0); + strbuf_addstr(&out, "Squashed commit of the following:\n"); + while ((commit = get_revision(&rev)) != NULL) { + strbuf_addch(&out, '\n'); + strbuf_addf(&out, "commit %s\n", + sha1_to_hex(commit->object.sha1)); + pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev, + NULL, NULL, rev.date_mode, 0); + } + write(fd, out.buf, out.len); + close(fd); + strbuf_release(&out); +} + +static int run_hook(const char *name) +{ + struct child_process hook; + const char *argv[3], *env[2]; + char index[PATH_MAX]; + + argv[0] = git_path("hooks/%s", name); + if (access(argv[0], X_OK) < 0) + return 0; + + snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file()); + env[0] = index; + env[1] = NULL; + + if (squash) + argv[1] = "1"; + else + argv[1] = "0"; + argv[2] = NULL; + + memset(&hook, 0, sizeof(hook)); + hook.argv = argv; + hook.no_stdin = 1; + hook.stdout_to_stderr = 1; + hook.env = env; + + return run_command(&hook); +} + +static void finish(const unsigned char *new_head, const char *msg) +{ + struct strbuf reflog_message; + + strbuf_init(&reflog_message, 0); + if (!msg) + strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION")); + else { + printf("%s\n", msg); + strbuf_addf(&reflog_message, "%s: %s", + getenv("GIT_REFLOG_ACTION"), msg); + } + if (squash) { + squash_message(); + } else { + if (!merge_msg.len) + printf("No merge message -- not updating HEAD\n"); + else { + const char *argv_gc_auto[] = { "gc", "--auto", NULL }; + update_ref(reflog_message.buf, "HEAD", + new_head, head, 0, + DIE_ON_ERR); + /* + * We ignore errors in 'gc --auto', since the + * user should see them. + */ + run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + } + } + if (new_head && show_diffstat) { + struct diff_options opts; + diff_setup(&opts); + opts.output_format |= + DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; + opts.detect_rename = DIFF_DETECT_RENAME; + if (diff_use_color_default > 0) + DIFF_OPT_SET(&opts, COLOR_DIFF); + if (diff_setup_done(&opts) < 0) + die("diff_setup_done failed"); + diff_tree_sha1(head, new_head, "", &opts); + diffcore_std(&opts); + diff_flush(&opts); + } + + /* Run a post-merge hook */ + run_hook("post-merge"); + + strbuf_release(&reflog_message); +} + +/* Get the name for the merge commit's message. */ +static void merge_name(const char *remote, struct strbuf *msg) +{ + struct object *remote_head; + unsigned char branch_head[20], buf_sha[20]; + struct strbuf buf; + const char *ptr; + int len, early; + + memset(branch_head, 0, sizeof(branch_head)); + remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT); + if (!remote_head) + die("'%s' does not point to a commit", remote); + + strbuf_init(&buf, 0); + strbuf_addstr(&buf, "refs/heads/"); + strbuf_addstr(&buf, remote); + resolve_ref(buf.buf, branch_head, 0, 0); + + if (!hashcmp(remote_head->sha1, branch_head)) { + strbuf_addf(msg, "%s\t\tbranch '%s' of .\n", + sha1_to_hex(branch_head), remote); + return; + } + + /* See if remote matches ^^^.. or ~ */ + for (len = 0, ptr = remote + strlen(remote); + remote < ptr && ptr[-1] == '^'; + ptr--) + len++; + if (len) + early = 1; + else { + early = 0; + ptr = strrchr(remote, '~'); + if (ptr) { + int seen_nonzero = 0; + + len++; /* count ~ */ + while (*++ptr && isdigit(*ptr)) { + seen_nonzero |= (*ptr != '0'); + len++; + } + if (*ptr) + len = 0; /* not ...~ */ + else if (seen_nonzero) + early = 1; + else if (len == 1) + early = 1; /* "name~" is "name~1"! */ + } + } + if (len) { + struct strbuf truname = STRBUF_INIT; + strbuf_addstr(&truname, "refs/heads/"); + strbuf_addstr(&truname, remote); + strbuf_setlen(&truname, len+11); + if (resolve_ref(truname.buf, buf_sha, 0, 0)) { + strbuf_addf(msg, + "%s\t\tbranch '%s'%s of .\n", + sha1_to_hex(remote_head->sha1), + truname.buf, + (early ? " (early part)" : "")); + return; + } + } + + if (!strcmp(remote, "FETCH_HEAD") && + !access(git_path("FETCH_HEAD"), R_OK)) { + FILE *fp; + struct strbuf line; + char *ptr; + + strbuf_init(&line, 0); + fp = fopen(git_path("FETCH_HEAD"), "r"); + if (!fp) + die("could not open %s for reading: %s", + git_path("FETCH_HEAD"), strerror(errno)); + strbuf_getline(&line, fp, '\n'); + fclose(fp); + ptr = strstr(line.buf, "\tnot-for-merge\t"); + if (ptr) + strbuf_remove(&line, ptr-line.buf+1, 13); + strbuf_addbuf(msg, &line); + strbuf_release(&line); + return; + } + strbuf_addf(msg, "%s\t\tcommit '%s'\n", + sha1_to_hex(remote_head->sha1), remote); +} + +int git_merge_config(const char *k, const char *v, void *cb) +{ + if (branch && !prefixcmp(k, "branch.") && + !prefixcmp(k + 7, branch) && + !strcmp(k + 7 + strlen(branch), ".mergeoptions")) { + const char **argv; + int argc; + char *buf; + + buf = xstrdup(v); + argc = split_cmdline(buf, &argv); + argv = xrealloc(argv, sizeof(*argv) * (argc + 2)); + memmove(argv + 1, argv, sizeof(*argv) * (argc + 1)); + argc++; + parse_options(argc, argv, builtin_merge_options, + builtin_merge_usage, 0); + free(buf); + } + + if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) + show_diffstat = git_config_bool(k, v); + else if (!strcmp(k, "pull.twohead")) + return git_config_string(&pull_twohead, k, v); + else if (!strcmp(k, "pull.octopus")) + return git_config_string(&pull_octopus, k, v); + return git_diff_ui_config(k, v, cb); +} + +static int read_tree_trivial(unsigned char *common, unsigned char *head, + unsigned char *one) +{ + int i, nr_trees = 0; + struct tree *trees[MAX_UNPACK_TREES]; + struct tree_desc t[MAX_UNPACK_TREES]; + struct unpack_trees_options opts; + + memset(&opts, 0, sizeof(opts)); + opts.head_idx = 2; + opts.src_index = &the_index; + opts.dst_index = &the_index; + opts.update = 1; + opts.verbose_update = 1; + opts.trivial_merges_only = 1; + opts.merge = 1; + trees[nr_trees] = parse_tree_indirect(common); + if (!trees[nr_trees++]) + return -1; + trees[nr_trees] = parse_tree_indirect(head); + if (!trees[nr_trees++]) + return -1; + trees[nr_trees] = parse_tree_indirect(one); + if (!trees[nr_trees++]) + return -1; + opts.fn = threeway_merge; + cache_tree_free(&active_cache_tree); + for (i = 0; i < nr_trees; i++) { + parse_tree(trees[i]); + init_tree_desc(t+i, trees[i]->buffer, trees[i]->size); + } + if (unpack_trees(nr_trees, t, &opts)) + return -1; + return 0; +} + +static void write_tree_trivial(unsigned char *sha1) +{ + if (write_cache_as_tree(sha1, 0, NULL)) + die("git write-tree failed to write a tree"); +} + +static int try_merge_strategy(const char *strategy, struct commit_list *common, + const char *head_arg) +{ + const char **args; + int i = 0, ret; + struct commit_list *j; + struct strbuf buf; + + args = xmalloc((4 + commit_list_count(common) + + commit_list_count(remoteheads)) * sizeof(char *)); + strbuf_init(&buf, 0); + strbuf_addf(&buf, "merge-%s", strategy); + args[i++] = buf.buf; + for (j = common; j; j = j->next) + args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); + args[i++] = "--"; + args[i++] = head_arg; + for (j = remoteheads; j; j = j->next) + args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); + args[i] = NULL; + ret = run_command_v_opt(args, RUN_GIT_CMD); + strbuf_release(&buf); + i = 1; + for (j = common; j; j = j->next) + free((void *)args[i++]); + i += 2; + for (j = remoteheads; j; j = j->next) + free((void *)args[i++]); + free(args); + return -ret; +} + +static void count_diff_files(struct diff_queue_struct *q, + struct diff_options *opt, void *data) +{ + int *count = data; + + (*count) += q->nr; +} + +static int count_unmerged_entries(void) +{ + const struct index_state *state = &the_index; + int i, ret = 0; + + for (i = 0; i < state->cache_nr; i++) + if (ce_stage(state->cache[i])) + ret++; + + return ret; +} + +static int checkout_fast_forward(unsigned char *head, unsigned char *remote) +{ + struct tree *trees[MAX_UNPACK_TREES]; + struct unpack_trees_options opts; + struct tree_desc t[MAX_UNPACK_TREES]; + int i, fd, nr_trees = 0; + struct dir_struct dir; + struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); + + if (read_cache_unmerged()) + die("you need to resolve your current index first"); + + fd = hold_locked_index(lock_file, 1); + + memset(&trees, 0, sizeof(trees)); + memset(&opts, 0, sizeof(opts)); + memset(&t, 0, sizeof(t)); + dir.show_ignored = 1; + dir.exclude_per_dir = ".gitignore"; + opts.dir = &dir; + + opts.head_idx = 1; + opts.src_index = &the_index; + opts.dst_index = &the_index; + opts.update = 1; + opts.verbose_update = 1; + opts.merge = 1; + opts.fn = twoway_merge; + + trees[nr_trees] = parse_tree_indirect(head); + if (!trees[nr_trees++]) + return -1; + trees[nr_trees] = parse_tree_indirect(remote); + if (!trees[nr_trees++]) + return -1; + for (i = 0; i < nr_trees; i++) { + parse_tree(trees[i]); + init_tree_desc(t+i, trees[i]->buffer, trees[i]->size); + } + if (unpack_trees(nr_trees, t, &opts)) + return -1; + if (write_cache(fd, active_cache, active_nr) || + commit_locked_index(lock_file)) + die("unable to write new index file"); + return 0; +} + +static void split_merge_strategies(const char *string, struct strategy **list, + int *nr, int *alloc) +{ + char *p, *q, *buf; + + if (!string) + return; + + buf = xstrdup(string); + q = buf; + for (;;) { + p = strchr(q, ' '); + if (!p) { + ALLOC_GROW(*list, *nr + 1, *alloc); + (*list)[(*nr)++].name = xstrdup(q); + free(buf); + return; + } else { + *p = '\0'; + ALLOC_GROW(*list, *nr + 1, *alloc); + (*list)[(*nr)++].name = xstrdup(q); + q = ++p; + } + } +} + +static void add_strategies(const char *string, unsigned attr) +{ + struct strategy *list = NULL; + int list_alloc = 0, list_nr = 0, i; + + memset(&list, 0, sizeof(list)); + split_merge_strategies(string, &list, &list_nr, &list_alloc); + if (list != NULL) { + for (i = 0; i < list_nr; i++) { + struct strategy *s; + + s = get_strategy(list[i].name); + if (s) + append_strategy(s); + } + return; + } + for (i = 0; i < ARRAY_SIZE(all_strategy); i++) + if (all_strategy[i].attr & attr) + append_strategy(&all_strategy[i]); + +} + +static int merge_trivial(void) +{ + unsigned char result_tree[20], result_commit[20]; + struct commit_list parent; + + write_tree_trivial(result_tree); + printf("Wonderful.\n"); + parent.item = remoteheads->item; + parent.next = NULL; + commit_tree(merge_msg.buf, result_tree, &parent, result_commit); + finish(result_commit, "In-index merge"); + drop_save(); + return 0; +} + +static int finish_automerge(struct commit_list *common, + unsigned char *result_tree, + const char *wt_strategy) +{ + struct commit_list *parents = NULL, *j; + struct strbuf buf = STRBUF_INIT; + unsigned char result_commit[20]; + + free_commit_list(common); + if (allow_fast_forward) { + parents = remoteheads; + commit_list_insert(lookup_commit(head), &parents); + parents = reduce_heads(parents); + } else { + struct commit_list **pptr = &parents; + + pptr = &commit_list_insert(lookup_commit(head), + pptr)->next; + for (j = remoteheads; j; j = j->next) + pptr = &commit_list_insert(j->item, pptr)->next; + } + free_commit_list(remoteheads); + strbuf_addch(&merge_msg, '\n'); + commit_tree(merge_msg.buf, result_tree, parents, result_commit); + strbuf_addf(&buf, "Merge made by %s.", wt_strategy); + finish(result_commit, buf.buf); + strbuf_release(&buf); + drop_save(); + return 0; +} + +static int suggest_conflicts(void) +{ + FILE *fp; + int pos; + + fp = fopen(git_path("MERGE_MSG"), "a"); + if (!fp) + die("Could open %s for writing", git_path("MERGE_MSG")); + fprintf(fp, "\nConflicts:\n"); + for (pos = 0; pos < active_nr; pos++) { + struct cache_entry *ce = active_cache[pos]; + + if (ce_stage(ce)) { + fprintf(fp, "\t%s\n", ce->name); + while (pos + 1 < active_nr && + !strcmp(ce->name, + active_cache[pos + 1]->name)) + pos++; + } + } + fclose(fp); + rerere(); + printf("Automatic merge failed; " + "fix conflicts and then commit the result.\n"); + return 1; +} + +static struct commit *is_old_style_invocation(int argc, const char **argv) +{ + struct commit *second_token = NULL; + if (argc > 1) { + unsigned char second_sha1[20]; + + if (get_sha1(argv[1], second_sha1)) + return NULL; + second_token = lookup_commit_reference_gently(second_sha1, 0); + if (!second_token) + die("'%s' is not a commit", argv[1]); + if (hashcmp(second_token->object.sha1, head)) + return NULL; + } + return second_token; +} + +static int evaluate_result(void) +{ + int cnt = 0; + struct rev_info rev; + + if (read_cache() < 0) + die("failed to read the cache"); + + /* Check how many files differ. */ + init_revisions(&rev, ""); + setup_revisions(0, NULL, &rev, NULL); + rev.diffopt.output_format |= + DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = count_diff_files; + rev.diffopt.format_callback_data = &cnt; + run_diff_files(&rev, 0); + + /* + * Check how many unmerged entries are + * there. + */ + cnt += count_unmerged_entries(); + + return cnt; +} + +int cmd_merge(int argc, const char **argv, const char *prefix) +{ + unsigned char result_tree[20]; + struct strbuf buf; + const char *head_arg; + int flag, head_invalid = 0, i; + int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0; + struct commit_list *common = NULL; + const char *best_strategy = NULL, *wt_strategy = NULL; + struct commit_list **remotes = &remoteheads; + + setup_work_tree(); + if (unmerged_cache()) + die("You are in the middle of a conflicted merge."); + + /* + * Check if we are _not_ on a detached HEAD, i.e. if there is a + * current branch. + */ + branch = resolve_ref("HEAD", head, 0, &flag); + if (branch && !prefixcmp(branch, "refs/heads/")) + branch += 11; + if (is_null_sha1(head)) + head_invalid = 1; + + git_config(git_merge_config, NULL); + + /* for color.ui */ + if (diff_use_color_default == -1) + diff_use_color_default = git_use_color_default; + + argc = parse_options(argc, argv, builtin_merge_options, + builtin_merge_usage, 0); + + if (squash) { + if (!allow_fast_forward) + die("You cannot combine --squash with --no-ff."); + option_commit = 0; + } + + if (!argc) + usage_with_options(builtin_merge_usage, + builtin_merge_options); + + /* + * This could be traditional "merge HEAD ..." and + * the way we can tell it is to see if the second token is HEAD, + * but some people might have misused the interface and used a + * committish that is the same as HEAD there instead. + * Traditional format never would have "-m" so it is an + * additional safety measure to check for it. + */ + strbuf_init(&buf, 0); + + if (!have_message && is_old_style_invocation(argc, argv)) { + strbuf_addstr(&merge_msg, argv[0]); + head_arg = argv[1]; + argv += 2; + argc -= 2; + } else if (head_invalid) { + struct object *remote_head; + /* + * If the merged head is a valid one there is no reason + * to forbid "git merge" into a branch yet to be born. + * We do the same for "git pull". + */ + if (argc != 1) + die("Can merge only exactly one commit into " + "empty head"); + remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT); + if (!remote_head) + die("%s - not something we can merge", argv[0]); + update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0, + DIE_ON_ERR); + reset_hard(remote_head->sha1, 0); + return 0; + } else { + struct strbuf msg; + + /* We are invoked directly as the first-class UI. */ + head_arg = "HEAD"; + + /* + * All the rest are the commits being merged; + * prepare the standard merge summary message to + * be appended to the given message. If remote + * is invalid we will die later in the common + * codepath so we discard the error in this + * loop. + */ + strbuf_init(&msg, 0); + for (i = 0; i < argc; i++) + merge_name(argv[i], &msg); + fmt_merge_msg(option_log, &msg, &merge_msg); + if (merge_msg.len) + strbuf_setlen(&merge_msg, merge_msg.len-1); + } + + if (head_invalid || !argc) + usage_with_options(builtin_merge_usage, + builtin_merge_options); + + strbuf_addstr(&buf, "merge"); + for (i = 0; i < argc; i++) + strbuf_addf(&buf, " %s", argv[i]); + setenv("GIT_REFLOG_ACTION", buf.buf, 0); + strbuf_reset(&buf); + + for (i = 0; i < argc; i++) { + struct object *o; + + o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT); + if (!o) + die("%s - not something we can merge", argv[i]); + remotes = &commit_list_insert(lookup_commit(o->sha1), + remotes)->next; + + strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1)); + setenv(buf.buf, argv[i], 1); + strbuf_reset(&buf); + } + + if (!use_strategies) { + if (!remoteheads->next) + add_strategies(pull_twohead, DEFAULT_TWOHEAD); + else + add_strategies(pull_octopus, DEFAULT_OCTOPUS); + } + + for (i = 0; i < use_strategies_nr; i++) { + if (use_strategies[i]->attr & NO_FAST_FORWARD) + allow_fast_forward = 0; + if (use_strategies[i]->attr & NO_TRIVIAL) + allow_trivial = 0; + } + + if (!remoteheads->next) + common = get_merge_bases(lookup_commit(head), + remoteheads->item, 1); + else { + struct commit_list *list = remoteheads; + commit_list_insert(lookup_commit(head), &list); + common = get_octopus_merge_bases(list); + free(list); + } + + update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0, + DIE_ON_ERR); + + if (!common) + ; /* No common ancestors found. We need a real merge. */ + else if (!remoteheads->next && !common->next && + common->item == remoteheads->item) { + /* + * If head can reach all the merge then we are up to date. + * but first the most common case of merging one remote. + */ + finish_up_to_date("Already up-to-date."); + return 0; + } else if (allow_fast_forward && !remoteheads->next && + !common->next && + !hashcmp(common->item->object.sha1, head)) { + /* Again the most common case of merging one remote. */ + struct strbuf msg; + struct object *o; + char hex[41]; + + strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV)); + + printf("Updating %s..%s\n", + hex, + find_unique_abbrev(remoteheads->item->object.sha1, + DEFAULT_ABBREV)); + refresh_cache(REFRESH_QUIET); + strbuf_init(&msg, 0); + strbuf_addstr(&msg, "Fast forward"); + if (have_message) + strbuf_addstr(&msg, + " (no commit created; -m option ignored)"); + o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1), + 0, NULL, OBJ_COMMIT); + if (!o) + return 1; + + if (checkout_fast_forward(head, remoteheads->item->object.sha1)) + return 1; + + finish(o->sha1, msg.buf); + drop_save(); + return 0; + } else if (!remoteheads->next && common->next) + ; + /* + * We are not doing octopus and not fast forward. Need + * a real merge. + */ + else if (!remoteheads->next && !common->next && option_commit) { + /* + * We are not doing octopus, not fast forward, and have + * only one common. + */ + refresh_cache(REFRESH_QUIET); + if (allow_trivial) { + /* See if it is really trivial. */ + git_committer_info(IDENT_ERROR_ON_NO_NAME); + printf("Trying really trivial in-index merge...\n"); + if (!read_tree_trivial(common->item->object.sha1, + head, remoteheads->item->object.sha1)) + return merge_trivial(); + printf("Nope.\n"); + } + } else { + /* + * An octopus. If we can reach all the remote we are up + * to date. + */ + int up_to_date = 1; + struct commit_list *j; + + for (j = remoteheads; j; j = j->next) { + struct commit_list *common_one; + + /* + * Here we *have* to calculate the individual + * merge_bases again, otherwise "git merge HEAD^ + * HEAD^^" would be missed. + */ + common_one = get_merge_bases(lookup_commit(head), + j->item, 1); + if (hashcmp(common_one->item->object.sha1, + j->item->object.sha1)) { + up_to_date = 0; + break; + } + } + if (up_to_date) { + finish_up_to_date("Already up-to-date. Yeeah!"); + return 0; + } + } + + /* We are going to make a new commit. */ + git_committer_info(IDENT_ERROR_ON_NO_NAME); + + /* + * At this point, we need a real merge. No matter what strategy + * we use, it would operate on the index, possibly affecting the + * working tree, and when resolved cleanly, have the desired + * tree in the index -- this means that the index must be in + * sync with the head commit. The strategies are responsible + * to ensure this. + */ + if (use_strategies_nr != 1) { + /* + * Stash away the local changes so that we can try more + * than one. + */ + save_state(); + } else { + memcpy(stash, null_sha1, 20); + } + + for (i = 0; i < use_strategies_nr; i++) { + int ret; + if (i) { + printf("Rewinding the tree to pristine...\n"); + restore_state(); + } + if (use_strategies_nr != 1) + printf("Trying merge strategy %s...\n", + use_strategies[i]->name); + /* + * Remember which strategy left the state in the working + * tree. + */ + wt_strategy = use_strategies[i]->name; + + ret = try_merge_strategy(use_strategies[i]->name, + common, head_arg); + if (!option_commit && !ret) { + merge_was_ok = 1; + /* + * This is necessary here just to avoid writing + * the tree, but later we will *not* exit with + * status code 1 because merge_was_ok is set. + */ + ret = 1; + } + + if (ret) { + /* + * The backend exits with 1 when conflicts are + * left to be resolved, with 2 when it does not + * handle the given merge at all. + */ + if (ret == 1) { + int cnt = evaluate_result(); + + if (best_cnt <= 0 || cnt <= best_cnt) { + best_strategy = use_strategies[i]->name; + best_cnt = cnt; + } + } + if (merge_was_ok) + break; + else + continue; + } + + /* Automerge succeeded. */ + write_tree_trivial(result_tree); + automerge_was_ok = 1; + break; + } + + /* + * If we have a resulting tree, that means the strategy module + * auto resolved the merge cleanly. + */ + if (automerge_was_ok) + return finish_automerge(common, result_tree, wt_strategy); + + /* + * Pick the result from the best strategy and have the user fix + * it up. + */ + if (!best_strategy) { + restore_state(); + if (use_strategies_nr > 1) + fprintf(stderr, + "No merge strategy handled the merge.\n"); + else + fprintf(stderr, "Merge with strategy %s failed.\n", + use_strategies[0]->name); + return 2; + } else if (best_strategy == wt_strategy) + ; /* We already have its result in the working tree. */ + else { + printf("Rewinding the tree to pristine...\n"); + restore_state(); + printf("Using the %s to prepare resolving by hand.\n", + best_strategy); + try_merge_strategy(best_strategy, common, head_arg); + } + + if (squash) + finish(NULL, NULL); + else { + int fd; + struct commit_list *j; + + for (j = remoteheads; j; j = j->next) + strbuf_addf(&buf, "%s\n", + sha1_to_hex(j->item->object.sha1)); + fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666); + if (fd < 0) + die("Could open %s for writing", + git_path("MERGE_HEAD")); + if (write_in_full(fd, buf.buf, buf.len) != buf.len) + die("Could not write to %s", git_path("MERGE_HEAD")); + close(fd); + strbuf_addch(&merge_msg, '\n'); + fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666); + if (fd < 0) + die("Could open %s for writing", git_path("MERGE_MSG")); + if (write_in_full(fd, merge_msg.buf, merge_msg.len) != + merge_msg.len) + die("Could not write to %s", git_path("MERGE_MSG")); + close(fd); + } + + if (merge_was_ok) { + fprintf(stderr, "Automatic merge went well; " + "stopped before committing as requested\n"); + return 0; + } else + return suggest_conflicts(); +} diff --git a/builtin.h b/builtin.h index 05ee56f21b1a54..0e605d4f4aa748 100644 --- a/builtin.h +++ b/builtin.h @@ -64,6 +64,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); extern int cmd_ls_remote(int argc, const char **argv, const char *prefix); extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); +extern int cmd_merge(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); diff --git a/git-merge.sh b/contrib/examples/git-merge.sh similarity index 100% rename from git-merge.sh rename to contrib/examples/git-merge.sh diff --git a/git.c b/git.c index 2fbe96b9bac352..770aadd0a4ad35 100644 --- a/git.c +++ b/git.c @@ -271,6 +271,7 @@ static void handle_internal_command(int argc, const char **argv) { "ls-remote", cmd_ls_remote }, { "mailinfo", cmd_mailinfo }, { "mailsplit", cmd_mailsplit }, + { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE }, { "merge-base", cmd_merge_base, RUN_SETUP }, { "merge-file", cmd_merge_file }, { "merge-ours", cmd_merge_ours, RUN_SETUP }, diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh index f3a4bb2ea231f6..fcb8285746420e 100755 --- a/t/t7602-merge-octopus-many.sh +++ b/t/t7602-merge-octopus-many.sh @@ -23,7 +23,7 @@ test_expect_success 'setup' ' done ' -test_expect_failure 'merge c1 with c2, c3, c4, ... c29' ' +test_expect_success 'merge c1 with c2, c3, c4, ... c29' ' git reset --hard c1 && i=2 && refs="" && From 4393c2374101fef9053643f9b4ec638b05bd0b26 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 10 Jul 2008 00:50:59 -0700 Subject: [PATCH 16/20] Teach merge.log to "git-merge" again The command forgot the configuration variable when rewritten in C. Signed-off-by: Junio C Hamano --- builtin-merge.c | 2 ++ t/t7600-merge.sh | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/builtin-merge.c b/builtin-merge.c index b2e702a11fe0c1..2ee16746908179 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -464,6 +464,8 @@ int git_merge_config(const char *k, const char *v, void *cb) return git_config_string(&pull_twohead, k, v); else if (!strcmp(k, "pull.octopus")) return git_config_string(&pull_octopus, k, v); + else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) + option_log = git_config_bool(k, v); return git_diff_ui_config(k, v, cb); } diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index d21cd290d3d8e3..72ef2e55d9f9c1 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -465,8 +465,15 @@ test_expect_success 'merge log message' ' git merge --no-log c2 && git show -s --pretty=format:%b HEAD >msg.act && verify_diff msg.nolog msg.act "[OOPS] bad merge log message" && + git merge --log c3 && git show -s --pretty=format:%b HEAD >msg.act && + verify_diff msg.log msg.act "[OOPS] bad merge log message" && + + git reset --hard HEAD^ && + git config merge.log yes && + git merge c3 && + git show -s --pretty=format:%b HEAD >msg.act && verify_diff msg.log msg.act "[OOPS] bad merge log message" ' From 8ec0dd66fa277615fe79ad10096d81edb263d6d1 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Sat, 12 Jul 2008 20:42:10 +0200 Subject: [PATCH 17/20] t6021: add a new test for git-merge-resolve It should fail properly if there are multiple merge bases, but there were no test for this till now. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- t/t6021-merge-criss-cross.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/t/t6021-merge-criss-cross.sh b/t/t6021-merge-criss-cross.sh index 0ab14a6e81302d..331b9b07d4eedb 100755 --- a/t/t6021-merge-criss-cross.sh +++ b/t/t6021-merge-criss-cross.sh @@ -89,4 +89,8 @@ EOF test_expect_success 'Criss-cross merge result' 'cmp file file-expect' +test_expect_success 'Criss-cross merge fails (-s resolve)' \ +'git reset --hard A^ && +test_must_fail git merge -s resolve -m "final merge" B' + test_done From 3f4d1c639347317788f2c5080f89de53772499ce Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Sun, 13 Jul 2008 00:33:35 +0200 Subject: [PATCH 18/20] Add a new test for git-merge-resolve Actually this is a simple test, just to ensure merge-resolve properly calls read-tree. read-tree itself already has more complex tests. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- t/t7605-merge-resolve.sh | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100755 t/t7605-merge-resolve.sh diff --git a/t/t7605-merge-resolve.sh b/t/t7605-merge-resolve.sh new file mode 100755 index 00000000000000..ee21a107fd99e3 --- /dev/null +++ b/t/t7605-merge-resolve.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +test_description='git-merge + +Testing the resolve strategy.' + +. ./test-lib.sh + +test_expect_success 'setup' ' + echo c0 > c0.c && + git add c0.c && + git commit -m c0 && + git tag c0 && + echo c1 > c1.c && + git add c1.c && + git commit -m c1 && + git tag c1 && + git reset --hard c0 && + echo c2 > c2.c && + git add c2.c && + git commit -m c2 && + git tag c2 && + git reset --hard c0 && + echo c3 > c2.c && + git add c2.c && + git commit -m c3 && + git tag c3 +' + +test_expect_success 'merge c1 to c2' ' + git reset --hard c1 && + git merge -s resolve c2 && + test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" && + test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" && + test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" && + git diff --exit-code && + test -f c0.c && + test -f c1.c && + test -f c2.c +' + +test_expect_success 'merge c2 to c3 (fails)' ' + git reset --hard c2 && + test_must_fail git merge -s resolve c3 +' +test_done From 3d1dd4728b83e4c08d9fa7aaf2aa946e1012e061 Mon Sep 17 00:00:00 2001 From: Sverre Hvammen Johansen Date: Sun, 13 Jul 2008 08:13:55 +0000 Subject: [PATCH 19/20] reduce_heads(): thinkofix When comparing two commit objects for equality, it is sufficient to compare their in-core pointers because the object layer guarantees the uniqueness. However, comparing pointers to two "struct commit_list" instances that point at the same commit does not make any sense. Spotted by Sverre Hvammen Johansen who wrote an additional test to expose the problem, fixed by Miklos Vajna. Signed-off-by: Junio C Hamano --- commit.c | 2 +- t/t7600-merge.sh | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/commit.c b/commit.c index d20b14ee3efa71..03e73f323ae082 100644 --- a/commit.c +++ b/commit.c @@ -747,7 +747,7 @@ struct commit_list *reduce_heads(struct commit_list *heads) num_other = 0; for (q = heads; q; q = q->next) { - if (p == q) + if (p->item == q->item) continue; other[num_other++] = q->item; } diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 72ef2e55d9f9c1..f035ea376e4f02 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -479,4 +479,15 @@ test_expect_success 'merge log message' ' test_debug 'gitk --all' +test_expect_success 'merge c1 with c0, c2, c0, and c1' ' + git reset --hard c1 && + git config branch.master.mergeoptions "" && + test_tick && + git merge c0 c2 c0 c1 && + verify_merge file result.1-5 && + verify_parents $c1 $c2 +' + +test_debug 'gitk --all' + test_done From 711f6b295cf463aae07eb76e009faed3d3699623 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Jul 2008 00:09:41 -0700 Subject: [PATCH 20/20] reduce_heads(): protect from duplicate input Because we do not try computing merge base with itself for obvious reasons, the code was not prepared for an arguably insane case of the caller feeding the same commit twice to it. Noticed and test written by Sverre Hvammen Johansen Signed-off-by: Junio C Hamano --- commit.c | 11 +++++++++-- t/t7600-merge.sh | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/commit.c b/commit.c index 03e73f323ae082..5148ec552708bb 100644 --- a/commit.c +++ b/commit.c @@ -745,15 +745,22 @@ struct commit_list *reduce_heads(struct commit_list *heads) for (p = heads; p; p = p->next) { struct commit_list *q, *base; + /* Do we already have this in the result? */ + for (q = result; q; q = q->next) + if (p->item == q->item) + break; + if (q) + continue; + num_other = 0; for (q = heads; q; q = q->next) { if (p->item == q->item) continue; other[num_other++] = q->item; } - if (num_other) { + if (num_other) base = get_merge_bases_many(p->item, num_other, other, 1); - } else + else base = NULL; /* * If p->item does not have anything common with other diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index f035ea376e4f02..d4cf6289a4c06e 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -490,4 +490,26 @@ test_expect_success 'merge c1 with c0, c2, c0, and c1' ' test_debug 'gitk --all' +test_expect_success 'merge c1 with c0, c2, c0, and c1' ' + git reset --hard c1 && + git config branch.master.mergeoptions "" && + test_tick && + git merge c0 c2 c0 c1 && + verify_merge file result.1-5 && + verify_parents $c1 $c2 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c1 and c2' ' + git reset --hard c1 && + git config branch.master.mergeoptions "" && + test_tick && + git merge c1 c2 && + verify_merge file result.1-5 && + verify_parents $c1 $c2 +' + +test_debug 'gitk --all' + test_done