Merge branch 'ag/blame'
authorJunio C Hamano <gitster@pobox.com>
Thu, 17 Jul 2008 00:12:11 +0000 (17:12 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 17 Jul 2008 00:12:11 +0000 (17:12 -0700)
* ag/blame:
Do not try to detect move/copy for entries below threshold.
Avoid rescanning unchanged entries in search for copies.

70 files changed:
Documentation/RelNotes-1.5.6.4.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/git-am.txt
Documentation/git-cherry-pick.txt
Documentation/git-rebase.txt
Documentation/git-revert.txt
Documentation/pretty-formats.txt
Documentation/user-manual.txt
Makefile
alias.c
attr.c
builtin-commit-tree.c
builtin-describe.c
builtin-fmt-merge-msg.c
builtin-grep.c
builtin-init-db.c
builtin-merge-recursive.c
builtin-merge.c [new file with mode: 0644]
builtin-read-tree.c
builtin-remote.c
builtin-tag.c
builtin.h
cache-tree.c
cache-tree.h
cache.h
commit.c
commit.h
compat/mingw.c
compat/mingw.h
config.c
contrib/completion/git-completion.bash
contrib/emacs/git.el
contrib/examples/git-merge.sh [new file with mode: 0755]
convert.c
diff-lib.c
diff.c
diff.h
exec_cmd.c
exec_cmd.h
git-add--interactive.perl
git-am.sh
git-compat-util.h
git-cvsserver.perl
git-merge.sh [deleted file]
git-quiltimport.sh
git-rebase--interactive.sh
git-rebase.sh
git.c
help.c
index-pack.c
parse-options.c
pretty.c
read-cache.c
revision.c
t/Makefile
t/aggregate-results.sh
t/lib-git-svn.sh
t/t3401-rebase-partial.sh
t/t3404-rebase-interactive.sh
t/t3407-rebase-abort.sh
t/t4150-am.sh
t/t6021-merge-criss-cross.sh
t/t7600-merge.sh
t/t7601-merge-pull-config.sh [new file with mode: 0755]
t/t7602-merge-octopus-many.sh [new file with mode: 0755]
t/t7603-merge-reduce-heads.sh [new file with mode: 0755]
t/t7604-merge-custom-message.sh [new file with mode: 0755]
t/t7605-merge-resolve.sh [new file with mode: 0755]
t/t9106-git-svn-commit-diff-clobber.sh
tree-diff.c
diff --git a/Documentation/RelNotes-1.5.6.4.txt b/Documentation/RelNotes-1.5.6.4.txt
new file mode 100644 (file)
index 0000000..1304188
--- /dev/null
@@ -0,0 +1,43 @@
+GIT v1.5.6.4 Release Notes
+==========================
+
+Fixes since v1.5.6.3
+--------------------
+
+* Various commands could overflow its internal buffer on a platform
+  with small PATH_MAX value in a repository that has contents with
+  long pathnames.
+
+* There wasn't a way to make --pretty=format:%<> specifiers to honor
+  .mailmap name rewriting for authors and committers.  Now you can with
+  %aN and %cN.
+
+* Bash completion wasted too many cycles; this has been optimized to be
+  usable again.
+
+* Bash completion lost ref part when completing something like "git show
+  pu:Makefile".
+
+* "git-cvsserver" did not clean up its temporary working area after annotate
+  request.
+
+* "git-daemon" called syslog() from its signal handler, which was a
+  no-no.
+
+* "git-fetch" into an empty repository used to remind that the fetch will
+   be huge by saying "no common commits", but this was an unnecessary
+   noise; it is already known by the user anyway.
+
+* "git-mailinfo" (hence "git-am") did not correctly handle in-body [PATCH]
+  line to override the commit title taken from the mail Subject header.
+
+* "git-rebase -i -p" lost parents that are not involved in the history
+  being rewritten.
+
+Contains other various documentation fixes.
+
+--
+exec >/var/tmp/1
+echo O=$(git describe maint)
+O=v1.5.6.3-21-gebcce31
+git shortlog --no-merges $O..maint
index b1164753e13dbe015d073346bc0b7c992532efe8..fdfa5364414358e006ac20a20672f6c4408cde5d 100644 (file)
@@ -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.
 
index 1395c7d986c82eb1e7e91140c8f97b80e7a8a41d..2d7f162594cb69d5a5f6d8ef1714111ad0bbc67a 100644 (file)
@@ -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
index a691173ba13370330b97907cf751342cf1c0fe44..50fb3d5d545351ce5caf1576f529dc537d8353da 100644 (file)
@@ -58,14 +58,14 @@ OPTIONS
        Usually the command automatically creates a commit with
        a commit log message stating which commit was
        cherry-picked.  This flag applies the change necessary
-       to cherry-pick the named commit to your working tree,
-       but does not make the commit.  In addition, when this
-       option is used, your working tree does not have to match
+       to cherry-pick the named commit to your working tree
+       and the index, but does not make the commit.  In addition,
+       when this option is used, your index does not have to match
        the HEAD commit.  The cherry-pick is done against the
-       beginning state of your working tree.
+       beginning state of your index.
 +
 This is useful when cherry-picking more than one commits'
-effect to your working tree in a row.
+effect to your index in a row.
 
 -s::
 --signoff::
index e30f6a6982a8d9ee48c6a7d46fa435c7aee773af..51afc87e173ca135e4a663ce58b8378c1aef1d25 100644 (file)
@@ -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 <branch> and remove the .dotest working files, use the command
+original <branch> 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":
index 5411edca96b50e28996357bf75ce4e32796ee620..271850f51134ee9b991048b5da07775b5dde65fc 100644 (file)
@@ -43,16 +43,16 @@ OPTIONS
 -n::
 --no-commit::
        Usually the command automatically creates a commit with
-       a commit log message stating which commit was reverted.
-       This flag applies the change necessary to revert the
-       named commit to your working tree, but does not make the
-       commit.  In addition, when this option is used, your
-       working tree does not have to match the HEAD commit.
-       The revert is done against the beginning state of your
-       working tree.
+       a commit log message stating which commit was
+       reverted.  This flag applies the change necessary
+       to revert the named commit to your working tree
+       and the index, but does not make the commit.  In addition,
+       when this option is used, your index does not have to match
+       the HEAD commit.  The revert is done against the
+       beginning state of your index.
 +
 This is useful when reverting more than one commits'
-effect to your working tree in a row.
+effect to your index in a row.
 
 -s::
 --signoff::
index 69e6d2fa4459313e4a6bdda77847d74d9219e2e1..c11d4957714db202a012209e2437b9e050a28ae0 100644 (file)
@@ -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
index 92d400753d192b4b6df256fc3b872d775ecaacd4..8761ee7e7d7ffe1202daff13c68a81dd2b19e18f 100644 (file)
@@ -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:
 
index de151638bb5c20fe34a0ca5869652004cd90911d..9b52071b7304b5b3b2f88802210f7ed13274c0bd 100644 (file)
--- 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-mergetool.sh
 SCRIPT_SH += git-parse-remote.sh
 SCRIPT_SH += git-pull.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/alias.c b/alias.c
index 995f3e6a0ac84fa71c3196de6673225d4a5d8231..ccb1108c94436035d0da8b1d6f08f859b68294a3 100644 (file)
--- 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/attr.c b/attr.c
index 0fb47d34346ca96addde5cb3722fa5e586057285..17f6a4dca521d9690377f2e93a0192d8a874d2ad 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -459,7 +459,9 @@ static void prepare_attr_stack(const char *path, int dirlen)
 {
        struct attr_stack *elem, *info;
        int len;
-       char pathbuf[PATH_MAX];
+       struct strbuf pathbuf;
+
+       strbuf_init(&pathbuf, dirlen+2+strlen(GITATTRIBUTES_FILE));
 
        /*
         * At the bottom of the attribute stack is the built-in
@@ -510,13 +512,14 @@ static void prepare_attr_stack(const char *path, int dirlen)
                        len = strlen(attr_stack->origin);
                        if (dirlen <= len)
                                break;
-                       memcpy(pathbuf, path, dirlen);
-                       memcpy(pathbuf + dirlen, "/", 2);
-                       cp = strchr(pathbuf + len + 1, '/');
+                       strbuf_reset(&pathbuf);
+                       strbuf_add(&pathbuf, path, dirlen);
+                       strbuf_addch(&pathbuf, '/');
+                       cp = strchr(pathbuf.buf + len + 1, '/');
                        strcpy(cp + 1, GITATTRIBUTES_FILE);
-                       elem = read_attr(pathbuf, 0);
+                       elem = read_attr(pathbuf.buf, 0);
                        *cp = '\0';
-                       elem->origin = strdup(pathbuf);
+                       elem->origin = strdup(pathbuf.buf);
                        elem->prev = attr_stack;
                        attr_stack = elem;
                        debug_push(elem);
index 3881f6c2f56f317726a688e44b98806987036999..7a9a309be0543da7d27e7710ef82271f2582e0a9 100644 (file)
@@ -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;
        }
index e515f9ca9b5d0ec13e96a7866e27bdd9e852b435..62a19056ef05989b0b48d0939758a66175b3f442 100644 (file)
@@ -20,7 +20,7 @@ static int tags;      /* But allow any tags if --tags is specified */
 static int longformat;
 static int abbrev = DEFAULT_ABBREV;
 static int max_candidates = 10;
-const char *pattern = NULL;
+static const char *pattern;
 static int always;
 
 struct commit_name {
index b892621ab50346ff29a0a1335de76e3f2fbbc723..dbd7d2ddae2d4b44bcccf9977d435cf334a0c418 100644 (file)
@@ -159,23 +159,24 @@ static int handle_line(char *line)
 }
 
 static void print_joined(const char *singular, const char *plural,
-               struct list *list)
+               struct list *list, struct strbuf *out)
 {
        if (list->nr == 0)
                return;
        if (list->nr == 1) {
-               printf("%s%s", singular, list->list[0]);
+               strbuf_addf(out, "%s%s", singular, list->list[0]);
        } else {
                int i;
-               printf("%s", plural);
+               strbuf_addstr(out, plural);
                for (i = 0; i < list->nr - 1; i++)
-                       printf("%s%s", i > 0 ? ", " : "", list->list[i]);
-               printf(" and %s", list->list[list->nr - 1]);
+                       strbuf_addf(out, "%s%s", i > 0 ? ", " : "", list->list[i]);
+               strbuf_addf(out, " and %s", list->list[list->nr - 1]);
        }
 }
 
 static void shortlog(const char *name, unsigned char *sha1,
-               struct commit *head, struct rev_info *rev, int limit)
+               struct commit *head, struct rev_info *rev, int limit,
+               struct strbuf *out)
 {
        int i, count = 0;
        struct commit *commit;
@@ -232,15 +233,15 @@ static void shortlog(const char *name, unsigned char *sha1,
        }
 
        if (count > limit)
-               printf("\n* %s: (%d commits)\n", name, count);
+               strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
        else
-               printf("\n* %s:\n", name);
+               strbuf_addf(out, "\n* %s:\n", name);
 
        for (i = 0; i < subjects.nr; i++)
                if (i >= limit)
-                       printf("  ...\n");
+                       strbuf_addf(out, "  ...\n");
                else
-                       printf("  %s\n", subjects.list[i]);
+                       strbuf_addf(out, "  %s\n", subjects.list[i]);
 
        clear_commit_marks((struct commit *)branch, flags);
        clear_commit_marks(head, flags);
@@ -251,43 +252,13 @@ static void shortlog(const char *name, unsigned char *sha1,
        free_list(&subjects);
 }
 
-int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
-{
-       int limit = 20, i = 0;
+int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
+       int limit = 20, i = 0, pos = 0;
        char line[1024];
-       FILE *in = stdin;
-       const char *sep = "";
+       char *p = line, *sep = "";
        unsigned char head_sha1[20];
        const char *current_branch;
 
-       git_config(fmt_merge_msg_config, NULL);
-
-       while (argc > 1) {
-               if (!strcmp(argv[1], "--log") || !strcmp(argv[1], "--summary"))
-                       merge_summary = 1;
-               else if (!strcmp(argv[1], "--no-log")
-                               || !strcmp(argv[1], "--no-summary"))
-                       merge_summary = 0;
-               else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) {
-                       if (argc < 3)
-                               die ("Which file?");
-                       if (!strcmp(argv[2], "-"))
-                               in = stdin;
-                       else {
-                               fclose(in);
-                               in = fopen(argv[2], "r");
-                               if (!in)
-                                       die("cannot open %s", argv[2]);
-                       }
-                       argc--; argv++;
-               } else
-                       break;
-               argc--; argv++;
-       }
-
-       if (argc > 1)
-               usage(fmt_merge_msg_usage);
-
        /* get current branch */
        current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
        if (!current_branch)
@@ -295,75 +266,127 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
        if (!prefixcmp(current_branch, "refs/heads/"))
                current_branch += 11;
 
-       while (fgets(line, sizeof(line), in)) {
+       /* get a line */
+       while (pos < in->len) {
+               int len;
+               char *newline;
+
+               p = in->buf + pos;
+               newline = strchr(p, '\n');
+               len = newline ? newline - p : strlen(p);
+               pos += len + !!newline;
                i++;
-               if (line[0] == 0)
-                       continue;
-               if (handle_line(line))
-                       die ("Error in line %d: %s", i, line);
+               p[len] = 0;
+               if (handle_line(p))
+                       die ("Error in line %d: %.*s", i, len, p);
        }
 
-       printf("Merge ");
+       strbuf_addstr(out, "Merge ");
        for (i = 0; i < srcs.nr; i++) {
                struct src_data *src_data = srcs.payload[i];
                const char *subsep = "";
 
-               printf(sep);
+               strbuf_addstr(out, sep);
                sep = "; ";
 
                if (src_data->head_status == 1) {
-                       printf(srcs.list[i]);
+                       strbuf_addstr(out, srcs.list[i]);
                        continue;
                }
                if (src_data->head_status == 3) {
                        subsep = ", ";
-                       printf("HEAD");
+                       strbuf_addstr(out, "HEAD");
                }
                if (src_data->branch.nr) {
-                       printf(subsep);
+                       strbuf_addstr(out, subsep);
                        subsep = ", ";
-                       print_joined("branch ", "branches ", &src_data->branch);
+                       print_joined("branch ", "branches ", &src_data->branch,
+                                       out);
                }
                if (src_data->r_branch.nr) {
-                       printf(subsep);
+                       strbuf_addstr(out, subsep);
                        subsep = ", ";
                        print_joined("remote branch ", "remote branches ",
-                                       &src_data->r_branch);
+                                       &src_data->r_branch, out);
                }
                if (src_data->tag.nr) {
-                       printf(subsep);
+                       strbuf_addstr(out, subsep);
                        subsep = ", ";
-                       print_joined("tag ", "tags ", &src_data->tag);
+                       print_joined("tag ", "tags ", &src_data->tag, out);
                }
                if (src_data->generic.nr) {
-                       printf(subsep);
-                       print_joined("commit ", "commits ", &src_data->generic);
+                       strbuf_addstr(out, subsep);
+                       print_joined("commit ", "commits ", &src_data->generic,
+                                       out);
                }
                if (strcmp(".", srcs.list[i]))
-                       printf(" of %s", srcs.list[i]);
+                       strbuf_addf(out, " of %s", srcs.list[i]);
        }
 
        if (!strcmp("master", current_branch))
-               putchar('\n');
+               strbuf_addch(out, '\n');
        else
-               printf(" into %s\n", current_branch);
+               strbuf_addf(out, " into %s\n", current_branch);
 
        if (merge_summary) {
                struct commit *head;
                struct rev_info rev;
 
                head = lookup_commit(head_sha1);
-               init_revisions(&rev, prefix);
+               init_revisions(&rev, NULL);
                rev.commit_format = CMIT_FMT_ONELINE;
                rev.ignore_merges = 1;
                rev.limited = 1;
 
                for (i = 0; i < origins.nr; i++)
                        shortlog(origins.list[i], origins.payload[i],
-                                       head, &rev, limit);
+                                       head, &rev, limit, out);
        }
