aboutsummaryrefslogtreecommitdiff
path: root/builtin/tag.c
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/tag.c')
-rw-r--r--builtin/tag.c217
1 files changed, 156 insertions, 61 deletions
diff --git a/builtin/tag.c b/builtin/tag.c
index 6c7c6bde9..f048cae0e 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -19,9 +19,9 @@
#include "column.h"
static const char * const git_tag_usage[] = {
- N_("git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]"),
+ N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"),
N_("git tag -d <tagname>..."),
- N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>] "
+ N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
"\n\t\t[<pattern>...]"),
N_("git tag -v <tagname>..."),
NULL
@@ -32,6 +32,8 @@ static const char * const git_tag_usage[] = {
#define SORT_MASK 0x7fff
#define REVERSE_SORT 0x8000
+static int tag_sort;
+
struct tag_filter {
const char **patterns;
int lines;
@@ -80,11 +82,19 @@ static int in_commit_list(const struct commit_list *want, struct commit *c)
return 0;
}
-static int contains_recurse(struct commit *candidate,
+enum contains_result {
+ CONTAINS_UNKNOWN = -1,
+ CONTAINS_NO = 0,
+ CONTAINS_YES = 1
+};
+
+/*
+ * Test whether the candidate or one of its parents is contained in the list.
+ * Do not recurse to find out, though, but return -1 if inconclusive.
+ */
+static enum contains_result contains_test(struct commit *candidate,
const struct commit_list *want)
{
- struct commit_list *p;
-
/* was it previously marked as containing a want commit? */
if (candidate->object.flags & TMP_MARK)
return 1;
@@ -92,29 +102,81 @@ static int contains_recurse(struct commit *candidate,
if (candidate->object.flags & UNINTERESTING)
return 0;
/* or are we it? */
- if (in_commit_list(want, candidate))
+ if (in_commit_list(want, candidate)) {
+ candidate->object.flags |= TMP_MARK;
return 1;
+ }
if (parse_commit(candidate) < 0)
return 0;
- /* Otherwise recurse and mark ourselves for future traversals. */
- for (p = candidate->parents; p; p = p->next) {
- if (contains_recurse(p->item, want)) {
- candidate->object.flags |= TMP_MARK;
- return 1;
- }
- }
- candidate->object.flags |= UNINTERESTING;
- return 0;
+ return -1;
+}
+
+/*
+ * Mimicking the real stack, this stack lives on the heap, avoiding stack
+ * overflows.
+ *
+ * At each recursion step, the stack items points to the commits whose
+ * ancestors are to be inspected.
+ */
+struct stack {
+ int nr, alloc;
+ struct stack_entry {
+ struct commit *commit;
+ struct commit_list *parents;
+ } *stack;
+};
+
+static void push_to_stack(struct commit *candidate, struct stack *stack)
+{
+ int index = stack->nr++;
+ ALLOC_GROW(stack->stack, stack->nr, stack->alloc);
+ stack->stack[index].commit = candidate;
+ stack->stack[index].parents = candidate->parents;
}
-static int contains(struct commit *candidate, const struct commit_list *want)
+static enum contains_result contains(struct commit *candidate,
+ const struct commit_list *want)
{
- return contains_recurse(candidate, want);
+ struct stack stack = { 0, 0, NULL };
+ int result = contains_test(candidate, want);
+
+ if (result != CONTAINS_UNKNOWN)
+ return result;
+
+ push_to_stack(candidate, &stack);
+ while (stack.nr) {
+ struct stack_entry *entry = &stack.stack[stack.nr - 1];
+ struct commit *commit = entry->commit;
+ struct commit_list *parents = entry->parents;
+
+ if (!parents) {
+ commit->object.flags |= UNINTERESTING;
+ stack.nr--;
+ }
+ /*
+ * If we just popped the stack, parents->item has been marked,
+ * therefore contains_test will return a meaningful 0 or 1.
+ */
+ else switch (contains_test(parents->item, want)) {
+ case CONTAINS_YES:
+ commit->object.flags |= TMP_MARK;
+ stack.nr--;
+ break;
+ case CONTAINS_NO:
+ entry->parents = parents->next;
+ break;
+ case CONTAINS_UNKNOWN:
+ push_to_stack(parents->item, &stack);
+ break;
+ }
+ }
+ free(stack.stack);
+ return contains_test(candidate, want);
}
-static void show_tag_lines(const unsigned char *sha1, int lines)
+static void show_tag_lines(const struct object_id *oid, int lines)
{
int i;
unsigned long size;
@@ -122,14 +184,14 @@ static void show_tag_lines(const unsigned char *sha1, int lines)
char *buf, *sp, *eol;
size_t len;
- buf = read_sha1_file(sha1, &type, &size);
+ buf = read_sha1_file(oid->hash, &type, &size);
if (!buf)
- die_errno("unable to read object %s", sha1_to_hex(sha1));
+ die_errno("unable to read object %s", oid_to_hex(oid));
if (type != OBJ_COMMIT && type != OBJ_TAG)
goto free_return;
if (!size)
die("an empty %s object %s?",
- typename(type), sha1_to_hex(sha1));
+ typename(type), oid_to_hex(oid));
/* skip header */
sp = strstr(buf, "\n\n");
@@ -153,7 +215,7 @@ free_return:
free(buf);
}
-static int show_reference(const char *refname, const unsigned char *sha1,
+static int show_reference(const char *refname, const struct object_id *oid,
int flag, void *cb_data)
{
struct tag_filter *filter = cb_data;
@@ -162,14 +224,14 @@ static int show_reference(const char *refname, const unsigned char *sha1,
if (filter->with_commit) {
struct commit *commit;
- commit = lookup_commit_reference_gently(sha1, 1);
+ commit = lookup_commit_reference_gently(oid->hash, 1);
if (!commit)
return 0;
if (!contains(commit, filter->with_commit))
return 0;
}
- if (points_at.nr && !match_points_at(refname, sha1))
+ if (points_at.nr && !match_points_at(refname, oid->hash))
return 0;
if (!filter->lines) {
@@ -180,7 +242,7 @@ static int show_reference(const char *refname, const unsigned char *sha1,
return 0;
}
printf("%-15s ", refname);
- show_tag_lines(sha1, filter->lines);
+ show_tag_lines(oid, filter->lines);
putchar('\n');
}
@@ -206,7 +268,7 @@ static int list_tags(const char **patterns, int lines,
memset(&filter.tags, 0, sizeof(filter.tags));
filter.tags.strdup_strings = 1;
- for_each_tag_ref(show_reference, (void *) &filter);
+ for_each_tag_ref(show_reference, (void *)&filter);
if (sort) {
int i;
if ((sort & SORT_MASK) == VERCMP_SORT)
@@ -278,17 +340,59 @@ static int do_sign(struct strbuf *buffer)
}
static const char tag_template[] =
- N_("\nWrite a tag message\n"
+ N_("\nWrite a message for tag:\n %s\n"
"Lines starting with '%c' will be ignored.\n");
static const char tag_template_nocleanup[] =
- N_("\nWrite a tag message\n"
+ N_("\nWrite a message for tag:\n %s\n"
"Lines starting with '%c' will be kept; you may remove them"
" yourself if you want to.\n");
+/*
+ * Parse a sort string, and return 0 if parsed successfully. Will return
+ * non-zero when the sort string does not parse into a known type. If var is
+ * given, the error message becomes a warning and includes information about
+ * the configuration value.
+ */
+static int parse_sort_string(const char *var, const char *arg, int *sort)
+{
+ int type = 0, flags = 0;
+
+ if (skip_prefix(arg, "-", &arg))
+ flags |= REVERSE_SORT;
+
+ if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg))
+ type = VERCMP_SORT;
+ else
+ type = STRCMP_SORT;
+
+ if (strcmp(arg, "refname")) {
+ if (!var)
+ return error(_("unsupported sort specification '%s'"), arg);
+ else {
+ warning(_("unsupported sort specification '%s' in variable '%s'"),
+ var, arg);
+ return -1;
+ }
+ }
+
+ *sort = (type | flags);
+
+ return 0;
+}
+
static int git_tag_config(const char *var, const char *value, void *cb)
{
- int status = git_gpg_config(var, value, cb);
+ int status;
+
+ if (!strcmp(var, "tag.sort")) {
+ if (!value)
+ return config_error_nonbool(var);
+ parse_sort_string(var, value, &tag_sort);
+ return 0;
+ }
+
+ status = git_gpg_config(var, value, cb);
if (status)
return status;
if (starts_with(var, "column."))
@@ -378,9 +482,9 @@ static void create_tag(const unsigned char *object, const char *tag,
struct strbuf buf = STRBUF_INIT;
strbuf_addch(&buf, '\n');
if (opt->cleanup_mode == CLEANUP_ALL)
- strbuf_commented_addf(&buf, _(tag_template), comment_line_char);
+ strbuf_commented_addf(&buf, _(tag_template), tag, comment_line_char);
else
- strbuf_commented_addf(&buf, _(tag_template_nocleanup), comment_line_char);
+ strbuf_commented_addf(&buf, _(tag_template_nocleanup), tag, comment_line_char);
write_or_die(fd, buf.buf, buf.len);
strbuf_release(&buf);
}
@@ -394,7 +498,7 @@ static void create_tag(const unsigned char *object, const char *tag,
}
if (opt->cleanup_mode != CLEANUP_NONE)
- stripspace(buf, opt->cleanup_mode == CLEANUP_ALL);
+ strbuf_stripspace(buf, opt->cleanup_mode == CLEANUP_ALL);
if (!opt->message_given && !buf->len)
die(_("no tag message?"));
@@ -462,24 +566,8 @@ static int parse_opt_points_at(const struct option *opt __attribute__((unused)),
static int parse_opt_sort(const struct option *opt, const char *arg, int unset)
{
int *sort = opt->value;
- int flags = 0;
- if (*arg == '-') {
- flags |= REVERSE_SORT;
- arg++;
- }
- if (starts_with(arg, "version:")) {
- *sort = VERCMP_SORT;
- arg += 8;
- } else if (starts_with(arg, "v:")) {
- *sort = VERCMP_SORT;
- arg += 2;
- } else
- *sort = STRCMP_SORT;
- if (strcmp(arg, "refname"))
- die(_("unsupported sort specification %s"), arg);
- *sort |= flags;
- return 0;
+ return parse_sort_string(NULL, arg, sort);
}
int cmd_tag(int argc, const char **argv, const char *prefix)
@@ -488,14 +576,16 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
struct strbuf ref = STRBUF_INIT;
unsigned char object[20], prev[20];
const char *object_ref, *tag;
- struct ref_lock *lock;
struct create_tag_options opt;
char *cleanup_arg = NULL;
int annotate = 0, force = 0, lines = -1;
- int cmdmode = 0, sort = 0;
+ int create_reflog = 0;
+ int cmdmode = 0;
const char *msgfile = NULL, *keyid = NULL;
struct msg_arg msg = { 0, STRBUF_INIT };
struct commit_list *with_commit = NULL;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
struct option options[] = {
OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
{ OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
@@ -516,13 +606,14 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
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")),
+ 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")),
{
- OPTION_CALLBACK, 0, "sort", &sort, N_("type"), N_("sort tags"),
+ OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"),
PARSE_OPT_NONEG, parse_opt_sort
},
-
- OPT_GROUP(N_("Tag listing options")),
{
OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
N_("print only tags that contain the commit"),
@@ -574,9 +665,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
copts.padding = 2;
run_column_filter(colopts, &copts);
}
- if (lines != -1 && sort)
+ if (lines != -1 && tag_sort)
die(_("--sort and -n are incompatible"));
- ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, sort);
+ ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort);
if (column_active(colopts))
stop_column_filter();
return ret;
@@ -641,14 +732,18 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (annotate)
create_tag(object, tag, &buf, &opt, prev, object);
- lock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);
- if (!lock)
- die(_("%s: cannot lock the ref"), ref.buf);
- if (write_ref_sha1(lock, object, NULL) < 0)
- die(_("%s: cannot update the ref"), ref.buf);
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref.buf, object, prev,
+ create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
+ NULL, &err) ||
+ ref_transaction_commit(transaction, &err))
+ die("%s", err.buf);
+ ref_transaction_free(transaction);
if (force && !is_null_sha1(prev) && hashcmp(prev, object))
printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV));
+ strbuf_release(&err);
strbuf_release(&buf);
strbuf_release(&ref);
return 0;