Merge branch 'jc/merge'
authorJunio C Hamano <gitster@pobox.com>
Tue, 19 May 2015 20:17:57 +0000 (13:17 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 19 May 2015 20:17:57 +0000 (13:17 -0700)
"git merge FETCH_HEAD" learned that the previous "git fetch" could
be to create an Octopus merge, i.e. recording multiple branches
that are not marked as "not-for-merge"; this allows us to lose an
old style invocation "git merge <msg> HEAD $commits..." in the
implementation of "git pull" script; the old style syntax can now
be deprecated.

* jc/merge:
merge: deprecate 'git merge <message> HEAD <commit>' syntax
merge: handle FETCH_HEAD internally
merge: decide if we auto-generate the message early in collect_parents()
merge: make collect_parents() auto-generate the merge message
merge: extract prepare_merge_message() logic out
merge: narrow scope of merge_names
merge: split reduce_parents() out of collect_parents()
merge: clarify collect_parents() logic
merge: small leakfix and code simplification
merge: do not check argc to determine number of remote heads
merge: clarify "pulling into void" special case
t5520: test pulling an octopus into an unborn branch
t5520: style fixes
merge: simplify code flow
merge: test the top-level merge driver

1  2 
Documentation/git-merge.txt
builtin/merge.c
git-pull.sh
index 1f94908e3c0a803c32eb2392ef4dc8158c6e1c53,d9aa6b6f118b10604f2579af3f987dfe8e4e8184..273a1009be002e64ee83e1835bdd1c3e1e84f526
@@@ -104,6 -104,10 +104,10 @@@ commit or stash your changes before run
  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
@@@ -232,7 -236,7 +236,7 @@@ Barbie's remark on your side.  The onl
  side wants to say it is hard and you'd prefer to go shopping, while the
  other side wants to claim it is easy.
  
 -An alternative style can be used by setting the "merge.conflictstyle"
 +An alternative style can be used by setting the "merge.conflictStyle"
  configuration variable to "diff3".  In "diff3" style, the above conflict
  may look like this:
  
@@@ -329,7 -333,7 +333,7 @@@ CONFIGURATIO
  -------------
  include::merge-config.txt[]
  
 -branch.<name>.mergeoptions::
 +branch.<name>.mergeOptions::
        Sets default options for merging into branch <name>. The syntax and
        supported options are the same as those of 'git merge', but option
        values containing whitespace characters are currently not supported.
diff --combined builtin/merge.c
index 3b0f8f96d4168463139d15f1cde655facc73426c,67fbfafa027bfb8dbb7410626957a8975b89c4f5..f89f60e11a523d45a81c3bd3a92fa9b4c5021673
@@@ -29,7 -29,6 +29,7 @@@
  #include "remote.h"
  #include "fmt-merge-msg.h"
  #include "gpg-interface.h"
 +#include "sequencer.h"
  
  #define DEFAULT_TWOHEAD (1<<0)
  #define DEFAULT_OCTOPUS (1<<1)
@@@ -42,8 -41,8 +42,8 @@@ struct strategy 
  };
  
  static const char * const builtin_merge_usage[] = {
 -      N_("git merge [options] [<commit>...]"),
 -      N_("git merge [options] <msg> HEAD <commit>"),
 +      N_("git merge [<options>] [<commit>...]"),
 +      N_("git merge [<options>] <msg> HEAD <commit>"),
        N_("git merge --abort"),
        NULL
  };