+       return 0;
+}
+
+int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
+{
+       FILE *in = stdin;
+       struct strbuf input, output;
+       int ret;
+
+       git_config(fmt_merge_msg_config, NULL);
+
+       while (argc > 1) {
+               if (!strcmp(argv[1], "--log") || !strcmp(argv[1], "--summary"))
+                       merge_summary = 1;
+               else if (!strcmp(argv[1], "--no-log")
+                               || !strcmp(argv[1], "--no-summary"))
+                       merge_summary = 0;
+               else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) {
+                       if (argc < 3)
+                               die ("Which file?");
+                       if (!strcmp(argv[2], "-"))
+                               in = stdin;
+                       else {
+                               fclose(in);
+                               in = fopen(argv[2], "r");
+                               if (!in)
+                                       die("cannot open %s", argv[2]);
+                       }
+                       argc--; argv++;
+               } else
+                       break;
+               argc--; argv++;
+       }
+
+       if (argc > 1)
+               usage(fmt_merge_msg_usage);
 
-       /* No cleanup yet; is standalone anyway */
+       strbuf_init(&input, 0);
+       if (strbuf_read(&input, fileno(in), 0) < 0)
+               die("could not read input file %s", strerror(errno));
+       strbuf_init(&output, 0);
 
+       ret = fmt_merge_msg(merge_summary, &input, &output);
+       if (ret)
+               return ret;
+       printf("%s", output.buf);
        return 0;
 }
index ef299108f529fa5fa931d765828f04535c3a6351..647535061c350b68f1583fc12b049a37eb17481c 100644 (file)
@@ -427,33 +427,35 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
        struct name_entry entry;
        char *down;
        int tn_len = strlen(tree_name);
-       char *path_buf = xmalloc(PATH_MAX + tn_len + 100);
+       struct strbuf pathbuf;
+
+       strbuf_init(&pathbuf, PATH_MAX + tn_len);
 
        if (tn_len) {
-               tn_len = sprintf(path_buf, "%s:", tree_name);
-               down = path_buf + tn_len;
-               strcat(down, base);
-       }
-       else {
-               down = path_buf;
-               strcpy(down, base);
+               strbuf_add(&pathbuf, tree_name, tn_len);
+               strbuf_addch(&pathbuf, ':');
+               tn_len = pathbuf.len;
        }
-       len = strlen(path_buf);
+       strbuf_addstr(&pathbuf, base);
+       len = pathbuf.len;
 
        while (tree_entry(tree, &entry)) {
-               strcpy(path_buf + len, entry.path);
+               int te_len = tree_entry_len(entry.path, entry.sha1);
+               pathbuf.len = len;
+               strbuf_add(&pathbuf, entry.path, te_len);
 
                if (S_ISDIR(entry.mode))
                        /* Match "abc/" against pathspec to
                         * decide if we want to descend into "abc"
                         * directory.
                         */
-                       strcpy(path_buf + len + tree_entry_len(entry.path, entry.sha1), "/");
+                       strbuf_addch(&pathbuf, '/');
 
+               down = pathbuf.buf + tn_len;
                if (!pathspec_matches(paths, down))
                        ;
                else if (S_ISREG(entry.mode))
-                       hit |= grep_sha1(opt, entry.sha1, path_buf, tn_len);
+                       hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
                else if (S_ISDIR(entry.mode)) {
                        enum object_type type;
                        struct tree_desc sub;
@@ -469,6 +471,7 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
                        free(data);
                }
        }
+       strbuf_release(&pathbuf);
        return hit;
 }
 
index e23b8438c741651f48bbf529f89d0336cc718fed..5ba213a595fb9d7de4d36ff5cab888bde2035722 100644 (file)
@@ -115,18 +115,8 @@ static void copy_templates(const char *template_dir)
 
        if (!template_dir)
                template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
-       if (!template_dir) {
-               /*
-                * if the hard-coded template is relative, it is
-                * interpreted relative to the exec_dir
-                */
-               template_dir = DEFAULT_GIT_TEMPLATE_DIR;
-               if (!is_absolute_path(template_dir)) {
-                       struct strbuf d = STRBUF_INIT;
-                       strbuf_addf(&d, "%s/%s", git_exec_path(), template_dir);
-                       template_dir = strbuf_detach(&d, NULL);
-               }
-       }
+       if (!template_dir)
+               template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR);
        strcpy(template_path, template_dir);
        template_len = strlen(template_path);
        if (template_path[template_len-1] != '/') {
index 43bf6aa45eeacbb67490c809070f40023ce4e434..3731853f83be37d3790a5db8ef037af0b0a4f7c5 100644 (file)
@@ -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/builtin-merge.c b/builtin-merge.c
new file mode 100644 (file)
index 0000000..129b4e6
--- /dev/null
@@ -0,0 +1,1156 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * 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"
+#include "rerere.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] <remote>...",
+       "git-merge [options] <msg> HEAD <remote>",
+       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 <name>^^^.. or <name>~<number> */
+       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 ...~<number> */
+                       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);
+       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);
+}
+
+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 <msg> HEAD <commit>..."  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();
+}
index 5a09e17f1ad4fe19cf4e7cc29235d14d35405168..72a6de302f88728af17ce5c5c6983c5267afc6f6 100644 (file)
@@ -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;
index 145dd8568c7a644344d8bb25ba395b10c5835c5c..1491354a9d5d22546acde09021b0f666f6740257 100644 (file)
@@ -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");
                }
index 3c97c696a50cb55e78774c743436cf50cad35f96..a70922b21c4bc2ff552e00cd65ecba6c17a33000 100644 (file)
@@ -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;
 }
 
index b460b2da6f41797ca3190646eb3fe5cafd1abd84..0e605d4f4aa748c0b3c34aeb75a1b5a765114b12 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -2,6 +2,9 @@
 #define BUILTIN_H
 
 #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[];
