]> git.ipfire.org Git - thirdparty/git.git/commitdiff
builtin/tag: add --trailer option
authorJohn Passaro <john.a.passaro@gmail.com>
Sun, 5 May 2024 18:49:10 +0000 (18:49 +0000)
committerJunio C Hamano <gitster@pobox.com>
Tue, 7 May 2024 17:06:03 +0000 (10:06 -0700)
git-tag supports interpreting trailers from an annotated tag message,
using --list --format="%(trailers)". However, the available methods to
add a trailer to a tag message (namely -F or --editor) are not as
ergonomic.

In a previous patch, we moved git-commit's implementation of its
--trailer option to the trailer.h API. Let's use that new function to
teach git-tag the same --trailer option, emulating as much of
git-commit's behavior as much as possible.

Helped-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: John Passaro <john.a.passaro@gmail.com>
Acked-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-tag.txt
builtin/tag.c
t/t7004-tag.sh

index 5fe519c31ec478e205223dbed919254a7ed4c610..4494729f5e167fd2c3c152ebbe275472a097f6d3 100644 (file)
@@ -10,6 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]
+       [(--trailer <token>[(=|:)<value>])...]
        <tagname> [<commit> | <object>]
 'git tag' -d <tagname>...
 'git tag' [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]
@@ -31,8 +32,8 @@ creates a 'tag' object, and requires a tag message.  Unless
 `-m <msg>` or `-F <file>` is given, an editor is started for the user to type
 in the tag message.
 
-If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <key-id>`
-are absent, `-a` is implied.
+If `-m <msg>` or `-F <file>` or `--trailer <token>[=<value>]` is given
+and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied.
 
 Otherwise, a tag reference that points directly at the given object
 (i.e., a lightweight tag) is created.
@@ -178,6 +179,17 @@ This option is only applicable when listing tags without annotation lines.
        Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`
        is given.
 
+--trailer <token>[(=|:)<value>]::
+       Specify a (<token>, <value>) pair that should be applied as a
+       trailer. (e.g. `git tag --trailer "Custom-Key: value"`
+       will add a "Custom-Key" trailer to the tag message.)
+       The `trailer.*` configuration variables
+       (linkgit:git-interpret-trailers[1]) can be used to define if
+       a duplicated trailer is omitted, where in the run of trailers
+       each trailer would appear, and other details.
+       The trailers can be extracted in `git tag --list`, using
+       `--format="%(trailers)"` placeholder.
+
 -e::
 --edit::
        The message taken from file with `-F` and command line with
index 9a33cb50b4557317bfad8904e189a6909a4490c0..01b19a5b6c9d6585e69cffdfc8870433f2e36e27 100644 (file)
 #include "date.h"
 #include "write-or-die.h"
 #include "object-file-convert.h"
