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] | ||||
| '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 | ||||
|  |  | |||
|  | @ -28,9 +28,11 @@ | |||
| #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,11 +344,20 @@ static void create_tag(const struct object_id *object, const char *object_ref, | |||
| 		} | ||||
| 		close(fd); | ||||
|  | ||||
| 		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); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (opt->cleanup_mode != CLEANUP_NONE) | ||||
|  | @ -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; | ||||
| } | ||||
|  |  | |||
							
								
								
									
										114
									
								
								t/t7004-tag.sh
								
								
								
								
							
							
						
						
									
										114
									
								
								t/t7004-tag.sh
								
								
								
								
							|  | @ -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 && | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 John Passaro
						John Passaro