@@ -11,6 +14,10 @@ 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 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);
@@ -57,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);
index 73cb3407077275f82677839d2c9e794c12833c95..5f8ee87bb1c446341b640c2f978a658d6bfcfcd0 100644 (file)
@@ -507,7 +507,7 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size)
        return read_one(&buffer, &size);
 }
 
-struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path)
+static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path)
 {
        while (*path) {
                const char *slash;
index 44aad426d319b83eda013e115f35e066cc590cb8..cf8b790874c4a4f5890b360c237ccdd4a5a03de4 100644 (file)
@@ -28,8 +28,6 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);
 int cache_tree_fully_valid(struct cache_tree *);
 int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int);
 
-struct cache_tree *cache_tree_find(struct cache_tree *, const char *);
-
 #define WRITE_TREE_UNREADABLE_INDEX (-1)
 #define WRITE_TREE_UNMERGED_INDEX (-2)
 #define WRITE_TREE_PREFIX_ERROR (-3)
diff --git a/cache.h b/cache.h
index a8254e2f9e076b2b69dd214379578e6fc065ddcc..a779d9207c8ea4d4e8455a89b17f312e0e71332d 100644 (file)
--- 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 *);
@@ -837,5 +839,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 */
index e2d8624d9c19adde87ae521361f4ccd8260c06a0..5148ec552708bb6c98cc64a84647ec7715ad1c29 100644 (file)
--- 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) {
@@ -525,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;
@@ -592,21 +608,53 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two)
        return result;
 }
 
-struct commit_list *get_merge_bases(struct commit *one,
-                                       struct commit *two, int cleanup)
+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_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;
        }
@@ -624,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) {
@@ -651,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;
@@ -670,3 +725,55 @@ 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;
+
+               /* 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)
+                       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;
+}
index fda7ad03c0b1d0f2c2cd1e758dbaecf47742c576..77de9621d9c926c6fb8a2bf9ca81c6c376a2ad41 100644 (file)
--- 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);
@@ -120,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);
@@ -137,4 +139,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 */
index 3a05fe7da656ebb5d1699937013a1ab517bbee3c..c0bc849e45dc511202d1e7602f570de47630e6aa 100644 (file)
@@ -1017,3 +1017,25 @@ sig_handler_t mingw_signal(int sig, sig_handler_t handler)
        timer_fn = handler;
        return old;
 }
+
+static const char *make_backslash_path(const char *path)
+{
+       static char buf[PATH_MAX + 1];
+       char *c;
+
+       if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
+               die("Too long path: %.*s", 60, path);
+
+       for (c = buf; *c; c++) {
+               if (*c == '/')
+                       *c = '\\';
+       }
+       return buf;
+}
+
+void mingw_open_html(const char *unixpath)
+{
+       const char *htmlpath = make_backslash_path(unixpath);
+       printf("Launching default browser to display HTML ...\n");
+       ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0);
+}
index 6bc049ad993cb95885fbb936c67a3b7c986e56eb..5a3bcee29b7e39f52dcae40aeed611c01cec920e 100644 (file)
@@ -202,6 +202,9 @@ sig_handler_t mingw_signal(int sig, sig_handler_t handler);
 #define PATH_SEP ';'
 #define PRIuMAX "I64u"
 
+void mingw_open_html(const char *path);
+#define open_html mingw_open_html
+
 /*
  * helpers
  */
index 2862cc45cbb1752153bb6050b4e6bd53f6660724..1e066c71e0fbc4b172ef548663e660e636d6a916 100644 (file)
--- a/config.c
+++ b/config.c
@@ -581,15 +581,8 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
 const char *git_etc_gitconfig(void)
 {
        static const char *system_wide;
-       if (!system_wide) {
-               system_wide = ETC_GITCONFIG;
-               if (!is_absolute_path(system_wide)) {
-                       /* interpret path relative to exec-dir */
-                       struct strbuf d = STRBUF_INIT;
-                       strbuf_addf(&d, "%s/%s", git_exec_path(), system_wide);
-                       system_wide = strbuf_detach(&d, NULL);
-               }
-       }
+       if (!system_wide)
+               system_wide = system_path(ETC_GITCONFIG);
        return system_wide;
 }
 