@@@ -492,8 -491,7 +492,7 @@@ static void merge_name(const char *remo
        }
        if (len) {
                struct strbuf truname = STRBUF_INIT;
-               strbuf_addstr(&truname, "refs/heads/");
-               strbuf_addstr(&truname, remote);
+               strbuf_addf(&truname, "refs/heads/%s", remote);
                strbuf_setlen(&truname, truname.len - len);
                if (ref_exists(truname.buf)) {
                        strbuf_addf(msg,
                        strbuf_release(&truname);
                        goto cleanup;
                }
-       }
-       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;
+               strbuf_release(&truname);
        }
  
        if (remote_head->util) {
@@@ -881,20 -858,28 +859,20 @@@ static int finish_automerge(struct comm
        return 0;
  }
  
 -static int suggest_conflicts(int renormalizing)
 +static int suggest_conflicts(void)
  {
        const char *filename;
        FILE *fp;
 -      int pos;
 +      struct strbuf msgbuf = STRBUF_INIT;
  
        filename = git_path("MERGE_MSG");
        fp = fopen(filename, "a");
        if (!fp)
                die_errno(_("Could not open '%s' for writing"), filename);
 -      fprintf(fp, "\nConflicts:\n");
 -      for (pos = 0; pos < active_nr; pos++) {
 -              const 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++;
 -              }
 -      }
 +
 +      append_conflicts_hint(&msgbuf);
 +      fputs(msgbuf.buf, fp);
 +      strbuf_release(&msgbuf);
        fclose(fp);
        rerere(allow_rerere_auto);
        printf(_("Automatic merge failed; "
@@@ -1037,28 -1022,24 +1015,24 @@@ static int default_edit_option(void
                st_stdin.st_mode == st_stdout.st_mode);
  }
  
- static struct commit_list *collect_parents(struct commit *head_commit,
-                                          int *head_subsumed,
-                                          int argc, const char **argv)
+ static struct commit_list *reduce_parents(struct commit *head_commit,
+                                         int *head_subsumed,
+                                         struct commit_list *remoteheads)
  {
-       int i;
-       struct commit_list *remoteheads = NULL, *parents, *next;
-       struct commit_list **remotes = &remoteheads;
+       struct commit_list *parents, *next, **remotes = &remoteheads;
  
-       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;
-       }
-       *remotes = NULL;
+       /*
+        * Is the current HEAD reachable from another commit being
+        * merged?  If so we do not want to record it as a parent of
+        * the resulting merge, unless --no-ff is given.  We will flip
+        * this variable to 0 when we find HEAD among the independent
+        * tips being merged.
+        */
+       *head_subsumed = 1;
  
+       /* Find what parents to record by checking independent ones. */
        parents = reduce_heads(remoteheads);
  
-       *head_subsumed = 1; /* we will flip this to 0 when we find it */
        for (remoteheads = NULL, remotes = &remoteheads;
             parents;
             parents = next) {
                        *head_subsumed = 0;
                else
                        remotes = &commit_list_insert(commit, remotes)->next;
+               free(parents);
        }
        return remoteheads;
  }
  
+ static void prepare_merge_message(struct strbuf *merge_names, struct strbuf *merge_msg)
+ {
+       struct fmt_merge_msg_opts opts;
+       memset(&opts, 0, sizeof(opts));
+       opts.add_title = !have_message;
+       opts.shortlog_len = shortlog_len;
+       opts.credit_people = (0 < option_edit);
+       fmt_merge_msg(merge_names, merge_msg, &opts);
+       if (merge_msg->len)
+               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,
+                                          struct strbuf *merge_msg)
+ {
+       int i;
+       struct commit_list *remoteheads = NULL;
+       struct commit_list **remotes = &remoteheads;
+       struct strbuf merge_names = STRBUF_INIT, *autogen = NULL;
+       if (merge_msg && (!have_message || shortlog_len))
+               autogen = &merge_names;
+       if (head_commit)
+               remotes = &commit_list_insert(head_commit, remotes)->next;
+       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) {
+               prepare_merge_message(autogen, merge_msg);
+               strbuf_release(autogen);
+       }
+       return remoteheads;
+ }
  int cmd_merge(int argc, const char **argv, const char *prefix)
  {
        unsigned char result_tree[20];
                option_commit = 0;
        }
  
