merge: handle FETCH_HEAD internally
authorJunio C Hamano <gitster@pobox.com>
Sun, 26 Apr 2015 01:47:21 +0000 (18:47 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 29 Apr 2015 20:27:31 +0000 (13:27 -0700)
The collect_parents() function now is responsible for

1. parsing the commits given on the command line into a list of
commits to be merged;

2. filtering these parents into independent ones; and

3. optionally calling fmt_merge_msg() via prepare_merge_message()
to prepare an auto-generated merge log message, using fake
contents that FETCH_HEAD would have had if these commits were
fetched from the current repository with "git pull . $args..."

Make "git merge FETCH_HEAD" to be the same as the traditional

git merge "$(git fmt-merge-msg <.git/FETCH_HEAD)" $commits

invocation of the command in "git pull", where $commits are the ones
that appear in FETCH_HEAD that are not marked as not-for-merge, by
making it do a bit more, specifically:

- noticing "FETCH_HEAD" is the only "commit" on the command line
and picking the commits that are not marked as not-for-merge as
the list of commits to be merged (substitute for step #1 above);

- letting the resulting list fed to step #2 above;

- doing the step #3 above, using the contents of the FETCH_HEAD
instead of fake contents crafted from the list of commits parsed
in the step #1 above.

Note that this changes the semantics. "git merge FETCH_HEAD" has
always behaved as if the first commit in the FETCH_HEAD file were
directly specified on the command line, creating a two-way merge
whose auto-generated merge log said "merge commit xyz". With this
change, if the previous fetch was to grab multiple branches (e.g.
"git fetch $there topic-a topic-b"), the new world order is to
create an octopus, behaving as if "git pull $there topic-a topic-b"
were run. This is a deliberate change to make that happen, and
can be seen in the changes to t3033 tests.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-merge.txt
builtin/merge.c
t/t3033-merge-toplevel.sh
index cf2c374b710673e891f8337cb9bcc90d72be460a..d9aa6b6f118b10604f2579af3f987dfe8e4e8184 100644 (file)
@@ -104,6 +104,10 @@ commit or stash your changes before running 'git merge'.
 If no commit is given from the command line, merge the remote-tracking
 branches that the current branch is configured to use as its upstream.
 See also the configuration section of this manual page.
 If no commit is given from the command line, merge the remote-tracking
 branches that the current branch is configured to use as its upstream.
 See also the configuration section of this manual page.
++
+When `FETCH_HEAD` (and no other commit) is specified, the branches
+recorded in the `.git/FETCH_HEAD` file by the previous invocation
+of `git fetch` for merging are merged to the current branch.
 
 
 PRE-MERGE CHECKS
 
 
 PRE-MERGE CHECKS
index eb3be6817efa24584deaae333130971173577ce2..42f9fcc1745c05f13d685981b14a9ac22f82dc77 100644 (file)
@@ -505,28 +505,6 @@ static void merge_name(const char *remote, struct strbuf *msg)
                strbuf_release(&truname);
        }
 
                strbuf_release(&truname);
        }
 