index 03e4e02785fb1e62c230cbbdab14d77836d6b52f..29f6cd4e9eb07f39cc08838ed52be6cf2c438c2e 100755 (executable)
@@ -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
index 4fa853fae76dc2ac132e489f5f1b630b3fc0f1de..43b059bbaa193a07c5a020286a3fbba343ccfbc1 100644 (file)
@@ -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/contrib/examples/git-merge.sh b/contrib/examples/git-merge.sh
new file mode 100755 (executable)
index 0000000..8026ccf
--- /dev/null
@@ -0,0 +1,554 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC="\
+git-merge [options] <remote>...
+git-merge [options] <msg> HEAD <remote>
+--
+stat                 show a diffstat at the end of the merge
+n                    don't show a diffstat at the end of the merge
+summary              (synonym to --stat)
+log                  add list of one-line log to merge commit message
+squash               create a single commit instead of doing a merge
+commit               perform a commit if the merge succeeds (default)
+ff                   allow fast forward (default)
+s,strategy=          merge strategy to use
+m,message=           message to be used for the merge commit (if any)
+"
+
+SUBDIRECTORY_OK=Yes
+. git-sh-setup
+require_work_tree
+cd_to_toplevel
+
+test -z "$(git ls-files -u)" ||
+       die "You are in the middle of a conflicted merge."
+
+LF='
+'
+
+all_strategies='recur recursive octopus resolve stupid ours subtree'
+default_twohead_strategies='recursive'
+default_octopus_strategies='octopus'
+no_fast_forward_strategies='subtree ours'
+no_trivial_strategies='recursive recur subtree ours'
+use_strategies=
+
+allow_fast_forward=t
+allow_trivial_merge=t
+squash= no_commit= log_arg=
+
+dropsave() {
+       rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
+                "$GIT_DIR/MERGE_STASH" || exit 1
+}
+
+savestate() {
+       # Stash away any local modifications.
+       git stash create >"$GIT_DIR/MERGE_STASH"
+}
+
+restorestate() {
+        if test -f "$GIT_DIR/MERGE_STASH"
+       then
+               git reset --hard $head >/dev/null
+               git stash apply $(cat "$GIT_DIR/MERGE_STASH")
+               git update-index --refresh >/dev/null
+       fi
+}
+
+finish_up_to_date () {
+       case "$squash" in
+       t)
+               echo "$1 (nothing to squash)" ;;
+       '')
+               echo "$1" ;;
+       esac
+       dropsave
+}
+
+squash_message () {
+       echo Squashed commit of the following:
+       echo
+       git log --no-merges --pretty=medium ^"$head" $remoteheads
+}
+
+finish () {
+       if test '' = "$2"
+       then
+               rlogm="$GIT_REFLOG_ACTION"
+       else
+               echo "$2"
+               rlogm="$GIT_REFLOG_ACTION: $2"
+       fi
+       case "$squash" in
+       t)
+               echo "Squash commit -- not updating HEAD"
+               squash_message >"$GIT_DIR/SQUASH_MSG"
+               ;;
+       '')
+               case "$merge_msg" in
+               '')
+                       echo "No merge message -- not updating HEAD"
+                       ;;
+               *)
+                       git update-ref -m "$rlogm" HEAD "$1" "$head" || exit 1
+                       git gc --auto
+                       ;;
+               esac
+               ;;
+       esac
+       case "$1" in
+       '')
+               ;;
+       ?*)
+               if test "$show_diffstat" = t
+               then
+                       # We want color (if set), but no pager
+                       GIT_PAGER='' git diff --stat --summary -M "$head" "$1"
+               fi
+               ;;
+       esac
+
+       # Run a post-merge hook
+        if test -x "$GIT_DIR"/hooks/post-merge
+        then
+           case "$squash" in
+           t)
+                "$GIT_DIR"/hooks/post-merge 1
+               ;;
+           '')
+                "$GIT_DIR"/hooks/post-merge 0
+               ;;
+           esac
+        fi
+}
+
+merge_name () {
+       remote="$1"
+       rh=$(git rev-parse --verify "$remote^0" 2>/dev/null) || return
+       bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null)
+       if test "$rh" = "$bh"
+       then
+               echo "$rh               branch '$remote' of ."
+       elif truname=$(expr "$remote" : '\(.*\)~[1-9][0-9]*$') &&
+               git show-ref -q --verify "refs/heads/$truname" 2>/dev/null
+       then
+               echo "$rh               branch '$truname' (early part) of ."
+       elif test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD"
+       then
+               sed -e 's/      not-for-merge   /               /' -e 1q \
+                       "$GIT_DIR/FETCH_HEAD"
+       else
+               echo "$rh               commit '$remote'"
+       fi
+}
+
+parse_config () {
+       while test $# != 0; do
+               case "$1" in
+               -n|--no-stat|--no-summary)
+                       show_diffstat=false ;;
+               --stat|--summary)
+                       show_diffstat=t ;;
+               --log|--no-log)
+                       log_arg=$1 ;;
+               --squash)
+                       test "$allow_fast_forward" = t ||
+                               die "You cannot combine --squash with --no-ff."
+                       squash=t no_commit=t ;;
+               --no-squash)
+                       squash= no_commit= ;;
+               --commit)
+                       no_commit= ;;
+               --no-commit)
+                       no_commit=t ;;
+               --ff)
+                       allow_fast_forward=t ;;
+               --no-ff)
+                       test "$squash" != t ||
+                               die "You cannot combine --squash with --no-ff."
+                       allow_fast_forward=f ;;
+               -s|--strategy)
+                       shift
+                       case " $all_strategies " in
+                       *" $1 "*)
+                               use_strategies="$use_strategies$1 " ;;
+                       *)
+                               die "available strategies are: $all_strategies" ;;
+                       esac
+                       ;;
+               -m|--message)
+                       shift
+                       merge_msg="$1"
+                       have_message=t
+                       ;;
+               --)
+                       shift
+                       break ;;
+               *)      usage ;;
+               esac
+               shift
+       done
+       args_left=$#
+}
+
+test $# != 0 || usage
+
+have_message=
+
+if branch=$(git-symbolic-ref -q HEAD)
+then
+       mergeopts=$(git config "branch.${branch#refs/heads/}.mergeoptions")
+       if test -n "$mergeopts"
+       then
+               parse_config $mergeopts --
+       fi
+fi
+
+parse_config "$@"
+while test $args_left -lt $#; do shift; done
+
+if test -z "$show_diffstat"; then
+    test "$(git config --bool merge.diffstat)" = false && show_diffstat=false
+    test "$(git config --bool merge.stat)" = false && show_diffstat=false
+    test -z "$show_diffstat" && show_diffstat=t
+fi
+
+# This could be traditional "merge <msg> HEAD <commit>..."  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.
+
+if test -z "$have_message" &&
+       second_token=$(git rev-parse --verify "$2^0" 2>/dev/null) &&
+       head_commit=$(git rev-parse --verify "HEAD" 2>/dev/null) &&
+       test "$second_token" = "$head_commit"
+then
+       merge_msg="$1"
+       shift
+       head_arg="$1"
+       shift
+elif ! git rev-parse --verify HEAD >/dev/null 2>&1
+then
+       # 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 test 1 -ne $#
+       then
+               echo >&2 "Can merge only exactly one commit into empty head"
+               exit 1
+       fi
+
+       rh=$(git rev-parse --verify "$1^0") ||
+               die "$1 - not something we can merge"
+
+       git update-ref -m "initial pull" HEAD "$rh" "" &&
+       git read-tree --reset -u HEAD
+       exit
+
+else
+       # 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.
+       merge_name=$(for remote
+               do
+                       merge_name "$remote"
+               done | git fmt-merge-msg $log_arg
+       )
+       merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name"
+fi
+head=$(git rev-parse --verify "$head_arg"^0) || usage
+
+# All the rest are remote heads
+test "$#" = 0 && usage ;# we need at least one remote head.
+set_reflog_action "merge $*"
+
+remoteheads=
+for remote
+do
+       remotehead=$(git rev-parse --verify "$remote"^0 2>/dev/null) ||
+           die "$remote - not something we can merge"
+       remoteheads="${remoteheads}$remotehead "
+       eval GITHEAD_$remotehead='"$remote"'
+       export GITHEAD_$remotehead
+done
+set x $remoteheads ; shift
+
+case "$use_strategies" in
+'')
+       case "$#" in
+       1)
+               var="`git config --get pull.twohead`"
+               if test -n "$var"
+               then
+                       use_strategies="$var"
+               else
+                       use_strategies="$default_twohead_strategies"
+               fi ;;
+       *)
+               var="`git config --get pull.octopus`"
+               if test -n "$var"
+               then
+                       use_strategies="$var"
+               else
+                       use_strategies="$default_octopus_strategies"
+               fi ;;
+       esac
+       ;;
+esac
+
+for s in $use_strategies
+do
+       for ss in $no_fast_forward_strategies
+       do
+               case " $s " in
+               *" $ss "*)
+                       allow_fast_forward=f
+                       break
+                       ;;
+               esac
+       done
+       for ss in $no_trivial_strategies
+       do
+               case " $s " in
+               *" $ss "*)
+                       allow_trivial_merge=f
+                       break
+                       ;;
+               esac
+       done
+done
+
+case "$#" in
+1)
+       common=$(git merge-base --all $head "$@")
+       ;;
+*)
+       common=$(git show-branch --merge-base $head "$@")
+       ;;
+esac
+echo "$head" >"$GIT_DIR/ORIG_HEAD"
+
+case "$allow_fast_forward,$#,$common,$no_commit" in
+?,*,'',*)
+       # No common ancestors found. We need a real merge.
+       ;;
+?,1,"$1",*)
+       # 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."
+       exit 0
+       ;;
+t,1,"$head",*)
+       # Again the most common case of merging one remote.
+       echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $1)"
+       git update-index --refresh 2>/dev/null
+       msg="Fast forward"
+       if test -n "$have_message"
+       then
+               msg="$msg (no commit created; -m option ignored)"
+       fi
+       new_head=$(git rev-parse --verify "$1^0") &&
+       git read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" &&
+       finish "$new_head" "$msg" || exit
+       dropsave
+       exit 0
+       ;;
+?,1,?*"$LF"?*,*)
+       # We are not doing octopus and not fast forward.  Need a
+       # real merge.
+       ;;
+?,1,*,)
+       # We are not doing octopus, not fast forward, and have only
+       # one common.
+       git update-index --refresh 2>/dev/null
+       case "$allow_trivial_merge" in
+       t)
+               # See if it is really trivial.
+               git var GIT_COMMITTER_IDENT >/dev/null || exit
+               echo "Trying really trivial in-index merge..."
+               if git read-tree --trivial -m -u -v $common $head "$1" &&
+                  result_tree=$(git write-tree)
+               then
+                       echo "Wonderful."
+                       result_commit=$(
+                               printf '%s\n' "$merge_msg" |
+                               git commit-tree $result_tree -p HEAD -p "$1"
+                       ) || exit
+                       finish "$result_commit" "In-index merge"
+                       dropsave
+                       exit 0
+               fi
+               echo "Nope."
+       esac
+       ;;
+*)
+       # An octopus.  If we can reach all the remote we are up to date.
+       up_to_date=t
+       for remote
+       do
+               common_one=$(git merge-base --all $head $remote)
+               if test "$common_one" != "$remote"
+               then
+                       up_to_date=f
+                       break
+               fi
+       done
+       if test "$up_to_date" = t
+       then
+               finish_up_to_date "Already up-to-date. Yeeah!"
+               exit 0
+       fi
+       ;;
+esac
+
+# We are going to make a new commit.
+git var GIT_COMMITTER_IDENT >/dev/null || exit
+
+# 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.
+
+case "$use_strategies" in
+?*' '?*)
+    # Stash away the local changes so that we can try more than one.
+    savestate
+    single_strategy=no
+    ;;
+*)
+    rm -f "$GIT_DIR/MERGE_STASH"
+    single_strategy=yes
+    ;;
+esac
+
+result_tree= best_cnt=-1 best_strategy= wt_strategy=
+merge_was_ok=
+for strategy in $use_strategies
+do
+    test "$wt_strategy" = '' || {
+       echo "Rewinding the tree to pristine..."
+       restorestate
+    }
+    case "$single_strategy" in
+    no)
+       echo "Trying merge strategy $strategy..."
+       ;;
+    esac
+
+    # Remember which strategy left the state in the working tree
+    wt_strategy=$strategy
+
+    git-merge-$strategy $common -- "$head_arg" "$@"
+    exit=$?
+    if test "$no_commit" = t && test "$exit" = 0
+    then
+        merge_was_ok=t
+       exit=1 ;# pretend it left conflicts.
+    fi
+
+    test "$exit" = 0 || {
+
+       # 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 test "$exit" -eq 1
+       then
+           cnt=`{
+               git diff-files --name-only
+               git ls-files --unmerged
+           } | wc -l`
+           if test $best_cnt -le 0 -o $cnt -le $best_cnt
+           then
+               best_strategy=$strategy
+               best_cnt=$cnt
+           fi
+       fi
+       continue
+    }
+
+    # Automerge succeeded.
+    result_tree=$(git write-tree) && break
+done
+
+# If we have a resulting tree, that means the strategy module
+# auto resolved the merge cleanly.
+if test '' != "$result_tree"
+then
+    if test "$allow_fast_forward" = "t"
+    then
+        parents=$(git show-branch --independent "$head" "$@")
+    else
+        parents=$(git rev-parse "$head" "$@")
+    fi
+    parents=$(echo "$parents" | sed -e 's/^/-p /')
+    result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit
+    finish "$result_commit" "Merge made by $wt_strategy."
+    dropsave
+    exit 0
+fi
+
+# Pick the result from the best strategy and have the user fix it up.
+case "$best_strategy" in
+'')
+       restorestate
+       case "$use_strategies" in
+       ?*' '?*)
+               echo >&2 "No merge strategy handled the merge."
+               ;;
+       *)
+               echo >&2 "Merge with strategy $use_strategies failed."
+               ;;
+       esac
+       exit 2
+       ;;
+"$wt_strategy")
+       # We already have its result in the working tree.
+       ;;
+*)
+       echo "Rewinding the tree to pristine..."
+       restorestate
+       echo "Using the $best_strategy to prepare resolving by hand."
+       git-merge-$best_strategy $common -- "$head_arg" "$@"
+       ;;
+esac
+
+if test "$squash" = t
+then
+       finish
+else
+       for remote
+       do
+               echo $remote
+       done >"$GIT_DIR/MERGE_HEAD"
+       printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG"
+fi
+
+if test "$merge_was_ok" = t
+then
+       echo >&2 \
+       "Automatic merge went well; stopped before committing as requested"
+       exit 0
+else
+       {
+           echo '
+Conflicts:
+'
+               git ls-files --unmerged |
+               sed -e 's/^[^   ]*      /       /' |
+               uniq
+       } >>"$GIT_DIR/MERGE_MSG"
+       git rerere
+       die "Automatic merge failed; fix conflicts and then commit the result."
+fi
index 352b69d4ceeec411a81469a6146e023aa1d6b338..78efed800d4d64898d438d9590b01be008cfcd36 100644 (file)
--- 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--;
 }
 
 /*
index b17722d66a7008b822125bb96ab1b7921e8b627b..e7eaff9a68ccbcc692522c9956f0dae9af45f3f1 100644 (file)
@@ -171,7 +171,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                        if (silent_on_removed)
                                continue;
                        diff_addremove(&revs->diffopt, '-', ce->ce_mode,
-                                      ce->sha1, ce->name, NULL);
+                                      ce->sha1, ce->name);
                        continue;
                }
                changed = ce_match_stat(ce, &st, ce_option);
@@ -184,7 +184,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                newmode = ce_mode_from_stat(ce, st.st_mode);
                diff_change(&revs->diffopt, oldmode, newmode,
                            ce->sha1, (changed ? null_sha1 : ce->sha1),
-                           ce->name, NULL);
+                           ce->name);
 
        }
        diffcore_std(&revs->diffopt);
@@ -208,7 +208,7 @@ static void diff_index_show_file(struct rev_info *revs,
                                 const unsigned char *sha1, unsigned int mode)
 {
        diff_addremove(&revs->diffopt, prefix[0], mode,
-                      sha1, ce->name, NULL);
+                      sha1, ce->name);
 }
 
 static int get_stat_data(struct cache_entry *ce,
@@ -312,7 +312,7 @@ static int show_modified(struct oneway_unpack_data *cbdata,
                return 0;
 
        diff_change(&revs->diffopt, oldmode, mode,
-                   old->sha1, sha1, old->name, NULL);
+                   old->sha1, sha1, old->name);
        return 0;
 }
 
diff --git a/diff.c b/diff.c
index 6a39b393f3e276dd0e0352b5c05eec6989940854..a07812c5c75f1f579295df392efed27f6a5f6fc1 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -3412,9 +3412,8 @@ int diff_result_code(struct diff_options *opt, int status)
 void diff_addremove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
-                   const char *base, const char *path)
+                   const char *concatpath)
 {
-       char concatpath[PATH_MAX];
        struct diff_filespec *one, *two;
 
        if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(mode))
@@ -3436,9 +3435,6 @@ void diff_addremove(struct diff_options *options,
                addremove = (addremove == '+' ? '-' :
                             addremove == '-' ? '+' : addremove);
 
-       if (!path) path = "";
-       sprintf(concatpath, "%s%s", base, path);
-
        if (options->prefix &&
            strncmp(concatpath, options->prefix, options->prefix_length))
                return;
@@ -3459,9 +3455,8 @@ void diff_change(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
-                const char *base, const char *path)
+                const char *concatpath)
 {
-       char concatpath[PATH_MAX];
        struct diff_filespec *one, *two;
 
        if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(old_mode)
@@ -3474,8 +3469,6 @@ void diff_change(struct diff_options *options,
                tmp = old_mode; old_mode = new_mode; new_mode = tmp;
                tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
        }
-       if (!path) path = "";
-       sprintf(concatpath, "%s%s", base, path);
 
        if (options->prefix &&
            strncmp(concatpath, options->prefix, options->prefix_length))
diff --git a/diff.h b/diff.h
index 5dc0cb595b64868ca8b8e26e5311d2df102c89e0..50fb5ddb0bec02b0cd5498d6ecc37d44bf874476 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -14,12 +14,12 @@ typedef void (*change_fn_t)(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
-                const char *base, const char *path);
+                const char *fullpath);
 
 typedef void (*add_remove_fn_t)(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
-                   const char *base, const char *path);
+                   const char *fullpath);
 
 typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
                struct diff_options *options, void *data);
@@ -164,14 +164,13 @@ extern void diff_addremove(struct diff_options *,
                           int addremove,
                           unsigned mode,
                           const unsigned char *sha1,
-                          const char *base,
-                          const char *path);
+                          const char *fullpath);
 
 extern void diff_change(struct diff_options *,
                        unsigned mode1, unsigned mode2,
                        const unsigned char *sha1,
                        const unsigned char *sha2,
-                       const char *base, const char *path);
+                       const char *fullpath);
 
 extern void diff_unmerge(struct diff_options *,
                         const char *path,
index da04efe951e5b8dc4e7b72246212d8ebabc09ec6..8899e31b3b9edb528a339df394f891f76b8de594 100644 (file)
@@ -40,6 +40,16 @@ static const char *builtin_exec_path(void)
 #endif
 }
 
+const char *system_path(const char *path)
+{
+       if (!is_absolute_path(path)) {
+               struct strbuf d = STRBUF_INIT;
+               strbuf_addf(&d, "%s/%s", git_exec_path(), path);
+               path = strbuf_detach(&d, NULL);
+       }
+       return path;
+}
+
 void git_set_argv_exec_path(const char *exec_path)
 {
        argv_exec_path = exec_path;
index a892355c8212298130fb3925c6cba352ed6999b6..7eb94e5e11c07dd269cf59fa5c85845c787d86dd 100644 (file)
@@ -6,6 +6,6 @@ extern const char* git_exec_path(void);
 extern void setup_path(const char *);
 extern int execv_git_cmd(const char **argv); /* NULL terminated */
 extern int execl_git_cmd(const char *cmd, ...);
