Skip to content

Commit f9d4eaf

Browse files
committed
Merge branch 'jp/tag-trailer'
"git tag" learned the "--trailer" option to futz with the trailers in the same way as "git commit" does. * jp/tag-trailer: builtin/tag: add --trailer option builtin/commit: refactor --trailer logic builtin/commit: use ARGV macro to collect trailers
2 parents fe3ccc7 + 066cef7 commit f9d4eaf

File tree

6 files changed

+181
-28
lines changed

6 files changed

+181
-28
lines changed

Documentation/git-tag.txt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ SYNOPSIS
1010
--------
1111
[verse]
1212
'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]
13+
[(--trailer <token>[(=|:)<value>])...]
1314
<tagname> [<commit> | <object>]
1415
'git tag' -d <tagname>...
1516
'git tag' [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]
@@ -31,8 +32,8 @@ creates a 'tag' object, and requires a tag message. Unless
3132
`-m <msg>` or `-F <file>` is given, an editor is started for the user to type
3233
in the tag message.
3334

34-
If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <key-id>`
35-
are absent, `-a` is implied.
35+
If `-m <msg>` or `-F <file>` or `--trailer <token>[=<value>]` is given
36+
and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied.
3637

3738
Otherwise, a tag reference that points directly at the given object
3839
(i.e., a lightweight tag) is created.
@@ -178,6 +179,17 @@ This option is only applicable when listing tags without annotation lines.
178179
Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`
179180
is given.
180181

182+
--trailer <token>[(=|:)<value>]::
183+
Specify a (<token>, <value>) pair that should be applied as a
184+
trailer. (e.g. `git tag --trailer "Custom-Key: value"`
185+
will add a "Custom-Key" trailer to the tag message.)
186+
The `trailer.*` configuration variables
187+
(linkgit:git-interpret-trailers[1]) can be used to define if
188+
a duplicated trailer is omitted, where in the run of trailers
189+
each trailer would appear, and other details.
190+
The trailers can be extracted in `git tag --list`, using
191+
`--format="%(trailers)"` placeholder.
192+
181193
-e::
182194
--edit::
183195
The message taken from file with `-F` and command line with

builtin/commit.c

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "commit-reach.h"
3838
#include "commit-graph.h"
3939
#include "pretty.h"
40+
#include "trailer.h"
4041

