Merge branch 'cw/tag-reflog-message'
authorJunio C Hamano <gitster@pobox.com>
Mon, 27 Feb 2017 21:57:13 +0000 (13:57 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 27 Feb 2017 21:57:13 +0000 (13:57 -0800)
"git tag" did not leave useful message when adding a new entry to
reflog; this was left unnoticed for a long time because refs/tags/*
doesn't keep reflog by default.

* cw/tag-reflog-message:
tag: generate useful reflog message

1  2 
builtin/tag.c
t/t7004-tag.sh
diff --combined builtin/tag.c
index e6c1cf7006896482c37b52981c7f8b8904efafda,085d8abcaff3c8ce1e4f59b0386c5550c9140504..e5e2c6a446261f5e4543ae0ead0cbadb8ed7c6cb
@@@ -24,7 -24,7 +24,7 @@@ static const char * const git_tag_usage
        N_("git tag -d <tagname>..."),
        N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
                "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
 -      N_("git tag -v <tagname>..."),
 +      N_("git tag -v [--format=<format>] <tagname>..."),
        NULL
  };
  
@@@ -45,11 -45,11 +45,11 @@@ static int list_tags(struct ref_filter 
        if (!format) {
                if (filter->lines) {
                        to_free = xstrfmt("%s %%(contents:lines=%d)",
 -                                        "%(align:15)%(refname:strip=2)%(end)",
 +                                        "%(align:15)%(refname:lstrip=2)%(end)",
                                          filter->lines);
                        format = to_free;
                } else
 -                      format = "%(refname:strip=2)";
 +                      format = "%(refname:lstrip=2)";
        }
  
        verify_ref_format(format);
  }
  
  typedef int (*each_tag_name_fn)(const char *name, const char *ref,
 -                              const unsigned char *sha1);
 +                              const unsigned char *sha1, const void *cb_data);
  
 -static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
 +static int for_each_tag_name(const char **argv, each_tag_name_fn fn,
 +                           const void *cb_data)
  {
        const char **p;
        char ref[PATH_MAX];
                        had_error = 1;
                        continue;
                }
 -              if (fn(*p, ref, sha1))
 +              if (fn(*p, ref, sha1, cb_data))
                        had_error = 1;
        }
        return had_error;
  }
  
  static int delete_tag(const char *name, const char *ref,
 -                              const unsigned char *sha1)
 +                    const unsigned char *sha1, const void *cb_data)
  {
        if (delete_ref(ref, sha1, 0))
                return 1;
  }
  
  static int verify_tag(const char *name, const char *ref,
 -                              const unsigned char *sha1)
 +                    const unsigned char *sha1, const void *cb_data)
  {
 -      return gpg_verify_tag(sha1, name, GPG_VERIFY_VERBOSE);
 +      int flags;
 +      const char *fmt_pretty = cb_data;
 +      flags = GPG_VERIFY_VERBOSE;
 +
 +      if (fmt_pretty)
 +              flags = GPG_VERIFY_OMIT_STATUS;
 +
 +      if (gpg_verify_tag(sha1, name, flags))
 +              return -1;
 +
 +      if (fmt_pretty)
 +              pretty_print_ref(name, sha1, fmt_pretty);
 +
 +      return 0;
  }
  
  static int do_sign(struct strbuf *buffer)
@@@ -302,6 -288,54 +302,54 @@@ static void create_tag(const unsigned c
        }
  }
  
+ static void create_reflog_msg(const unsigned char *sha1, struct strbuf *sb)
+ {
+       enum object_type type;
+       struct commit *c;
+       char *buf;
+       unsigned long size;
+       int subject_len = 0;
+       const char *subject_start;
+       char *rla = getenv("GIT_REFLOG_ACTION");
+       if (rla) {
+               strbuf_addstr(sb, rla);
+       } else {
+               strbuf_addstr(sb, _("tag: tagging "));
+               strbuf_add_unique_abbrev(sb, sha1, DEFAULT_ABBREV);
+       }
+       strbuf_addstr(sb, " (");
+       type = sha1_object_info(sha1, NULL);
+       switch (type) {
+       default:
+               strbuf_addstr(sb, _("object of unknown type"));
+               break;
+       case OBJ_COMMIT:
+               if ((buf = read_sha1_file(sha1, &type, &size)) != NULL) {
+                       subject_len = find_commit_subject(buf, &subject_start);
+                       strbuf_insert(sb, sb->len, subject_start, subject_len);
+               } else {
+                       strbuf_addstr(sb, _("commit object"));
+               }
+               free(buf);
+               if ((c = lookup_commit_reference(sha1)) != NULL)
+                       strbuf_addf(sb, ", %s", show_date(c->date, 0, DATE_MODE(SHORT)));
+               break;
+       case OBJ_TREE:
+               strbuf_addstr(sb, _("tree object"));
+               break;
+       case OBJ_BLOB:
+               strbuf_addstr(sb, _("blob object"));
+               break;
+       case OBJ_TAG:
+               strbuf_addstr(sb, _("other tag object"));
+               break;
+       }
+       strbuf_addch(sb, ')');
+ }
  struct msg_arg {
        int given;
        struct strbuf buf;
@@@ -335,6 -369,7 +383,7 @@@ int cmd_tag(int argc, const char **argv
  {
        struct strbuf buf = STRBUF_INIT;
        struct strbuf ref = STRBUF_INIT;
+       struct strbuf reflog_msg = STRBUF_INIT;
        unsigned char object[20], prev[20];
        const char *object_ref, *tag;
        struct create_tag_options opt;
        struct ref_filter filter;
        static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
        const char *format = NULL;
 +      int icase = 0;
        struct option options[] = {
                OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
                { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
                        N_("print only tags of the object"), 0, parse_opt_object_name
                },
                OPT_STRING(  0 , "format", &format, N_("format"), N_("format to use for the output")),
 +              OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
                OPT_END()
        };
  
 +      setup_ref_filter_porcelain_msg();
 +
        git_config(git_tag_config, sorting_tail);
  
        memset(&opt, 0, sizeof(opt));
        }
        if (!sorting)
                sorting = ref_default_sorting();
 +      sorting->ignore_case = icase;
 +      filter.ignore_case = icase;
        if (cmdmode == 'l') {
                int ret;
                if (column_active(colopts)) {
        if (filter.merge_commit)
                die(_("--merged and --no-merged option are only allowed with -l"));
        if (cmdmode == 'd')
 -              return for_each_tag_name(argv, delete_tag);
 -      if (cmdmode == 'v')
 -              return for_each_tag_name(argv, verify_tag);
 +              return for_each_tag_name(argv, delete_tag, NULL);
 +      if (cmdmode == 'v') {
 +              if (format)
 +                      verify_ref_format(format);
 +              return for_each_tag_name(argv, verify_tag, format);
 +      }
  
        if (msg.given || msgfile) {
                if (msg.given && msgfile)
        else
                die(_("Invalid cleanup mode %s"), cleanup_arg);
  
+       create_reflog_msg(object, &reflog_msg);
        if (create_tag_object) {
                if (force_sign_annotate && !annotate)
                        opt.sign = 1;
        if (!transaction ||
            ref_transaction_update(transaction, ref.buf, object, prev,
                                   create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
-                                  NULL, &err) ||
+                                  reflog_msg.buf, &err) ||
            ref_transaction_commit(transaction, &err))
                die("%s", err.buf);
        ref_transaction_free(transaction);
        strbuf_release(&err);
        strbuf_release(&buf);
        strbuf_release(&ref);
+       strbuf_release(&reflog_msg);
        return 0;
  }
diff --combined t/t7004-tag.sh
index 072e6c6b88447f21e89a0087d894ae8ab2868ad7,e5d3fa71296df5029993036b5459e0bc2e83b71b..b4698ab5f53c2ce92bd349fe4fa336e5fc5ca3e5
@@@ -27,30 -27,6 +27,30 @@@ test_expect_success 'listing all tags i
        test $(git tag | wc -l) -eq 0
  '
  
 +test_expect_success 'sort tags, ignore case' '
 +      (
 +              git init sort &&
 +              cd sort &&
 +              test_commit initial &&
 +              git tag tag-one &&
 +              git tag TAG-two &&
 +              git tag -l >actual &&
 +              cat >expected <<-\EOF &&
 +              TAG-two
 +              initial
 +              tag-one
 +              EOF
 +              test_cmp expected actual &&
 +              git tag -l -i >actual &&
 +              cat >expected <<-\EOF &&
 +              initial
 +              tag-one
 +              TAG-two
 +              EOF
 +              test_cmp expected actual
 +      )
 +'
 +
  test_expect_success 'looking for a tag in an empty tree should fail' \
        '! (tag_exists mytag)'
  
@@@ -71,7 -47,6 +71,7 @@@ test_expect_success 'creating a tag fo
  
  # commit used in the tests, test_tick is also called here to freeze the date:
  test_expect_success 'creating a tag using default HEAD should succeed' '
 +      test_config core.logAllRefUpdates true &&
        test_tick &&
        echo foo >foo &&
        git add foo &&
  '
  
  test_expect_success 'creating a tag with --create-reflog should create reflog' '
+       git log -1 \
+               --format="format:tag: tagging %h (%s, %cd)%n" \
+               --date=format:%Y-%m-%d >expected &&
        test_when_finished "git tag -d tag_with_reflog" &&
        git tag --create-reflog tag_with_reflog &&
-       git reflog exists refs/tags/tag_with_reflog
+       git reflog exists refs/tags/tag_with_reflog &&
+       sed -e "s/^.*   //" .git/logs/refs/tags/tag_with_reflog >actual &&
+       test_cmp expected actual
+ '
+ test_expect_success 'annotated tag with --create-reflog has correct message' '
+       git log -1 \
+               --format="format:tag: tagging %h (%s, %cd)%n" \
+               --date=format:%Y-%m-%d >expected &&
+       test_when_finished "git tag -d tag_with_reflog" &&
+       git tag -m "annotated tag" --create-reflog tag_with_reflog &&
+       git reflog exists refs/tags/tag_with_reflog &&
+       sed -e "s/^.*   //" .git/logs/refs/tags/tag_with_reflog >actual &&
+       test_cmp expected actual
  '
  
  test_expect_success '--create-reflog does not create reflog on failure' '
        test_must_fail git reflog exists refs/tags/mytag
  '
  
 +test_expect_success 'option core.logAllRefUpdates=always creates reflog' '
 +      test_when_finished "git tag -d tag_with_reflog" &&
 +      test_config core.logAllRefUpdates always &&
 +      git tag tag_with_reflog &&
 +      git reflog exists refs/tags/tag_with_reflog
 +'
 +
  test_expect_success 'listing all tags if one exists should succeed' '
        git tag -l &&
        git tag
@@@ -113,9 -97,6 +129,9 @@@ test_expect_success 'listing all tags i
  test_expect_success 'listing a tag using a matching pattern should succeed' \
        'git tag -l mytag'
  
 +test_expect_success 'listing a tag with --ignore-case' \
 +      'test $(git tag -l --ignore-case MYTAG) = mytag'
 +
  test_expect_success \
        'listing a tag using a matching pattern should output that tag' \
        'test $(git tag -l mytag) = mytag'
@@@ -157,11 -138,11 +173,11 @@@ test_expect_success '--force can creat
        tag_exists mytag'
  
  test_expect_success '--force is moot with a non-existing tag name' '
 +      test_when_finished git tag -d newtag forcetag &&
        git tag newtag >expect &&
        git tag --force forcetag >actual &&
        test_cmp expect actual
  '
 -git tag -d newtag forcetag
  
  # deleting tags:
  
@@@ -332,9 -313,11 +348,9 @@@ EO
  '
  
  test_expect_success 'listing tags in column with column.*' '
 -      git config column.tag row &&
 -      git config column.ui dense &&
 +      test_config column.tag row &&
 +      test_config column.ui dense &&
        COLUMNS=40 git tag -l >actual &&
 -      git config --unset column.ui &&
 -      git config --unset column.tag &&
        cat >expected <<\EOF &&
  a1      aa1   cba     t210    t211
  v0.2.1  v1.0  v1.0.1  v1.1.3
@@@ -347,8 -330,9 +363,8 @@@ test_expect_success 'listing tag with -
  '
  
  test_expect_success 'listing tags -n in column with column.ui ignored' '
 -      git config column.ui "row dense" &&
 +      test_config column.ui "row dense" &&
        COLUMNS=40 git tag -l -n >actual &&
 -      git config --unset column.ui &&
        cat >expected <<\EOF &&
  a1              Foo
  aa1             Foo
@@@ -879,22 -863,6 +895,22 @@@ test_expect_success GPG 'verifying a fo
        test_must_fail git tag -v forged-tag
  '
  
 +test_expect_success 'verifying a proper tag with --format pass and format accordingly' '
 +      cat >expect <<-\EOF
 +      tagname : signed-tag
 +      EOF &&
 +      git tag -v --format="tagname : %(tag)" "signed-tag" >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'verifying a forged tag with --format fail and format accordingly' '
 +      cat >expect <<-\EOF
 +      tagname : forged-tag
 +      EOF &&
 +      test_must_fail git tag -v --format="tagname : %(tag)" "forged-tag" >actual &&
 +      test_cmp expect actual
 +'
 +
  # blank and empty messages for signed tags:
  
  get_tag_header empty-signed-tag $commit commit $time >expect
@@@ -1248,10 -1216,11 +1264,10 @@@ test_expect_success GPG,RFC1991 
  '
  
  # try to sign with bad user.signingkey
 -git config user.signingkey BobTheMouse
  test_expect_success GPG \
        'git tag -s fails if gpg is misconfigured (bad key)' \
 -      'test_must_fail git tag -s -m tail tag-gpg-failure'
 -git config --unset user.signingkey
 +      'test_config user.signingkey BobTheMouse &&
 +      test_must_fail git tag -s -m tail tag-gpg-failure'
  
  # try to produce invalid signature
  test_expect_success GPG \
@@@ -1531,7 -1500,7 +1547,7 @@@ test_expect_success 'reverse lexical so
  '
  
  test_expect_success 'configured lexical sort' '
 -      git config tag.sort "v:refname" &&
 +      test_config tag.sort "v:refname" &&
        git tag -l "foo*" >actual &&
        cat >expect <<-\EOF &&
        foo1.3
  '
  
  test_expect_success 'option override configured sort' '
 +      test_config tag.sort "v:refname" &&
        git tag -l --sort=-refname "foo*" >actual &&
        cat >expect <<-\EOF &&
        foo1.6
@@@ -1557,12 -1525,13 +1573,12 @@@ test_expect_success 'invalid sort param
  '
  
  test_expect_success 'invalid sort parameter in configuratoin' '
 -      git config tag.sort "v:notvalid" &&
 +      test_config tag.sort "v:notvalid" &&
        test_must_fail git tag -l "foo*"
  '
  
  test_expect_success 'version sort with prerelease reordering' '
 -      git config --unset tag.sort &&
 -      git config versionsort.prereleaseSuffix -rc &&
 +      test_config versionsort.prereleaseSuffix -rc &&
        git tag foo1.6-rc1 &&
        git tag foo1.6-rc2 &&
        git tag -l --sort=version:refname "foo*" >actual &&
  '
  
  test_expect_success 'reverse version sort with prerelease reordering' '
 +      test_config versionsort.prereleaseSuffix -rc &&
        git tag -l --sort=-version:refname "foo*" >actual &&
        cat >expect <<-\EOF &&
        foo1.10
        test_cmp expect actual
  '
  
 +test_expect_success 'version sort with prerelease reordering and common leading character' '
 +      test_config versionsort.prereleaseSuffix -before &&
 +      git tag foo1.7-before1 &&
 +      git tag foo1.7 &&
 +      git tag foo1.7-after1 &&
 +      git tag -l --sort=version:refname "foo1.7*" >actual &&
 +      cat >expect <<-\EOF &&
 +      foo1.7-before1
 +      foo1.7
 +      foo1.7-after1
 +      EOF
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'version sort with prerelease reordering, multiple suffixes and common leading character' '
 +      test_config versionsort.prereleaseSuffix -before &&
 +      git config --add versionsort.prereleaseSuffix -after &&
 +      git tag -l --sort=version:refname "foo1.7*" >actual &&
 +      cat >expect <<-\EOF &&
 +      foo1.7-before1
 +      foo1.7-after1
 +      foo1.7
 +      EOF
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'version sort with prerelease reordering, multiple suffixes match the same tag' '
 +      test_config versionsort.prereleaseSuffix -bar &&
 +      git config --add versionsort.prereleaseSuffix -foo-baz &&
 +      git config --add versionsort.prereleaseSuffix -foo-bar &&
 +      git tag foo1.8-foo-bar &&
 +      git tag foo1.8-foo-baz &&
 +      git tag foo1.8 &&
 +      git tag -l --sort=version:refname "foo1.8*" >actual &&
 +      cat >expect <<-\EOF &&
 +      foo1.8-foo-baz
 +      foo1.8-foo-bar
 +      foo1.8
 +      EOF
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'version sort with prerelease reordering, multiple suffixes match starting at the same position' '
 +      test_config versionsort.prereleaseSuffix -pre &&
 +      git config --add versionsort.prereleaseSuffix -prerelease &&
 +      git tag foo1.9-pre1 &&
 +      git tag foo1.9-pre2 &&
 +      git tag foo1.9-prerelease1 &&
 +      git tag -l --sort=version:refname "foo1.9*" >actual &&
 +      cat >expect <<-\EOF &&
 +      foo1.9-pre1
 +      foo1.9-pre2
 +      foo1.9-prerelease1
 +      EOF
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'version sort with general suffix reordering' '
 +      test_config versionsort.suffix -alpha &&
 +      git config --add versionsort.suffix -beta &&
 +      git config --add versionsort.suffix ""  &&
 +      git config --add versionsort.suffix -gamma &&
 +      git config --add versionsort.suffix -delta &&
 +      git tag foo1.10-alpha &&
 +      git tag foo1.10-beta &&
 +      git tag foo1.10-gamma &&
 +      git tag foo1.10-delta &&
 +      git tag foo1.10-unlisted-suffix &&
 +      git tag -l --sort=version:refname "foo1.10*" >actual &&
 +      cat >expect <<-\EOF &&
 +      foo1.10-alpha
 +      foo1.10-beta
 +      foo1.10
 +      foo1.10-unlisted-suffix
 +      foo1.10-gamma
 +      foo1.10-delta
 +      EOF
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'versionsort.suffix overrides versionsort.prereleaseSuffix' '
 +      test_config versionsort.suffix -before &&
 +      test_config versionsort.prereleaseSuffix -after &&
 +      git tag -l --sort=version:refname "foo1.7*" >actual &&
 +      cat >expect <<-\EOF &&
 +      foo1.7-before1
 +      foo1.7
 +      foo1.7-after1
 +      EOF
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'version sort with very long prerelease suffix' '
 +      test_config versionsort.prereleaseSuffix -very-looooooooooooooooooooooooong-prerelease-suffix &&
 +      git tag -l --sort=version:refname
 +'
 +
  run_with_limited_stack () {
        (ulimit -s 128 && "$@")
  }
  
  test_expect_success '--format should list tags as per format given' '
        cat >expect <<-\EOF &&
 -      refname : refs/tags/foo1.10
 -      refname : refs/tags/foo1.3
 -      refname : refs/tags/foo1.6
 -      refname : refs/tags/foo1.6-rc1
 -      refname : refs/tags/foo1.6-rc2
 +      refname : refs/tags/v1.0
 +      refname : refs/tags/v1.0.1
 +      refname : refs/tags/v1.1.3
        EOF
 -      git tag -l --format="refname : %(refname)" "foo*" >actual &&
 +      git tag -l --format="refname : %(refname)" "v1*" >actual &&
        test_cmp expect actual
  '