-
+extern const char *system_path(const char *path);
 
 #endif /* GIT_EXEC_CMD_H */
index a6a5c52b59b893d6f69fce483c82c762a089003d..da768ee7acc22e6480f0a067e109239561d43927 100755 (executable)
@@ -54,7 +54,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 ? "\"$_\"": $_ } @_;
index 52b5669b431140cb44cc65a25ae980f45d70fbe0..cc8787b460e601889fd90c5dba2055e9eb85bc22 100755 (executable)
--- 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
                }
index 8c7e11473390c14956e43843c17a2d85a336a087..cf89cdf4598b3796724a85aa707f740245155cdc 100644 (file)
@@ -157,6 +157,12 @@ extern void set_warn_routine(void (*routine)(const char *warn, va_list params));
 extern int prefixcmp(const char *str, const char *prefix);
 extern time_t tm_to_time_t(const struct tm *tm);
 
+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
index 920bbe15a3bbc7768d47a2adbc84aa81264e2226..b00d1c29cd3a7d2cd8bbcd7ea4f531e050fcfcb9 100755 (executable)
@@ -1884,7 +1884,7 @@ sub req_annotate
     }
 
     # done; get out of the tempdir
-    cleanupWorkDir();
+    cleanupWorkTree();
 
     print "ok\n";
 