+#include "trailer.h"
 
 static const char * const git_tag_usage[] = {
        N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\n"
+          "        [(--trailer <token>[(=|:)<value>])...]\n"
           "        <tagname> [<commit> | <object>]"),
        N_("git tag -d <tagname>..."),
        N_("git tag [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\n"
@@ -290,10 +292,12 @@ static const char message_advice_nested_tag[] =
 static void create_tag(const struct object_id *object, const char *object_ref,
                       const char *tag,
                       struct strbuf *buf, struct create_tag_options *opt,
-                      struct object_id *prev, struct object_id *result, char *path)
+                      struct object_id *prev, struct object_id *result,
+                      struct strvec *trailer_args, char *path)
 {
        enum object_type type;
        struct strbuf header = STRBUF_INIT;
+       int should_edit;
 
        type = oid_object_info(the_repository, object, NULL);
        if (type <= OBJ_NONE)
@@ -313,13 +317,15 @@ static void create_tag(const struct object_id *object, const char *object_ref,
                    tag,
                    git_committer_info(IDENT_STRICT));
 
-       if (!opt->message_given || opt->use_editor) {
+       should_edit = opt->use_editor || !opt->message_given;
+       if (should_edit || trailer_args->nr) {
                int fd;
 
                /* write the template message before editing: */
                fd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
 
-               if (opt->message_given) {
+               if (opt->message_given && buf->len) {
+                       strbuf_complete(buf, '\n');
                        write_or_die(fd, buf->buf, buf->len);
                        strbuf_reset(buf);
                } else if (!is_null_oid(prev)) {
@@ -338,10 +344,19 @@ static void create_tag(const struct object_id *object, const char *object_ref,
                }
                close(fd);
 
-               if (launch_editor(path, buf, NULL)) {
-                       fprintf(stderr,
-                       _("Please supply the message using either -m or -F option.\n"));
-                       exit(1);
+               if (trailer_args->nr && amend_file_with_trailers(path, trailer_args))
+                       die(_("unable to pass trailers to --trailers"));
+
+               if (should_edit) {
+                       if (launch_editor(path, buf, NULL)) {
+                               fprintf(stderr,
+                                       _("Please supply the message using either -m or -F option.\n"));
+                               exit(1);
+                       }
+               } else if (trailer_args->nr) {
+                       strbuf_reset(buf);
+                       if (strbuf_read_file(buf, path, 0) < 0)
+                               die_errno(_("failed to read '%s'"), path);
                }
        }
 
@@ -463,6 +478,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        struct ref_sorting *sorting;
        struct string_list sorting_options = STRING_LIST_INIT_DUP;
        struct ref_format format = REF_FORMAT_INIT;
+       struct strvec trailer_args = STRVEC_INIT;
        int icase = 0;
        int edit_flag = 0;
        struct option options[] = {
@@ -479,6 +495,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                OPT_CALLBACK_F('m', "message", &msg, N_("message"),
                               N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg),
                OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
+               OPT_PASSTHRU_ARGV(0, "trailer", &trailer_args, N_("trailer"),
+                                 N_("add custom trailer(s)"), PARSE_OPT_NONEG),
                OPT_BOOL('e', "edit", &edit_flag, N_("force edit of tag message")),
                OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
                OPT_CLEANUP(&cleanup_arg),
@@ -548,7 +566,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                opt.sign = 1;
                set_signing_key(keyid);
        }
-       create_tag_object = (opt.sign || annotate || msg.given || msgfile);
+       create_tag_object = (opt.sign || annotate || msg.given || msgfile ||
+                            edit_flag || trailer_args.nr);
 
        if ((create_tag_object || force) && (cmdmode != 0))
                usage_with_options(git_tag_usage, options);
@@ -654,7 +673,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                        opt.sign = 1;
                path = git_pathdup("TAG_EDITMSG");
                create_tag(&object, object_ref, tag, &buf, &opt, &prev, &object,
-                          path);
+                          &trailer_args, path);
        }
 
        transaction = ref_transaction_begin(&err);
@@ -686,6 +705,7 @@ cleanup:
        strbuf_release(&reflog_msg);
        strbuf_release(&msg.buf);
        strbuf_release(&err);
+       strvec_clear(&trailer_args);
        free(msgfile);
        return ret;
 }
index 696866d7794e1fdd72760ed093b9ff9737d97969..fa6336edf98350566fd754c9d8d252d27af45e6e 100755 (executable)
@@ -668,6 +668,115 @@ test_expect_success \
        test_cmp expect actual
 '
 
+# trailers
+
+test_expect_success 'create tag with -m and --trailer' '
+       get_tag_header tag-with-inline-message-and-trailers $commit commit $time >expect &&
+       cat >>expect <<-\EOF &&
+       create tag with trailers
+
+       my-trailer: here
+       alt-trailer: there
+       EOF
+       git tag -m "create tag with trailers" \
+               --trailer my-trailer=here \
+               --trailer alt-trailer=there \
+               tag-with-inline-message-and-trailers &&
+       get_tag_msg tag-with-inline-message-and-trailers >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'list tag extracting trailers' '
+       cat >expect <<-\EOF &&
+       my-trailer: here
+       alt-trailer: there
+
+       EOF
+       git tag --list --format="%(trailers)" tag-with-inline-message-and-trailers >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'create tag with -F and --trailer' '
+       echo "create tag from message file using --trailer" >messagefilewithnotrailers &&
+       get_tag_header tag-with-file-message-and-trailers $commit commit $time >expect &&
+       cat >>expect <<-\EOF &&
+       create tag from message file using --trailer
+
+       my-trailer: here
+       alt-trailer: there
+       EOF
+       git tag -F messagefilewithnotrailers \
+               --trailer my-trailer=here \
+               --trailer alt-trailer=there \
+               tag-with-file-message-and-trailers &&
+       get_tag_msg tag-with-file-message-and-trailers >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'create tag with -m and --trailer and --edit' '
+       write_script fakeeditor <<-\EOF &&
+       sed -e "1s/^/EDITED: /g" <"$1" >"$1-"
+       mv "$1-" "$1"
+       EOF
+       get_tag_header tag-with-edited-inline-message-and-trailers $commit commit $time >expect &&
+       cat >>expect <<-\EOF &&
+       EDITED: create tag with trailers
+
+       my-trailer: here
+       alt-trailer: there
+       EOF
+       GIT_EDITOR=./fakeeditor git tag --edit \
+               -m "create tag with trailers" \
+               --trailer my-trailer=here \
+               --trailer alt-trailer=there \
+               tag-with-edited-inline-message-and-trailers &&
+       get_tag_msg tag-with-edited-inline-message-and-trailers >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'create tag with -F and --trailer and --edit' '
+       echo "create tag from message file using --trailer" >messagefilewithnotrailers &&
+       get_tag_header tag-with-edited-file-message-and-trailers $commit commit $time >expect &&
+       cat >>expect <<-\EOF &&
+       EDITED: create tag from message file using --trailer
+
+       my-trailer: here
+       alt-trailer: there
+       EOF
+       GIT_EDITOR=./fakeeditor git tag --edit \
+               -F messagefilewithnotrailers \
+               --trailer my-trailer=here \
+               --trailer alt-trailer=there \
+               tag-with-edited-file-message-and-trailers &&
+       get_tag_msg tag-with-edited-file-message-and-trailers >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'create annotated tag and force editor when only --trailer is given' '
+       write_script fakeeditor <<-\EOF &&
+       echo "add a line" >"$1-"
+       cat <"$1" >>"$1-"
+       mv "$1-" "$1"
+       EOF
+       get_tag_header tag-with-trailers-and-no-message $commit commit $time >expect &&
+       cat >>expect <<-\EOF &&
+       add a line
+
+       my-trailer: here
+       alt-trailer: there
+       EOF
+       GIT_EDITOR=./fakeeditor git tag \
+               --trailer my-trailer=here \
+               --trailer alt-trailer=there \
+               tag-with-trailers-and-no-message &&
+       get_tag_msg tag-with-trailers-and-no-message >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'bad editor causes panic when only --trailer is given' '
+       test_must_fail env GIT_EDITOR=false git tag --trailer my-trailer=here tag-will-not-exist
+'
+
 # listing messages for annotated non-signed tags:
 
 test_expect_success \
@@ -810,6 +919,11 @@ test_expect_success 'git tag --format with ahead-behind' '
        refs/tags/tag-lines 0 1 !
        refs/tags/tag-one-line 0 1 !
        refs/tags/tag-right 0 0 !
+       refs/tags/tag-with-edited-file-message-and-trailers 0 1 !
+       refs/tags/tag-with-edited-inline-message-and-trailers 0 1 !
+       refs/tags/tag-with-file-message-and-trailers 0 1 !
+       refs/tags/tag-with-inline-message-and-trailers 0 1 !
+       refs/tags/tag-with-trailers-and-no-message 0 1 !
        refs/tags/tag-zero-lines 0 1 !
        EOF
        git tag -l --format="%(refname) %(ahead-behind:HEAD) !" >actual 2>err &&