Merge branch 'jc/allow-ff-merging-kept-tags' into next
authorJunio C Hamano <gitster@pobox.com>
Tue, 27 Feb 2018 19:15:12 +0000 (11:15 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 27 Feb 2018 19:15:12 +0000 (11:15 -0800)
Since Git 1.7.9, "git merge" defaulted to --no-ff (i.e. even when
the side branch being merged is a descendant of the current commit,
create a merge commit instead of fast-forwarding) when merging a
tag object. This was appropriate default for integrators who pull
signed tags from their downstream contributors, but caused an
unnecessary merges when used by downstream contributors who
habitually "catch up" their topic branches with tagged releases
from the upstream. Update "git merge" to default to --no-ff only
when merging a tag object that does *not* sit at its usual place in
refs/tags/ hierarchy, and allow fast-forwarding otherwise, to
mitigate the problem.

* jc/allow-ff-merging-kept-tags:
merge: allow fast-forward when merging a tracked tag

1  2 
builtin/merge.c
diff --combined builtin/merge.c
index 92ba99a1a5efff8598251c2ed806bae12832ae0a,532522a854791658e751e349b7173db6331280e6..f598001db2584941a2b9b936bcd00bd0f8c22fc1
@@@ -33,6 -33,7 +33,7 @@@
  #include "sequencer.h"
  #include "string-list.h"
  #include "packfile.h"
+ #include "tag.h"
  
  #define DEFAULT_TWOHEAD (1<<0)
  #define DEFAULT_OCTOPUS (1<<1)
@@@ -820,8 -821,8 +821,8 @@@ static int merge_trivial(struct commit 
        pptr = commit_list_append(head, pptr);
        pptr = commit_list_append(remoteheads->item, pptr);
        prepare_to_commit(remoteheads);
 -      if (commit_tree(merge_msg.buf, merge_msg.len, result_tree.hash, parents,
 -                      result_commit.hash, NULL, sign_commit))
 +      if (commit_tree(merge_msg.buf, merge_msg.len, &result_tree, parents,
 +                      &result_commit, NULL, sign_commit))
                die(_("failed to write commit object"));
        finish(head, remoteheads, &result_commit, "In-index merge");
        drop_save();
@@@ -845,8 -846,8 +846,8 @@@ static int finish_automerge(struct comm
                commit_list_insert(head, &parents);
        strbuf_addch(&merge_msg, '\n');
        prepare_to_commit(remoteheads);
 -      if (commit_tree(merge_msg.buf, merge_msg.len, result_tree->hash, parents,
 -                      result_commit.hash, NULL, sign_commit))
 +      if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents,
 +                      &result_commit, NULL, sign_commit))
                die(_("failed to write commit object"));
        strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
        finish(head, remoteheads, &result_commit, buf.buf);
@@@ -1125,6 -1126,43 +1126,43 @@@ static struct commit_list *collect_pare
        return remoteheads;
  }
  
+ static int merging_a_throwaway_tag(struct commit *commit)
+ {
+       char *tag_ref;
+       struct object_id oid;
+       int is_throwaway_tag = 0;
+       /* Are we merging a tag? */
+       if (!merge_remote_util(commit) ||
+           !merge_remote_util(commit)->obj ||
+           merge_remote_util(commit)->obj->type != OBJ_TAG)
+               return is_throwaway_tag;
+       /*
+        * Now we know we are merging a tag object.  Are we downstream
+        * and following the tags from upstream?  If so, we must have
+        * the tag object pointed at by "refs/tags/$T" where $T is the
+        * tagname recorded in the tag object.  We want to allow such
+        * a "just to catch up" merge to fast-forward.
+        *
+        * Otherwise, we are playing an integrator's role, making a
+        * merge with a throw-away tag from a contributor with
+        * something like "git pull $contributor $signed_tag".
+        * We want to forbid such a merge from fast-forwarding
+        * by default; otherwise we would not keep the signature
+        * anywhere.
+        */
+       tag_ref = xstrfmt("refs/tags/%s",
+                         ((struct tag *)merge_remote_util(commit)->obj)->tag);
+       if (!read_ref(tag_ref, &oid) &&
+           !oidcmp(&oid, &merge_remote_util(commit)->obj->oid))
+               is_throwaway_tag = 0;
+       else
+               is_throwaway_tag = 1;
+       free(tag_ref);
+       return is_throwaway_tag;
+ }
  int cmd_merge(int argc, const char **argv, const char *prefix)
  {
        struct object_id result_tree, stash, head_oid;
                            oid_to_hex(&commit->object.oid));
                setenv(buf.buf, merge_remote_util(commit)->name, 1);
                strbuf_reset(&buf);
-               if (fast_forward != FF_ONLY &&
-                   merge_remote_util(commit) &&
-                   merge_remote_util(commit)->obj &&
-                   merge_remote_util(commit)->obj->type == OBJ_TAG)
+               if (fast_forward != FF_ONLY && merging_a_throwaway_tag(commit))
                        fast_forward = FF_NO;
        }