4142
static const char * const builtin_commit_usage[] = {
4243
N_("git commit [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]\n"
@@ -141,14 +142,6 @@ static struct strbuf message = STRBUF_INIT;
141142

142143
static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
143144

144-
static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
145-
{
146-
BUG_ON_OPT_NEG(unset);
147-
148-
strvec_pushl(opt->value, "--trailer", arg, NULL);
149-
return 0;
150-
}
151-
152145
static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
153146
{
154147
enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -1037,14 +1030,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
10371030
fclose(s->fp);
10381031

10391032
if (trailer_args.nr) {
1040-
struct child_process run_trailer = CHILD_PROCESS_INIT;
1041-
1042-
strvec_pushl(&run_trailer.args, "interpret-trailers",
1043-
"--in-place", "--no-divider",
1044-
git_path_commit_editmsg(), NULL);
1045-
strvec_pushv(&run_trailer.args, trailer_args.v);
1046-
run_trailer.git_cmd = 1;
1047-
if (run_command(&run_trailer))
1033+
if (amend_file_with_trailers(git_path_commit_editmsg(), &trailer_args))
10481034
die(_("unable to pass trailers to --trailers"));
10491035
strvec_clear(&trailer_args);
10501036
}
@@ -1672,7 +1658,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
16721658
OPT_STRING(0, "fixup", &fixup_message, N_("[(amend|reword):]commit"), N_("use autosquash formatted message to fixup or amend/reword specified commit")),
16731659
OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
16741660
OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
1675-
OPT_CALLBACK_F(0, "trailer", &trailer_args, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer),
1661+
OPT_PASSTHRU_ARGV(0, "trailer", &trailer_args, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG),
16761662
OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
16771663
OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
16781664
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),

builtin/tag.c

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@
2828
#include "date.h"
2929
#include "write-or-die.h"
3030
#include "object-file-convert.h"
31+
#include "trailer.h"
3132

3233
static const char * const git_tag_usage[] = {
3334
N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\n"
35+
" [(--trailer <token>[(=|:)<value>])...]\n"
3436
" <tagname> [<commit> | <object>]"),
3537
N_("git tag -d <tagname>..."),
3638
N_("git tag [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\n"
@@ -290,10 +292,12 @@ static const char message_advice_nested_tag[] =
290292
static void create_tag(const struct object_id *object, const char *object_ref,
291293
const char *tag,
292294
struct strbuf *buf, struct create_tag_options *opt,
293-
struct object_id *prev, struct object_id *result, char *path)
295+
struct object_id *prev, struct object_id *result,
296+
struct strvec *trailer_args, char *path)
294297
{
295298
enum object_type type;
296299
struct strbuf header = STRBUF_INIT;
300+
int should_edit;
297301

298302
type = oid_object_info(the_repository, object, NULL);
299303
if (type <= OBJ_NONE)
@@ -313,13 +317,15 @@ static void create_tag(const struct object_id *object, const char *object_ref,
313317
tag,
314318
git_committer_info(IDENT_STRICT));
315319

316-
if (!opt->message_given || opt->use_editor) {
320+
should_edit = opt->use_editor || !opt->message_given;
321+
if (should_edit || trailer_args->nr) {
317322
int fd;
318323

319324
/* write the template message before editing: */
320325
fd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
321326

322-
if (opt->message_given) {
327+
if (opt->message_given && buf->len) {
328+
strbuf_complete(buf, '\n');
323329
write_or_die(fd, buf->buf, buf->len);
324330
strbuf_reset(buf);
325331
} else if (!is_null_oid(prev)) {
@@ -338,10 +344,19 @@ static void create_tag(const struct object_id *object, const char *object_ref,
338344
}
339345
close(fd);
340346

341-
if (launch_editor(path, buf, NULL)) {
342-
fprintf(stderr,
343-
_("Please supply the message using either -m or -F option.\n"));
344-
exit(1);
347+
if (trailer_args->nr && amend_file_with_trailers(path, trailer_args))
348+
die(_("unable to pass trailers to --trailers"));
349+
350+
if (should_edit) {
351+
if (launch_editor(path, buf, NULL)) {
352+
fprintf(stderr,
353+
_("Please supply the message using either -m or -F option.\n"));
354+
exit(1);
355+
}
356+
} else if (trailer_args->nr) {
357+
strbuf_reset(buf);
358+
if (strbuf_read_file(buf, path, 0) < 0)
359+
die_errno(_("failed to read '%s'"), path);
345360
}
346361
}
347362

@@ -463,6 +478,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
463478
struct ref_sorting *sorting;
464479
struct string_list sorting_options = STRING_LIST_INIT_DUP;
465480
struct ref_format format = REF_FORMAT_INIT;
481+
struct strvec trailer_args = STRVEC_INIT;
466482
int icase = 0;
467483
int edit_flag = 0;
468484
struct option options[] = {
@@ -479,6 +495,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
479495
OPT_CALLBACK_F('m', "message", &msg, N_("message"),
480496
N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg),
481497
OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
498+
OPT_PASSTHRU_ARGV(0, "trailer", &trailer_args, N_("trailer"),
499+
N_("add custom trailer(s)"), PARSE_OPT_NONEG),
482500
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of tag message")),
483501
OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
484502
OPT_CLEANUP(&cleanup_arg),
@@ -548,7 +566,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
548566
opt.sign = 1;
549567
set_signing_key(keyid);
550568
}
551-
create_tag_object = (opt.sign || annotate || msg.given || msgfile);
569+
create_tag_object = (opt.sign || annotate || msg.given || msgfile ||
570+
edit_flag || trailer_args.nr);
552571

553572
if ((create_tag_object || force) && (cmdmode != 0))
554573
usage_with_options(git_tag_usage, options);
@@ -654,7 +673,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
654673
opt.sign = 1;
655674
path = git_pathdup("TAG_EDITMSG");
656675
create_tag(&object, object_ref, tag, &buf, &opt, &prev, &object,
657-
path);
676+
&trailer_args, path);
658677
}
659678

660679
transaction = ref_transaction_begin(&err);
@@ -686,6 +705,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
686705
strbuf_release(&reflog_msg);
687706
strbuf_release(&msg.buf);
688707
strbuf_release(&err);
708+
strvec_clear(&trailer_args);
689709
free(msgfile);
690710
return ret;
691711
}

t/t7004-tag.sh

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,115 @@ test_expect_success \
668668
test_cmp expect actual
669669
'
670670

671+
# trailers
672+
673+
test_expect_success 'create tag with -m and --trailer' '
674+
get_tag_header tag-with-inline-message-and-trailers $commit commit $time >expect &&
675+
cat >>expect <<-\EOF &&
676+
create tag with trailers
677+
678+
my-trailer: here
679+
alt-trailer: there
680+
EOF
681+
git tag -m "create tag with trailers" \
682+
--trailer my-trailer=here \
683+
--trailer alt-trailer=there \
684+
tag-with-inline-message-and-trailers &&
685+
get_tag_msg tag-with-inline-message-and-trailers >actual &&
686+
test_cmp expect actual
687+
'
688+
689+
test_expect_success 'list tag extracting trailers' '
690+
cat >expect <<-\EOF &&
691+
my-trailer: here
692+
alt-trailer: there
693+
694+
EOF
695+
git tag --list --format="%(trailers)" tag-with-inline-message-and-trailers >actual &&
696+
test_cmp expect actual
697+
'
698+
699+
test_expect_success 'create tag with -F and --trailer' '
700+
echo "create tag from message file using --trailer" >messagefilewithnotrailers &&
701+
get_tag_header tag-with-file-message-and-trailers $commit commit $time >expect &&
702+
cat >>expect <<-\EOF &&
703+
create tag from message file using --trailer
704+
705+
my-trailer: here
706+
alt-trailer: there
707+
EOF
708+
git tag -F messagefilewithnotrailers \
709+
--trailer my-trailer=here \
710+
--trailer alt-trailer=there \
711+
tag-with-file-message-and-trailers &&
712+
get_tag_msg tag-with-file-message-and-trailers >actual &&
713+
test_cmp expect actual
714+
'
715+
716+
test_expect_success 'create tag with -m and --trailer and --edit' '
717+
write_script fakeeditor <<-\EOF &&
718+
sed -e "1s/^/EDITED: /g" <"$1" >"$1-"
719+
mv "$1-" "$1"
720+
EOF
721+
get_tag_header tag-with-edited-inline-message-and-trailers $commit commit $time >expect &&
722+
cat >>expect <<-\EOF &&
723+
EDITED: create tag with trailers
724+
725+
my-trailer: here
726+
alt-trailer: there
727+
EOF
728+
GIT_EDITOR=./fakeeditor git tag --edit \
729+
-m "create tag with trailers" \
730+
--trailer my-trailer=here \
731+
--trailer alt-trailer=there \
732+
tag-with-edited-inline-message-and-trailers &&
733+
get_tag_msg tag-with-edited-inline-message-and-trailers >actual &&
734+
test_cmp expect actual
735+
'
736+
737+
test_expect_success 'create tag with -F and --trailer and --edit' '
738+
echo "create tag from message file using --trailer" >messagefilewithnotrailers &&
739+
get_tag_header tag-with-edited-file-message-and-trailers $commit commit $time >expect &&
740+
cat >>expect <<-\EOF &&
741+
EDITED: create tag from message file using --trailer
742+
743+
my-trailer: here
744+
alt-trailer: there
745+
EOF
746+
GIT_EDITOR=./fakeeditor git tag --edit \
747+
-F messagefilewithnotrailers \
748+
--trailer my-trailer=here \
749+
--trailer alt-trailer=there \
750+
tag-with-edited-file-message-and-trailers &&
751+
get_tag_msg tag-with-edited-file-message-and-trailers >actual &&
752+
test_cmp expect actual
753+
'
754+
755+
test_expect_success 'create annotated tag and force editor when only --trailer is given' '
756+
write_script fakeeditor <<-\EOF &&
757+
echo "add a line" >"$1-"
758+
cat <"$1" >>"$1-"
759+
mv "$1-" "$1"
760+
EOF
761+
get_tag_header tag-with-trailers-and-no-message $commit commit $time >expect &&
762+
cat >>expect <<-\EOF &&
763+
add a line
764+
765+
my-trailer: here
766+
alt-trailer: there
767+
EOF
768+
GIT_EDITOR=./fakeeditor git tag \
769+
--trailer my-trailer=here \
770+
--trailer alt-trailer=there \
771+
tag-with-trailers-and-no-message &&
772+
get_tag_msg tag-with-trailers-and-no-message >actual &&
773+
test_cmp expect actual
774+
'
775+
776+
test_expect_success 'bad editor causes panic when only --trailer is given' '
777+
test_must_fail env GIT_EDITOR=false git tag --trailer my-trailer=here tag-will-not-exist
778+
'
779+
671780
# listing messages for annotated non-signed tags:
672781

673782
test_expect_success \
@@ -810,6 +919,11 @@ test_expect_success 'git tag --format with ahead-behind' '
810919
refs/tags/tag-lines 0 1 !
811920
refs/tags/tag-one-line 0 1 !
812921
refs/tags/tag-right 0 0 !
922+
refs/tags/tag-with-edited-file-message-and-trailers 0 1 !
923+
refs/tags/tag-with-edited-inline-message-and-trailers 0 1 !
924+
refs/tags/tag-with-file-message-and-trailers 0 1 !
925+
refs/tags/tag-with-inline-message-and-trailers 0 1 !
926+
refs/tags/tag-with-trailers-and-no-message 0 1 !
813927
refs/tags/tag-zero-lines 0 1 !
814928
EOF
815929
git tag -l --format="%(refname) %(ahead-behind:HEAD) !" >actual 2>err &&

trailer.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,3 +1170,15 @@ void trailer_iterator_release(struct trailer_iterator *iter)
11701170
strbuf_release(&iter->val);
11711171
strbuf_release(&iter->key);
11721172
}
1173+
1174+
int amend_file_with_trailers(const char *path, const struct strvec *trailer_args)
1175+
{
1176+
struct child_process run_trailer = CHILD_PROCESS_INIT;
1177+
1178+
run_trailer.git_cmd = 1;
1179+
strvec_pushl(&run_trailer.args, "interpret-trailers",
1180+
"--in-place", "--no-divider",
1181+
path, NULL);
1182+
strvec_pushv(&run_trailer.args, trailer_args->v);
1183+
return run_command(&run_trailer);
1184+
}

trailer.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include "list.h"
55
#include "strbuf.h"
66

7+
struct strvec;
8+
79
enum trailer_where {
810
WHERE_DEFAULT,
911
WHERE_END,
@@ -158,4 +160,11 @@ int trailer_iterator_advance(struct trailer_iterator *iter);
158160
*/
159161
void trailer_iterator_release(struct trailer_iterator *iter);
160162

163+
/*
164+
* Augment a file to add trailers to it by running git-interpret-trailers.
165+
* This calls run_command() and its return value is the same (i.e. 0 for
166+
* success, various non-zero for other errors). See run-command.h.
167+
*/
168+
int amend_file_with_trailers(const char *path, const struct strvec *trailer_args);
169+
161170
#endif /* TRAILER_H */

0 commit comments

Comments
 (0)