diff --git a/git-merge.sh b/git-merge.sh
deleted file mode 100755 (executable)
index 8026ccf..0000000
+++ /dev/null
@@ -1,554 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano
-#
-
-OPTIONS_KEEPDASHDASH=
-OPTIONS_SPEC="\
-git-merge [options] <remote>...
-git-merge [options] <msg> HEAD <remote>
---
-stat                 show a diffstat at the end of the merge
-n                    don't show a diffstat at the end of the merge
-summary              (synonym to --stat)
-log                  add list of one-line log to merge commit message
-squash               create a single commit instead of doing a merge
-commit               perform a commit if the merge succeeds (default)
-ff                   allow fast forward (default)
-s,strategy=          merge strategy to use
-m,message=           message to be used for the merge commit (if any)
-"
-
-SUBDIRECTORY_OK=Yes
-. git-sh-setup
-require_work_tree
-cd_to_toplevel
-
-test -z "$(git ls-files -u)" ||
-       die "You are in the middle of a conflicted merge."
-
-LF='
-'
-
-all_strategies='recur recursive octopus resolve stupid ours subtree'
-default_twohead_strategies='recursive'
-default_octopus_strategies='octopus'
-no_fast_forward_strategies='subtree ours'
-no_trivial_strategies='recursive recur subtree ours'
-use_strategies=
-
-allow_fast_forward=t
-allow_trivial_merge=t
-squash= no_commit= log_arg=
-
-dropsave() {
-       rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
-                "$GIT_DIR/MERGE_STASH" || exit 1
-}
-
-savestate() {
-       # Stash away any local modifications.
-       git stash create >"$GIT_DIR/MERGE_STASH"
-}
-
-restorestate() {
-        if test -f "$GIT_DIR/MERGE_STASH"
-       then
-               git reset --hard $head >/dev/null
-               git stash apply $(cat "$GIT_DIR/MERGE_STASH")
-               git update-index --refresh >/dev/null
-       fi
-}
-
-finish_up_to_date () {
-       case "$squash" in
-       t)
-               echo "$1 (nothing to squash)" ;;
-       '')
-               echo "$1" ;;
-       esac
-       dropsave
-}
-
-squash_message () {
-       echo Squashed commit of the following:
-       echo
-       git log --no-merges --pretty=medium ^"$head" $remoteheads
-}
-
-finish () {
-       if test '' = "$2"
-       then
-               rlogm="$GIT_REFLOG_ACTION"
-       else
-               echo "$2"
-               rlogm="$GIT_REFLOG_ACTION: $2"
-       fi
-       case "$squash" in
-       t)
-               echo "Squash commit -- not updating HEAD"
-               squash_message >"$GIT_DIR/SQUASH_MSG"
-               ;;
-       '')
-               case "$merge_msg" in
-               '')
-                       echo "No merge message -- not updating HEAD"
-                       ;;
-               *)
-                       git update-ref -m "$rlogm" HEAD "$1" "$head" || exit 1
-                       git gc --auto
-                       ;;
-               esac
-               ;;
-       esac
-       case "$1" in
-       '')
-               ;;
-       ?*)
-               if test "$show_diffstat" = t
-               then
-                       # We want color (if set), but no pager
-                       GIT_PAGER='' git diff --stat --summary -M "$head" "$1"
-               fi
-               ;;
-       esac
-
-       # Run a post-merge hook
-        if test -x "$GIT_DIR"/hooks/post-merge
-        then
-           case "$squash" in
-           t)
-                "$GIT_DIR"/hooks/post-merge 1
-               ;;
-           '')
-                "$GIT_DIR"/hooks/post-merge 0
-               ;;
-           esac
-        fi
-}
-
-merge_name () {
-       remote="$1"
-       rh=$(git rev-parse --verify "$remote^0" 2>/dev/null) || return
-       bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null)
-       if test "$rh" = "$bh"
-       then
-               echo "$rh               branch '$remote' of ."
-       elif truname=$(expr "$remote" : '\(.*\)~[1-9][0-9]*$') &&
-               git show-ref -q --verify "refs/heads/$truname" 2>/dev/null
-       then
-               echo "$rh               branch '$truname' (early part) of ."
-       elif test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD"
-       then
-               sed -e 's/      not-for-merge   /               /' -e 1q \
-                       "$GIT_DIR/FETCH_HEAD"
-       else
-               echo "$rh               commit '$remote'"
-       fi
-}
-
-parse_config () {
-       while test $# != 0; do
-               case "$1" in
-               -n|--no-stat|--no-summary)
-                       show_diffstat=false ;;
-               --stat|--summary)
-                       show_diffstat=t ;;
-               --log|--no-log)
-                       log_arg=$1 ;;
-               --squash)
-                       test "$allow_fast_forward" = t ||
-                               die "You cannot combine --squash with --no-ff."
-                       squash=t no_commit=t ;;
-               --no-squash)
-                       squash= no_commit= ;;
-               --commit)
-                       no_commit= ;;
-               --no-commit)
-                       no_commit=t ;;
-               --ff)
-                       allow_fast_forward=t ;;
-               --no-ff)
-                       test "$squash" != t ||
-                               die "You cannot combine --squash with --no-ff."
-                       allow_fast_forward=f ;;
-               -s|--strategy)
-                       shift
-                       case " $all_strategies " in
-                       *" $1 "*)
-                               use_strategies="$use_strategies$1 " ;;
-                       *)
-                               die "available strategies are: $all_strategies" ;;
-                       esac
-                       ;;
-               -m|--message)
-                       shift
-                       merge_msg="$1"
-                       have_message=t
-                       ;;
-               --)
-                       shift
-                       break ;;
-               *)      usage ;;
-               esac
-               shift
-       done
-       args_left=$#
-}
-
-test $# != 0 || usage
-
-have_message=
-
-if branch=$(git-symbolic-ref -q HEAD)
-then
-       mergeopts=$(git config "branch.${branch#refs/heads/}.mergeoptions")
-       if test -n "$mergeopts"
-       then
-               parse_config $mergeopts --
-       fi
-fi
-
-parse_config "$@"
-while test $args_left -lt $#; do shift; done
-
-if test -z "$show_diffstat"; then
-    test "$(git config --bool merge.diffstat)" = false && show_diffstat=false
-    test "$(git config --bool merge.stat)" = false && show_diffstat=false
-    test -z "$show_diffstat" && show_diffstat=t
-fi
-
-# This could be traditional "merge <msg> HEAD <commit>..."  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.
-
-if test -z "$have_message" &&
-       second_token=$(git rev-parse --verify "$2^0" 2>/dev/null) &&
-       head_commit=$(git rev-parse --verify "HEAD" 2>/dev/null) &&
-       test "$second_token" = "$head_commit"
-then
-       merge_msg="$1"
-       shift
-       head_arg="$1"
-       shift
-elif ! git rev-parse --verify HEAD >/dev/null 2>&1
-then
-       # 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 test 1 -ne $#
-       then
-               echo >&2 "Can merge only exactly one commit into empty head"
-               exit 1
-       fi
-
-       rh=$(git rev-parse --verify "$1^0") ||
-               die "$1 - not something we can merge"
-
-       git update-ref -m "initial pull" HEAD "$rh" "" &&
-       git read-tree --reset -u HEAD
-       exit
-
-else
-       # 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.
-       merge_name=$(for remote
-               do
-                       merge_name "$remote"
-               done | git fmt-merge-msg $log_arg
-       )
-       merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name"
-fi
-head=$(git rev-parse --verify "$head_arg"^0) || usage
-
-# All the rest are remote heads
-test "$#" = 0 && usage ;# we need at least one remote head.
-set_reflog_action "merge $*"
-
-remoteheads=
-for remote
-do
-       remotehead=$(git rev-parse --verify "$remote"^0 2>/dev/null) ||
-           die "$remote - not something we can merge"
-       remoteheads="${remoteheads}$remotehead "
-       eval GITHEAD_$remotehead='"$remote"'
-       export GITHEAD_$remotehead
-done
-set x $remoteheads ; shift
-
-case "$use_strategies" in
-'')
-       case "$#" in
-       1)
-               var="`git config --get pull.twohead`"
-               if test -n "$var"
-               then
-                       use_strategies="$var"
-               else
-                       use_strategies="$default_twohead_strategies"
-               fi ;;
-       *)
-               var="`git config --get pull.octopus`"
-               if test -n "$var"
-               then
-                       use_strategies="$var"
-               else
-                       use_strategies="$default_octopus_strategies"
-               fi ;;
-       esac
-       ;;
-esac
-
-for s in $use_strategies
-do
-       for ss in $no_fast_forward_strategies
-       do
-               case " $s " in
-               *" $ss "*)
-                       allow_fast_forward=f
-                       break
-                       ;;
-               esac
-       done
-       for ss in $no_trivial_strategies
-       do
-               case " $s " in
-               *" $ss "*)
-                       allow_trivial_merge=f
-                       break
-                       ;;
-               esac
-       done
-done
-
-case "$#" in
-1)
-       common=$(git merge-base --all $head "$@")
-       ;;
-*)
-       common=$(git show-branch --merge-base $head "$@")
-       ;;
-esac
-echo "$head" >"$GIT_DIR/ORIG_HEAD"
-
-case "$allow_fast_forward,$#,$common,$no_commit" in
-?,*,'',*)
-       # No common ancestors found. We need a real merge.
-       ;;
-?,1,"$1",*)
-       # 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."
-       exit 0
-       ;;
-t,1,"$head",*)
-       # Again the most common case of merging one remote.
-       echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $1)"
-       git update-index --refresh 2>/dev/null
-       msg="Fast forward"
-       if test -n "$have_message"
-       then
-               msg="$msg (no commit created; -m option ignored)"
-       fi
-       new_head=$(git rev-parse --verify "$1^0") &&
-       git read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" &&
-       finish "$new_head" "$msg" || exit
-       dropsave
-       exit 0
-       ;;
-?,1,?*"$LF"?*,*)
-       # We are not doing octopus and not fast forward.  Need a
-       # real merge.
-       ;;
-?,1,*,)
-       # We are not doing octopus, not fast forward, and have only
-       # one common.
-       git update-index --refresh 2>/dev/null
-       case "$allow_trivial_merge" in
-       t)
-               # See if it is really trivial.
-               git var GIT_COMMITTER_IDENT >/dev/null || exit
-               echo "Trying really trivial in-index merge..."
-               if git read-tree --trivial -m -u -v $common $head "$1" &&
-                  result_tree=$(git write-tree)
-               then
-                       echo "Wonderful."
-                       result_commit=$(
-                               printf '%s\n' "$merge_msg" |
-                               git commit-tree $result_tree -p HEAD -p "$1"
-                       ) || exit
-                       finish "$result_commit" "In-index merge"
-                       dropsave
-                       exit 0
-               fi
-               echo "Nope."
-       esac
-       ;;
-*)
-       # An octopus.  If we can reach all the remote we are up to date.
-       up_to_date=t
-       for remote
-       do
-               common_one=$(git merge-base --all $head $remote)
-               if test "$common_one" != "$remote"
-               then
-                       up_to_date=f
-                       break
-               fi
-       done
-       if test "$up_to_date" = t
-       then
-               finish_up_to_date "Already up-to-date. Yeeah!"
-               exit 0
-       fi
-       ;;
-esac
-
-# We are going to make a new commit.
-git var GIT_COMMITTER_IDENT >/dev/null || exit
-
-# 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.
-
-case "$use_strategies" in
-?*' '?*)
-    # Stash away the local changes so that we can try more than one.
-    savestate
-    single_strategy=no
-    ;;
-*)
-    rm -f "$GIT_DIR/MERGE_STASH"
-    single_strategy=yes
-    ;;
-esac
-
-result_tree= best_cnt=-1 best_strategy= wt_strategy=
-merge_was_ok=
-for strategy in $use_strategies
-do
-    test "$wt_strategy" = '' || {
-       echo "Rewinding the tree to pristine..."
-       restorestate
-    }
-    case "$single_strategy" in
-    no)
-       echo "Trying merge strategy $strategy..."
-       ;;
-    esac
-
-    # Remember which strategy left the state in the working tree
-    wt_strategy=$strategy
-
-    git-merge-$strategy $common -- "$head_arg" "$@"
-    exit=$?
-    if test "$no_commit" = t && test "$exit" = 0
-    then
-        merge_was_ok=t
-       exit=1 ;# pretend it left conflicts.
-    fi
-
-    test "$exit" = 0 || {
-
-       # 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 test "$exit" -eq 1
-       then
-           cnt=`{
-               git diff-files --name-only
-               git ls-files --unmerged
-           } | wc -l`
-           if test $best_cnt -le 0 -o $cnt -le $best_cnt
-           then
-               best_strategy=$strategy
-               best_cnt=$cnt
-           fi
-       fi
-       continue
-    }
-
-    # Automerge succeeded.
-    result_tree=$(git write-tree) && break
-done
-
-# If we have a resulting tree, that means the strategy module
-# auto resolved the merge cleanly.
-if test '' != "$result_tree"
-then
-    if test "$allow_fast_forward" = "t"
-    then
-        parents=$(git show-branch --independent "$head" "$@")
-    else
-        parents=$(git rev-parse "$head" "$@")
-    fi
-    parents=$(echo "$parents" | sed -e 's/^/-p /')
-    result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit
-    finish "$result_commit" "Merge made by $wt_strategy."
-    dropsave
-    exit 0
-fi
-
-# Pick the result from the best strategy and have the user fix it up.
-case "$best_strategy" in
-'')
-       restorestate
-       case "$use_strategies" in
-       ?*' '?*)
-               echo >&2 "No merge strategy handled the merge."
-               ;;
-       *)
-               echo >&2 "Merge with strategy $use_strategies failed."
-               ;;
-       esac
-       exit 2
-       ;;
-"$wt_strategy")
-       # We already have its result in the working tree.
-       ;;
-*)
-       echo "Rewinding the tree to pristine..."
-       restorestate
-       echo "Using the $best_strategy to prepare resolving by hand."
-       git-merge-$best_strategy $common -- "$head_arg" "$@"
-       ;;
-esac
-
-if test "$squash" = t
-then
-       finish
-else
-       for remote
-       do
-               echo $remote
-       done >"$GIT_DIR/MERGE_HEAD"
-       printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG"
-fi
-
-if test "$merge_was_ok" = t
-then
-       echo >&2 \
-       "Automatic merge went well; stopped before committing as requested"
-       exit 0
-else
-       {
-           echo '
-Conflicts:
-'
-               git ls-files --unmerged |
-               sed -e 's/^[^   ]*      /       /' |
-               uniq
-       } >>"$GIT_DIR/MERGE_MSG"
-       git rerere
-       die "Automatic merge failed; fix conflicts and then commit the result."
-fi
index 7cd8f7134e696312d243d73acebb6ecfe07d1e13..d1efa1d7419c23b65af1dccf4b8a00ed0e94c79c 100755 (executable)
@@ -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"
index a35212d9de23e97055936b23dd8657a584116d76..e63a864c7b1b5057737fda1e52c88d1d5e40322f 100755 (executable)
@@ -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
@@ -174,6 +174,8 @@ pick_one_preserving_merges () {
                                new_parents="$new_parents $new_p"
                                ;;
                        esac
+               else
+                       new_parents="$new_parents $p"
                fi
        done
        case $fast_forward in
index 2597d777d6fa8e9beb2a660ecdc10366747b4e23..6ef5754814babdb908d794ea030aceca6597b190 100755 (executable)
@@ -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 <branch> and remove the .dotest working files, use the command
+original <branch> and remove the .git/rebase working files, use the command
 git rebase --abort instead.
 
 Note that if <branch> 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
@@ -376,8 +376,7 @@ fi
 
 # Detach HEAD and reset the tree
 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 checkout -q "$onto^0" || die "could not detach HEAD"
 git update-ref ORIG_HEAD $branch
 
 # If the $onto is a proper descendant of the tip of the branch, then
@@ -396,10 +395,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/git.c b/git.c
index 7075533aa7243abacf100cb8a8987284d5d144de..74ea0e6cc52f627df1bf018770aec67584325ab5 100644 (file)
--- a/git.c
+++ b/git.c
@@ -127,59 +127,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;
@@ -366,6 +313,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/help.c b/help.c
index ca9632b6c58267776086f48e87e2236ff1f6053f..52d39b88a36e92b00828fdb8c8d056f9b79a37e0 100644 (file)
--- a/help.c
+++ b/help.c
@@ -633,15 +633,29 @@ static void show_info_page(const char *git_cmd)
 static void get_html_page_path(struct strbuf *page_path, const char *page)
 {
        struct stat st;
+       const char *html_path = system_path(GIT_HTML_PATH);
 
        /* Check that we have a git documentation directory. */
-       if (stat(GIT_HTML_PATH "/git.html", &st) || !S_ISREG(st.st_mode))
-               die("'%s': not a documentation directory.", GIT_HTML_PATH);
+       if (stat(mkpath("%s/git.html", html_path), &st)
+           || !S_ISREG(st.st_mode))
+               die("'%s': not a documentation directory.", html_path);
 
        strbuf_init(page_path, 0);
-       strbuf_addf(page_path, GIT_HTML_PATH "/%s.html", page);
+       strbuf_addf(page_path, "%s/%s.html", html_path, page);
 }
 
+/*
+ * If open_html is not defined in a platform-specific way (see for
+ * example compat/mingw.h), we use the script web--browse to display
+ * HTML.
+ */
+#ifndef open_html
+void open_html(const char *path)
+{
+       execl_git_cmd("web--browse", "-c", "help.browser", path, NULL);
+}
+#endif
+
 static void show_html_page(const char *git_cmd)
 {
        const char *page = cmd_to_page(git_cmd);
@@ -649,7 +663,7 @@ static void show_html_page(const char *git_cmd)
 
        get_html_page_path(&page_path, page);
 
-       execl_git_cmd("web--browse", "-c", "help.browser", page_path.buf, NULL);
+       open_html(page_path.buf);
 }
 
 void help_unknown_cmd(const char *cmd)
index 25db5db24b239409657510e8c2eeb3845b374bbf..b4ec7361748640c7ced04c10e9087e12c553d928 100644 (file)
@@ -26,6 +26,14 @@ union delta_base {
        off_t offset;
 };
 
+struct base_data {
+       struct base_data *base;
+       struct base_data *child;
+       struct object_entry *obj;
+       void *data;
+       unsigned long size;
+};
+
 /*
  * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
  * to memcmp() only the first 20 bytes.
@@ -43,6 +51,8 @@ struct delta_entry
 
 static struct object_entry *objects;
 static struct delta_entry *deltas;
+static struct base_data *base_cache;
+static size_t base_cache_used;
 static int nr_objects;
 static int nr_deltas;
 static int nr_resolved_deltas;
@@ -211,6 +221,46 @@ static void bad_object(unsigned long offset, const char *format, ...)
        die("pack has bad object at offset %lu: %s", offset, buf);
 }
 
+static void prune_base_data(struct base_data *retain)
+{
+       struct base_data *b = base_cache;
+       for (b = base_cache;
+            base_cache_used > delta_base_cache_limit && b;
+            b = b->child) {
+               if (b->data && b != retain) {
+                       free(b->data);
+                       b->data = NULL;
+                       base_cache_used -= b->size;
+               }
+       }
+}
+
+static void link_base_data(struct base_data *base, struct base_data *c)
+{
+       if (base)
+               base->child = c;
+       else
+               base_cache = c;
+
+       c->base = base;
+       c->child = NULL;
+       base_cache_used += c->size;
+       prune_base_data(c);
+}
+
+static void unlink_base_data(struct base_data *c)
+{
+       struct base_data *base = c->base;
+       if (base)
+               base->child = NULL;
+       else
+               base_cache = NULL;
+       if (c->data) {
+               free(c->data);
+               base_cache_used -= c->size;
+       }
+}
+
 static void *unpack_entry_data(unsigned long offset, unsigned long size)
 {
        z_stream stream;
@@ -426,33 +476,60 @@ static void sha1_object(const void *data, unsigned long size,
        }
 }
 
-static void resolve_delta(struct object_entry *delta_obj, void *base_data,
-                         unsigned long base_size, enum object_type type)
+static void *get_base_data(struct base_data *c)
+{
+       if (!c->data) {
+               struct object_entry *obj = c->obj;
+
+               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+                       void *base = get_base_data(c->base);
+                       void *raw = get_data_from_pack(obj);
+                       c->data = patch_delta(
+                               base, c->base->size,
+                               raw, obj->size,
+                               &c->size);
+                       free(raw);
+                       if (!c->data)
+                               bad_object(obj->idx.offset, "failed to apply delta");
+               } else
+                       c->data = get_data_from_pack(obj);
+
+               base_cache_used += c->size;
+               prune_base_data(c);
+       }
+       return c->data;
+}
+
+static void resolve_delta(struct object_entry *delta_obj,
+                         struct base_data *base_obj, enum object_type type)
 {
        void *delta_data;
        unsigned long delta_size;
-       void *result;
-       unsigned long result_size;
        union delta_base delta_base;
        int j, first, last;
+       struct base_data result;
 
        delta_obj->real_type = type;
        delta_data = get_data_from_pack(delta_obj);
        delta_size = delta_obj->size;
-       result = patch_delta(base_data, base_size, delta_data, delta_size,
-                            &result_size);
+       result.data = patch_delta(get_base_data(base_obj), base_obj->size,
+                            delta_data, delta_size,
+                            &result.size);
        free(delta_data);
-       if (!result)
+       if (!result.data)
                bad_object(delta_obj->idx.offset, "failed to apply delta");
-       sha1_object(result, result_size, type, delta_obj->idx.sha1);
+       sha1_object(result.data, result.size, type, delta_obj->idx.sha1);
        nr_resolved_deltas++;
 
+       result.obj = delta_obj;
+       link_base_data(base_obj, &result);
+
        hashcpy(delta_base.sha1, delta_obj->idx.sha1);
        if (!find_delta_children(&delta_base, &first, &last)) {
                for (j = first; j <= last; j++) {
                        struct object_entry *child = objects + deltas[j].obj_no;
                        if (child->real_type == OBJ_REF_DELTA)
-                               resolve_delta(child, result, result_size, type);
+                               resolve_delta(child, &result, type);
                }
        }
 
@@ -462,11 +539,11 @@ static void resolve_delta(struct object_entry *delta_obj, void *base_data,
                for (j = first; j <= last; j++) {
                        struct object_entry *child = objects + deltas[j].obj_no;
                        if (child->real_type == OBJ_OFS_DELTA)
-                               resolve_delta(child, result, result_size, type);
+                               resolve_delta(child, &result, type);
                }
        }
 
-       free(result);
+       unlink_base_data(&result);
 }
 
 static int compare_delta_entry(const void *a, const void *b)
@@ -481,7 +558,6 @@ static void parse_pack_objects(unsigned char *sha1)
 {
        int i;
        struct delta_entry *delta = deltas;
-       void *data;
        struct stat st;
 
        /*
@@ -496,7 +572,7 @@ static void parse_pack_objects(unsigned char *sha1)
                                nr_objects);
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = &objects[i];
-               data = unpack_raw_entry(obj, &delta->base);
+               void *data = unpack_raw_entry(obj, &delta->base);
                obj->real_type = obj->type;
                if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
                        nr_deltas++;
@@ -545,6 +621,7 @@ static void parse_pack_objects(unsigned char *sha1)
                struct object_entry *obj = &objects[i];
                union delta_base base;
                int j, ref, ref_first, ref_last, ofs, ofs_first, ofs_last;
+               struct base_data base_obj;
 
                if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
                        continue;
@@ -555,22 +632,24 @@ static void parse_pack_objects(unsigned char *sha1)
                ofs = !find_delta_children(&base, &ofs_first, &ofs_last);
                if (!ref && !ofs)
                        continue;
-               data = get_data_from_pack(obj);
+               base_obj.data = get_data_from_pack(obj);
+               base_obj.size = obj->size;
+               base_obj.obj = obj;
+               link_base_data(NULL, &base_obj);
+
                if (ref)
                        for (j = ref_first; j <= ref_last; j++) {
                                struct object_entry *child = objects + deltas[j].obj_no;
                                if (child->real_type == OBJ_REF_DELTA)
-                                       resolve_delta(child, data,
-                                                     obj->size, obj->type);
+                                       resolve_delta(child, &base_obj, obj->type);
                        }
                if (ofs)
                        for (j = ofs_first; j <= ofs_last; j++) {
                                struct object_entry *child = objects + deltas[j].obj_no;
                                if (child->real_type == OBJ_OFS_DELTA)
-                                       resolve_delta(child, data,
-                                                     obj->size, obj->type);
+                                       resolve_delta(child, &base_obj, obj->type);
                        }
-               free(data);
+               unlink_base_data(&base_obj);
                display_progress(progress, nr_resolved_deltas);
        }
 }
@@ -601,7 +680,8 @@ static int write_compressed(int fd, void *in, unsigned int size, uint32_t *obj_c
        return size;
 }
 
-static void append_obj_to_pack(const unsigned char *sha1, void *buf,
+static struct object_entry *append_obj_to_pack(
+                              const unsigned char *sha1, void *buf,
                               unsigned long size, enum object_type type)
 {
        struct object_entry *obj = &objects[nr_objects++];
@@ -622,6 +702,7 @@ static void append_obj_to_pack(const unsigned char *sha1, void *buf,
        obj[1].idx.offset = obj[0].idx.offset + n;
        obj[1].idx.offset += write_compressed(output_fd, buf, size, &obj[0].idx.crc32);
        hashcpy(obj->idx.sha1, sha1);
+       return obj;
 }
 
 static int delta_pos_compare(const void *_a, const void *_b)
@@ -656,28 +737,31 @@ static void fix_unresolved_deltas(int nr_unresolved)
 
        for (i = 0; i < n; i++) {
                struct delta_entry *d = sorted_by_pos[i];
-               void *data;
-               unsigned long size;
                enum object_type type;
                int j, first, last;
+               struct base_data base_obj;
 
                if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
                        continue;
-               data = read_sha1_file(d->base.sha1, &type, &size);
-               if (!data)
+               base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size);
+               if (!base_obj.data)
                        continue;
 
+               if (check_sha1_signature(d->base.sha1, base_obj.data,
+                               base_obj.size, typename(type)))
+                       die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
+               base_obj.obj = append_obj_to_pack(d->base.sha1, base_obj.data,
+                       base_obj.size, type);
+               link_base_data(NULL, &base_obj);
+
                find_delta_children(&d->base, &first, &last);
                for (j = first; j <= last; j++) {
                        struct object_entry *child = objects + deltas[j].obj_no;
                        if (child->real_type == OBJ_REF_DELTA)
-                               resolve_delta(child, data, size, type);
+                               resolve_delta(child, &base_obj, type);
                }
 
-               if (check_sha1_signature(d->base.sha1, data, size, typename(type)))
-                       die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
-               append_obj_to_pack(d->base.sha1, data, size, type);
-               free(data);
+               unlink_base_data(&base_obj);
                display_progress(progress, nr_resolved_deltas);
        }
        free(sorted_by_pos);
index ae88885d4da573c85dbfbeea3061f3b026223710..987b0157192b9fe4c7451d94f5948bdbd7f524bb 100644 (file)
@@ -5,12 +5,6 @@
 #define OPT_SHORT 1
 #define OPT_UNSET 2
 
-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)
@@ -220,7 +214,7 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
        return -2;
 }
 
-void check_typos(const char *arg, const struct option *options)
+static void check_typos(const char *arg, const struct option *options)
 {
        if (strlen(arg) < 3)
                return;
index 8eb39e915e730ff5039247695adc1e25c14773f4..628a5201c1e8aa3b985bf0b382e5511e17d953d4 100644 (file)
--- 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 */
index f83de8c4158e08bab3e0445c4d15caf2d4d105aa..d801f9d1cf9eca5ddeff2f0af4549bbc1e72423f 100644 (file)
@@ -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;
+}
index bbd563e6514f98ed5678314573fc4521b9fa0a95..3897fec53170c50921eb1952bc4bdf9c08480638 100644 (file)
@@ -260,7 +260,7 @@ static int tree_difference = REV_TREE_SAME;
 static void file_add_remove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
