builtin/tag: add --trailer option
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>maint
							parent
							
								
									4a8618785e
								
							
						
					
					
						commit
						066cef7707
					
				|  | @ -10,6 +10,7 @@ SYNOPSIS | ||||||
| -------- | -------- | ||||||
| [verse] | [verse] | ||||||
| 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e] | 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e] | ||||||
|  | 	[(--trailer <token>[(=|:)<value>])...] | ||||||
| 	<tagname> [<commit> | <object>] | 	<tagname> [<commit> | <object>] | ||||||
| 'git tag' -d <tagname>... | 'git tag' -d <tagname>... | ||||||
| 'git tag' [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>] | '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 | `-m <msg>` or `-F <file>` is given, an editor is started for the user to type | ||||||
| in the tag message. | in the tag message. | ||||||
|  |  | ||||||
| If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <key-id>` | If `-m <msg>` or `-F <file>` or `--trailer <token>[=<value>]` is given | ||||||
| are absent, `-a` is implied. | and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied. | ||||||
|  |  | ||||||
| Otherwise, a tag reference that points directly at the given object | Otherwise, a tag reference that points directly at the given object | ||||||
| (i.e., a lightweight tag) is created. | (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>` | 	Implies `-a` if none of `-a`, `-s`, or `-u <key-id>` | ||||||
| 	is given. | 	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:: | -e:: | ||||||
| --edit:: | --edit:: | ||||||
| 	The message taken from file with `-F` and command line with | 	The message taken from file with `-F` and command line with | ||||||
|  |  | ||||||
|  | @ -28,9 +28,11 @@ | ||||||
| #include "date.h" | #include "date.h" | ||||||
| #include "write-or-die.h" | #include "write-or-die.h" | ||||||
| #include "object-file-convert.h" | #include "object-file-convert.h" | ||||||
|  | #include "trailer.h" | ||||||
|  |  | ||||||
| static const char * const git_tag_usage[] = { | static const char * const git_tag_usage[] = { | ||||||
| 	N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\n" | 	N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\n" | ||||||
|  | 	   "        [(--trailer <token>[(=|:)<value>])...]\n" | ||||||
| 	   "        <tagname> [<commit> | <object>]"), | 	   "        <tagname> [<commit> | <object>]"), | ||||||
| 	N_("git tag -d <tagname>..."), | 	N_("git tag -d <tagname>..."), | ||||||
| 	N_("git tag [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\n" | 	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, | static void create_tag(const struct object_id *object, const char *object_ref, | ||||||
| 		       const char *tag, | 		       const char *tag, | ||||||
| 		       struct strbuf *buf, struct create_tag_options *opt, | 		       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; | 	enum object_type type; | ||||||
| 	struct strbuf header = STRBUF_INIT; | 	struct strbuf header = STRBUF_INIT; | ||||||
|  | 	int should_edit; | ||||||
|  |  | ||||||
| 	type = oid_object_info(the_repository, object, NULL); | 	type = oid_object_info(the_repository, object, NULL); | ||||||
| 	if (type <= OBJ_NONE) | 	if (type <= OBJ_NONE) | ||||||
|  | @ -313,13 +317,15 @@ static void create_tag(const struct object_id *object, const char *object_ref, | ||||||
| 		    tag, | 		    tag, | ||||||
| 		    git_committer_info(IDENT_STRICT)); | 		    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; | 		int fd; | ||||||
|  |  | ||||||
| 		/* write the template message before editing: */ | 		/* write the template message before editing: */ | ||||||
| 		fd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); | 		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); | 			write_or_die(fd, buf->buf, buf->len); | ||||||
| 			strbuf_reset(buf); | 			strbuf_reset(buf); | ||||||
| 		} else if (!is_null_oid(prev)) { | 		} 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); | 		close(fd); | ||||||
|  |  | ||||||
| 		if (launch_editor(path, buf, NULL)) { | 		if (trailer_args->nr && amend_file_with_trailers(path, trailer_args)) | ||||||
| 			fprintf(stderr, | 			die(_("unable to pass trailers to --trailers")); | ||||||
| 			_("Please supply the message using either -m or -F option.\n")); |  | ||||||
| 			exit(1); | 		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 ref_sorting *sorting; | ||||||
| 	struct string_list sorting_options = STRING_LIST_INIT_DUP; | 	struct string_list sorting_options = STRING_LIST_INIT_DUP; | ||||||
| 	struct ref_format format = REF_FORMAT_INIT; | 	struct ref_format format = REF_FORMAT_INIT; | ||||||
|  | 	struct strvec trailer_args = STRVEC_INIT; | ||||||
| 	int icase = 0; | 	int icase = 0; | ||||||
| 	int edit_flag = 0; | 	int edit_flag = 0; | ||||||
| 	struct option options[] = { | 	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"), | 		OPT_CALLBACK_F('m', "message", &msg, N_("message"), | ||||||
| 			       N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg), | 			       N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg), | ||||||
| 		OPT_FILENAME('F', "file", &msgfile, N_("read message from file")), | 		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('e', "edit", &edit_flag, N_("force edit of tag message")), | ||||||
| 		OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")), | 		OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")), | ||||||
| 		OPT_CLEANUP(&cleanup_arg), | 		OPT_CLEANUP(&cleanup_arg), | ||||||
|  | @ -548,7 +566,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) | ||||||
| 		opt.sign = 1; | 		opt.sign = 1; | ||||||
| 		set_signing_key(keyid); | 		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)) | 	if ((create_tag_object || force) && (cmdmode != 0)) | ||||||
| 		usage_with_options(git_tag_usage, options); | 		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; | 			opt.sign = 1; | ||||||
| 		path = git_pathdup("TAG_EDITMSG"); | 		path = git_pathdup("TAG_EDITMSG"); | ||||||
| 		create_tag(&object, object_ref, tag, &buf, &opt, &prev, &object, | 		create_tag(&object, object_ref, tag, &buf, &opt, &prev, &object, | ||||||
| 			   path); | 			   &trailer_args, path); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	transaction = ref_transaction_begin(&err); | 	transaction = ref_transaction_begin(&err); | ||||||
|  | @ -686,6 +705,7 @@ cleanup: | ||||||
| 	strbuf_release(&reflog_msg); | 	strbuf_release(&reflog_msg); | ||||||
| 	strbuf_release(&msg.buf); | 	strbuf_release(&msg.buf); | ||||||
| 	strbuf_release(&err); | 	strbuf_release(&err); | ||||||
|  | 	strvec_clear(&trailer_args); | ||||||
| 	free(msgfile); | 	free(msgfile); | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										114
									
								
								t/t7004-tag.sh
								
								
								
								
							
							
						
						
									
										114
									
								
								t/t7004-tag.sh
								
								
								
								
							|  | @ -668,6 +668,115 @@ test_expect_success \ | ||||||
| 	test_cmp expect actual | 	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: | # listing messages for annotated non-signed tags: | ||||||
|  |  | ||||||
| test_expect_success \ | 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-lines 0 1 ! | ||||||
| 	refs/tags/tag-one-line 0 1 ! | 	refs/tags/tag-one-line 0 1 ! | ||||||
| 	refs/tags/tag-right 0 0 ! | 	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 ! | 	refs/tags/tag-zero-lines 0 1 ! | ||||||
| 	EOF | 	EOF | ||||||
| 	git tag -l --format="%(refname) %(ahead-behind:HEAD) !" >actual 2>err && | 	git tag -l --format="%(refname) %(ahead-behind:HEAD) !" >actual 2>err && | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 John Passaro
						John Passaro