-       if (!strcmp(remote, "FETCH_HEAD") &&
-                       !access(git_path("FETCH_HEAD"), R_OK)) {
-               const char *filename;
-               FILE *fp;
-               struct strbuf line = STRBUF_INIT;
-               char *ptr;
-
-               filename = git_path("FETCH_HEAD");
-               fp = fopen(filename, "r");
-               if (!fp)
-                       die_errno(_("could not open '%s' for reading"),
-                                 filename);
-               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);
-               goto cleanup;
-       }
-
        if (remote_head->util) {
                struct merge_remote_desc *desc;
                desc = merge_remote_util(remote_head);
        if (remote_head->util) {
                struct merge_remote_desc *desc;
                desc = merge_remote_util(remote_head);
@@ -1090,6 +1068,60 @@ static void prepare_merge_message(struct strbuf *merge_names, struct strbuf *mer
                strbuf_setlen(merge_msg, merge_msg->len - 1);
 }
 
                strbuf_setlen(merge_msg, merge_msg->len - 1);
 }
 
+static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge_names)
+{
+       const char *filename;
+       int fd, pos, npos;
+       struct strbuf fetch_head_file = STRBUF_INIT;
+
+       if (!merge_names)
+               merge_names = &fetch_head_file;
+
+       filename = git_path("FETCH_HEAD");
+       fd = open(filename, O_RDONLY);
+       if (fd < 0)
+               die_errno(_("could not open '%s' for reading"), filename);
+
+       if (strbuf_read(merge_names, fd, 0) < 0)
+               die_errno(_("could not read '%s'"), filename);
+       if (close(fd) < 0)
+               die_errno(_("could not close '%s'"), filename);
+
+       for (pos = 0; pos < merge_names->len; pos = npos) {
+               unsigned char sha1[20];
+               char *ptr;
+               struct commit *commit;
+
+               ptr = strchr(merge_names->buf + pos, '\n');
+               if (ptr)
+                       npos = ptr - merge_names->buf + 1;
+               else
+                       npos = merge_names->len;
+
+               if (npos - pos < 40 + 2 ||
+                   get_sha1_hex(merge_names->buf + pos, sha1))
+                       commit = NULL; /* bad */
+               else if (memcmp(merge_names->buf + pos + 40, "\t\t", 2))
+                       continue; /* not-for-merge */
+               else {
+                       char saved = merge_names->buf[pos + 40];
+                       merge_names->buf[pos + 40] = '\0';
+                       commit = get_merge_parent(merge_names->buf + pos);
+                       merge_names->buf[pos + 40] = saved;
+               }
+               if (!commit) {
+                       if (ptr)
+                               *ptr = '\0';
+                       die("not something we can merge in %s: %s",
+                           filename, merge_names->buf + pos);
+               }
+               remotes = &commit_list_insert(commit, remotes)->next;
+       }
+
+       if (merge_names == &fetch_head_file)
+               strbuf_release(&fetch_head_file);
+}
+
 static struct commit_list *collect_parents(struct commit *head_commit,
                                           int *head_subsumed,
                                           int argc, const char **argv,
 static struct commit_list *collect_parents(struct commit *head_commit,
                                           int *head_subsumed,
                                           int argc, const char **argv,
@@ -1105,21 +1137,27 @@ static struct commit_list *collect_parents(struct commit *head_commit,
 
        if (head_commit)
                remotes = &commit_list_insert(head_commit, remotes)->next;
 
        if (head_commit)
                remotes = &commit_list_insert(head_commit, remotes)->next;
-       for (i = 0; i < argc; i++) {
-               struct commit *commit = get_merge_parent(argv[i]);
-               if (!commit)
-                       help_unknown_ref(argv[i], "merge",
-                                        "not something we can merge");
-               remotes = &commit_list_insert(commit, remotes)->next;
-       }
 
 
-       remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads);
+       if (argc == 1 && !strcmp(argv[0], "FETCH_HEAD")) {
+               handle_fetch_head(remotes, autogen);
+               remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads);
+       } else {
+               for (i = 0; i < argc; i++) {
+                       struct commit *commit = get_merge_parent(argv[i]);
+                       if (!commit)
+                               help_unknown_ref(argv[i], "merge",
+                                                "not something we can merge");
+                       remotes = &commit_list_insert(commit, remotes)->next;
+               }
+               remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads);
+               if (autogen) {
+                       struct commit_list *p;
+                       for (p = remoteheads; p; p = p->next)
+                               merge_name(merge_remote_util(p->item)->name, autogen);
+               }
+       }
 
        if (autogen) {
 
        if (autogen) {
-               struct commit_list *p;
-               for (p = remoteheads; p; p = p->next)
-                       merge_name(merge_remote_util(p->item)->name, autogen);
-
                prepare_merge_message(autogen, merge_msg);
                strbuf_release(autogen);
        }
                prepare_merge_message(autogen, merge_msg);
                strbuf_release(autogen);
        }
index 9d92d3c1a27d08f15ffa2510b5017e32aecfce94..46aadc410bc470d9279ed1fd3ee72583d0963d30 100755 (executable)
@@ -77,7 +77,7 @@ test_expect_success 'merge octopus, non-fast-forward' '
 
 # The same set with FETCH_HEAD
 
 
 # The same set with FETCH_HEAD
 
-test_expect_failure 'merge FETCH_HEAD octopus into void' '
+test_expect_success 'merge FETCH_HEAD octopus into void' '
        t3033_reset &&
        git checkout --orphan test &&
        git rm -fr . &&
        t3033_reset &&
        git checkout --orphan test &&
        git rm -fr . &&
@@ -88,7 +88,7 @@ test_expect_failure 'merge FETCH_HEAD octopus into void' '
        test_must_fail git rev-parse HEAD
 '
 
        test_must_fail git rev-parse HEAD
 '
 
-test_expect_failure 'merge FETCH_HEAD octopus fast-forward (ff)' '
+test_expect_success 'merge FETCH_HEAD octopus fast-forward (ff)' '
        t3033_reset &&
        git reset --hard one &&
        git fetch . left right &&
        t3033_reset &&
        git reset --hard one &&
        git fetch . left right &&
@@ -100,7 +100,7 @@ test_expect_failure 'merge FETCH_HEAD octopus fast-forward (ff)' '
        test_cmp expect actual
 '
 
        test_cmp expect actual
 '
 
-test_expect_failure 'merge FETCH_HEAD octopus non-fast-forward (ff)' '
+test_expect_success 'merge FETCH_HEAD octopus non-fast-forward (ff)' '
        t3033_reset &&
        git reset --hard one &&
        git fetch . left right &&
        t3033_reset &&
        git reset --hard one &&
        git fetch . left right &&
@@ -112,7 +112,7 @@ test_expect_failure 'merge FETCH_HEAD octopus non-fast-forward (ff)' '
        test_cmp expect actual
 '
 
        test_cmp expect actual
 '
 
-test_expect_failure 'merge FETCH_HEAD octopus fast-forward (does not ff)' '
+test_expect_success 'merge FETCH_HEAD octopus fast-forward (does not ff)' '
        t3033_reset &&
        git fetch . left right &&
        git merge FETCH_HEAD &&
        t3033_reset &&
        git fetch . left right &&
        git merge FETCH_HEAD &&
@@ -123,7 +123,7 @@ test_expect_failure 'merge FETCH_HEAD octopus fast-forward (does not ff)' '
        test_cmp expect actual
 '
 
        test_cmp expect actual
 '
 
-test_expect_failure 'merge FETCH_HEAD octopus non-fast-forward' '
+test_expect_success 'merge FETCH_HEAD octopus non-fast-forward' '
        t3033_reset &&
        git fetch . left right &&
        git merge --no-ff FETCH_HEAD &&
        t3033_reset &&
        git fetch . left right &&
        git merge --no-ff FETCH_HEAD &&