-                   const char *base, const char *path)
+                   const char *fullpath)
 {
        int diff = REV_TREE_DIFFERENT;
 
@@ -286,7 +286,7 @@ static void file_change(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
-                const char *base, const char *path)
+                const char *fullpath)
 {
        tree_difference = REV_TREE_DIFFERENT;
        DIFF_OPT_SET(options, HAS_CHANGES);
@@ -413,10 +413,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;
@@ -446,7 +462,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;
        }
@@ -471,7 +487,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;
@@ -612,7 +628,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);
@@ -1415,10 +1431,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;
@@ -1542,7 +1560,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;
                }
 
index a778865ae7d015535763b856391ba751f2a9f87c..0d65cedaa6566a6dd654753cb574c9ee64b1c90b 100644 (file)
@@ -26,7 +26,7 @@ clean:
        $(RM) -r 'trash directory' test-results
 
 aggregate-results:
-       ./aggregate-results.sh test-results/t*-*
+       '$(SHELL_PATH_SQ)' ./aggregate-results.sh test-results/t*-*
 
 # we can test NO_OPTIMIZE_COMMITS independently of LC_ALL
 full-svn-test:
index 52e88e30465b8de4f0d9bd592bdd525e0f31145a..d5bab75d7da49ebb53e368d67f6b867f5417a125 100755 (executable)
@@ -10,9 +10,9 @@ for file
 do
        while read type value
        do
