From 0989fe9623dc8d98033f6acdcc0c84ec28571b19 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Fri, 27 Jun 2008 18:21:54 +0200 Subject: [PATCH 01/60] 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 995f3e6a0a..ccb1108c94 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 64ef86e129..6913573d1a 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 59f0fcc1f2..2fbe96b9ba 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/60] 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 43bf6aa45e..3731853f83 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 e2d8624d9c..bbf9c75416 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 2d94d4148e..7f8c5ee0fc 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/60] 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 145dd8568c..1491354a9d 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 6f94a8197f..31edc98f30 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 b8bde2b04a..bbc3ca4a9f 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/60] 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 0000000000..32585f8e0d --- /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/60] 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 5a09e17f1a..72a6de302f 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 6913573d1a..d0d74a3ff8 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 f83de8c415..d801f9d1cf 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/60] 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 | 205 ++++++++++++++++++++++------------------ builtin.h | 3 + 2 files changed, 117 insertions(+), 91 deletions(-) diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index b892621ab5..dbd7d2ddae 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,15 +252,105 @@ 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; + /* get current branch */ + current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); + if (!current_branch) + die("No current branch"); + if (!prefixcmp(current_branch, "refs/heads/")) + current_branch += 11; + + /* 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++; + p[len] = 0; + if (handle_line(p)) + die ("Error in line %d: %.*s", i, len, p); + } + + strbuf_addstr(out, "Merge "); + for (i = 0; i < srcs.nr; i++) { + struct src_data *src_data = srcs.payload[i]; + const char *subsep = ""; + + strbuf_addstr(out, sep); + sep = "; "; + + if (src_data->head_status == 1) { + strbuf_addstr(out, srcs.list[i]); + continue; + } + if (src_data->head_status == 3) { + subsep = ", "; + strbuf_addstr(out, "HEAD"); + } + if (src_data->branch.nr) { + strbuf_addstr(out, subsep); + subsep = ", "; + print_joined("branch ", "branches ", &src_data->branch, + out); + } + if (src_data->r_branch.nr) { + strbuf_addstr(out, subsep); + subsep = ", "; + print_joined("remote branch ", "remote branches ", + &src_data->r_branch, out); + } + if (src_data->tag.nr) { + strbuf_addstr(out, subsep); + subsep = ", "; + print_joined("tag ", "tags ", &src_data->tag, out); + } + if (src_data->generic.nr) { + strbuf_addstr(out, subsep); + print_joined("commit ", "commits ", &src_data->generic, + out); + } + if (strcmp(".", srcs.list[i])) + strbuf_addf(out, " of %s", srcs.list[i]); + } + + if (!strcmp("master", current_branch)) + strbuf_addch(out, '\n'); + else + 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, 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, 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) { @@ -288,82 +379,14 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) if (argc > 1) usage(fmt_merge_msg_usage); - /* get current branch */ - current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); - if (!current_branch) - die("No current branch"); - if (!prefixcmp(current_branch, "refs/heads/")) - current_branch += 11; - - while (fgets(line, sizeof(line), in)) { - i++; - if (line[0] == 0) - continue; - if (handle_line(line)) - die ("Error in line %d: %s", i, line); - } - - printf("Merge "); - for (i = 0; i < srcs.nr; i++) { - struct src_data *src_data = srcs.payload[i]; - const char *subsep = ""; - - printf(sep); - sep = "; "; - - if (src_data->head_status == 1) { - printf(srcs.list[i]); - continue; - } - if (src_data->head_status == 3) { - subsep = ", "; - printf("HEAD"); - } - if (src_data->branch.nr) { - printf(subsep); - subsep = ", "; - print_joined("branch ", "branches ", &src_data->branch); - } - if (src_data->r_branch.nr) { - printf(subsep); - subsep = ", "; - print_joined("remote branch ", "remote branches ", - &src_data->r_branch); - } - if (src_data->tag.nr) { - printf(subsep); - subsep = ", "; - print_joined("tag ", "tags ", &src_data->tag); - } - if (src_data->generic.nr) { - printf(subsep); - print_joined("commit ", "commits ", &src_data->generic); - } - if (strcmp(".", srcs.list[i])) - printf(" of %s", srcs.list[i]); - } - - if (!strcmp("master", current_branch)) - putchar('\n'); - else - printf(" into %s\n", current_branch); - - if (merge_summary) { - struct commit *head; - struct rev_info rev; - - head = lookup_commit(head_sha1); - init_revisions(&rev, prefix); - 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); - } - - /* 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 b460b2da6f..2b01feabcb 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/60] 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 bbf9c75416..6052ca34c8 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 7f8c5ee0fc..dcec7fb9a2 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/60] 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 0000000000..f3a4bb2ea2 --- /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/60] 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 6052ca34c8..cafed26928 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/60] 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 cafed26928..d20b14ee3e 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 dcec7fb9a2..2acfc79d34 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/60] 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 0000000000..17b19dc11f --- /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 dcaceb0a278ca1293cb0b50cc60cdde721dc0de0 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Wed, 2 Jul 2008 09:03:53 +0200 Subject: [PATCH 12/60] Revert "Change the name of hook scripts to make them not executable by default." This reverts commits 13cae476, bdb6d4ec, daabe66f. They are not necessary anymore, because the sample hooks now have a .sample postfix. --- Makefile | 3 +-- templates/Makefile | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Makefile b/Makefile index bc7256d170..78e08d3745 100644 --- a/Makefile +++ b/Makefile @@ -743,7 +743,6 @@ ifneq (,$(findstring MINGW,$(uname_S))) COMPAT_OBJS += compat/mingw.o compat/fnmatch.o compat/regex.o EXTLIBS += -lws2_32 X = .exe - NOEXECTEMPL = .noexec template_dir = ../share/git-core/templates/ ETC_GITCONFIG = ../etc/gitconfig endif @@ -1058,7 +1057,7 @@ ifndef NO_TCLTK $(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all endif $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all - $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) NOEXECTEMPL='$(NOEXECTEMPL)' + $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) strip: $(PROGRAMS) git$X $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X diff --git a/templates/Makefile b/templates/Makefile index ab2c823ae0..9f3f1fc352 100644 --- a/templates/Makefile +++ b/templates/Makefile @@ -10,8 +10,6 @@ RM ?= rm -f prefix ?= $(HOME) template_instdir ?= $(prefix)/share/git-core/templates # DESTDIR= -# set NOEXECTEMPL to non-empty to change the names of hook scripts -# so that the tools will not find them # Shell quote (do not use $(call) to accommodate ancient setups); DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) @@ -34,7 +32,6 @@ boilerplates.made : $(bpsrc) $(INSTALL) -d -m 755 blt/$$dir && \ case "$$boilerplate" in \ *--) ;; \ - hooks--*) cp -p $$boilerplate blt/$${dst}$(NOEXECTEMPL) ;; \ *) cp -p $$boilerplate blt/$$dst ;; \ esac || exit; \ done && \ From a3fb126011ff47be25b194f80b74e851ef3c7482 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Wed, 2 Jul 2008 09:29:45 +0200 Subject: [PATCH 13/60] t4127-apply-same-fn: Avoid sed -i Signed-off-by: Johannes Sixt --- t/t4127-apply-same-fn.sh | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/t/t4127-apply-same-fn.sh b/t/t4127-apply-same-fn.sh index 2a6ed77c65..1f859dd908 100755 --- a/t/t4127-apply-same-fn.sh +++ b/t/t4127-apply-same-fn.sh @@ -4,6 +4,11 @@ test_description='apply same filename' . ./test-lib.sh +modify () { + sed -e "$1" < "$2" > "$2".x && + mv "$2".x "$2" +} + test_expect_success setup ' for i in a b c d e f g h i j k l m do @@ -14,10 +19,10 @@ test_expect_success setup ' git commit -m initial ' test_expect_success 'apply same filename with independent changes' ' - sed -i -e "s/^d/z/" same_fn && + modify "s/^d/z/" same_fn && git diff > patch0 && git add same_fn && - sed -i -e "s/^i/y/" same_fn && + modify "s/^i/y/" same_fn && git diff >> patch0 && cp same_fn same_fn2 && git reset --hard && @@ -27,10 +32,10 @@ test_expect_success 'apply same filename with independent changes' ' test_expect_success 'apply same filename with overlapping changes' ' git reset --hard - sed -i -e "s/^d/z/" same_fn && + modify "s/^d/z/" same_fn && git diff > patch0 && git add same_fn && - sed -i -e "s/^e/y/" same_fn && + modify "s/^e/y/" same_fn && git diff >> patch0 && cp same_fn same_fn2 && git reset --hard && @@ -41,10 +46,10 @@ test_expect_success 'apply same filename with overlapping changes' ' test_expect_success 'apply same new filename after rename' ' git reset --hard git mv same_fn new_fn - sed -i -e "s/^d/z/" new_fn && + modify "s/^d/z/" new_fn && git add new_fn && git diff -M --cached > patch1 && - sed -i -e "s/^e/y/" new_fn && + modify "s/^e/y/" new_fn && git diff >> patch1 && cp new_fn new_fn2 && git reset --hard && @@ -55,11 +60,11 @@ test_expect_success 'apply same new filename after rename' ' test_expect_success 'apply same old filename after rename -- should fail.' ' git reset --hard git mv same_fn new_fn - sed -i -e "s/^d/z/" new_fn && + modify "s/^d/z/" new_fn && git add new_fn && git diff -M --cached > patch1 && git mv new_fn same_fn - sed -i -e "s/^e/y/" same_fn && + modify "s/^e/y/" same_fn && git diff >> patch1 && git reset --hard && test_must_fail git apply patch1 @@ -68,15 +73,15 @@ test_expect_success 'apply same old filename after rename -- should fail.' ' test_expect_success 'apply A->B (rename), C->A (rename), A->A -- should pass.' ' git reset --hard git mv same_fn new_fn - sed -i -e "s/^d/z/" new_fn && + modify "s/^d/z/" new_fn && git add new_fn && git diff -M --cached > patch1 && git commit -m "a rename" && git mv other_fn same_fn - sed -i -e "s/^e/y/" same_fn && + modify "s/^e/y/" same_fn && git add same_fn && git diff -M --cached >> patch1 && - sed -i -e "s/^g/x/" same_fn && + modify "s/^g/x/" same_fn && git diff >> patch1 && git reset --hard HEAD^ && git apply patch1 From 56cfc88a46842653218c1575d63068a0b1b8bb66 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Wed, 2 Jul 2008 11:07:38 +0200 Subject: [PATCH 14/60] Avoid /dev/zero in tests. On Windows, we do not have /dev/zero or an equivalent. Work around this using a shell loop that produces zero bytes. Signed-off-by: Johannes Sixt --- t/t5302-pack-index.sh | 8 +++++++- t/t5303-pack-corruption-resilience.sh | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index ecec591634..74e10aaf09 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -6,6 +6,12 @@ test_description='pack index with 64-bit offsets and object CRC' . ./test-lib.sh +zeros () { + while :; do + echo -n xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + done | tr x '\0' +} + test_expect_success \ 'setup' \ 'rm -rf .git @@ -168,7 +174,7 @@ test_expect_success \ git-index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" && git verify-pack ".git/objects/pack/pack-${pack1}.pack" && chmod +w ".git/objects/pack/pack-${pack1}.idx" && - dd if=/dev/zero of=".git/objects/pack/pack-${pack1}.idx" conv=notrunc \ + zeros | dd of=".git/objects/pack/pack-${pack1}.idx" conv=notrunc \ bs=1 count=4 seek=$((8 + 256 * 4 + `wc -l /dev/null || exit 1 diff --git a/t/t5303-pack-corruption-resilience.sh b/t/t5303-pack-corruption-resilience.sh index 31b20b21d2..874c6fe705 100755 --- a/t/t5303-pack-corruption-resilience.sh +++ b/t/t5303-pack-corruption-resilience.sh @@ -41,11 +41,17 @@ create_new_pack() { git verify-pack -v ${pack}.pack } +zeros () { + while :; do + echo -n xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + done | tr x '\0' +} + do_corrupt_object() { ofs=`git show-index < ${pack}.idx | grep $1 | cut -f1 -d" "` && ofs=$(($ofs + $2)) && chmod +w ${pack}.pack && - dd if=/dev/zero of=${pack}.pack count=1 bs=1 conv=notrunc seek=$ofs && + zeros | dd of=${pack}.pack count=1 bs=1 conv=notrunc seek=$ofs && test_must_fail git verify-pack ${pack}.pack } From 2d556c22398cc4b76edce448a8f24239eb0e8631 Mon Sep 17 00:00:00 2001 From: Eric Raible Date: Thu, 26 Jun 2008 22:04:47 +0000 Subject: [PATCH 15/60] Teach lookup_prog not to select directories Without this simple fix "git gui" in the git source directory finds the git-gui directory instead of the tcl script in /usr/bin. Signed-off-by: Eric Raible Signed-off-by: Johannes Sixt --- compat/mingw.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compat/mingw.c b/compat/mingw.c index 3a05fe7da6..bb33c8dfa2 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -536,7 +536,8 @@ static char *lookup_prog(const char *dir, const char *cmd, int isexe, int exe_on return xstrdup(path); path[strlen(path)-4] = '\0'; if ((!exe_only || isexe) && access(path, F_OK) == 0) - return xstrdup(path); + if (!(GetFileAttributes(path) & FILE_ATTRIBUTE_DIRECTORY)) + return xstrdup(path); return NULL; } From c1fb35b98aa36ce789c60520028d95e5f9fee43c Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Fri, 27 Jun 2008 18:22:07 +0200 Subject: [PATCH 16/60] 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 0000000000..6081677d23 --- /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 17/60] 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 3881f6c2f5..7a9a309be0 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 2b01feabcb..05ee56f21b 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 18/60] 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 32585f8e0d..95b4d71c51 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 2a337eda67f4e62520fb38a48c51577f572065da Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Mon, 7 Jul 2008 11:10:20 +0200 Subject: [PATCH 19/60] builtin-clone: Use is_dir_sep() instead of '/' Signed-off-by: Johannes Sixt --- builtin-clone.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-clone.c b/builtin-clone.c index 643c7d4169..fddf47fabf 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -115,7 +115,7 @@ static char *guess_dir_name(const char *repo, int is_bundle) if (!after_slash_or_colon) end = p; p += 7; - } else if (*p == '/' || *p == ':') { + } else if (is_dir_sep(*p) || *p == ':') { if (end == limit) end = p; after_slash_or_colon = 1; From 22e407951ef2572c1e68a39364fd3c4b649a3495 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 7 Jul 2008 00:16:38 -0700 Subject: [PATCH 20/60] Teach "am" and "rebase" to mark the original position with ORIG_HEAD "merge" and "reset" leave the original point in history in ORIG_HEAD, which makes it easy to go back to where you were before you inflict a major damage to your history and realize that you do not like the result at all. These days with reflog, we technically do not need to use ORIG_HEAD, but it is a handy way nevertheless. This teaches "am" and "rebase" (all forms --- the vanilla one that uses "am" as its backend, "-m" variant that cherry-picks, and "--interactive") to do the same. The original idea and a partial implementation to do this only for "rebase -m" was by Brian Gernhardt; this extends on his idea. Signed-off-by: Junio C Hamano --- git-am.sh | 1 + git-rebase--interactive.sh | 1 + git-rebase.sh | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/git-am.sh b/git-am.sh index 2c517ede59..fe53608c94 100755 --- a/git-am.sh +++ b/git-am.sh @@ -241,6 +241,7 @@ else : >"$dotest/rebasing" else : >"$dotest/applying" + git update-ref ORIG_HEAD HEAD fi fi diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index a64d9d57ab..02d7e3c7b0 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -549,6 +549,7 @@ EOF has_action "$TODO" || die_abort "Nothing to do" + git update-ref ORIG_HEAD $HEAD output git checkout $ONTO && do_rest ;; esac diff --git a/git-rebase.sh b/git-rebase.sh index e2d85eeeab..2597d777d6 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -378,7 +378,7 @@ fi echo "First, rewinding head to replay your work on top of it..." git checkout "$onto^0" >/dev/null 2>&1 || die "could not detach HEAD" -# git reset --hard "$onto^0" +git update-ref ORIG_HEAD $branch # If the $onto is a proper descendant of the tip of the branch, then # we just fast forwarded. From 1c7b76be7d620bbaf2e6b8417f04012326bbb9df Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Mon, 7 Jul 2008 19:24:20 +0200 Subject: [PATCH 21/60] 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 bf77292f1c..fbc53e9cb3 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 0000000000..b2e702a11f --- /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 05ee56f21b..0e605d4f4a 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 2fbe96b9ba..770aadd0a4 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 f3a4bb2ea2..fcb8285746 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 af894943cb9dbad1d6892dc983dc6ac0fa6ca8e8 Mon Sep 17 00:00:00 2001 From: Soeren Finster Date: Mon, 7 Jul 2008 18:50:13 +0200 Subject: [PATCH 22/60] git-gui: Exit shortcut in MacOSX repaired Now, as in all OSX apps, there is only one quit menu entry. It's automatically in the wish menu and calls ::tk::mac::Quit when used. Signed-off-by: Soeren Finster Signed-off-by: Shawn O. Pearce --- git-gui.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index d89f156fd5..940677cbd8 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1995,9 +1995,13 @@ if {[is_enabled multicommit]} { } } -.mbar.repository add command -label [mc Quit] \ - -command do_quit \ - -accelerator $M1T-Q +if {[is_MacOSX]} { + proc ::tk::mac::Quit {args} { do_quit } +} else { + .mbar.repository add command -label [mc Quit] \ + -command do_quit \ + -accelerator $M1T-Q +} # -- Edit Menu # From 9869099bee332e4c351c3aaa15a74786d85909c7 Mon Sep 17 00:00:00 2001 From: Brian Gernhardt Date: Tue, 8 Jul 2008 00:12:22 -0400 Subject: [PATCH 23/60] Documentation: mention ORIG_HEAD in am, merge, and rebase Merge has always set ORIG_HEAD but never mentioned it, while we recently added it to am and rebase. These facts should be reflected in the documentation. git-reset also sets ORIG_HEAD, but that fact is already mentioned in the very first example so no changes were needed there. Signed-off-by: Brian Gernhardt Signed-off-by: Junio C Hamano --- Documentation/git-am.txt | 6 ++++++ Documentation/git-merge.txt | 4 +++- Documentation/git-rebase.txt | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 3863eebcef..88ca5f1183 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -145,6 +145,12 @@ directory exists, so if you decide to start over from scratch, run `rm -f -r .dotest` before running the command with mailbox names. +Before any patches are applied, ORIG_HEAD is set to the tip of the +current branch. This is useful if you have problems with multiple +commits, like running 'git am' on the wrong branch or an error in the +commits that is more easily fixed by changing the mailbox (e.g. +errors in the "From:" lines). + SEE ALSO -------- diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 62f99b5f3b..019e4ca8f5 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -81,7 +81,9 @@ Otherwise, merge will refuse to do any harm to your repository (that is, it may fetch the objects from remote, and it may even update the local branch used to keep track of the remote branch with `git pull remote rbranch:lbranch`, but your working tree, -`.git/HEAD` pointer and index file are left intact). +`.git/HEAD` pointer and index file are left intact). In addition, +merge always sets `.git/ORIG_HEAD` to the original state of HEAD so +a problematic merge can be removed by using `git reset ORIG_HEAD`. You may have local modifications in the working tree files. In other words, 'git-diff' is allowed to report changes. diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index f3459c7de7..e30f6a6982 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -26,7 +26,8 @@ of commits that would be shown by `git log ..HEAD`. The current branch is reset to , or if the --onto option was supplied. This has the exact same effect as -`git reset --hard ` (or ). +`git reset --hard ` (or ). ORIG_HEAD is set +to point at the tip of the branch before the reset. The commits that were previously saved into the temporary area are then reapplied to the current branch, one by one, in order. Note that From 1cc6985ca7dd3aaab0617ec0fd00d4eb0b424465 Mon Sep 17 00:00:00 2001 From: Pierre Habouzit Date: Tue, 8 Jul 2008 12:34:08 +0200 Subject: [PATCH 24/60] parse-options: add PARSE_OPT_LASTARG_DEFAULT flag If you set this for a given option, and the optoin appears without an argument on the command line, then the `defval' is used as its argument. Note that this flag is meaningless in presence of OPTARG or NOARG flags. (in the current implementation it will be ignored, but don't rely on it). Signed-off-by: Pierre Habouzit Signed-off-by: Junio C Hamano --- parse-options.c | 55 ++++++++++++++++++++++++------------------------- parse-options.h | 1 + 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/parse-options.c b/parse-options.c index 469831d21b..ae88885d4d 100644 --- a/parse-options.c +++ b/parse-options.c @@ -5,17 +5,6 @@ #define OPT_SHORT 1 #define OPT_UNSET 2 -static inline const char *get_arg(struct parse_opt_ctx_t *p) -{ - if (p->opt) { - const char *res = p->opt; - p->opt = NULL; - return res; - } - p->argc--; - return *++p->argv; -} - static inline const char *skip_prefix(const char *str, const char *prefix) { size_t len = strlen(prefix); @@ -31,8 +20,24 @@ static int opterror(const struct option *opt, const char *reason, int flags) return error("option `%s' %s", opt->long_name, reason); } +static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt, + int flags, const char **arg) +{ + if (p->opt) { + *arg = p->opt; + p->opt = NULL; + } else if (p->argc == 1 && (opt->flags & PARSE_OPT_LASTARG_DEFAULT)) { + *arg = (const char *)opt->defval; + } else if (p->argc) { + p->argc--; + *arg = *++p->argv; + } else + return opterror(opt, "requires a value", flags); + return 0; +} + static int get_value(struct parse_opt_ctx_t *p, - const struct option *opt, int flags) + const struct option *opt, int flags) { const char *s, *arg; const int unset = flags & OPT_UNSET; @@ -58,7 +63,6 @@ static int get_value(struct parse_opt_ctx_t *p, } } - arg = p->opt ? p->opt : (p->argc > 1 ? p->argv[1] : NULL); switch (opt->type) { case OPTION_BIT: if (unset) @@ -80,17 +84,12 @@ static int get_value(struct parse_opt_ctx_t *p, return 0; case OPTION_STRING: - if (unset) { + if (unset) *(const char **)opt->value = NULL; - return 0; - } - if (opt->flags & PARSE_OPT_OPTARG && !p->opt) { + else if (opt->flags & PARSE_OPT_OPTARG && !p->opt) *(const char **)opt->value = (const char *)opt->defval; - return 0; - } - if (!arg) - return opterror(opt, "requires a value", flags); - *(const char **)opt->value = get_arg(p); + else + return get_arg(p, opt, flags, (const char **)opt->value); return 0; case OPTION_CALLBACK: @@ -100,9 +99,9 @@ static int get_value(struct parse_opt_ctx_t *p, return (*opt->callback)(opt, NULL, 0) ? (-1) : 0; if (opt->flags & PARSE_OPT_OPTARG && !p->opt) return (*opt->callback)(opt, NULL, 0) ? (-1) : 0; - if (!arg) - return opterror(opt, "requires a value", flags); - return (*opt->callback)(opt, get_arg(p), 0) ? (-1) : 0; + if (get_arg(p, opt, flags, &arg)) + return -1; + return (*opt->callback)(opt, arg, 0) ? (-1) : 0; case OPTION_INTEGER: if (unset) { @@ -113,9 +112,9 @@ static int get_value(struct parse_opt_ctx_t *p, *(int *)opt->value = opt->defval; return 0; } - if (!arg) - return opterror(opt, "requires a value", flags); - *(int *)opt->value = strtol(get_arg(p), (char **)&s, 10); + if (get_arg(p, opt, flags, &arg)) + return -1; + *(int *)opt->value = strtol(arg, (char **)&s, 10); if (*s) return opterror(opt, "expects a numerical value", flags); return 0; diff --git a/parse-options.h b/parse-options.h index c5f0b4b4da..bc317e7512 100644 --- a/parse-options.h +++ b/parse-options.h @@ -28,6 +28,7 @@ enum parse_opt_option_flags { PARSE_OPT_NOARG = 2, PARSE_OPT_NONEG = 4, PARSE_OPT_HIDDEN = 8, + PARSE_OPT_LASTARG_DEFAULT = 16, }; struct option; From e84fb2ff75f861a708ea5a914883e178a845f4ef Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 8 Jul 2008 17:31:51 -0700 Subject: [PATCH 25/60] branch --contains: default to HEAD We used to require the name of the commit to limit the branches shown to the --contains option, but more recent --merged/--no-meregd defaults to HEAD (and they do not allow arbitrary commit, which is a separate issue). This teaches --contains to default to HEAD when no parameter is given. Signed-off-by: Junio C Hamano --- builtin-branch.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/builtin-branch.c b/builtin-branch.c index d279702ba9..50cbc9c81a 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -438,13 +438,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"), OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches", REF_REMOTE_BRANCH), - OPT_CALLBACK(0, "contains", &with_commit, "commit", - "print only branches that contain the commit", - opt_parse_with_commit), + { + OPTION_CALLBACK, 0, "contains", &with_commit, "commit", + "print only branches that contain the commit", + PARSE_OPT_LASTARG_DEFAULT, + opt_parse_with_commit, (intptr_t)"HEAD", + }, { OPTION_CALLBACK, 0, "with", &with_commit, "commit", "print only branches that contain the commit", - PARSE_OPT_HIDDEN, opt_parse_with_commit, + PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT, + opt_parse_with_commit, (intptr_t) "HEAD", }, OPT__ABBREV(&abbrev), From 049716b370f2cebdbdeb278eb2a8c4eff8ed0acd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 8 Jul 2008 17:55:47 -0700 Subject: [PATCH 26/60] branch --merged/--no-merged: allow specifying arbitrary commit "git-branch --merged" is a handy way to list all the branches that have already been merged to the current branch, but it did not allow checking against anything but the current branch. Having to switch branches only to list the branches that are merged with another branch made the feature practically useless. This updates the option parser so that "git branch --merged next" is accepted when you are on 'master' branch. Signed-off-by: Junio C Hamano --- Documentation/git-branch.txt | 27 ++++++++++--------- builtin-branch.c | 50 ++++++++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 0fd58083eb..450f90368f 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -8,24 +8,27 @@ git-branch - List, create, or delete branches SYNOPSIS -------- [verse] -'git-branch' [--color | --no-color] [-r | -a] [--merged | --no-merged] - [-v [--abbrev= | --no-abbrev]] - [--contains ] +'git-branch' [--color | --no-color] [-r | -a] + [-v [--abbrev= | --no-abbrev]] + [(--merged | --no-merged | --contains) []] 'git-branch' [--track | --no-track] [-l] [-f] [] 'git-branch' (-m | -M) [] 'git-branch' (-d | -D) [-r] ... DESCRIPTION ----------- -With no arguments given a list of existing branches -will be shown, the current branch will be highlighted with an asterisk. -Option `-r` causes the remote-tracking branches to be listed, -and option `-a` shows both. -With `--contains `, shows only the branches that -contains the named commit (in other words, the branches whose -tip commits are descendant of the named commit). -With `--merged`, only branches merged into HEAD will be listed, and -with `--no-merged` only branches not merged into HEAD will be listed. + +With no arguments, existing branches are listed, the current branch will +be highlighted with an asterisk. Option `-r` causes the remote-tracking +branches to be listed, and option `-a` shows both. + +With `--contains`, shows only the branches that contains the named commit +(in other words, the branches whose tip commits are descendant of the +named commit). With `--merged`, only branches merged into the named +commit (i.e. the branches whose tip commits are reachable from the named +commit) will be listed. With `--no-merged` only branches not merged into +the named commit will be listed. Missing argument defaults to +'HEAD' (i.e. the tip of the current branch). In its second form, a new branch named will be created. It will start out with a head equal to the one given as . diff --git a/builtin-branch.c b/builtin-branch.c index 50cbc9c81a..1926c47581 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -46,7 +46,12 @@ enum color_branch { COLOR_BRANCH_CURRENT = 4, }; -static int mergefilter = -1; +static enum merge_filter { + NO_FILTER = 0, + SHOW_NOT_MERGED, + SHOW_MERGED, +} merge_filter; +static unsigned char merge_filter_ref[20]; static int parse_branch_color_slot(const char *var, int ofs) { @@ -234,13 +239,15 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, if ((kind & ref_list->kinds) == 0) return 0; - if (mergefilter > -1) { + if (merge_filter != NO_FILTER) { branch.item = lookup_commit_reference_gently(sha1, 1); if (!branch.item) die("Unable to lookup tip of branch %s", refname); - if (mergefilter == 0 && has_commit(head_sha1, &branch)) + if (merge_filter == SHOW_NOT_MERGED && + has_commit(merge_filter_ref, &branch)) return 0; - if (mergefilter == 1 && !has_commit(head_sha1, &branch)) + if (merge_filter == SHOW_MERGED && + !has_commit(merge_filter_ref, &branch)) return 0; } @@ -421,6 +428,20 @@ static int opt_parse_with_commit(const struct option *opt, const char *arg, int return 0; } +static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset) +{ + merge_filter = ((opt->long_name[0] == 'n') + ? SHOW_NOT_MERGED + : SHOW_MERGED); + if (unset) + merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */ + if (!arg) + arg = "HEAD"; + if (get_sha1(arg, merge_filter_ref)) + die("malformed object name %s", arg); + return 0; +} + int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, rename = 0, force_create = 0; @@ -461,7 +482,18 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2), OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"), OPT_BOOLEAN('f', NULL, &force_create, "force creation (when already exists)"), - OPT_SET_INT(0, "merged", &mergefilter, "list only merged branches", 1), + { + OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref, + "commit", "print only not merged branches", + PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, + opt_parse_merge_filter, (intptr_t) "HEAD", + }, + { + OPTION_CALLBACK, 0, "merged", &merge_filter_ref, + "commit", "print only merged branches", + PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, + opt_parse_merge_filter, (intptr_t) "HEAD", + }, OPT_END(), }; @@ -471,9 +503,6 @@ int cmd_branch(int argc, const char **argv, const char *prefix) branch_use_color = git_use_color_default; track = git_branch_track; - argc = parse_options(argc, argv, options, builtin_branch_usage, 0); - if (!!delete + !!rename + !!force_create > 1) - usage_with_options(builtin_branch_usage, options); head = resolve_ref("HEAD", head_sha1, 0, NULL); if (!head) @@ -486,6 +515,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix) die("HEAD not found below refs/heads!"); head += 11; } + hashcpy(merge_filter_ref, head_sha1); + + argc = parse_options(argc, argv, options, builtin_branch_usage, 0); + if (!!delete + !!rename + !!force_create > 1) + usage_with_options(builtin_branch_usage, options); if (delete) return delete_branches(argc, argv, delete > 1, kinds); From 14c8eef4e02f9c2f7955c435467f42fa3aeac072 Mon Sep 17 00:00:00 2001 From: Peter Harris Date: Wed, 9 Jul 2008 17:34:20 -0400 Subject: [PATCH 27/60] Add ANSI control code emulation for the Windows console This adds only the minimum necessary to keep git pull/merge's diffstat from wrapping. Notably absent is support for the K (erase) operation, and support for POSIX write. Signed-off-by: Peter Harris Signed-off-by: Johannes Sixt --- Makefile | 2 +- compat/mingw.h | 11 ++ compat/winansi.c | 345 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 compat/winansi.c diff --git a/Makefile b/Makefile index 78e08d3745..2c6cc9d196 100644 --- a/Makefile +++ b/Makefile @@ -740,7 +740,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat COMPAT_CFLAGS += -DSNPRINTF_SIZE_CORR=1 COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" - COMPAT_OBJS += compat/mingw.o compat/fnmatch.o compat/regex.o + COMPAT_OBJS += compat/mingw.o compat/fnmatch.o compat/regex.o compat/winansi.o EXTLIBS += -lws2_32 X = .exe template_dir = ../share/git-core/templates/ diff --git a/compat/mingw.h b/compat/mingw.h index 6bc049ad99..5f11114890 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -193,6 +193,17 @@ static inline unsigned int git_ntohl(unsigned int x) sig_handler_t mingw_signal(int sig, sig_handler_t handler); #define signal mingw_signal +/* + * ANSI emulation wrappers + */ + +int winansi_fputs(const char *str, FILE *stream); +int winansi_printf(const char *format, ...) __attribute__((format (printf, 1, 2))); +int winansi_fprintf(FILE *stream, const char *format, ...) __attribute__((format (printf, 2, 3))); +#define fputs winansi_fputs +#define printf(...) winansi_printf(__VA_ARGS__) +#define fprintf(...) winansi_fprintf(__VA_ARGS__) + /* * git specific compatibility */ diff --git a/compat/winansi.c b/compat/winansi.c new file mode 100644 index 0000000000..e2d96dfe6f --- /dev/null +++ b/compat/winansi.c @@ -0,0 +1,345 @@ +/* + * Copyright 2008 Peter Harris + */ + +#include +#include "../git-compat-util.h" + +/* + Functions to be wrapped: +*/ +#undef printf +#undef fprintf +#undef fputs +/* TODO: write */ + +/* + ANSI codes used by git: m, K + + This file is git-specific. Therefore, this file does not attempt + to implement any codes that are not used by git. + + TODO: K +*/ + +static HANDLE console; +static WORD plain_attr; +static WORD attr; +static int negative; + +static void init(void) +{ + CONSOLE_SCREEN_BUFFER_INFO sbi; + + static int initialized = 0; + if (initialized) + return; + + console = GetStdHandle(STD_OUTPUT_HANDLE); + if (console == INVALID_HANDLE_VALUE) + console = NULL; + + if (!console) + return; + + GetConsoleScreenBufferInfo(console, &sbi); + attr = plain_attr = sbi.wAttributes; + negative = 0; + + initialized = 1; +} + + +#define FOREGROUND_ALL (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE) +#define BACKGROUND_ALL (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE) + +static void set_console_attr(void) +{ + WORD attributes = attr; + if (negative) { + attributes &= ~FOREGROUND_ALL; + attributes &= ~BACKGROUND_ALL; + + /* This could probably use a bitmask + instead of a series of ifs */ + if (attr & FOREGROUND_RED) + attributes |= BACKGROUND_RED; + if (attr & FOREGROUND_GREEN) + attributes |= BACKGROUND_GREEN; + if (attr & FOREGROUND_BLUE) + attributes |= BACKGROUND_BLUE; + + if (attr & BACKGROUND_RED) + attributes |= FOREGROUND_RED; + if (attr & BACKGROUND_GREEN) + attributes |= FOREGROUND_GREEN; + if (attr & BACKGROUND_BLUE) + attributes |= FOREGROUND_BLUE; + } + SetConsoleTextAttribute(console, attributes); +} + +static const char *set_attr(const char *str) +{ + const char *func; + size_t len = strspn(str, "0123456789;"); + func = str + len; + + switch (*func) { + case 'm': + do { + long val = strtol(str, (char **)&str, 10); + switch (val) { + case 0: /* reset */ + attr = plain_attr; + negative = 0; + break; + case 1: /* bold */ + attr |= FOREGROUND_INTENSITY; + break; + case 2: /* faint */ + case 22: /* normal */ + attr &= ~FOREGROUND_INTENSITY; + break; + case 3: /* italic */ + /* Unsupported */ + break; + case 4: /* underline */ + case 21: /* double underline */ + /* Wikipedia says this flag does nothing */ + /* Furthermore, mingw doesn't define this flag + attr |= COMMON_LVB_UNDERSCORE; */ + break; + case 24: /* no underline */ + /* attr &= ~COMMON_LVB_UNDERSCORE; */ + break; + case 5: /* slow blink */ + case 6: /* fast blink */ + /* We don't have blink, but we do have + background intensity */ + attr |= BACKGROUND_INTENSITY; + break; + case 25: /* no blink */ + attr &= ~BACKGROUND_INTENSITY; + break; + case 7: /* negative */ + negative = 1; + break; + case 27: /* positive */ + negative = 0; + break; + case 8: /* conceal */ + case 28: /* reveal */ + /* Unsupported */ + break; + case 30: /* Black */ + attr &= ~FOREGROUND_ALL; + break; + case 31: /* Red */ + attr &= ~FOREGROUND_ALL; + attr |= FOREGROUND_RED; + break; + case 32: /* Green */ + attr &= ~FOREGROUND_ALL; + attr |= FOREGROUND_GREEN; + break; + case 33: /* Yellow */ + attr &= ~FOREGROUND_ALL; + attr |= FOREGROUND_RED | FOREGROUND_GREEN; + break; + case 34: /* Blue */ + attr &= ~FOREGROUND_ALL; + attr |= FOREGROUND_BLUE; + break; + case 35: /* Magenta */ + attr &= ~FOREGROUND_ALL; + attr |= FOREGROUND_RED | FOREGROUND_BLUE; + break; + case 36: /* Cyan */ + attr &= ~FOREGROUND_ALL; + attr |= FOREGROUND_GREEN | FOREGROUND_BLUE; + break; + case 37: /* White */ + attr |= FOREGROUND_RED | + FOREGROUND_GREEN | + FOREGROUND_BLUE; + break; + case 38: /* Unknown */ + break; + case 39: /* reset */ + attr &= ~FOREGROUND_ALL; + attr |= (plain_attr & FOREGROUND_ALL); + break; + case 40: /* Black */ + attr &= ~BACKGROUND_ALL; + break; + case 41: /* Red */ + attr &= ~BACKGROUND_ALL; + attr |= BACKGROUND_RED; + break; + case 42: /* Green */ + attr &= ~BACKGROUND_ALL; + attr |= BACKGROUND_GREEN; + break; + case 43: /* Yellow */ + attr &= ~BACKGROUND_ALL; + attr |= BACKGROUND_RED | BACKGROUND_GREEN; + break; + case 44: /* Blue */ + attr &= ~BACKGROUND_ALL; + attr |= BACKGROUND_BLUE; + break; + case 45: /* Magenta */ + attr &= ~BACKGROUND_ALL; + attr |= BACKGROUND_RED | BACKGROUND_BLUE; + break; + case 46: /* Cyan */ + attr &= ~BACKGROUND_ALL; + attr |= BACKGROUND_GREEN | BACKGROUND_BLUE; + break; + case 47: /* White */ + attr |= BACKGROUND_RED | + BACKGROUND_GREEN | + BACKGROUND_BLUE; + break; + case 48: /* Unknown */ + break; + case 49: /* reset */ + attr &= ~BACKGROUND_ALL; + attr |= (plain_attr & BACKGROUND_ALL); + break; + default: + /* Unsupported code */ + break; + } + str++; + } while (*(str-1) == ';'); + + set_console_attr(); + break; + case 'K': + /* TODO */ + break; + default: + /* Unsupported code */ + break; + } + + return func + 1; +} + +static int ansi_emulate(const char *str, FILE *stream) +{ + int rv = 0; + const char *pos = str; + + while (*pos) { + pos = strstr(str, "\033["); + if (pos) { + size_t len = pos - str; + + if (len) { + size_t out_len = fwrite(str, 1, len, stream); + rv += out_len; + if (out_len < len) + return rv; + } + + str = pos + 2; + rv += 2; + + fflush(stream); + + pos = set_attr(str); + rv += pos - str; + str = pos; + } else { + rv += strlen(str); + fputs(str, stream); + return rv; + } + } + return rv; +} + +int winansi_fputs(const char *str, FILE *stream) +{ + int rv; + + if (!isatty(fileno(stream))) + return fputs(str, stream); + + init(); + + if (!console) + return fputs(str, stream); + + rv = ansi_emulate(str, stream); + + if (rv >= 0) + return 0; + else + return EOF; +} + +static int winansi_vfprintf(FILE *stream, const char *format, va_list list) +{ + int len, rv; + char small_buf[256]; + char *buf = small_buf; + va_list cp; + + if (!isatty(fileno(stream))) + goto abort; + + init(); + + if (!console) + goto abort; + + va_copy(cp, list); + len = vsnprintf(small_buf, sizeof(small_buf), format, cp); + va_end(cp); + + if (len > sizeof(small_buf) - 1) { + buf = malloc(len + 1); + if (!buf) + goto abort; + + len = vsnprintf(buf, len + 1, format, list); + } + + rv = ansi_emulate(buf, stream); + + if (buf != small_buf) + free(buf); + return rv; + +abort: + rv = vfprintf(stream, format, list); + return rv; +} + +int winansi_fprintf(FILE *stream, const char *format, ...) +{ + va_list list; + int rv; + + va_start(list, format); + rv = winansi_vfprintf(stream, format, list); + va_end(list); + + return rv; +} + +int winansi_printf(const char *format, ...) +{ + va_list list; + int rv; + + va_start(list, format); + rv = winansi_vfprintf(stdout, format, list); + va_end(list); + + return rv; +} From 99d698f1e703754422f1dd780487ddbff3726dc3 Mon Sep 17 00:00:00 2001 From: Olivier Marin Date: Mon, 7 Jul 2008 14:42:48 +0200 Subject: [PATCH 28/60] builtin-rerere: more carefully find conflict markers When a conflicting file contains a line that begin with "=======", rerere failed to parse conflict markers. This result to a wrong preimage file and an unexpected error for the user. The boundary between ours and theirs not just begin with 7 equals, but is followed by either a SP or a LF. This patch enforces parsing rules so that markers match in the right order, and when ambiguous, the command does not autoresolve the conflicted file. Especially because we are introducing rerere.autoupdate configuration (which is off by default for safety) that automatically stages the resolution made by rerere, it is necessary to make sure that we do not autoresolve when there is any ambiguity. Signed-off-by: Olivier Marin Signed-off-by: Junio C Hamano --- builtin-rerere.c | 16 +++++++++++++--- t/t4200-rerere.sh | 26 ++++++++++++++++++++------ 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/builtin-rerere.c b/builtin-rerere.c index 839b26e8e0..69c3a52d5e 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -112,11 +112,17 @@ static int handle_file(const char *path, strbuf_init(&one, 0); strbuf_init(&two, 0); while (fgets(buf, sizeof(buf), f)) { - if (!prefixcmp(buf, "<<<<<<< ")) + if (!prefixcmp(buf, "<<<<<<< ")) { + if (hunk) + goto bad; hunk = 1; - else if (!prefixcmp(buf, "=======")) + } else if (!prefixcmp(buf, "=======") && isspace(buf[7])) { + if (hunk != 1) + goto bad; hunk = 2; - else if (!prefixcmp(buf, ">>>>>>> ")) { + } else if (!prefixcmp(buf, ">>>>>>> ")) { + if (hunk != 2) + goto bad; if (strbuf_cmp(&one, &two) > 0) strbuf_swap(&one, &two); hunk_no++; @@ -142,6 +148,10 @@ static int handle_file(const char *path, strbuf_addstr(&two, buf); else if (out) fputs(buf, out); + continue; + bad: + hunk = 99; /* force error exit */ + break; } strbuf_release(&one); strbuf_release(&two); diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index a64727d5ad..cf10557dd2 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -9,6 +9,8 @@ test_description='git rerere . ./test-lib.sh cat > a1 << EOF +Some title +========== Whether 'tis nobler in the mind to suffer The slings and arrows of outrageous fortune, Or to take arms against a sea of troubles, @@ -24,6 +26,8 @@ git commit -q -a -m initial git checkout -b first cat >> a1 << EOF +Some title +========== To die, to sleep; To sleep: perchance to dream: ay, there's the rub; For in that sleep of death what dreams may come @@ -35,7 +39,7 @@ git commit -q -a -m first git checkout -b second master git show first:a1 | -sed -e 's/To die, t/To die! T/' > a1 +sed -e 's/To die, t/To die! T/' -e 's/Some title/Some Title/' > a1 echo "* END *" >>a1 git commit -q -a -m second @@ -55,14 +59,14 @@ test_expect_success 'conflicting merge' ' sha1=$(sed -e 's/ .*//' .git/rr-cache/MERGE_RR) rr=.git/rr-cache/$sha1 -test_expect_success 'recorded preimage' "grep ======= $rr/preimage" +test_expect_success 'recorded preimage' "grep ^=======$ $rr/preimage" test_expect_success 'rerere.enabled works, too' ' rm -rf .git/rr-cache && git config rerere.enabled true && git reset --hard && ! git merge first && - grep ======= $rr/preimage + grep ^=======$ $rr/preimage ' test_expect_success 'no postimage or thisimage yet' \ @@ -71,7 +75,7 @@ test_expect_success 'no postimage or thisimage yet' \ test_expect_success 'preimage has right number of lines' ' cnt=$(sed -ne "/^<<<<<<>>>>>>/p" $rr/preimage | wc -l) && - test $cnt = 9 + test $cnt = 13 ' @@ -80,13 +84,23 @@ git show first:a1 > a1 cat > expect << EOF --- a/a1 +++ b/a1 -@@ -6,17 +6,9 @@ +@@ -1,4 +1,4 @@ +-Some Title ++Some title + ========== + Whether 'tis nobler in the mind to suffer + The slings and arrows of outrageous fortune, +@@ -8,21 +8,11 @@ The heart-ache and the thousand natural shocks That flesh is heir to, 'tis a consummation Devoutly to be wish'd. -<<<<<<< +-Some Title +-========== -To die! To sleep; -======= + Some title + ========== To die, to sleep; ->>>>>>> To sleep: perchance to dream: ay, there's the rub; @@ -124,7 +138,7 @@ test_expect_success 'another conflicting merge' ' ' git show first:a1 | sed 's/To die: t/To die! T/' > expect -test_expect_success 'rerere kicked in' "! grep ======= a1" +test_expect_success 'rerere kicked in' "! grep ^=======$ a1" test_expect_success 'rerere prefers first change' 'test_cmp a1 expect' From 5b2fd95606cd6d564f96d9d253e7cd19263bc352 Mon Sep 17 00:00:00 2001 From: Stephan Beyer Date: Wed, 9 Jul 2008 14:58:57 +0200 Subject: [PATCH 29/60] rerere: Separate libgit and builtin functions This patch moves rerere()-related functions into a newly created rerere.c file. The setup_rerere() function is needed by both rerere() and cmd_rerere(), so this function is moved to rerere.c and declared non-static (and "extern") in newly created rerere.h file. Signed-off-by: Stephan Beyer Signed-off-by: Junio C Hamano --- Makefile | 2 + builtin-commit.c | 1 + builtin-rerere.c | 372 +++-------------------------------------------- commit.h | 1 - rerere.c | 360 +++++++++++++++++++++++++++++++++++++++++++++ rerere.h | 9 ++ 6 files changed, 390 insertions(+), 355 deletions(-) create mode 100644 rerere.c create mode 100644 rerere.h diff --git a/Makefile b/Makefile index 4796565ab3..de151638bb 100644 --- a/Makefile +++ b/Makefile @@ -363,6 +363,7 @@ LIB_H += quote.h LIB_H += reflog-walk.h LIB_H += refs.h LIB_H += remote.h +LIB_H += rerere.h LIB_H += revision.h LIB_H += run-command.h LIB_H += sha1-lookup.h @@ -447,6 +448,7 @@ LIB_OBJS += read-cache.o LIB_OBJS += reflog-walk.o LIB_OBJS += refs.o LIB_OBJS += remote.o +LIB_OBJS += rerere.o LIB_OBJS += revision.o LIB_OBJS += run-command.o LIB_OBJS += server-info.o diff --git a/builtin-commit.c b/builtin-commit.c index 745c11e773..bdc83df55b 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -22,6 +22,7 @@ #include "utf8.h" #include "parse-options.h" #include "path-list.h" +#include "rerere.h" #include "unpack-trees.h" static const char * const builtin_commit_usage[] = { diff --git a/builtin-rerere.c b/builtin-rerere.c index 69c3a52d5e..5d40e16932 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -1,11 +1,10 @@ #include "builtin.h" #include "cache.h" #include "path-list.h" +#include "rerere.h" #include "xdiff/xdiff.h" #include "xdiff-interface.h" -#include - static const char git_rerere_usage[] = "git-rerere [clear | status | diff | gc]"; @@ -13,14 +12,6 @@ static const char git_rerere_usage[] = static int cutoff_noresolve = 15; static int cutoff_resolve = 60; -/* if rerere_enabled == -1, fall back to detection of .git/rr-cache */ -static int rerere_enabled = -1; - -/* automatically update cleanly resolved paths to the index */ -static int rerere_autoupdate; - -static char *merge_rr_path; - static const char *rr_path(const char *name, const char *file) { return git_path("rr-cache/%s/%s", name, file); @@ -38,189 +29,6 @@ static int has_resolution(const char *name) return !stat(rr_path(name, "postimage"), &st); } -static void read_rr(struct path_list *rr) -{ - unsigned char sha1[20]; - char buf[PATH_MAX]; - FILE *in = fopen(merge_rr_path, "r"); - if (!in) - return; - while (fread(buf, 40, 1, in) == 1) { - int i; - char *name; - if (get_sha1_hex(buf, sha1)) - die("corrupt MERGE_RR"); - buf[40] = '\0'; - name = xstrdup(buf); - if (fgetc(in) != '\t') - die("corrupt MERGE_RR"); - for (i = 0; i < sizeof(buf) && (buf[i] = fgetc(in)); i++) - ; /* do nothing */ - if (i == sizeof(buf)) - die("filename too long"); - path_list_insert(buf, rr)->util = name; - } - fclose(in); -} - -static struct lock_file write_lock; - -static int write_rr(struct path_list *rr, int out_fd) -{ - int i; - for (i = 0; i < rr->nr; i++) { - const char *path; - int length; - if (!rr->items[i].util) - continue; - path = rr->items[i].path; - length = strlen(path) + 1; - if (write_in_full(out_fd, rr->items[i].util, 40) != 40 || - write_in_full(out_fd, "\t", 1) != 1 || - write_in_full(out_fd, path, length) != length) - die("unable to write rerere record"); - } - if (commit_lock_file(&write_lock) != 0) - die("unable to write rerere record"); - return 0; -} - -static int handle_file(const char *path, - unsigned char *sha1, const char *output) -{ - SHA_CTX ctx; - char buf[1024]; - int hunk = 0, hunk_no = 0; - struct strbuf one, two; - FILE *f = fopen(path, "r"); - FILE *out = NULL; - - if (!f) - return error("Could not open %s", path); - - if (output) { - out = fopen(output, "w"); - if (!out) { - fclose(f); - return error("Could not write %s", output); - } - } - - if (sha1) - SHA1_Init(&ctx); - - strbuf_init(&one, 0); - strbuf_init(&two, 0); - while (fgets(buf, sizeof(buf), f)) { - if (!prefixcmp(buf, "<<<<<<< ")) { - if (hunk) - goto bad; - hunk = 1; - } else if (!prefixcmp(buf, "=======") && isspace(buf[7])) { - if (hunk != 1) - goto bad; - hunk = 2; - } else if (!prefixcmp(buf, ">>>>>>> ")) { - if (hunk != 2) - goto bad; - if (strbuf_cmp(&one, &two) > 0) - strbuf_swap(&one, &two); - hunk_no++; - hunk = 0; - if (out) { - fputs("<<<<<<<\n", out); - fwrite(one.buf, one.len, 1, out); - fputs("=======\n", out); - fwrite(two.buf, two.len, 1, out); - fputs(">>>>>>>\n", out); - } - if (sha1) { - SHA1_Update(&ctx, one.buf ? one.buf : "", - one.len + 1); - SHA1_Update(&ctx, two.buf ? two.buf : "", - two.len + 1); - } - strbuf_reset(&one); - strbuf_reset(&two); - } else if (hunk == 1) - strbuf_addstr(&one, buf); - else if (hunk == 2) - strbuf_addstr(&two, buf); - else if (out) - fputs(buf, out); - continue; - bad: - hunk = 99; /* force error exit */ - break; - } - strbuf_release(&one); - strbuf_release(&two); - - fclose(f); - if (out) - fclose(out); - if (sha1) - SHA1_Final(sha1, &ctx); - if (hunk) { - if (output) - unlink(output); - return error("Could not parse conflict hunks in %s", path); - } - return hunk_no; -} - -static int find_conflict(struct path_list *conflict) -{ - int i; - if (read_cache() < 0) - return error("Could not read index"); - for (i = 0; i+1 < active_nr; i++) { - struct cache_entry *e2 = active_cache[i]; - struct cache_entry *e3 = active_cache[i+1]; - if (ce_stage(e2) == 2 && - ce_stage(e3) == 3 && - ce_same_name(e2, e3) && - S_ISREG(e2->ce_mode) && - S_ISREG(e3->ce_mode)) { - path_list_insert((const char *)e2->name, conflict); - i++; /* skip over both #2 and #3 */ - } - } - return 0; -} - -static int merge(const char *name, const char *path) -{ - int ret; - mmfile_t cur, base, other; - mmbuffer_t result = {NULL, 0}; - xpparam_t xpp = {XDF_NEED_MINIMAL}; - - if (handle_file(path, NULL, rr_path(name, "thisimage")) < 0) - return 1; - - if (read_mmfile(&cur, rr_path(name, "thisimage")) || - read_mmfile(&base, rr_path(name, "preimage")) || - read_mmfile(&other, rr_path(name, "postimage"))) - return 1; - ret = xdl_merge(&base, &cur, "", &other, "", - &xpp, XDL_MERGE_ZEALOUS, &result); - if (!ret) { - FILE *f = fopen(path, "w"); - if (!f) - return error("Could not write to %s", path); - fwrite(result.ptr, result.size, 1, f); - fclose(f); - } - - free(cur.ptr); - free(base.ptr); - free(other.ptr); - free(result.ptr); - - return ret; -} - static void unlink_rr_item(const char *name) { unlink(rr_path(name, "thisimage")); @@ -229,6 +37,17 @@ static void unlink_rr_item(const char *name) rmdir(git_path("rr-cache/%s", name)); } +static int git_rerere_gc_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "gc.rerereresolved")) + cutoff_resolve = git_config_int(var, value); + else if (!strcmp(var, "gc.rerereunresolved")) + cutoff_noresolve = git_config_int(var, value); + else + return git_default_config(var, value, cb); + return 0; +} + static void garbage_collect(struct path_list *rr) { struct path_list to_remove = { NULL, 0, 0, 1 }; @@ -237,6 +56,7 @@ static void garbage_collect(struct path_list *rr) int i, cutoff; time_t now = time(NULL), then; + git_config(git_rerere_gc_config, NULL); dir = opendir(git_path("rr-cache")); while ((e = readdir(dir))) { const char *name = e->d_name; @@ -289,181 +109,25 @@ static int diff_two(const char *file1, const char *label1, return 0; } -static struct lock_file index_lock; - -static int update_paths(struct path_list *update) -{ - int i; - int fd = hold_locked_index(&index_lock, 0); - int status = 0; - - if (fd < 0) - return -1; - - for (i = 0; i < update->nr; i++) { - struct path_list_item *item = &update->items[i]; - if (add_file_to_cache(item->path, ADD_CACHE_IGNORE_ERRORS)) - status = -1; - } - - if (!status && active_cache_changed) { - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(&index_lock)) - die("Unable to write new index file"); - } else if (fd >= 0) - rollback_lock_file(&index_lock); - return status; -} - -static int do_plain_rerere(struct path_list *rr, int fd) -{ - struct path_list conflict = { NULL, 0, 0, 1 }; - struct path_list update = { NULL, 0, 0, 1 }; - int i; - - find_conflict(&conflict); - - /* - * MERGE_RR records paths with conflicts immediately after merge - * failed. Some of the conflicted paths might have been hand resolved - * in the working tree since then, but the initial run would catch all - * and register their preimages. - */ - - for (i = 0; i < conflict.nr; i++) { - const char *path = conflict.items[i].path; - if (!path_list_has_path(rr, path)) { - unsigned char sha1[20]; - char *hex; - int ret; - ret = handle_file(path, sha1, NULL); - if (ret < 1) - continue; - hex = xstrdup(sha1_to_hex(sha1)); - path_list_insert(path, rr)->util = hex; - if (mkdir(git_path("rr-cache/%s", hex), 0755)) - continue;; - handle_file(path, NULL, rr_path(hex, "preimage")); - fprintf(stderr, "Recorded preimage for '%s'\n", path); - } - } - - /* - * Now some of the paths that had conflicts earlier might have been - * hand resolved. Others may be similar to a conflict already that - * was resolved before. - */ - - for (i = 0; i < rr->nr; i++) { - int ret; - const char *path = rr->items[i].path; - const char *name = (const char *)rr->items[i].util; - - if (has_resolution(name)) { - if (!merge(name, path)) { - fprintf(stderr, "Resolved '%s' using " - "previous resolution.\n", path); - if (rerere_autoupdate) - path_list_insert(path, &update); - goto mark_resolved; - } - } - - /* Let's see if we have resolved it. */ - ret = handle_file(path, NULL, NULL); - if (ret) - continue; - - fprintf(stderr, "Recorded resolution for '%s'.\n", path); - copy_file(rr_path(name, "postimage"), path, 0666); - mark_resolved: - rr->items[i].util = NULL; - } - - if (update.nr) - update_paths(&update); - - return write_rr(rr, fd); -} - -static int git_rerere_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "gc.rerereresolved")) - cutoff_resolve = git_config_int(var, value); - else if (!strcmp(var, "gc.rerereunresolved")) - cutoff_noresolve = git_config_int(var, value); - else if (!strcmp(var, "rerere.enabled")) - rerere_enabled = git_config_bool(var, value); - else if (!strcmp(var, "rerere.autoupdate")) - rerere_autoupdate = git_config_bool(var, value); - else - return git_default_config(var, value, cb); - return 0; -} - -static int is_rerere_enabled(void) -{ - struct stat st; - const char *rr_cache; - int rr_cache_exists; - - if (!rerere_enabled) - return 0; - - rr_cache = git_path("rr-cache"); - rr_cache_exists = !stat(rr_cache, &st) && S_ISDIR(st.st_mode); - if (rerere_enabled < 0) - return rr_cache_exists; - - if (!rr_cache_exists && - (mkdir(rr_cache, 0777) || adjust_shared_perm(rr_cache))) - die("Could not create directory %s", rr_cache); - return 1; -} - -static int setup_rerere(struct path_list *merge_rr) -{ - int fd; - - git_config(git_rerere_config, NULL); - if (!is_rerere_enabled()) - return -1; - - merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR")); - fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1); - read_rr(merge_rr); - return fd; -} - -int rerere(void) -{ - struct path_list merge_rr = { NULL, 0, 0, 1 }; - int fd; - - fd = setup_rerere(&merge_rr); - if (fd < 0) - return 0; - return do_plain_rerere(&merge_rr, fd); -} - int cmd_rerere(int argc, const char **argv, const char *prefix) { struct path_list merge_rr = { NULL, 0, 0, 1 }; int i, fd; + if (argc < 2) + return rerere(); + fd = setup_rerere(&merge_rr); if (fd < 0) return 0; - if (argc < 2) - return do_plain_rerere(&merge_rr, fd); - else if (!strcmp(argv[1], "clear")) { + if (!strcmp(argv[1], "clear")) { for (i = 0; i < merge_rr.nr; i++) { const char *name = (const char *)merge_rr.items[i].util; if (!has_resolution(name)) unlink_rr_item(name); } - unlink(merge_rr_path); + unlink(git_path("rr-cache/MERGE_RR")); } else if (!strcmp(argv[1], "gc")) garbage_collect(&merge_rr); else if (!strcmp(argv[1], "status")) diff --git a/commit.h b/commit.h index 2d94d4148e..fda7ad03c0 100644 --- a/commit.h +++ b/commit.h @@ -131,7 +131,6 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads, int in_merge_bases(struct commit *, struct commit **, int); extern int interactive_add(int argc, const char **argv, const char *prefix); -extern int rerere(void); static inline int single_parent(struct commit *commit) { diff --git a/rerere.c b/rerere.c new file mode 100644 index 0000000000..eec2b50107 --- /dev/null +++ b/rerere.c @@ -0,0 +1,360 @@ +#include "cache.h" +#include "path-list.h" +#include "rerere.h" +#include "xdiff/xdiff.h" +#include "xdiff-interface.h" + +/* if rerere_enabled == -1, fall back to detection of .git/rr-cache */ +static int rerere_enabled = -1; + +/* automatically update cleanly resolved paths to the index */ +static int rerere_autoupdate; + +static char *merge_rr_path; + +static const char *rr_path(const char *name, const char *file) +{ + return git_path("rr-cache/%s/%s", name, file); +} + +static int has_resolution(const char *name) +{ + struct stat st; + return !stat(rr_path(name, "postimage"), &st); +} + +static void read_rr(struct path_list *rr) +{ + unsigned char sha1[20]; + char buf[PATH_MAX]; + FILE *in = fopen(merge_rr_path, "r"); + if (!in) + return; + while (fread(buf, 40, 1, in) == 1) { + int i; + char *name; + if (get_sha1_hex(buf, sha1)) + die("corrupt MERGE_RR"); + buf[40] = '\0'; + name = xstrdup(buf); + if (fgetc(in) != '\t') + die("corrupt MERGE_RR"); + for (i = 0; i < sizeof(buf) && (buf[i] = fgetc(in)); i++) + ; /* do nothing */ + if (i == sizeof(buf)) + die("filename too long"); + path_list_insert(buf, rr)->util = name; + } + fclose(in); +} + +static struct lock_file write_lock; + +static int write_rr(struct path_list *rr, int out_fd) +{ + int i; + for (i = 0; i < rr->nr; i++) { + const char *path; + int length; + if (!rr->items[i].util) + continue; + path = rr->items[i].path; + length = strlen(path) + 1; + if (write_in_full(out_fd, rr->items[i].util, 40) != 40 || + write_in_full(out_fd, "\t", 1) != 1 || + write_in_full(out_fd, path, length) != length) + die("unable to write rerere record"); + } + if (commit_lock_file(&write_lock) != 0) + die("unable to write rerere record"); + return 0; +} + +static int handle_file(const char *path, + unsigned char *sha1, const char *output) +{ + SHA_CTX ctx; + char buf[1024]; + int hunk = 0, hunk_no = 0; + struct strbuf one, two; + FILE *f = fopen(path, "r"); + FILE *out = NULL; + + if (!f) + return error("Could not open %s", path); + + if (output) { + out = fopen(output, "w"); + if (!out) { + fclose(f); + return error("Could not write %s", output); + } + } + + if (sha1) + SHA1_Init(&ctx); + + strbuf_init(&one, 0); + strbuf_init(&two, 0); + while (fgets(buf, sizeof(buf), f)) { + if (!prefixcmp(buf, "<<<<<<< ")) { + if (hunk) + goto bad; + hunk = 1; + } else if (!prefixcmp(buf, "=======") && isspace(buf[7])) { + if (hunk != 1) + goto bad; + hunk = 2; + } else if (!prefixcmp(buf, ">>>>>>> ")) { + if (hunk != 2) + goto bad; + if (strbuf_cmp(&one, &two) > 0) + strbuf_swap(&one, &two); + hunk_no++; + hunk = 0; + if (out) { + fputs("<<<<<<<\n", out); + fwrite(one.buf, one.len, 1, out); + fputs("=======\n", out); + fwrite(two.buf, two.len, 1, out); + fputs(">>>>>>>\n", out); + } + if (sha1) { + SHA1_Update(&ctx, one.buf ? one.buf : "", + one.len + 1); + SHA1_Update(&ctx, two.buf ? two.buf : "", + two.len + 1); + } + strbuf_reset(&one); + strbuf_reset(&two); + } else if (hunk == 1) + strbuf_addstr(&one, buf); + else if (hunk == 2) + strbuf_addstr(&two, buf); + else if (out) + fputs(buf, out); + continue; + bad: + hunk = 99; /* force error exit */ + break; + } + strbuf_release(&one); + strbuf_release(&two); + + fclose(f); + if (out) + fclose(out); + if (sha1) + SHA1_Final(sha1, &ctx); + if (hunk) { + if (output) + unlink(output); + return error("Could not parse conflict hunks in %s", path); + } + return hunk_no; +} + +static int find_conflict(struct path_list *conflict) +{ + int i; + if (read_cache() < 0) + return error("Could not read index"); + for (i = 0; i+1 < active_nr; i++) { + struct cache_entry *e2 = active_cache[i]; + struct cache_entry *e3 = active_cache[i+1]; + if (ce_stage(e2) == 2 && + ce_stage(e3) == 3 && + ce_same_name(e2, e3) && + S_ISREG(e2->ce_mode) && + S_ISREG(e3->ce_mode)) { + path_list_insert((const char *)e2->name, conflict); + i++; /* skip over both #2 and #3 */ + } + } + return 0; +} + +static int merge(const char *name, const char *path) +{ + int ret; + mmfile_t cur, base, other; + mmbuffer_t result = {NULL, 0}; + xpparam_t xpp = {XDF_NEED_MINIMAL}; + + if (handle_file(path, NULL, rr_path(name, "thisimage")) < 0) + return 1; + + if (read_mmfile(&cur, rr_path(name, "thisimage")) || + read_mmfile(&base, rr_path(name, "preimage")) || + read_mmfile(&other, rr_path(name, "postimage"))) + return 1; + ret = xdl_merge(&base, &cur, "", &other, "", + &xpp, XDL_MERGE_ZEALOUS, &result); + if (!ret) { + FILE *f = fopen(path, "w"); + if (!f) + return error("Could not write to %s", path); + fwrite(result.ptr, result.size, 1, f); + fclose(f); + } + + free(cur.ptr); + free(base.ptr); + free(other.ptr); + free(result.ptr); + + return ret; +} + +static struct lock_file index_lock; + +static int update_paths(struct path_list *update) +{ + int i; + int fd = hold_locked_index(&index_lock, 0); + int status = 0; + + if (fd < 0) + return -1; + + for (i = 0; i < update->nr; i++) { + struct path_list_item *item = &update->items[i]; + if (add_file_to_cache(item->path, ADD_CACHE_IGNORE_ERRORS)) + status = -1; + } + + if (!status && active_cache_changed) { + if (write_cache(fd, active_cache, active_nr) || + commit_locked_index(&index_lock)) + die("Unable to write new index file"); + } else if (fd >= 0) + rollback_lock_file(&index_lock); + return status; +} + +static int do_plain_rerere(struct path_list *rr, int fd) +{ + struct path_list conflict = { NULL, 0, 0, 1 }; + struct path_list update = { NULL, 0, 0, 1 }; + int i; + + find_conflict(&conflict); + + /* + * MERGE_RR records paths with conflicts immediately after merge + * failed. Some of the conflicted paths might have been hand resolved + * in the working tree since then, but the initial run would catch all + * and register their preimages. + */ + + for (i = 0; i < conflict.nr; i++) { + const char *path = conflict.items[i].path; + if (!path_list_has_path(rr, path)) { + unsigned char sha1[20]; + char *hex; + int ret; + ret = handle_file(path, sha1, NULL); + if (ret < 1) + continue; + hex = xstrdup(sha1_to_hex(sha1)); + path_list_insert(path, rr)->util = hex; + if (mkdir(git_path("rr-cache/%s", hex), 0755)) + continue;; + handle_file(path, NULL, rr_path(hex, "preimage")); + fprintf(stderr, "Recorded preimage for '%s'\n", path); + } + } + + /* + * Now some of the paths that had conflicts earlier might have been + * hand resolved. Others may be similar to a conflict already that + * was resolved before. + */ + + for (i = 0; i < rr->nr; i++) { + int ret; + const char *path = rr->items[i].path; + const char *name = (const char *)rr->items[i].util; + + if (has_resolution(name)) { + if (!merge(name, path)) { + fprintf(stderr, "Resolved '%s' using " + "previous resolution.\n", path); + if (rerere_autoupdate) + path_list_insert(path, &update); + goto mark_resolved; + } + } + + /* Let's see if we have resolved it. */ + ret = handle_file(path, NULL, NULL); + if (ret) + continue; + + fprintf(stderr, "Recorded resolution for '%s'.\n", path); + copy_file(rr_path(name, "postimage"), path, 0666); + mark_resolved: + rr->items[i].util = NULL; + } + + if (update.nr) + update_paths(&update); + + return write_rr(rr, fd); +} + +static int git_rerere_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "rerere.enabled")) + rerere_enabled = git_config_bool(var, value); + else if (!strcmp(var, "rerere.autoupdate")) + rerere_autoupdate = git_config_bool(var, value); + else + return git_default_config(var, value, cb); + return 0; +} + +static int is_rerere_enabled(void) +{ + struct stat st; + const char *rr_cache; + int rr_cache_exists; + + if (!rerere_enabled) + return 0; + + rr_cache = git_path("rr-cache"); + rr_cache_exists = !stat(rr_cache, &st) && S_ISDIR(st.st_mode); + if (rerere_enabled < 0) + return rr_cache_exists; + + if (!rr_cache_exists && + (mkdir(rr_cache, 0777) || adjust_shared_perm(rr_cache))) + die("Could not create directory %s", rr_cache); + return 1; +} + +int setup_rerere(struct path_list *merge_rr) +{ + int fd; + + git_config(git_rerere_config, NULL); + if (!is_rerere_enabled()) + return -1; + + merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR")); + fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1); + read_rr(merge_rr); + return fd; +} + +int rerere(void) +{ + struct path_list merge_rr = { NULL, 0, 0, 1 }; + int fd; + + fd = setup_rerere(&merge_rr); + if (fd < 0) + return 0; + return do_plain_rerere(&merge_rr, fd); +} diff --git a/rerere.h b/rerere.h new file mode 100644 index 0000000000..35b0fa86a3 --- /dev/null +++ b/rerere.h @@ -0,0 +1,9 @@ +#ifndef RERERE_H +#define RERERE_H + +#include "path-list.h" + +extern int setup_rerere(struct path_list *); +extern int rerere(void); + +#endif From 4393c2374101fef9053643f9b4ec638b05bd0b26 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 10 Jul 2008 00:50:59 -0700 Subject: [PATCH 30/60] 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 b2e702a11f..2ee1674690 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 d21cd290d3..72ef2e55d9 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 f9dd4bf4e58af0b4828c7e7013080dba59f8a6b9 Mon Sep 17 00:00:00 2001 From: Dmitry Kakurin Date: Fri, 11 Jul 2008 18:48:16 +0200 Subject: [PATCH 31/60] Fixed text file auto-detection: treat EOF character 032 at the end of file as printable Signed-off-by: Dmitry Kakurin Signed-off-by: Steffen Prohaska Signed-off-by: Junio C Hamano --- convert.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/convert.c b/convert.c index 352b69d4ce..78efed800d 100644 --- a/convert.c +++ b/convert.c @@ -61,6 +61,10 @@ static void gather_stats(const char *buf, unsigned long size, struct text_stat * else stats->printable++; } + + /* If file ends with EOF then don't count this EOF as non-printable. */ + if (size >= 1 && buf[size-1] == '\032') + stats->nonprintable--; } /* From c4adea82c5064971ff6d090be8db89c44456186b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 11 Jul 2008 18:55:57 +0200 Subject: [PATCH 32/60] Convert CR/LF to LF in tag signatures On Windows, gpg outputs CR/LF signatures. But since the tag messages are already stripped of the CR by stripspace(), it is arguably nicer to do the same for the tag signature. Actually, this patch does not look for CR/LF, but strips all CRs from the signature. It does so not only on Windows but on all platforms to keep the code simpler. [ spr: ported code to use strbuf ] Signed-off-by: Johannes Schindelin Signed-off-by: Steffen Prohaska Signed-off-by: Junio C Hamano --- builtin-tag.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/builtin-tag.c b/builtin-tag.c index 3c97c696a5..a70922b21c 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -202,6 +202,7 @@ static int do_sign(struct strbuf *buffer) const char *args[4]; char *bracket; int len; + int i, j; if (!*signingkey) { if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME), @@ -241,6 +242,15 @@ static int do_sign(struct strbuf *buffer) if (finish_command(&gpg) || !len || len < 0) return error("gpg failed to sign the tag"); + /* Strip CR from the line endings, in case we are on Windows. */ + for (i = j = 0; i < buffer->len; i++) + if (buffer->buf[i] != '\r') { + if (i != j) + buffer->buf[j] = buffer->buf[i]; + j++; + } + strbuf_setlen(buffer, j); + return 0; } From fdfd20080239c8564e40498bbaae63c1b87ccdba Mon Sep 17 00:00:00 2001 From: Mike Pape Date: Fri, 11 Jul 2008 18:52:42 +0200 Subject: [PATCH 33/60] We need to check for msys as well as Windows in add--interactive. Signed-off-by: Mike Pape Signed-off-by: Steffen Prohaska Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 903953e68e..78a64e6e8a 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -42,7 +42,7 @@ sub colored { my $patch_mode; sub run_cmd_pipe { - if ($^O eq 'MSWin32') { + if ($^O eq 'MSWin32' || $^O eq 'msys') { my @invalid = grep {m/[":*]/} @_; die "$^O does not support: @invalid\n" if @invalid; my @args = map { m/ /o ? "\"$_\"": $_ } @_; From e0cbc39768884a1e7edcf2dbf6e6825c4b23485a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 12 Jul 2008 00:28:18 +0100 Subject: [PATCH 34/60] Add pretty format %aN which gives the author name, respecting .mailmap The pretty format %an does not respect .mailmap, but gives the exact author name recorded in the commit. Sometimes it is more desirable, however, to look if the email has another name mapped to it in .mailmap. This commit adds %aN (and %cN for the committer name) to do exactly that. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/pretty-formats.txt | 2 ++ pretty.c | 27 +++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 69e6d2fa44..c11d495771 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -101,6 +101,7 @@ The placeholders are: - '%P': parent hashes - '%p': abbreviated parent hashes - '%an': author name +- '%aN': author name (respecting .mailmap) - '%ae': author email - '%ad': author date - '%aD': author date, RFC2822 style @@ -108,6 +109,7 @@ The placeholders are: - '%at': author date, UNIX timestamp - '%ai': author date, ISO 8601 format - '%cn': committer name +- '%cN': committer name (respecting .mailmap) - '%ce': committer email - '%cd': committer date - '%cD': committer date, RFC2822 style diff --git a/pretty.c b/pretty.c index 8eb39e915e..628a5201c1 100644 --- a/pretty.c +++ b/pretty.c @@ -3,6 +3,8 @@ #include "utf8.h" #include "diff.h" #include "revision.h" +#include "path-list.h" +#include "mailmap.h" static char *user_format; @@ -288,6 +290,25 @@ static char *logmsg_reencode(const struct commit *commit, return out; } +static int mailmap_name(struct strbuf *sb, const char *email) +{ + static struct path_list *mail_map; + char buffer[1024]; + + if (!mail_map) { + mail_map = xcalloc(1, sizeof(*mail_map)); + read_mailmap(mail_map, ".mailmap", NULL); + } + + if (!mail_map->nr) + return -1; + + if (!map_email(mail_map, email, buffer, sizeof(buffer))) + return -1; + strbuf_addstr(sb, buffer); + return 0; +} + static size_t format_person_part(struct strbuf *sb, char part, const char *msg, int len) { @@ -309,10 +330,12 @@ static size_t format_person_part(struct strbuf *sb, char part, if (end >= len - 2) goto skip; - if (part == 'n') { /* name */ + if (part == 'n' || part == 'N') { /* name */ while (end > 0 && isspace(msg[end - 1])) end--; - strbuf_add(sb, msg, end); + if (part != 'N' || !msg[end] || !msg[end + 1] || + mailmap_name(sb, msg + end + 2) < 0) + strbuf_add(sb, msg, end); return placeholder_len; } start = ++end; /* save email start position */ From 0af0ac7ebbbd2afbc4399d5658e193460b4caaa3 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 12 Jul 2008 15:56:19 +0100 Subject: [PATCH 35/60] Move MERGE_RR from .git/rr-cache/ into .git/ If you want to reuse the rerere cache in another repository, and set a symbolic link to it, you do not want to have the two repositories interfer with each other by accessing the _same_ MERGE_RR. For example, if you use contrib/git-new-workdir to set up a second working directory, and you have a conflict in one working directory, but commit in the other working directory first, the wrong "resolution" will be recorded. The easy solution is to move MERGE_RR out of the rr-cache/ directory, which also corresponds with the notion that rr-cache/ contains cached resolutions, not some intermediate temporary states. Noticed by Kalle Olavi Niemitalo. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- branch.c | 2 +- builtin-rerere.c | 2 +- t/t4200-rerere.sh | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/branch.c b/branch.c index 56e949232c..b1e59f2196 100644 --- a/branch.c +++ b/branch.c @@ -166,7 +166,7 @@ void create_branch(const char *head, void remove_branch_state(void) { unlink(git_path("MERGE_HEAD")); - unlink(git_path("rr-cache/MERGE_RR")); + unlink(git_path("MERGE_RR")); unlink(git_path("MERGE_MSG")); unlink(git_path("SQUASH_MSG")); } diff --git a/builtin-rerere.c b/builtin-rerere.c index 69c3a52d5e..1db2e0c8fb 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -429,7 +429,7 @@ static int setup_rerere(struct path_list *merge_rr) if (!is_rerere_enabled()) return -1; - merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR")); + merge_rr_path = xstrdup(git_path("MERGE_RR")); fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1); read_rr(merge_rr); return fd; diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index cf10557dd2..b5a4202998 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -57,7 +57,7 @@ test_expect_success 'conflicting merge' ' ! git merge first ' -sha1=$(sed -e 's/ .*//' .git/rr-cache/MERGE_RR) +sha1=$(sed -e 's/ .*//' .git/MERGE_RR) rr=.git/rr-cache/$sha1 test_expect_success 'recorded preimage' "grep ^=======$ $rr/preimage" @@ -143,7 +143,7 @@ test_expect_success 'rerere kicked in' "! grep ^=======$ a1" test_expect_success 'rerere prefers first change' 'test_cmp a1 expect' rm $rr/postimage -echo "$sha1 a1" | perl -pe 'y/\012/\000/' > .git/rr-cache/MERGE_RR +echo "$sha1 a1" | perl -pe 'y/\012/\000/' > .git/MERGE_RR test_expect_success 'rerere clear' 'git rerere clear' @@ -190,7 +190,7 @@ test_expect_success 'file2 added differently in two branches' ' git add file2 && git commit -m version2 && ! git merge fourth && - sha1=$(sed -e "s/ .*//" .git/rr-cache/MERGE_RR) && + sha1=$(sed -e "s/ .*//" .git/MERGE_RR) && rr=.git/rr-cache/$sha1 && echo Cello > file2 && git add file2 && From fce87ae53883d22a9912abb2d11a926de747006e Mon Sep 17 00:00:00 2001 From: "Alexander N. Gavrilov" Date: Sat, 12 Jul 2008 22:00:57 +0400 Subject: [PATCH 36/60] Fix quadratic performance in rewrite_one. Parent commits are usually older than their children. Thus, on each iteration of the loop in rewrite_one, add_parents_to_list traverses all commits previously processed by the loop. It performs very poorly in case of very long rewrite chains. Signed-off-by: Alexander Gavrilov Signed-off-by: Junio C Hamano --- revision.c | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/revision.c b/revision.c index fc66755259..d464f14d50 100644 --- a/revision.c +++ b/revision.c @@ -412,10 +412,26 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) commit->object.flags |= TREESAME; } -static int add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list) +static void insert_by_date_cached(struct commit *p, struct commit_list **head, + struct commit_list *cached_base, struct commit_list **cache) +{ + struct commit_list *new_entry; + + if (cached_base && p->date < cached_base->item->date) + new_entry = insert_by_date(p, &cached_base->next); + else + new_entry = insert_by_date(p, head); + + if (cache && (!*cache || p->date < (*cache)->item->date)) + *cache = new_entry; +} + +static int add_parents_to_list(struct rev_info *revs, struct commit *commit, + struct commit_list **list, struct commit_list **cache_ptr) { struct commit_list *parent = commit->parents; unsigned left_flag; + struct commit_list *cached_base = cache_ptr ? *cache_ptr : NULL; if (commit->object.flags & ADDED) return 0; @@ -445,7 +461,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, str if (p->object.flags & SEEN) continue; p->object.flags |= SEEN; - insert_by_date(p, list); + insert_by_date_cached(p, list, cached_base, cache_ptr); } return 0; } @@ -470,7 +486,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, str p->object.flags |= left_flag; if (!(p->object.flags & SEEN)) { p->object.flags |= SEEN; - insert_by_date(p, list); + insert_by_date_cached(p, list, cached_base, cache_ptr); } if(revs->first_parent_only) break; @@ -611,7 +627,7 @@ static int limit_list(struct rev_info *revs) if (revs->max_age != -1 && (commit->date < revs->max_age)) obj->flags |= UNINTERESTING; - if (add_parents_to_list(revs, commit, &list) < 0) + if (add_parents_to_list(revs, commit, &list, NULL) < 0) return -1; if (obj->flags & UNINTERESTING) { mark_parents_uninteresting(commit); @@ -1458,10 +1474,12 @@ enum rewrite_result { static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp) { + struct commit_list *cache = NULL; + for (;;) { struct commit *p = *pp; if (!revs->limited) - if (add_parents_to_list(revs, p, &revs->commits) < 0) + if (add_parents_to_list(revs, p, &revs->commits, &cache) < 0) return rewrite_one_error; if (p->parents && p->parents->next) return rewrite_one_ok; @@ -1580,7 +1598,7 @@ static struct commit *get_revision_1(struct rev_info *revs) if (revs->max_age != -1 && (commit->date < revs->max_age)) continue; - if (add_parents_to_list(revs, commit, &revs->commits) < 0) + if (add_parents_to_list(revs, commit, &revs->commits, NULL) < 0) return NULL; } From 8ec0dd66fa277615fe79ad10096d81edb263d6d1 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Sat, 12 Jul 2008 20:42:10 +0200 Subject: [PATCH 37/60] 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 0ab14a6e81..331b9b07d4 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 38/60] 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 0000000000..ee21a107fd --- /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 39/60] 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 d20b14ee3e..03e73f323a 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 72ef2e55d9..f035ea376e 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 f049e0944d32715ee8893d290d1c52888216fbe4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 12 Jul 2008 15:56:59 +0100 Subject: [PATCH 40/60] git-gui: MERGE_RR lives in .git/ directly with newer Git versions Now that MERGE_RR was moved out of .git/rr-cache/, we have to delete it somewhere else. Just in case somebody wants to use a newer git-gui with an older Git, the file .git/rr-cache/MERGE_RR is removed, too (if it exists). Signed-off-by: Johannes Schindelin Signed-off-by: Shawn O. Pearce --- lib/merge.tcl | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/merge.tcl b/lib/merge.tcl index cc26b07808..5c01875b05 100644 --- a/lib/merge.tcl +++ b/lib/merge.tcl @@ -257,6 +257,7 @@ proc _reset_wait {fd} { catch {file delete [gitdir MERGE_HEAD]} catch {file delete [gitdir rr-cache MERGE_RR]} + catch {file delete [gitdir MERGE_RR]} catch {file delete [gitdir SQUASH_MSG]} catch {file delete [gitdir MERGE_MSG]} catch {file delete [gitdir GITGUI_MSG]} From e9fe804a8282107084a35d3a64e757daf217b042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Sandstr=C3=B6m?= Date: Thu, 10 Jul 2008 23:41:33 +0200 Subject: [PATCH 41/60] git-mailinfo: Fix getting the subject from the in-body [PATCH] line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Subject: " isn't in the static array "header", and thus memcmp("Subject:", header[i], 7) will never match. Even if it did so, hdr_data[] may not have been allocated if there weren't a "Subject: " in-body when we process "[PATCH]" in the affected codepath. Signed-off-by: Lukas Sandström Signed-off-by: Junio C Hamano --- builtin-mailinfo.c | 4 +++- t/t5100-mailinfo.sh | 2 +- t/t5100/0010 | 35 +++++++++++++++++++++++++++++++++++ t/t5100/info0010 | 5 +++++ t/t5100/msg0010 | 5 +++++ t/t5100/patch0010 | 20 ++++++++++++++++++++ t/t5100/sample.mbox | 35 +++++++++++++++++++++++++++++++++++ 7 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 t/t5100/0010 create mode 100644 t/t5100/info0010 create mode 100644 t/t5100/msg0010 create mode 100644 t/t5100/patch0010 diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 962aa34c8e..13f0502b9e 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -334,7 +334,9 @@ static int check_header(char *line, unsigned linesize, char **hdr_data, int over return 1; if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) { for (i = 0; header[i]; i++) { - if (!memcmp("Subject: ", header[i], 9)) { + if (!memcmp("Subject", header[i], 7)) { + if (!hdr_data[i]) + hdr_data[i] = xmalloc(linesize + 20); if (! handle_header(line, hdr_data[i], 0)) { return 1; } diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh index 577ecc210a..e9f3e72c7e 100755 --- a/t/t5100-mailinfo.sh +++ b/t/t5100-mailinfo.sh @@ -11,7 +11,7 @@ test_expect_success 'split sample box' \ 'git mailsplit -o. ../t5100/sample.mbox >last && last=`cat last` && echo total is $last && - test `cat last` = 9' + test `cat last` = 10' for mail in `echo 00*` do diff --git a/t/t5100/0010 b/t/t5100/0010 new file mode 100644 index 0000000000..f5892c9da7 --- /dev/null +++ b/t/t5100/0010 @@ -0,0 +1,35 @@ +From b9704a518e21158433baa2cc2d591fea687967f6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Lukas=20Sandstr=C3=B6m?= +Date: Thu, 10 Jul 2008 23:41:33 +0200 +Subject: Re: discussion that lead to this patch +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +[PATCH] git-mailinfo: Fix getting the subject from the body + +"Subject: " isn't in the static array "header", and thus +memcmp("Subject: ", header[i], 7) will never match. + +Signed-off-by: Lukas Sandström +Signed-off-by: Junio C Hamano +--- + builtin-mailinfo.c | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c +index 962aa34..2d1520f 100644 +--- a/builtin-mailinfo.c ++++ b/builtin-mailinfo.c +@@ -334,7 +334,7 @@ static int check_header(char *line, unsigned linesize, char **hdr_data, int over + return 1; + if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) { + for (i = 0; header[i]; i++) { +- if (!memcmp("Subject: ", header[i], 9)) { ++ if (!memcmp("Subject", header[i], 7)) { + if (! handle_header(line, hdr_data[i], 0)) { + return 1; + } +-- +1.5.6.2.455.g1efb2 + diff --git a/t/t5100/info0010 b/t/t5100/info0010 new file mode 100644 index 0000000000..1791241e46 --- /dev/null +++ b/t/t5100/info0010 @@ -0,0 +1,5 @@ +Author: Lukas Sandström +Email: lukass@etek.chalmers.se +Subject: git-mailinfo: Fix getting the subject from the body +Date: Thu, 10 Jul 2008 23:41:33 +0200 + diff --git a/t/t5100/msg0010 b/t/t5100/msg0010 new file mode 100644 index 0000000000..a96c230092 --- /dev/null +++ b/t/t5100/msg0010 @@ -0,0 +1,5 @@ +"Subject: " isn't in the static array "header", and thus +memcmp("Subject: ", header[i], 7) will never match. + +Signed-off-by: Lukas Sandström +Signed-off-by: Junio C Hamano diff --git a/t/t5100/patch0010 b/t/t5100/patch0010 new file mode 100644 index 0000000000..f055481d56 --- /dev/null +++ b/t/t5100/patch0010 @@ -0,0 +1,20 @@ +--- + builtin-mailinfo.c | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c +index 962aa34..2d1520f 100644 +--- a/builtin-mailinfo.c ++++ b/builtin-mailinfo.c +@@ -334,7 +334,7 @@ static int check_header(char *line, unsigned linesize, char **hdr_data, int over + return 1; + if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) { + for (i = 0; header[i]; i++) { +- if (!memcmp("Subject: ", header[i], 9)) { ++ if (!memcmp("Subject", header[i], 7)) { + if (! handle_header(line, hdr_data[i], 0)) { + return 1; + } +-- +1.5.6.2.455.g1efb2 + diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox index 0476b96c33..aba57f922b 100644 --- a/t/t5100/sample.mbox +++ b/t/t5100/sample.mbox @@ -430,3 +430,38 @@ index b426a14..97756ec 100644 =20 =20 2. When the environment variable 'GIT_EXTERNAL_DIFF' is set, the +From b9704a518e21158433baa2cc2d591fea687967f6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Lukas=20Sandstr=C3=B6m?= +Date: Thu, 10 Jul 2008 23:41:33 +0200 +Subject: Re: discussion that lead to this patch +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +[PATCH] git-mailinfo: Fix getting the subject from the body + +"Subject: " isn't in the static array "header", and thus +memcmp("Subject: ", header[i], 7) will never match. + +Signed-off-by: Lukas Sandström +Signed-off-by: Junio C Hamano +--- + builtin-mailinfo.c | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c +index 962aa34..2d1520f 100644 +--- a/builtin-mailinfo.c ++++ b/builtin-mailinfo.c +@@ -334,7 +334,7 @@ static int check_header(char *line, unsigned linesize, char **hdr_data, int over + return 1; + if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) { + for (i = 0; header[i]; i++) { +- if (!memcmp("Subject: ", header[i], 9)) { ++ if (!memcmp("Subject", header[i], 7)) { + if (! handle_header(line, hdr_data[i], 0)) { + return 1; + } +-- +1.5.6.2.455.g1efb2 + From 778306e405b416d8073652c535777f2de5fc68ad Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 14 Jul 2008 00:22:03 +0000 Subject: [PATCH 42/60] bash completion: Append space after file names have been completed When completing `git show origin/maint:Makef` we should add a space after the filename has been completed, so that the user can immediately begin the next argument. I also added a special case for the symlink variant so we treat it just like a normal blob, as there are no items below it in the Git tree structure. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 821c9a7f9f..0a3bea44f7 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -294,9 +294,17 @@ __git_complete_file () ls="$ref" ;; esac + local IFS=$'\n' COMPREPLY=($(compgen -P "$pfx" \ -W "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \ - | sed '/^100... blob /s,^.* ,, + | sed '/^100... blob /{ + s,^.* ,, + s,$, , + } + /^120000 blob /{ + s,^.* ,, + s,$, , + } /^040000 tree /{ s,^.* ,, s,$,/, From dc6282d2013792c0df625527e12a54e40d07b122 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 13 Jul 2008 22:30:35 -0700 Subject: [PATCH 43/60] Update draft release notes for 1.6.0 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.6.0.txt | 39 +++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/Documentation/RelNotes-1.6.0.txt b/Documentation/RelNotes-1.6.0.txt index e1f013bd3b..9125ee01a6 100644 --- a/Documentation/RelNotes-1.6.0.txt +++ b/Documentation/RelNotes-1.6.0.txt @@ -97,15 +97,39 @@ Updates since v1.5.6 when you are working in a slow network disk and are outside any working tree, as bash-completion and "git help" may still need to run in these places. +* By default, stash entries never expire. Set reflogexpire in [gc + "refs/stash"] to a reasonable value to get traditional auto-expiration + behaviour back + +* Longstanding latency issue with bash completion script has been + addressed. This will need to be backmerged to 'maint' later. + +* pager. configuration variable can be used to enable/disable the + default paging behaviour per command. + +* "git-add -i" has a new action 'e/dit' to allow you edit the patch hunk + manually. + * git-apply can handle a patch that touches the same path more than once much better than before. * git-apply can be told not to trust the line counts recorded in the input patch but recount, with the new --recount option. +* git-apply can be told to apply a patch to a path deeper than what the + patch records with --directory option. + * git-archive can be told to omit certain paths from its output using export-ignore attributes. +* With -v option, git-branch describes the remote tracking statistics + similar to the way git-checkout reports by how many commits your branch + is ahead/behind. + +* git-bundle can read the revision arguments from the standard input. + +* git-cherry-pick can replay a root commit now. + * git-clone can clone from a remote whose URL would be rewritten by configuration stored in $HOME/.gitconfig now. @@ -123,8 +147,21 @@ Updates since v1.5.6 * "git rerere" can be told to update the index with auto-reused resolution with rerere.autoupdate configuration variable. +* git-rev-list learned --children option to show child commits it + encountered during the traversal, instead of shoing parent commits. + * git-send-mail can talk not just over SSL but over TLS now. +* "git-stash save" learned --keep-index option. This lets you stash away the + local changes and bring the changes staged in the index to your working + tree for examination and testing. + +* git-stash also learned branch subcommand to create a new branch out of + stashed changes. + +* git-status gives the remote tracking statistics similar to the way + git-checkout reports by how many commits your branch is ahead/behind. + * You can tell "git status -u" to even more aggressively omit checking untracked files with --untracked-files=no. @@ -148,6 +185,6 @@ this release, unless otherwise noted. --- exec >/var/tmp/1 -O=v1.5.6.2-246-g86d7244 +O=v1.5.6.3-315-g10ce020 echo O=$(git describe refs/heads/master) git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint From 711f6b295cf463aae07eb76e009faed3d3699623 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Jul 2008 00:09:41 -0700 Subject: [PATCH 44/60] 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 03e73f323a..5148ec5527 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 f035ea376e..d4cf6289a4 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 From db8a9ff03831a26aa8bfad8bb026b90739d684ec Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 15 Jul 2008 05:52:04 +0000 Subject: [PATCH 45/60] bash completion: Resolve git show ref:path losing ref: portion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Linus reported that the bash completion for git show often dropped the ref portion of the argument (stuff before the :) when trying to complete a file name of a file in another branch or tag. Björn Steinbrink tracked it down to the gvfs completion script which comes standard on many Fedora Core based systems. That is removing : from COMP_WORDBREAKS, making readline treat the entire argument (including the ref) as the name that must be completed. When the git completion routines supplied a completion of just the filename, readline replaced everything. Since Git users often need to use "ref:path" or "ref:ref" sort of arguments, and expect completion support on both sides of the : we really want the : in COMP_WORDBREAKS to provide a good user experience. This is also the default that ships with bash as it can be useful in other contexts, such as rcp/scp. We now try to add : back to COMP_WORDBREAKS if it has been removed by a script that loaded before us. However if this doesn't work (as the : is stripped after we load) we fallback in the completion routines to include "ref:" as part of the prefix for completions, allowing readine to fully insert the argument the user wanted. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 27 ++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 0a3bea44f7..72f02f208f 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -45,6 +45,11 @@ # git@vger.kernel.org # +case "$COMP_WORDBREAKS" in +*:*) : great ;; +*) COMP_WORDBREAKS="$COMP_WORDBREAKS:" +esac + __gitdir () { if [ -z "$1" ]; then @@ -294,6 +299,12 @@ __git_complete_file () ls="$ref" ;; esac + + case "$COMP_WORDBREAKS" in + *:*) : great ;; + *) pfx="$ref:$pfx" ;; + esac + local IFS=$'\n' COMPREPLY=($(compgen -P "$pfx" \ -W "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \ @@ -700,7 +711,12 @@ _git_fetch () *) case "$cur" in *:*) - __gitcomp "$(__git_refs)" "" "${cur#*:}" + local pfx="" + case "$COMP_WORDBREAKS" in + *:*) : great ;; + *) pfx="${cur%%:*}:" ;; + esac + __gitcomp "$(__git_refs)" "$pfx" "${cur#*:}" ;; *) local remote @@ -873,7 +889,14 @@ _git_push () git-push) remote="${COMP_WORDS[1]}" ;; git) remote="${COMP_WORDS[2]}" ;; esac - __gitcomp "$(__git_refs "$remote")" "" "${cur#*:}" + + local pfx="" + case "$COMP_WORDBREAKS" in + *:*) : great ;; + *) pfx="${cur%%:*}:" ;; + esac + + __gitcomp "$(__git_refs "$remote")" "$pfx" "${cur#*:}" ;; +*) __gitcomp "$(__git_refs)" + "${cur#+}" From 5d5e88af5cfe614be92bf977aa3ea39d2e8a8db8 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Thu, 10 Jul 2008 14:27:30 -0400 Subject: [PATCH 46/60] tutorial: use prompt with user names in example, to clarify who is doing what Signed-off-by: Ian Katz Signed-off-by: Junio C Hamano --- Documentation/gittutorial.txt | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt index e71b561172..e4248b6241 100644 --- a/Documentation/gittutorial.txt +++ b/Documentation/gittutorial.txt @@ -274,7 +274,7 @@ same machine, wants to contribute. Bob begins with: ------------------------------------------------ -$ git clone /home/alice/project myrepo +bob$ git clone /home/alice/project myrepo ------------------------------------------------ This creates a new directory "myrepo" containing a clone of Alice's @@ -285,7 +285,7 @@ Bob then makes some changes and commits them: ------------------------------------------------ (edit files) -$ git commit -a +bob$ git commit -a (repeat as necessary) ------------------------------------------------ @@ -293,8 +293,8 @@ When he's ready, he tells Alice to pull changes from the repository at /home/bob/myrepo. She does this with: ------------------------------------------------ -$ cd /home/alice/project -$ git pull /home/bob/myrepo master +alice$ cd /home/alice/project +alice$ git pull /home/bob/myrepo master ------------------------------------------------ This merges the changes from Bob's "master" branch into Alice's @@ -312,7 +312,7 @@ again. By defining 'remote' repository shorthand, you can make it easier: ------------------------------------------------ -$ git remote add bob /home/bob/myrepo +alice$ git remote add bob /home/bob/myrepo ------------------------------------------------ With this, Alice can perform the first operation alone using the @@ -320,7 +320,7 @@ With this, Alice can perform the first operation alone using the using: ------------------------------------- -$ git fetch bob +alice$ git fetch bob ------------------------------------- Unlike the longhand form, when Alice fetches from Bob using a @@ -329,7 +329,7 @@ fetched is stored in a remote tracking branch, in this case `bob/master`. So after this: ------------------------------------- -$ git log -p master..bob/master +alice$ git log -p master..bob/master ------------------------------------- shows a list of all the changes that Bob made since he branched from @@ -339,14 +339,14 @@ After examining those changes, Alice could merge the changes into her master branch: ------------------------------------- -$ git merge bob/master +alice$ git merge bob/master ------------------------------------- This `merge` can also be done by 'pulling from her own remote tracking branch', like this: ------------------------------------- -$ git pull . remotes/bob/master +alice$ git pull . remotes/bob/master ------------------------------------- Note that git pull always merges into the current branch, @@ -355,7 +355,7 @@ regardless of what else is given on the command line. Later, Bob can update his repo with Alice's latest changes using ------------------------------------- -$ git pull +bob$ git pull ------------------------------------- Note that he doesn't need to give the path to Alice's repository; @@ -364,7 +364,7 @@ repository in the repository configuration, and that location is used for pulls: ------------------------------------- -$ git config --get remote.origin.url +bob$ git config --get remote.origin.url /home/alice/project ------------------------------------- @@ -376,7 +376,7 @@ Git also keeps a pristine copy of Alice's master branch under the name "origin/master": ------------------------------------- -$ git branch -r +bob$ git branch -r origin/master ------------------------------------- @@ -384,7 +384,7 @@ If Bob later decides to work from a different host, he can still perform clones and pulls using the ssh protocol: ------------------------------------- -$ git clone alice.org:/home/alice/project myrepo +bob$ git clone alice.org:/home/alice/project myrepo ------------------------------------- Alternatively, git has a native protocol, or can use rsync or http; From dc29bc8bd243fcd20815c7dddc15dbae30747c28 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 10 Jul 2008 14:01:57 -0700 Subject: [PATCH 47/60] tutorial: clarify "pull" is "fetch + merge" The document says that a fetch with a configured remote stores what are fetched in the remote tracking branches "Unlike the longhand form", but there is no longhand form "fetch" demonstrated earlier. This adds a missing demonstration of the longhand form, and a new paragraph to explain why some people might want to fetch before pull. Signed-off-by: Junio C Hamano --- Documentation/gittutorial.txt | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt index e4248b6241..48d1454a90 100644 --- a/Documentation/gittutorial.txt +++ b/Documentation/gittutorial.txt @@ -306,6 +306,32 @@ is the default.) The "pull" command thus performs two operations: it fetches changes from a remote branch, then merges them into the current branch. +Note that in general, Alice would want her local changes committed before +initiating this "pull". If Bob's work conflicts with what Alice did since +their histories forked, Alice will use her working tree and the index to +resolve conflicts, and existing local changes will interfere with the +conflict resolution process (git will still perform the fetch but will +refuse to merge --- Alice will have to get rid of her local changes in +some way and pull again when this happens). + +Alice can peek at what Bob did without merging first, using the "fetch" +command; this allows Alice to inspect what Bob did, using a special +symbol "FETCH_HEAD", in order to determine if he has anything worth +pulling, like this: + +------------------------------------------------ +alice$ git fetch /home/bob/myrepo master +alice$ git log -p ..FETCH_HEAD +------------------------------------------------ + +This operation is safe even if Alice has uncommitted local changes. + +After inspecting what Bob did, if there is nothing urgent, Alice may +decide to continue working without pulling from Bob. If Bob's history +does have something Alice would immediately need, Alice may choose to +stash her work-in-progress first, do a "pull", and then finally unstash +her work-in-progress on top of the resulting history. + When you are working in a small closely knit group, it is not unusual to interact with the same repository over and over again. By defining 'remote' repository shorthand, you can make @@ -315,7 +341,7 @@ it easier: alice$ git remote add bob /home/bob/myrepo ------------------------------------------------ -With this, Alice can perform the first operation alone using the +With this, Alice can perform the first part of the "pull" operation alone using the 'git-fetch' command without merging them with her own branch, using: From 63c56022f84ae70dd68763b8eed99bccf58fc98b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Abecasis?= Date: Mon, 14 Jul 2008 16:28:04 +0100 Subject: [PATCH 48/60] git-svn: find-rev and rebase for SVN::Mirror repositories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit find-rev and rebase error out on svm because git-svn doesn't trace the original svn revision numbers back to git commits. The updated test case, included in the patch, shows the issue and passes with the rest of the patch applied. This fixes Git::SVN::find_by_url to find branches based on the svm:source URL, where useSvmProps is set. Also makes sure cmd_find_rev and working_head_info use the information they have to correctly track the source repository. This is enough to get find-rev and rebase working. Signed-off-by: João Abecasis Acked-by: Eric Wong Signed-off-by: Junio C Hamano --- git-svn.perl | 39 ++++++++++++++++++++++++++++---- t/t9110-git-svn-use-svm-props.sh | 9 ++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index a366c891dc..0aa994a436 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -537,13 +537,13 @@ sub cmd_find_rev { my $head = shift; $head ||= 'HEAD'; my @refs; - my (undef, undef, undef, $gs) = working_head_info($head, \@refs); + my (undef, undef, $uuid, $gs) = working_head_info($head, \@refs); unless ($gs) { die "Unable to determine upstream SVN information from ", "$head history\n"; } my $desired_revision = substr($revision_or_hash, 1); - $result = $gs->rev_map_get($desired_revision); + $result = $gs->rev_map_get($desired_revision, $uuid); } else { my (undef, $rev, undef) = cmt_metadata($revision_or_hash); $result = $rev; @@ -1162,7 +1162,7 @@ sub working_head_info { if (defined $url && defined $rev) { next if $max{$url} and $max{$url} < $rev; if (my $gs = Git::SVN->find_by_url($url)) { - my $c = $gs->rev_map_get($rev); + my $c = $gs->rev_map_get($rev, $uuid); if ($c && $c eq $hash) { close $fh; # break the pipe return ($url, $rev, $uuid, $gs); @@ -1416,11 +1416,17 @@ sub fetch_all { sub read_all_remotes { my $r = {}; + my $use_svm_props = eval { command_oneline(qw/config --bool + svn.useSvmProps/) }; + $use_svm_props = $use_svm_props eq 'true' if $use_svm_props; foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) { if (m!^(.+)\.fetch=\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!) { my ($remote, $local_ref, $remote_ref) = ($1, $2, $3); $local_ref =~ s{^/}{}; $r->{$remote}->{fetch}->{$local_ref} = $remote_ref; + $r->{$remote}->{svm} = {} if $use_svm_props; + } elsif (m!^(.+)\.usesvmprops=\s*(.*)\s*$!) { + $r->{$1}->{svm} = {}; } elsif (m!^(.+)\.url=\s*(.*)\s*$!) { $r->{$1}->{url} = $2; } elsif (m!^(.+)\.(branches|tags)= @@ -1437,6 +1443,23 @@ sub read_all_remotes { } } } + + map { + if (defined $r->{$_}->{svm}) { + my $svm; + eval { + my $section = "svn-remote.$_"; + $svm = { + source => tmp_config('--get', + "$section.svm-source"), + replace => tmp_config('--get', + "$section.svm-replace"), + } + }; + $r->{$_}->{svm} = $svm; + } + } keys %$r; + $r; } @@ -1563,13 +1586,21 @@ sub find_by_url { # repos_root and, path are optional } my $p = $path; my $rwr = rewrite_root({repo_id => $repo_id}); + my $svm = $remotes->{$repo_id}->{svm} + if defined $remotes->{$repo_id}->{svm}; unless (defined $p) { $p = $full_url; my $z = $u; + my $prefix = ''; if ($rwr) { $z = $rwr; + } elsif (defined $svm) { + $z = $svm->{source}; + $prefix = $svm->{replace}; + $prefix =~ s#^\Q$u\E(?:/|$)##; + $prefix =~ s#/$##; } - $p =~ s#^\Q$z\E(?:/|$)## or next; + $p =~ s#^\Q$z\E(?:/|$)#$prefix# or next; } foreach my $f (keys %$fetch) { next if $f ne $p; diff --git a/t/t9110-git-svn-use-svm-props.sh b/t/t9110-git-svn-use-svm-props.sh index 047659fde1..04d2a65c08 100755 --- a/t/t9110-git-svn-use-svm-props.sh +++ b/t/t9110-git-svn-use-svm-props.sh @@ -49,4 +49,13 @@ test_expect_success 'verify metadata for /dir' " grep '^git-svn-id: $dir_url@1 $uuid$' " +test_expect_success 'find commit based on SVN revision number' " + git-svn find-rev r12 | + grep `git rev-parse HEAD` + " + +test_expect_success 'empty rebase' " + git-svn rebase + " + test_done From 8f510bef13e6b0c342a49ee90220d568e56a15d7 Mon Sep 17 00:00:00 2001 From: Frederik Schwarzer Date: Mon, 14 Jul 2008 18:30:24 +0200 Subject: [PATCH 49/60] git-svn: typofix Signed-off-by: Frederik Schwarzer Signed-off-by: Junio C Hamano --- git-svn.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-svn.perl b/git-svn.perl index 0aa994a436..3750e47c20 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -4650,7 +4650,7 @@ sub migrate_from_v1 { mkpath([$svn_dir]); print STDERR "Data from a previous version of git-svn exists, but\n\t", "$svn_dir\n\t(required for this version ", - "($::VERSION) of git-svn) does not. exist\n"; + "($::VERSION) of git-svn) does not exist.\n"; my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/); while (<$fh>) { my $x = $_; From ec05df353c594f2afc6509831d3ce145856b0b89 Mon Sep 17 00:00:00 2001 From: Mark Levedahl Date: Wed, 9 Jul 2008 21:05:40 -0400 Subject: [PATCH 50/60] git-submodule - make "submodule add" more strict, and document it This change makes "submodule add" much more strict in the arguments it takes, and is intended to address confusion as recently noted on the git-list. With this change, the required syntax is: $ git submodule add URL path Specifically, this eliminates the form $ git submodule add URL which was confused by more than one person as $ git submodule add path With this patch, the URL locating the submodule's origin repository can be either an absolute URL, or (if it begins with ./ or ../) can express the submodule's repository location relative to the superproject's origin. This patch also eliminates a third form of URL, which was relative to the superproject's top-level directory (not its repository). Any URL that was neither absolute nor matched ./*|../* was assumed to point to a subdirectory of the superproject as the location of the submodule's origin repository. This URL form was confusing and does not seem to correspond to an important use-case. Specifically, no-one has identified the need to clone from a repository already in the superproject's tree, but if this is needed it is easily done using an absolute URL: $(pwd)/relative-path. So, no functionality is lost with this patch. (t6008-rev-list-submodule.sh did rely upon this relative URL, fixed by using $(pwd).) Following this change, there are exactly four variants of submodule-add, as both arguments have two flavors: URL can be absolute, or can begin with ./|../ and thus names the submodule's origin relative to the superproject's origin. Note: With this patch, "submodule add" discerns an absolute URL as matching /*|*:*: e.g., URL begins with /, or it contains a :. This works for all valid URLs, an absolute path in POSIX, as well as an absolute path on Windows). path can either already exist as a valid git repo, or will be cloned from the given URL. The first form here eases creation of a new submodule in an existing superproject as the submodule can be added and tested in-tree before pushing to the public repository. However, the more usual form is the second, where the repo is cloned from the given URL. This specifically addresses the issue of $ git submodule add a/b/c attempting to clone from a repository at "a/b/c" to create a new module in "c". This also simplifies description of "relative URL" as there is now exactly *one* form: a URL relative to the parent's origin repo. Signed-off-by: Mark Levedahl Signed-off-by: Junio C Hamano --- Documentation/git-submodule.txt | 36 +++++++++++++++------ git-submodule.sh | 55 ++++++++++++--------------------- t/t6008-rev-list-submodule.sh | 2 +- 3 files changed, 48 insertions(+), 45 deletions(-) diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 105fc2dcdc..76702a0a5a 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -9,7 +9,7 @@ git-submodule - Initialize, update or inspect submodules SYNOPSIS -------- [verse] -'git submodule' [--quiet] add [-b branch] [--] [] +'git submodule' [--quiet] add [-b branch] [--] 'git submodule' [--quiet] status [--cached] [--] [...] 'git submodule' [--quiet] init [--] [...] 'git submodule' [--quiet] update [--init] [--] [...] @@ -20,14 +20,31 @@ COMMANDS -------- add:: Add the given repository as a submodule at the given path - to the changeset to be committed next. If path is a valid - repository within the project, it is added as is. Otherwise, - repository is cloned at the specified path. path is added to the - changeset and registered in .gitmodules. If no path is - specified, the path is deduced from the repository specification. - If the repository url begins with ./ or ../, it is stored as - given but resolved as a relative path from the main project's - url when cloning. + to the changeset to be committed next to the current + project: the current project is termed termed the "superproject". ++ +This requires two arguments: and . ++ + is the URL of the new submodule's origin repository. +This may be either an absolute URL, or (if it begins with ./ +or ../), the location relative to the superproject's origin +repository. ++ + is the relative location for the cloned submodule to +exist in the superproject. If does not exist, then the +submodule is created by cloning from the named URL. If does +exist and is already a valid git repository, then this is added +to the changeset without cloning. This second form is provided +to ease creating a new submodule from scratch, and presumes +the user will later push the submodule to the given URL. ++ +In either case, the given URL is recorded into .gitmodules for +use by subsequent users cloning the superproject. If the URL is +given relative to the superproject's repository, the presumption +is the superproject and submodule repositories will be kept +together in the same relative location, and only the +superproject's URL need be provided: git-submodule will correctly +locate the submodule using the relative URL in .gitmodules. status:: Show the status of the submodules. This will print the SHA-1 of the @@ -85,6 +102,7 @@ OPTIONS :: Path to submodule(s). When specified this will restrict the command to only operate on the submodules found at the specified paths. + (This argument is required with add). FILES ----- diff --git a/git-submodule.sh b/git-submodule.sh index 099a7d7560..c2ce2fbe8a 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -5,7 +5,7 @@ # Copyright (c) 2007 Lars Hjemli USAGE="[--quiet] [--cached] \ -[add [-b branch]|status|init|update [-i|--init]|summary [-n|--summary-limit ] []] \ +[add [-b branch] ]|[status|init|update [-i|--init]|summary [-n|--summary-limit ] []] \ [--] [...]" OPTIONS_SPEC= . git-sh-setup @@ -27,18 +27,6 @@ say() fi } -# NEEDSWORK: identical function exists in get_repo_base in clone.sh -get_repo_base() { - ( - cd "`/bin/pwd`" && - cd "$1" || cd "$1.git" && - { - cd .git - pwd - } - ) 2>/dev/null -} - # Resolve relative url by appending to parent's url resolve_relative_url () { @@ -115,7 +103,7 @@ module_clone() # # Add a new submodule to the working tree, .gitmodules and the index # -# $@ = repo [path] +# $@ = repo path # # optional branch is stored in global branch variable # @@ -150,16 +138,27 @@ cmd_add() repo=$1 path=$2 - if test -z "$repo"; then + if test -z "$repo" -o -z "$path"; then usage fi - # Guess path from repo if not specified or strip trailing slashes - if test -z "$path"; then - path=$(echo "$repo" | sed -e 's|/*$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g') - else - path=$(echo "$path" | sed -e 's|/*$||') - fi + # assure repo is absolute or relative to parent + case "$repo" in + ./*|../*) + # dereference source url relative to parent's url + realrepo=$(resolve_relative_url "$repo") || exit + ;; + *:*|/*) + # absolute url + realrepo=$repo + ;; + *) + die "repo URL: '$repo' must be absolute or begin with ./|../" + ;; + esac + + # strip trailing slashes from path + path=$(echo "$path" | sed -e 's|/*$||') git ls-files --error-unmatch "$path" > /dev/null 2>&1 && die "'$path' already exists in the index" @@ -174,20 +173,6 @@ cmd_add() die "'$path' already exists and is not a valid git repo" fi else - case "$repo" in - ./*|../*) - # dereference source url relative to parent's url - realrepo=$(resolve_relative_url "$repo") || exit - ;; - *) - # Turn the source into an absolute path if - # it is local - if base=$(get_repo_base "$repo"); then - repo="$base" - fi - realrepo=$repo - ;; - esac module_clone "$path" "$realrepo" || exit (unset GIT_DIR; cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) || diff --git a/t/t6008-rev-list-submodule.sh b/t/t6008-rev-list-submodule.sh index 88e96fb91b..c4af9ca0a7 100755 --- a/t/t6008-rev-list-submodule.sh +++ b/t/t6008-rev-list-submodule.sh @@ -23,7 +23,7 @@ test_expect_success 'setup' ' : > super-file && git add super-file && - git submodule add . sub && + git submodule add "$(pwd)" sub && git symbolic-ref HEAD refs/heads/super && test_tick && git commit -m super-initial && From c2f939170c65173076bbd752bb3c764536b3b09b Mon Sep 17 00:00:00 2001 From: Mark Levedahl Date: Wed, 9 Jul 2008 21:05:41 -0400 Subject: [PATCH 51/60] git-submodule - register submodule URL if adding in place When adding a new submodule in place, meaning the user created the submodule as a git repo in the superproject's tree first, we don't go through "git submodule init" to register the module. Thus, the submodule's origin repository URL is not stored in .git/config, and no subsequent submodule operation will ever do so. In this case, assume the URL the user supplies to "submodule add" is the one that should be registered, and do so. Signed-off-by: Mark Levedahl Signed-off-by: Junio C Hamano --- git-submodule.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/git-submodule.sh b/git-submodule.sh index c2ce2fbe8a..9228f56bee 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -172,6 +172,16 @@ cmd_add() else die "'$path' already exists and is not a valid git repo" fi + + case "$repo" in + ./*|../*) + url=$(resolve_relative_url "$repo") || exit + ;; + *) + url="$repo" + ;; + esac + git config submodule."$path".url "$url" else module_clone "$path" "$realrepo" || exit From 25a1f374f0ff23a4d9191436226ab68f3da5e83a Mon Sep 17 00:00:00 2001 From: Teemu Likonen Date: Mon, 14 Jul 2008 11:21:02 +0300 Subject: [PATCH 52/60] bash: Add long option completion for 'git send-email' Add the following long options to be completed with 'git send-email': --bcc --cc --cc-cmd --chain-reply-to --compose --dry-run --envelope-sender --from --identity --in-reply-to --no-chain-reply-to --no-signed-off-by-cc --no-suppress-from --no-thread --quiet --signed-off-by-cc --smtp-pass --smtp-server --smtp-server-port --smtp-ssl --smtp-user --subject --suppress-cc --suppress-from --thread --to Short ones like --to and --cc are not usable for actual completion because of the shortness itself and because there are longer ones which start with same letters (--thread, --compose). It's still useful to have these shorter options _listed_ when user presses TAB key after typing two dashes. It gives user an idea what options are available (and --to and --cc are probably the most commonly used). Signed-off-by: Teemu Likonen Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index d268e6f0b3..d48dbf2672 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -905,6 +905,24 @@ _git_rebase () __gitcomp "$(__git_refs)" } +_git_send_email () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + __gitcomp "--bcc --cc --cc-cmd --chain-reply-to --compose + --dry-run --envelope-sender --from --identity + --in-reply-to --no-chain-reply-to --no-signed-off-by-cc + --no-suppress-from --no-thread --quiet + --signed-off-by-cc --smtp-pass --smtp-server + --smtp-server-port --smtp-ssl --smtp-user --subject + --suppress-cc --suppress-from --thread --to" + return + ;; + esac + COMPREPLY=() +} + _git_config () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -1376,6 +1394,7 @@ _git () rebase) _git_rebase ;; remote) _git_remote ;; reset) _git_reset ;; + send-email) _git_send_email ;; shortlog) _git_shortlog ;; show) _git_show ;; show-branch) _git_log ;; @@ -1435,6 +1454,7 @@ complete -o default -o nospace -F _git_rebase git-rebase complete -o default -o nospace -F _git_config git-config complete -o default -o nospace -F _git_remote git-remote complete -o default -o nospace -F _git_reset git-reset +complete -o default -o nospace -F _git_send_email git-send-email complete -o default -o nospace -F _git_shortlog git-shortlog complete -o default -o nospace -F _git_show git-show complete -o default -o nospace -F _git_stash git-stash From ac9391093f54370ff3f06470e24d78b536a12327 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Mon, 14 Jul 2008 21:46:48 -0400 Subject: [PATCH 53/60] restore legacy behavior for read_sha1_file() Since commit 8eca0b47ff1598a6d163df9358c0e0c9bd92d4c8, it is possible for read_sha1_file() to return NULL even with existing objects when they are corrupted. Previously a corrupted object would have terminated the program immediately, effectively making read_sha1_file() return NULL only when specified object is not found. Let's restore this behavior for all users of read_sha1_file() and provide a separate function with the ability to not terminate when bad objects are encountered. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- cache.h | 3 ++ sha1_file.c | 30 +++++++++++++-- t/t6011-rev-list-with-bad-commit.sh | 60 +++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100755 t/t6011-rev-list-with-bad-commit.sh diff --git a/cache.h b/cache.h index 0d8eddac77..a8254e2f9e 100644 --- a/cache.h +++ b/cache.h @@ -538,6 +538,9 @@ extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsig extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *); extern int force_object_loose(const unsigned char *sha1, time_t mtime); +/* just like read_sha1_file(), but non fatal in presence of bad objects */ +extern void *read_object(const unsigned char *sha1, enum object_type *type, unsigned long *size); + extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type); extern int move_temp_to_file(const char *tmpfile, const char *filename); diff --git a/sha1_file.c b/sha1_file.c index 2df78b5afd..e281c14f01 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1006,6 +1006,18 @@ static void mark_bad_packed_object(struct packed_git *p, p->num_bad_objects++; } +static int has_packed_and_bad(const unsigned char *sha1) +{ + struct packed_git *p; + unsigned i; + + for (p = packed_git; p; p = p->next) + for (i = 0; i < p->num_bad_objects; i++) + if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i)) + return 1; + return 0; +} + int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type) { unsigned char real_sha1[20]; @@ -1647,7 +1659,7 @@ static void *unpack_delta_entry(struct packed_git *p, sha1_to_hex(base_sha1), (uintmax_t)base_offset, p->pack_name); mark_bad_packed_object(p, base_sha1); - base = read_sha1_file(base_sha1, type, &base_size); + base = read_object(base_sha1, type, &base_size); if (!base) return NULL; } @@ -1945,7 +1957,7 @@ static void *read_packed_sha1(const unsigned char *sha1, error("failed to read object %s at offset %"PRIuMAX" from %s", sha1_to_hex(sha1), (uintmax_t)e.offset, e.p->pack_name); mark_bad_packed_object(e.p, sha1); - data = read_sha1_file(sha1, type, size); + data = read_object(sha1, type, size); } return data; } @@ -2010,8 +2022,8 @@ int pretend_sha1_file(void *buf, unsigned long len, enum object_type type, return 0; } -void *read_sha1_file(const unsigned char *sha1, enum object_type *type, - unsigned long *size) +void *read_object(const unsigned char *sha1, enum object_type *type, + unsigned long *size) { unsigned long mapsize; void *map, *buf; @@ -2037,6 +2049,16 @@ void *read_sha1_file(const unsigned char *sha1, enum object_type *type, return read_packed_sha1(sha1, type, size); } +void *read_sha1_file(const unsigned char *sha1, enum object_type *type, + unsigned long *size) +{ + void *data = read_object(sha1, type, size); + /* legacy behavior is to die on corrupted objects */ + if (!data && (has_loose_object(sha1) || has_packed_and_bad(sha1))) + die("object %s is corrupted", sha1_to_hex(sha1)); + return data; +} + void *read_object_with_reference(const unsigned char *sha1, const char *required_type_name, unsigned long *size, diff --git a/t/t6011-rev-list-with-bad-commit.sh b/t/t6011-rev-list-with-bad-commit.sh new file mode 100755 index 0000000000..e51eb41f4b --- /dev/null +++ b/t/t6011-rev-list-with-bad-commit.sh @@ -0,0 +1,60 @@ +#!/bin/sh + +test_description='git rev-list should notice bad commits' + +. ./test-lib.sh + +# Note: +# - compression level is set to zero to make "corruptions" easier to perform +# - reflog is disabled to avoid extra references which would twart the test + +test_expect_success 'setup' \ + ' + git init && + git config core.compression 0 && + git config core.logallrefupdates false && + echo "foo" > foo && + git add foo && + git commit -m "first commit" && + echo "bar" > bar && + git add bar && + git commit -m "second commit" && + echo "baz" > baz && + git add baz && + git commit -m "third commit" && + echo "foo again" >> foo && + git add foo && + git commit -m "fourth commit" && + git repack -a -f -d + ' + +test_expect_success 'verify number of revisions' \ + ' + revs=$(git rev-list --all | wc -l) && + test $revs -eq 4 && + first_commit=$(git rev-parse HEAD~3) + ' + +test_expect_success 'corrupt second commit object' \ + ' + perl -i.bak -pe "s/second commit/socond commit/" .git/objects/pack/*.pack && + test_must_fail git fsck --full + ' + +test_expect_success 'rev-list should fail' \ + ' + test_must_fail git rev-list --all > /dev/null + ' + +test_expect_success 'git repack _MUST_ fail' \ + ' + test_must_fail git repack -a -f -d + ' + +test_expect_success 'first commit is still available' \ + ' + git log $first_commit + ' + +test_done + From 055767194c15f9b7c39a509daf1a77472c77bed8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 15 Jul 2008 05:58:06 +0000 Subject: [PATCH 54/60] bash completion: Remove dashed command completion support Since only 'git' and 'gitk' are in the user's $PATH now we do not expect users to need completion support for git-fetch, and expect they will instead rely upon the completion support for 'git fetch'. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 54 -------------------------- 1 file changed, 54 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 37f52d5395..03e4e02785 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1459,65 +1459,11 @@ _gitk () complete -o default -o nospace -F _git git complete -o default -o nospace -F _gitk gitk -complete -o default -o nospace -F _git_am git-am -complete -o default -o nospace -F _git_apply git-apply -complete -o default -o nospace -F _git_bisect git-bisect -complete -o default -o nospace -F _git_branch git-branch -complete -o default -o nospace -F _git_bundle git-bundle -complete -o default -o nospace -F _git_checkout git-checkout -complete -o default -o nospace -F _git_cherry git-cherry -complete -o default -o nospace -F _git_cherry_pick git-cherry-pick -complete -o default -o nospace -F _git_commit git-commit -complete -o default -o nospace -F _git_describe git-describe -complete -o default -o nospace -F _git_diff git-diff -complete -o default -o nospace -F _git_fetch git-fetch -complete -o default -o nospace -F _git_format_patch git-format-patch -complete -o default -o nospace -F _git_gc git-gc -complete -o default -o nospace -F _git_log git-log -complete -o default -o nospace -F _git_ls_remote git-ls-remote -complete -o default -o nospace -F _git_ls_tree git-ls-tree -complete -o default -o nospace -F _git_merge git-merge -complete -o default -o nospace -F _git_merge_base git-merge-base -complete -o default -o nospace -F _git_name_rev git-name-rev -complete -o default -o nospace -F _git_pull git-pull -complete -o default -o nospace -F _git_push git-push -complete -o default -o nospace -F _git_rebase git-rebase -complete -o default -o nospace -F _git_config git-config -complete -o default -o nospace -F _git_remote git-remote -complete -o default -o nospace -F _git_reset git-reset -complete -o default -o nospace -F _git_send_email git-send-email -complete -o default -o nospace -F _git_shortlog git-shortlog -complete -o default -o nospace -F _git_show git-show -complete -o default -o nospace -F _git_stash git-stash -complete -o default -o nospace -F _git_submodule git-submodule -complete -o default -o nospace -F _git_svn git-svn -complete -o default -o nospace -F _git_log git-show-branch -complete -o default -o nospace -F _git_tag git-tag -complete -o default -o nospace -F _git_log git-whatchanged # The following are necessary only for Cygwin, and only are needed # when the user has tab-completed the executable name and consequently # included the '.exe' suffix. # if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then -complete -o default -o nospace -F _git_add git-add.exe -complete -o default -o nospace -F _git_apply git-apply.exe complete -o default -o nospace -F _git git.exe -complete -o default -o nospace -F _git_branch git-branch.exe -complete -o default -o nospace -F _git_bundle git-bundle.exe -complete -o default -o nospace -F _git_cherry git-cherry.exe -complete -o default -o nospace -F _git_describe git-describe.exe -complete -o default -o nospace -F _git_diff git-diff.exe -complete -o default -o nospace -F _git_format_patch git-format-patch.exe -complete -o default -o nospace -F _git_log git-log.exe -complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe -complete -o default -o nospace -F _git_merge_base git-merge-base.exe -complete -o default -o nospace -F _git_name_rev git-name-rev.exe -complete -o default -o nospace -F _git_push git-push.exe -complete -o default -o nospace -F _git_config git-config -complete -o default -o nospace -F _git_shortlog git-shortlog.exe -complete -o default -o nospace -F _git_show git-show.exe -complete -o default -o nospace -F _git_log git-show-branch.exe -complete -o default -o nospace -F _git_tag git-tag.exe -complete -o default -o nospace -F _git_log git-whatchanged.exe fi From e0e7163c215a6e74940cc1b66c7f4461359a7dc1 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Tue, 15 Jul 2008 10:02:57 +0200 Subject: [PATCH 55/60] Windows: set gitexecdir = $(bindir) The "dash-less" change aims to remove git commands from $PATH. It does so by defining a GIT_EXEC_PATH that is different from $(bindir). On Windows we want a relocatable installation of the git tool, so we cannot use an absolute GIT_EXEC_PATH. Therefore, the implementation of builtin_exec_path() on Windows derives the exec-path from the command invocation, and disregards GIT_EXEC_PATH. But this broke when $(gitexecdir) became different from $(bindir), so we restore the earlier behavior here. This counteracts the aims of the "dash-less" change on Windows, but better this way than having no working git at all. Signed-off-by: Johannes Sixt --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 36339d3aff..7d466b3c3b 100644 --- a/Makefile +++ b/Makefile @@ -742,6 +742,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) COMPAT_OBJS += compat/mingw.o compat/fnmatch.o compat/regex.o compat/winansi.o EXTLIBS += -lws2_32 X = .exe + gitexecdir = $(bindir) template_dir = ../share/git-core/templates/ ETC_GITCONFIG = ../etc/gitconfig endif From a271b2a9ceb2f65095cf789d2d7c7e44ca5d95f4 Mon Sep 17 00:00:00 2001 From: Pavel Roskin Date: Mon, 14 Jul 2008 20:20:19 -0400 Subject: [PATCH 56/60] t9600: allow testing with cvsps 2.2, including beta versions We've supported cvsps 2.1 so far. Newer 2.2b1 (beta) seems to work with us, too. Signed-off-by: Pavel Roskin Signed-off-by: Junio C Hamano --- t/t9600-cvsimport.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh index 655f88270b..1e01e5c748 100755 --- a/t/t9600-cvsimport.sh +++ b/t/t9600-cvsimport.sh @@ -18,7 +18,7 @@ fi cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'` case "$cvsps_version" in -2.1) +2.1 | 2.2*) ;; '') say 'skipping cvsimport tests, cvsps not found' @@ -26,7 +26,7 @@ case "$cvsps_version" in exit ;; *) - say 'skipping cvsimport tests, cvsps too old' + say 'skipping cvsimport tests, unsupported cvsps version' test_done exit ;; From 1e5aaa6db38964fd6ca679864c0a40f7af01cf1a Mon Sep 17 00:00:00 2001 From: Ciaran McCreesh Date: Mon, 14 Jul 2008 19:29:37 +0100 Subject: [PATCH 57/60] Make git-add -i accept ranges like 7- git-add -i ranges expect number-number. But for the supremely lazy, typing in that second number when selecting "from patch 7 to the end" is wasted effort. So treat an empty second number in a range as "until the last item". Signed-off-by: Ciaran McCreesh Signed-off-by: Junio C Hamano --- Documentation/git-add.txt | 5 +++-- git-add--interactive.perl | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 46dd56c12a..3558905a92 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -187,8 +187,9 @@ update:: "Update>>". When the prompt ends with double '>>', you can make more than one selection, concatenated with whitespace or comma. Also you can say ranges. E.g. "2-5 7,9" to choose - 2,3,4,5,7,9 from the list. You can say '*' to choose - everything. + 2,3,4,5,7,9 from the list. If the second number in a range is + omitted, all remaining patches are taken. E.g. "7-" to choose + 7,8,9 from the list. You can say '*' to choose everything. + What you chose are then highlighted with '*', like this: diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 801d7c0251..a6a5c52b59 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -406,9 +406,9 @@ sub list_and_choose { if ($choice =~ s/^-//) { $choose = 0; } - # A range can be specified like 5-7 - if ($choice =~ /^(\d+)-(\d+)$/) { - ($bottom, $top) = ($1, $2); + # A range can be specified like 5-7 or 5-. + if ($choice =~ /^(\d+)-(\d*)$/) { + ($bottom, $top) = ($1, length($2) ? $2 : 1 + @stuff); } elsif ($choice =~ /^\d+$/) { $bottom = $top = $choice; From b526f8ed4de646bb914da43b60322852febde758 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 14 Jul 2008 19:08:52 +0100 Subject: [PATCH 58/60] shortlog: support --pretty=format: option With this patch, the user can override the default setting, to print the commit messages using a user format instead of the onelines of the commits. Example: $ git shortlog --pretty='format:%s (%h)' .. Note that shortlog will only respect a user format setting, as the other formats do not make much sense. Wished for by Andrew Morton. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-shortlog.c | 11 +++++++++++ shortlog.h | 1 + 2 files changed, 12 insertions(+) diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 01362022c0..f8bcbfce40 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -154,6 +154,15 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) if (!author) die("Missing author: %s", sha1_to_hex(commit->object.sha1)); + if (log->user_format) { + struct strbuf buf = STRBUF_INIT; + + pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &buf, + DEFAULT_ABBREV, "", "", DATE_NORMAL, 0); + insert_one_record(log, author, buf.buf); + strbuf_release(&buf); + return; + } if (*buffer) buffer++; insert_one_record(log, author, !*buffer ? "" : buffer); @@ -271,6 +280,8 @@ parse_done: usage_with_options(shortlog_usage, options); } + log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT; + /* assume HEAD if from a tty */ if (!nongit && !rev.pending.nr && isatty(0)) add_head_to_pending(&rev); diff --git a/shortlog.h b/shortlog.h index 31ff491b74..6608ee80b0 100644 --- a/shortlog.h +++ b/shortlog.h @@ -11,6 +11,7 @@ struct shortlog { int wrap; int in1; int in2; + int user_format; char *common_repo_prefix; int email; From 6c11a5fd465eb64301093de8826c38018b474bde Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 15 Jul 2008 05:55:55 -0700 Subject: [PATCH 59/60] Update draft release notes to 1.6.0 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.6.0.txt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Documentation/RelNotes-1.6.0.txt b/Documentation/RelNotes-1.6.0.txt index 9125ee01a6..89ea1e9385 100644 --- a/Documentation/RelNotes-1.6.0.txt +++ b/Documentation/RelNotes-1.6.0.txt @@ -110,6 +110,9 @@ Updates since v1.5.6 * "git-add -i" has a new action 'e/dit' to allow you edit the patch hunk manually. +* git-am records the original tip of the branch in ORIG_HEAD before it + starts applying patches. + * git-apply can handle a patch that touches the same path more than once much better than before. @@ -126,6 +129,14 @@ Updates since v1.5.6 similar to the way git-checkout reports by how many commits your branch is ahead/behind. +* git-branch's --contains option used to always require a commit parameter + to limit the branches with; it now defaults to list branches that + contains HEAD if this parameter is omitted. + +* git-branch's --merged and --no-merged option used to always limit the + branches relative to the HEAD, but they can now take an optional commit + argument that is used in place of HEAD. + * git-bundle can read the revision arguments from the standard input. * git-cherry-pick can replay a root commit now. @@ -144,6 +155,9 @@ Updates since v1.5.6 * fast-export learned to export and import marks file; this can be used to interface with fast-import incrementally. +* git-rebase records the original tip of branch in ORIG_HEAD before it is + rewound. + * "git rerere" can be told to update the index with auto-reused resolution with rerere.autoupdate configuration variable. @@ -152,6 +166,8 @@ Updates since v1.5.6 * git-send-mail can talk not just over SSL but over TLS now. +* git-shortlog honors custom output format specified with "--pretty=format:". + * "git-stash save" learned --keep-index option. This lets you stash away the local changes and bring the changes staged in the index to your working tree for examination and testing. @@ -185,6 +201,6 @@ this release, unless otherwise noted. --- exec >/var/tmp/1 -O=v1.5.6.3-315-g10ce020 +O=v1.5.6.3-350-g499027b echo O=$(git describe refs/heads/master) git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint From 28ed6e7b321bee3dd7e4aa9c0ff7da64844136f6 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 16 Jul 2008 03:33:44 +0200 Subject: [PATCH 60/60] Rename ".dotest/" to ".git/rebase" and ".dotest-merge" to "rebase-merge" Since the files generated and used during a rebase are never to be tracked, they should live in $GIT_DIR. While at it, avoid the rather meaningless term "dotest" to "rebase", and unhide ".dotest-merge". This was wished for on the mailing list, but so far unimplemented. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/SubmittingPatches | 2 +- Documentation/git-am.txt | 4 +-- Documentation/git-rebase.txt | 2 +- Documentation/user-manual.txt | 2 +- contrib/completion/git-completion.bash | 20 ++++++------ contrib/emacs/git.el | 8 ++--- git-am.sh | 6 ++-- git-quiltimport.sh | 2 +- git-rebase--interactive.sh | 2 +- git-rebase.sh | 44 +++++++++++++------------- t/t3401-rebase-partial.sh | 4 +-- t/t3404-rebase-interactive.sh | 8 ++--- t/t3407-rebase-abort.sh | 4 +-- t/t4150-am.sh | 22 ++++++------- t/t9106-git-svn-commit-diff-clobber.sh | 2 +- 15 files changed, 66 insertions(+), 66 deletions(-) diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index b1164753e1..fdfa536441 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -301,7 +301,7 @@ If it does not apply correctly, there can be various reasons. patch appropriately. * Your MUA corrupted your patch; "am" would complain that - the patch does not apply. Look at .dotest/ subdirectory and + the patch does not apply. Look at .git/rebase/ subdirectory and see what 'patch' file contains and check for the common corruption patterns mentioned above. diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 1395c7d986..2d7f162594 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -140,9 +140,9 @@ aborts in the middle,. You can recover from this in one of two ways: the index file to bring it in a state that the patch should have produced. Then run the command with '--resolved' option. -The command refuses to process new mailboxes while `.dotest` +The command refuses to process new mailboxes while `.git/rebase` directory exists, so if you decide to start over from scratch, -run `rm -f -r .dotest` before running the command with mailbox +run `rm -f -r .git/rebase` before running the command with mailbox names. Before any patches are applied, ORIG_HEAD is set to the tip of the diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index e30f6a6982..51afc87e17 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -39,7 +39,7 @@ It is possible that a merge failure will prevent this process from being completely automatic. You will have to resolve any such merge failure and run `git rebase --continue`. Another option is to bypass the commit that caused the merge failure with `git rebase --skip`. To restore the -original and remove the .dotest working files, use the command +original and remove the .git/rebase working files, use the command `git rebase --abort` instead. Assume the following history exists and the current branch is "topic": diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 92d400753d..8761ee7e7d 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -2431,7 +2431,7 @@ $ git rebase origin ------------------------------------------------- This will remove each of your commits from mywork, temporarily saving -them as patches (in a directory named ".dotest"), update mywork to +them as patches (in a directory named ".git/rebase"), update mywork to point at the latest version of origin, then apply each of the saved patches to the new mywork. The result will look like: diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 03e4e02785..29f6cd4e9e 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -73,26 +73,26 @@ __git_ps1 () if [ -n "$g" ]; then local r local b - if [ -d "$g/../.dotest" ] + if [ -d "$g/rebase" ] then - if test -f "$g/../.dotest/rebasing" + if test -f "$g/rebase/rebasing" then r="|REBASE" - elif test -f "$g/../.dotest/applying" + elif test -f "$g/rebase/applying" then r="|AM" else r="|AM/REBASE" fi b="$(git symbolic-ref HEAD 2>/dev/null)" - elif [ -f "$g/.dotest-merge/interactive" ] + elif [ -f "$g/rebase-merge/interactive" ] then r="|REBASE-i" - b="$(cat "$g/.dotest-merge/head-name")" - elif [ -d "$g/.dotest-merge" ] + b="$(cat "$g/rebase-merge/head-name")" + elif [ -d "$g/rebase-merge" ] then r="|REBASE-m" - b="$(cat "$g/.dotest-merge/head-name")" + b="$(cat "$g/rebase-merge/head-name")" elif [ -f "$g/MERGE_HEAD" ] then r="|MERGING" @@ -487,8 +487,8 @@ __git_whitespacelist="nowarn warn error error-all strip" _git_am () { - local cur="${COMP_WORDS[COMP_CWORD]}" - if [ -d .dotest ]; then + local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)" + if [ -d "$dir"/rebase ]; then __gitcomp "--skip --resolved" return fi @@ -915,7 +915,7 @@ _git_push () _git_rebase () { local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)" - if [ -d .dotest ] || [ -d "$dir"/.dotest-merge ]; then + if [ -d "$dir"/rebase ] || [ -d "$dir"/rebase-merge ]; then __gitcomp "--continue --skip --abort" return fi diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 4fa853fae7..43b059bbaa 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -1252,8 +1252,8 @@ Return the list of files that haven't been handled." "\n") (when subject (insert subject "\n\n")) (cond (msg (insert msg "\n")) - ((file-readable-p ".dotest/msg") - (insert-file-contents ".dotest/msg")) + ((file-readable-p ".git/rebase/msg") + (insert-file-contents ".git/rebase/msg")) ((file-readable-p ".git/MERGE_MSG") (insert-file-contents ".git/MERGE_MSG"))) ; delete empty lines at end @@ -1272,9 +1272,9 @@ Return the list of files that haven't been handled." (coding-system (git-get-commits-coding-system)) author-name author-email subject date) (when (eq 0 (buffer-size buffer)) - (when (file-readable-p ".dotest/info") + (when (file-readable-p ".git/rebase/info") (with-temp-buffer - (insert-file-contents ".dotest/info") + (insert-file-contents ".git/rebase/info") (goto-char (point-min)) (when (re-search-forward "^Author: \\(.*\\)\nEmail: \\(.*\\)$" nil t) (setq author-name (match-string 1)) diff --git a/git-am.sh b/git-am.sh index 52b5669b43..cc8787b460 100755 --- a/git-am.sh +++ b/git-am.sh @@ -119,7 +119,7 @@ It does not apply to blobs recorded in its index." } prec=4 -dotest=".dotest" +dotest="$GIT_DIR/rebase" sign= utf8=t keep= skip= interactive= resolved= binary= rebasing= resolvemsg= resume= git_apply_opt= @@ -195,7 +195,7 @@ then false ;; esac || - die "previous dotest directory $dotest still exists but mbox given." + die "previous rebase directory $dotest still exists but mbox given." resume=yes else # Make sure we are not given --skip nor --resolved @@ -325,7 +325,7 @@ do <"$dotest"/info >/dev/null && go_next && continue - test -s $dotest/patch || { + test -s "$dotest/patch" || { echo "Patch is empty. Was it split wrong?" stop_here $this } diff --git a/git-quiltimport.sh b/git-quiltimport.sh index 7cd8f7134e..d1efa1d741 100755 --- a/git-quiltimport.sh +++ b/git-quiltimport.sh @@ -53,7 +53,7 @@ if ! [ -d "$QUILT_PATCHES" ] ; then fi # Temporary directories -tmp_dir=.dotest +tmp_dir="$GIT_DIR"/rebase tmp_msg="$tmp_dir/msg" tmp_patch="$tmp_dir/patch" tmp_info="$tmp_dir/info" diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index a35212d9de..da79a24568 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -31,7 +31,7 @@ skip skip current patch and continue rebasing process . git-sh-setup require_work_tree -DOTEST="$GIT_DIR/.dotest-merge" +DOTEST="$GIT_DIR/rebase-merge" TODO="$DOTEST"/git-rebase-todo DONE="$DOTEST"/done MSG="$DOTEST"/message diff --git a/git-rebase.sh b/git-rebase.sh index 2597d777d6..56cf6f0316 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -14,7 +14,7 @@ It is possible that a merge failure will prevent this process from being completely automatic. You will have to resolve any such merge failure and run git rebase --continue. Another option is to bypass the commit that caused the merge failure with git rebase --skip. To restore the -original and remove the .dotest working files, use the command +original and remove the .git/rebase working files, use the command git rebase --abort instead. Note that if is not specified on the command line, the @@ -42,7 +42,7 @@ To restore the original branch and stop rebasing run \"git rebase --abort\". unset newbase strategy=recursive do_merge= -dotest=$GIT_DIR/.dotest-merge +dotest="$GIT_DIR"/rebase-merge prec=4 verbose= git_am_opt= @@ -150,7 +150,7 @@ while test $# != 0 do case "$1" in --continue) - test -d "$dotest" -o -d .dotest || + test -d "$dotest" -o -d "$GIT_DIR"/rebase || die "No rebase in progress?" git diff-files --quiet --ignore-submodules || { @@ -173,15 +173,15 @@ do finish_rb_merge exit fi - head_name=$(cat .dotest/head-name) && - onto=$(cat .dotest/onto) && - orig_head=$(cat .dotest/orig-head) && + head_name=$(cat "$GIT_DIR"/rebase/head-name) && + onto=$(cat "$GIT_DIR"/rebase/onto) && + orig_head=$(cat "$GIT_DIR"/rebase/orig-head) && git am --resolved --3way --resolvemsg="$RESOLVEMSG" && move_to_original_branch exit ;; --skip) - test -d "$dotest" -o -d .dotest || + test -d "$dotest" -o -d "$GIT_DIR"/rebase || die "No rebase in progress?" git reset --hard HEAD || exit $? @@ -201,15 +201,15 @@ do finish_rb_merge exit fi - head_name=$(cat .dotest/head-name) && - onto=$(cat .dotest/onto) && - orig_head=$(cat .dotest/orig-head) && + head_name=$(cat "$GIT_DIR"/rebase/head-name) && + onto=$(cat "$GIT_DIR"/rebase/onto) && + orig_head=$(cat "$GIT_DIR"/rebase/orig-head) && git am -3 --skip --resolvemsg="$RESOLVEMSG" && move_to_original_branch exit ;; --abort) - test -d "$dotest" -o -d .dotest || + test -d "$dotest" -o -d "$GIT_DIR"/rebase || die "No rebase in progress?" git rerere clear @@ -217,7 +217,7 @@ do then move_to_original_branch else - dotest=.dotest + dotest="$GIT_DIR"/rebase move_to_original_branch fi git reset --hard $(cat "$dotest/orig-head") @@ -265,24 +265,24 @@ do shift done -# Make sure we do not have .dotest +# Make sure we do not have $GIT_DIR/rebase if test -z "$do_merge" then - if mkdir .dotest + if mkdir "$GIT_DIR"/rebase then - rmdir .dotest + rmdir "$GIT_DIR"/rebase else echo >&2 ' -It seems that I cannot create a .dotest directory, and I wonder if you +It seems that I cannot create a '"$GIT_DIR"'/rebase directory, and I wonder if you are in the middle of patch application or another rebase. If that is not -the case, please rm -fr .dotest and run me again. I am stopping in case +the case, please rm -fr '"$GIT_DIR"'/rebase and run me again. I am stopping in case you still have something valuable there.' exit 1 fi else if test -d "$dotest" then - die "previous dotest directory $dotest still exists." \ + die "previous rebase directory $dotest still exists." \ 'try git-rebase < --continue | --abort >' fi fi @@ -396,10 +396,10 @@ then git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" && move_to_original_branch ret=$? - test 0 != $ret -a -d .dotest && - echo $head_name > .dotest/head-name && - echo $onto > .dotest/onto && - echo $orig_head > .dotest/orig-head + test 0 != $ret -a -d "$GIT_DIR"/rebase && + echo $head_name > "$GIT_DIR"/rebase/head-name && + echo $onto > "$GIT_DIR"/rebase/onto && + echo $orig_head > "$GIT_DIR"/rebase/orig-head exit $ret fi diff --git a/t/t3401-rebase-partial.sh b/t/t3401-rebase-partial.sh index 4934a4e010..36d9a2ae3a 100755 --- a/t/t3401-rebase-partial.sh +++ b/t/t3401-rebase-partial.sh @@ -50,12 +50,12 @@ test_debug \ test_expect_success \ 'rebase topic branch against new master and check git-am did not get halted' \ - 'git-rebase master && test ! -d .dotest' + 'git-rebase master && test ! -d .git/rebase' test_expect_success \ 'rebase --merge topic branch that was partially merged upstream' \ 'git-checkout -f my-topic-branch-merge && git-rebase --merge master-merge && - test ! -d .git/.dotest-merge' + test ! -d .git/rebase-merge' test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 092aa26573..ffe3dd97b7 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -159,19 +159,19 @@ test_expect_success 'stop on conflicting pick' ' git tag new-branch1 && test_must_fail git rebase -i master && test "$(git rev-parse HEAD~3)" = "$(git rev-parse master)" && - test_cmp expect .git/.dotest-merge/patch && + test_cmp expect .git/rebase-merge/patch && test_cmp expect2 file1 && test "$(git-diff --name-status | sed -n -e "/^U/s/^U[^a-z]*//p")" = file1 && - test 4 = $(grep -v "^#" < .git/.dotest-merge/done | wc -l) && - test 0 = $(grep -c "^[^#]" < .git/.dotest-merge/git-rebase-todo) + test 4 = $(grep -v "^#" < .git/rebase-merge/done | wc -l) && + test 0 = $(grep -c "^[^#]" < .git/rebase-merge/git-rebase-todo) ' test_expect_success 'abort' ' git rebase --abort && test $(git rev-parse new-branch1) = $(git rev-parse HEAD) && test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" && - ! test -d .git/.dotest-merge + ! test -d .git/rebase-merge ' test_expect_success 'retain authorship' ' diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh index 1777ffe8a2..12c8804a03 100755 --- a/t/t3407-rebase-abort.sh +++ b/t/t3407-rebase-abort.sh @@ -74,7 +74,7 @@ testrebase() { ' } -testrebase "" .dotest -testrebase " --merge" .git/.dotest-merge +testrebase "" .git/rebase +testrebase " --merge" .git/rebase-merge test_done diff --git a/t/t4150-am.sh b/t/t4150-am.sh index bc982607d0..5cbd5ef679 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -102,7 +102,7 @@ test_expect_success 'am applies patch correctly' ' git checkout first && test_tick && git am >file && git add file && git am --resolved && - ! test -d .dotest && + ! test -d .git/rebase && test goodbye = "$(cat another)" ' test_expect_success 'am takes patches from a Pine mailbox' ' git checkout first && cat pine patch1 | git am && - ! test -d .dotest && + ! test -d .git/rebase && test -z "$(git diff master^..HEAD)" ' test_expect_success 'am fails on mail without patch' ' ! git am >failmail && ! git am file &&