#include "sequencer.h"
#include "string-list.h"
#include "packfile.h"
+#include "tag.h"
#define DEFAULT_TWOHEAD (1<<0)
#define DEFAULT_OCTOPUS (1<<1)
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;
}
test_cmp expected actual
'
+test_expect_success 'merge annotated/signed tag w/o tracking' '
+ test_when_finished "rm -rf dst; git tag -d anno1" &&
+ git tag -a -m "anno c1" anno1 c1 &&
+ git init dst &&
+ git rev-parse c1 >dst/expect &&
+ (
+ # c0 fast-forwards to c1 but because this repository
+ # is not a "downstream" whose refs/tags follows along
+ # tag from the "upstream", this pull defaults to --no-ff
+ cd dst &&
+ git pull .. c0 &&
+ git pull .. anno1 &&
+ git rev-parse HEAD^2 >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'merge annotated/signed tag w/ tracking' '
+ test_when_finished "rm -rf dst; git tag -d anno1" &&
+ git tag -a -m "anno c1" anno1 c1 &&
+ git init dst &&
+ git rev-parse c1 >dst/expect &&
+ (
+ # c0 fast-forwards to c1 and because this repository
+ # is a "downstream" whose refs/tags follows along
+ # tag from the "upstream", this pull defaults to --ff
+ cd dst &&
+ git remote add origin .. &&
+ git pull origin c0 &&
+ git fetch origin &&
+ git merge anno1 &&
+ git rev-parse HEAD >actual &&
+ test_cmp expect actual
+ )
+'
+
test_expect_success GPG 'merge --ff-only tag' '
git reset --hard c0 &&
git commit --allow-empty -m "A newer commit" &&
git tag -f -s -m "A newer commit" signed &&
git reset --hard c0 &&
- EDITOR=false git merge --no-edit signed &&
+ EDITOR=false git merge --no-edit --no-ff signed &&
git rev-parse signed^0 >expect &&
git rev-parse HEAD^2 >actual &&
test_cmp expect actual