-       if (!abort_current_merge) {
-               if (!argc) {
-                       if (default_to_upstream)
-                               argc = setup_with_upstream(&argv);
-                       else
-                               die(_("No commit specified and merge.defaultToUpstream not set."));
-               } else if (argc == 1 && !strcmp(argv[0], "-"))
-                       argv[0] = "@{-1}";
+       if (!argc) {
+               if (default_to_upstream)
+                       argc = setup_with_upstream(&argv);
+               else
+                       die(_("No commit specified and merge.defaultToUpstream not set."));
+       } else if (argc == 1 && !strcmp(argv[0], "-")) {
+               argv[0] = "@{-1}";
        }
        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
-        * commit-ish 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 (!have_message && head_commit &&
-           is_old_style_invocation(argc, argv, head_commit->object.sha1)) {
-               strbuf_addstr(&merge_msg, argv[0]);
-               head_arg = argv[1];
-               argv += 2;
-               argc -= 2;
-               remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
-       } else if (!head_commit) {
+       if (!head_commit) {
                struct commit *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"));
                if (squash)
                        die(_("Squash commit into empty head not supported yet"));
                if (fast_forward == FF_NO)
                        die(_("Non-fast-forward commit does not make sense into "
                            "an empty head"));
-               remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
+               remoteheads = collect_parents(head_commit, &head_subsumed,
+                                             argc, argv, NULL);
                remote_head = remoteheads->item;
                if (!remote_head)
                        die(_("%s - not something we can merge"), argv[0]);
+               if (remoteheads->next)
+                       die(_("Can merge only exactly one commit into empty head"));
                read_empty(remote_head->object.sha1, 0);
                update_ref("initial pull", "HEAD", remote_head->object.sha1,
                           NULL, 0, UPDATE_REFS_DIE_ON_ERR);
                goto done;
-       } else {
-               struct strbuf merge_names = STRBUF_INIT;
+       }
  
+       /*
+        * 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
+        * commit-ish 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 (!have_message &&
+           is_old_style_invocation(argc, argv, head_commit->object.sha1)) {
+               warning("old-style 'git merge <msg> HEAD <commit>' is deprecated.");
+               strbuf_addstr(&merge_msg, argv[0]);
+               head_arg = argv[1];
+               argv += 2;
+               argc -= 2;
+               remoteheads = collect_parents(head_commit, &head_subsumed,
+                                             argc, argv, NULL);
+       } else {
                /* We are invoked directly as the first-class UI. */
                head_arg = "HEAD";
  
                 * the standard merge summary message to be appended
                 * to the given message.
                 */
-               remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
-               for (p = remoteheads; p; p = p->next)
-                       merge_name(merge_remote_util(p->item)->name, &merge_names);
-               if (!have_message || shortlog_len) {
-                       struct fmt_merge_msg_opts opts;
-                       memset(&opts, 0, sizeof(opts));
-                       opts.add_title = !have_message;
-                       opts.shortlog_len = shortlog_len;
-                       opts.credit_people = (0 < option_edit);
-                       fmt_merge_msg(&merge_names, &merge_msg, &opts);
-                       if (merge_msg.len)
-                               strbuf_setlen(&merge_msg, merge_msg.len - 1);
-               }
+               remoteheads = collect_parents(head_commit, &head_subsumed,
+                                             argc, argv, &merge_msg);
        }
  
        if (!head_commit || !argc)
        if (!remoteheads)
                ; /* already up-to-date */
        else if (!remoteheads->next)
 -              common = get_merge_bases(head_commit, remoteheads->item, 1);
 +              common = get_merge_bases(head_commit, remoteheads->item);
        else {
                struct commit_list *list = remoteheads;
                commit_list_insert(head_commit, &list);
                         * merge_bases again, otherwise "git merge HEAD^
                         * HEAD^^" would be missed.
                         */
 -                      common_one = get_merge_bases(head_commit, j->item, 1);
 +                      common_one = get_merge_bases(head_commit, j->item);
                        if (hashcmp(common_one->item->object.sha1,
                                j->item->object.sha1)) {
                                up_to_date = 0;
                fprintf(stderr, _("Automatic merge went well; "
                        "stopped before committing as requested\n"));
        else
 -              ret = suggest_conflicts(option_renormalize);
 +              ret = suggest_conflicts();
  
  done:
        free(branch_to_free);
diff --combined git-pull.sh
index ad442264cbae0003dc548b85769cbeb2107c7d5f,15d943190c5bae23e28b5cfa63c4c99c84a5416a..9ed01fd21919f2e6b849aef20e9496bf52f41f41
@@@ -240,7 -240,7 +240,7 @@@ test true = "$rebase" && 
        if ! git rev-parse -q --verify HEAD >/dev/null
        then
                # On an unborn branch
 -              if test -f "$GIT_DIR/index"
 +              if test -f "$(git rev-parse --git-path index)"
                then
                        die "$(gettext "updating an unborn branch with changes added to the index")"
                fi
@@@ -323,7 -323,6 +323,6 @@@ the
        fi
  fi
  
- merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
  case "$rebase" in
  true)
        eval="git-rebase $diffstat $strategy_args $merge_args $rebase_args $verbosity"
        eval="git-merge $diffstat $no_commit $verify_signatures $edit $squash $no_ff $ff_only"
        eval="$eval $log_arg $strategy_args $merge_args $verbosity $progress"
        eval="$eval $gpg_sign_args"
-       eval="$eval \"\$merge_name\" HEAD $merge_head"
+       eval="$eval FETCH_HEAD"
        ;;
  esac
  eval "exec $eval"