-               case $type in
-               '')
-                       continue ;;
+               case $type in
+               '')
+                       continue ;;
                fixed)
                        fixed=$(($fixed + $value)) ;;
                success)
@@ -20,9 +20,9 @@ do
                failed)
                        failed=$(($failed + $value)) ;;
                broken)
-                       broken=$(( $broken + $value)) ;;
+                       broken=$(($broken + $value)) ;;
                total)
-                       total=$(( $total + $value)) ;;
+                       total=$(($total + $value)) ;;
                esac
        done <"$file"
 done
index 5d3bd9dda916d454997da0015c35d0fa7d9f72c0..a841df2a9e3a9ed64e81ab7b9778e59cfc714cad 100644 (file)
@@ -98,7 +98,7 @@ LoadModule dav_module $SVN_HTTPD_MODULE_PATH/mod_dav.so
 LoadModule dav_svn_module $SVN_HTTPD_MODULE_PATH/mod_dav_svn.so
 <Location /$repo_base_path>
        DAV svn
-       SVNPath $rawsvnrepo
+       SVNPath "$rawsvnrepo"
 </Location>
 EOF
        "$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k start
index 4934a4e01092e27e07254b10ed3e54e8699b54ac..36d9a2ae3aa7adb0bff18bb2a4180a2ba4369766 100755 (executable)
@@ -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
index 092aa265733b0c6cbf75ac532c3c006457668bc8..ffe3dd97b7b1c056d854e28795e1313ce1633452 100755 (executable)
@@ -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' '
index 1777ffe8a22d8f9dd28efadd3cb9ea65be9c2178..12c8804a030f889bc0a416d5743600aa8a427eb7 100755 (executable)
@@ -74,7 +74,7 @@ testrebase() {
        '
 }
 
-testrebase "" .dotest
-testrebase " --merge" .git/.dotest-merge
+testrebase "" .git/rebase
+testrebase " --merge" .git/rebase-merge
 
 test_done
index bc982607d09bbdeb0d486eb282f5756a48ae5bcb..5cbd5ef6798bcef1c011893a4759cac4047dc6b7 100755 (executable)
@@ -102,7 +102,7 @@ test_expect_success 'am applies patch correctly' '
        git checkout first &&
        test_tick &&
        git am <patch1 &&
-       ! test -d .dotest &&
+       ! test -d .git/rebase &&
        test -z "$(git diff second)" &&
        test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
        test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
@@ -123,7 +123,7 @@ test_expect_success 'am changes committer and keeps author' '
        test_tick &&
        git checkout first &&
        git am patch2 &&
-       ! test -d .dotest &&
+       ! test -d .git/rebase &&
        test "$(git rev-parse master^^)" = "$(git rev-parse HEAD^^)" &&
        test -z "$(git diff master..HEAD)" &&
        test -z "$(git diff master^..HEAD^)" &&
@@ -163,7 +163,7 @@ test_expect_success 'am without --keep removes Re: and [PATCH] stuff' '
 test_expect_success 'am --keep really keeps the subject' '
        git checkout HEAD^ &&
        git am --keep patch4 &&
-       ! test -d .dotest &&
+       ! test -d .git/rebase &&
        git-cat-file commit HEAD |
                grep -q -F "Re: Re: Re: [PATCH 1/5 v2] third"
 '
@@ -176,19 +176,19 @@ test_expect_success 'am -3 falls back to 3-way merge' '
        test_tick &&
        git commit -m "copied stuff" &&
        git am -3 lorem-move.patch &&
-       ! test -d .dotest &&
+       ! test -d .git/rebase &&
        test -z "$(git diff lorem)"
 '
 
 test_expect_success 'am pauses on conflict' '
        git checkout lorem2^^ &&
        ! git am lorem-move.patch &&
-       test -d .dotest
+       test -d .git/rebase
 '
 
 test_expect_success 'am --skip works' '
        git am --skip &&
-       ! test -d .dotest &&
+       ! test -d .git/rebase &&
        test -z "$(git diff lorem2^^ -- file)" &&
        test goodbye = "$(cat another)"
 '
@@ -196,31 +196,31 @@ test_expect_success 'am --skip works' '
 test_expect_success 'am --resolved works' '
        git checkout lorem2^^ &&
        ! git am lorem-move.patch &&
-       test -d .dotest &&
+       test -d .git/rebase &&
        echo resolved >>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 &&
-       rm -r .dotest/
+       rm -r .git/rebase/
 '
 
 test_expect_success 'am fails on empty patch' '
        echo "---" >>failmail &&
        ! git am <failmail &&
        git am --skip &&
-       ! test -d .dotest
+       ! test -d .git/rebase
 '
 
 test_expect_success 'am works from stdin in subdirectory' '
index 0ab14a6e81302db6a1239d68270ab909cb2fd216..331b9b07d4eedb07377de605ebb87691427b7bb4 100755 (executable)
@@ -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
index d21cd290d3d8e3d912b94045f3d3d9db8bdd7e7d..9ab5bdacc41d026ea81c65b0d95a61a61521429a 100755 (executable)
@@ -221,37 +221,13 @@ test_expect_success 'setup' '
 
 test_debug 'gitk --all'
 
-test_expect_success 'test option parsing' '
-       if git merge -$ c1
-       then
-               echo "[OOPS] -$ accepted"
-               false
-       fi &&
-       if git merge --no-such c1
-       then
-               echo "[OOPS] --no-such accepted"
-               false
-       fi &&
-       if git merge -s foobar c1
-       then
-               echo "[OOPS] -s foobar accepted"
-               false
-       fi &&
-       if git merge -s=foobar c1
-       then
-               echo "[OOPS] -s=foobar accepted"
-               false
-       fi &&
-       if git merge -m
-       then
-               echo "[OOPS] missing commit msg accepted"
-               false
-       fi &&
-       if git merge
-       then
-               echo "[OOPS] missing commit references accepted"
-               false
-       fi
+test_expect_failure 'test option parsing' '
+       test_must_fail git merge -$ c1 &&
+       test_must_fail git merge --no-such c1 &&
+       test_must_fail git merge -s foobar c1 &&
+       test_must_fail git merge -s=foobar c1 &&
+       test_must_fail git merge -m &&
+       test_must_fail git merge
 '
 
 test_expect_success 'merge c0 with c1' '
@@ -465,11 +441,51 @@ 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"
 '
 
 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 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
diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh
new file mode 100755 (executable)
index 0000000..95b4d71
--- /dev/null
@@ -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()
+{
+       {
+               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
+       resolve_count=$(conflict_count) &&
+       git reset --hard c5 &&
+       git merge -s recursive c6
+       recursive_count=$(conflict_count) &&
+       git reset --hard c5 &&
+       git merge c6
+       auto_count=$(conflict_count) &&
+       test $auto_count = $recursive_count &&
+       test $auto_count != $resolve_count
+'
+
+test_done
diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh
new file mode 100755 (executable)
index 0000000..fcb8285
--- /dev/null
@@ -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_success '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
diff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh
new file mode 100755 (executable)
index 0000000..17b19dc
--- /dev/null
@@ -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
diff --git a/t/t7604-merge-custom-message.sh b/t/t7604-merge-custom-message.sh
new file mode 100755 (executable)
index 0000000..6081677
--- /dev/null
@@ -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
diff --git a/t/t7605-merge-resolve.sh b/t/t7605-merge-resolve.sh
new file mode 100755 (executable)
index 0000000..ee21a10
--- /dev/null
@@ -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
index 58a3a7b1c333131051282acbae71dc6a1cb16b13..27a65e05df20c2695b9fb849be62d715c968a433 100755 (executable)
@@ -87,7 +87,7 @@ test_expect_success 'multiple dcommit from git-svn will not clobber svn' "
        "
 
 
-test_expect_success 'check that rebase really failed' 'test -d .dotest'
+test_expect_success 'check that rebase really failed' 'test -d .git/rebase'
 
 test_expect_success 'resolve, continue the rebase and dcommit' "
        echo clobber and I really mean it > file &&
index e1e2e6c6ce3c4effffb26e9aed7c6bdd2b00a5ae..bbb126fc46cfb28a0bc92cc0842c0dc72017751d 100644 (file)
@@ -15,6 +15,15 @@ static char *malloc_base(const char *base, int baselen, const char *path, int pa
        return newbase;
 }
 
+static char *malloc_fullname(const char *base, int baselen, const char *path, int pathlen)
+{
+       char *fullname = xmalloc(baselen + pathlen + 1);
+       memcpy(fullname, base, baselen);
+       memcpy(fullname + baselen, path, pathlen);
+       fullname[baselen + pathlen] = 0;
+       return fullname;
+}
+
 static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
                       const char *base, int baselen);
 
@@ -24,6 +33,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
        const char *path1, *path2;
        const unsigned char *sha1, *sha2;
        int cmp, pathlen1, pathlen2;
+       char *fullname;
 
        sha1 = tree_entry_extract(t1, &path1, &mode1);
        sha2 = tree_entry_extract(t2, &path2, &mode2);
@@ -55,15 +65,20 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
        if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) {
                int retval;
                char *newbase = malloc_base(base, baselen, path1, pathlen1);
-               if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE))
+               if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
+                       newbase[baselen + pathlen1] = 0;
                        opt->change(opt, mode1, mode2,
-                                   sha1, sha2, base, path1);
+                                   sha1, sha2, newbase);
+                       newbase[baselen + pathlen1] = '/';
+               }
                retval = diff_tree_sha1(sha1, sha2, newbase, opt);
                free(newbase);
                return retval;
        }
 
-       opt->change(opt, mode1, mode2, sha1, sha2, base, path1);
+       fullname = malloc_fullname(base, baselen, path1, pathlen1);
+       opt->change(opt, mode1, mode2, sha1, sha2, fullname);
+       free(fullname);
        return 0;
 }
 
@@ -205,10 +220,10 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
        unsigned mode;
        const char *path;
        const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
+       int pathlen = tree_entry_len(path, sha1);
 
        if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) {
                enum object_type type;
-               int pathlen = tree_entry_len(path, sha1);
                char *newbase = malloc_base(base, baselen, path, pathlen);
                struct tree_desc inner;
                void *tree;
@@ -224,7 +239,9 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
                free(tree);
                free(newbase);
        } else {
-               opt->add_remove(opt, prefix[0], mode, sha1, base, path);
+               char *fullname = malloc_fullname(base, baselen, path, pathlen);
+               opt->add_remove(opt, prefix[0], mode, sha1, fullname);
+               free(fullname);
        }
 }