in builtin/tag.c [422:645]
int cmd_tag(int argc, const char **argv, const char *prefix)
{
struct strbuf buf = STRBUF_INIT;
struct strbuf ref = STRBUF_INIT;
struct strbuf reflog_msg = STRBUF_INIT;
struct object_id object, prev;
const char *object_ref, *tag;
struct create_tag_options opt;
char *cleanup_arg = NULL;
int create_reflog = 0;
int annotate = 0, force = 0;
int cmdmode = 0, create_tag_object = 0;
const char *msgfile = NULL, *keyid = NULL;
struct msg_arg msg = { .buf = STRBUF_INIT };
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
struct ref_filter filter;
struct ref_sorting *sorting;
struct string_list sorting_options = STRING_LIST_INIT_DUP;
struct ref_format format = REF_FORMAT_INIT;
int icase = 0;
int edit_flag = 0;
struct option options[] = {
OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
{ OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
N_("print <n> lines of each tag message"),
PARSE_OPT_OPTARG, NULL, 1 },
OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'),
OPT_CMDMODE('v', "verify", &cmdmode, N_("verify tags"), 'v'),
OPT_GROUP(N_("Tag creation options")),
OPT_BOOL('a', "annotate", &annotate,
N_("annotated tag, needs a message")),
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_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),
OPT_STRING('u', "local-user", &keyid, N_("key-id"),
N_("use another key to sign the tag")),
OPT__FORCE(&force, N_("replace the tag if exists"), 0),
OPT_BOOL(0, "create-reflog", &create_reflog, N_("create a reflog")),
OPT_GROUP(N_("Tag listing options")),
OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")),
OPT_NO_CONTAINS(&filter.no_commit, N_("print only tags that don't contain the commit")),
OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")),
OPT_WITHOUT(&filter.no_commit, N_("print only tags that don't contain the commit")),
OPT_MERGED(&filter, N_("print only tags that are merged")),
OPT_NO_MERGED(&filter, N_("print only tags that are not merged")),
OPT_REF_SORT(&sorting_options),
{
OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
N_("print only tags of the object"), PARSE_OPT_LASTARG_DEFAULT,
parse_opt_object_name, (intptr_t) "HEAD"
},
OPT_STRING( 0 , "format", &format.format, N_("format"),
N_("format to use for the output")),
OPT__COLOR(&format.use_color, N_("respect format colors")),
OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
OPT_END()
};
int ret = 0;
const char *only_in_list = NULL;
setup_ref_filter_porcelain_msg();
git_config(git_tag_config, &sorting_options);
memset(&opt, 0, sizeof(opt));
memset(&filter, 0, sizeof(filter));
filter.lines = -1;
opt.sign = -1;
argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
if (!cmdmode) {
if (argc == 0)
cmdmode = 'l';
else if (filter.with_commit || filter.no_commit ||
filter.reachable_from || filter.unreachable_from ||
filter.points_at.nr || filter.lines != -1)
cmdmode = 'l';
}
if (cmdmode == 'l')
setup_auto_pager("tag", 1);
if (opt.sign == -1)
opt.sign = cmdmode ? 0 : config_sign_tag > 0;
if (keyid) {
opt.sign = 1;
set_signing_key(keyid);
}
create_tag_object = (opt.sign || annotate || msg.given || msgfile);
if ((create_tag_object || force) && (cmdmode != 0))
usage_with_options(git_tag_usage, options);
finalize_colopts(&colopts, -1);
if (cmdmode == 'l' && filter.lines != -1) {
if (explicitly_enable_column(colopts))
die(_("options '%s' and '%s' cannot be used together"), "--column", "-n");
colopts = 0;
}
sorting = ref_sorting_options(&sorting_options);
ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
filter.ignore_case = icase;
if (cmdmode == 'l') {
if (column_active(colopts)) {
struct column_options copts;
memset(&copts, 0, sizeof(copts));
copts.padding = 2;
run_column_filter(colopts, &copts);
}
filter.name_patterns = argv;
ret = list_tags(&filter, sorting, &format);
if (column_active(colopts))
stop_column_filter();
goto cleanup;
}
if (filter.lines != -1)
only_in_list = "-n";
else if (filter.with_commit)
only_in_list = "--contains";
else if (filter.no_commit)
only_in_list = "--no-contains";
else if (filter.points_at.nr)
only_in_list = "--points-at";
else if (filter.reachable_from)
only_in_list = "--merged";
else if (filter.unreachable_from)
only_in_list = "--no-merged";
if (only_in_list)
die(_("the '%s' option is only allowed in list mode"), only_in_list);
if (cmdmode == 'd') {
ret = delete_tags(argv);
goto cleanup;
}
if (cmdmode == 'v') {
if (format.format && verify_ref_format(&format))
usage_with_options(git_tag_usage, options);
ret = for_each_tag_name(argv, verify_tag, &format);
goto cleanup;
}
if (msg.given || msgfile) {
if (msg.given && msgfile)
die(_("options '%s' and '%s' cannot be used together"), "-F", "-m");
if (msg.given)
strbuf_addbuf(&buf, &(msg.buf));
else {
if (!strcmp(msgfile, "-")) {
if (strbuf_read(&buf, 0, 1024) < 0)
die_errno(_("cannot read '%s'"), msgfile);
} else {
if (strbuf_read_file(&buf, msgfile, 1024) < 0)
die_errno(_("could not open or read '%s'"),
msgfile);
}
}
}
tag = argv[0];
object_ref = argc == 2 ? argv[1] : "HEAD";
if (argc > 2)
die(_("too many arguments"));
if (get_oid(object_ref, &object))
die(_("Failed to resolve '%s' as a valid ref."), object_ref);
if (strbuf_check_tag_ref(&ref, tag))
die(_("'%s' is not a valid tag name."), tag);
if (read_ref(ref.buf, &prev))
oidclr(&prev);
else if (!force)
die(_("tag '%s' already exists"), tag);
opt.message_given = msg.given || msgfile;
opt.use_editor = edit_flag;
if (!cleanup_arg || !strcmp(cleanup_arg, "strip"))
opt.cleanup_mode = CLEANUP_ALL;
else if (!strcmp(cleanup_arg, "verbatim"))
opt.cleanup_mode = CLEANUP_NONE;
else if (!strcmp(cleanup_arg, "whitespace"))
opt.cleanup_mode = CLEANUP_SPACE;
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;
create_tag(&object, object_ref, tag, &buf, &opt, &prev, &object);
}
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, &object, &prev,
create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
reflog_msg.buf, &err) ||
ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_free(transaction);
if (force && !is_null_oid(&prev) && !oideq(&prev, &object))
printf(_("Updated tag '%s' (was %s)\n"), tag,
find_unique_abbrev(&prev, DEFAULT_ABBREV));
cleanup:
ref_sorting_release(sorting);
strbuf_release(&buf);
strbuf_release(&ref);
strbuf_release(&reflog_msg);
strbuf_release(&msg.buf);
strbuf_release(&err);
return ret;
}