replace.c: use the ref transaction functions for updates
[gitweb.git] / builtin / tag.c
index 6c7c6bde9de9cbc0ace94aa75df30b7b88b43aca..f3f172f9038d52824bde52d7baae4bee9c3158da 100644 (file)
@@ -80,11 +80,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,26 +100,78 @@ 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;
 }
 
-static int contains(struct commit *candidate, const struct commit_list *want)
+/*
+ * 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 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)
@@ -278,11 +338,11 @@ 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");
 
@@ -378,9 +438,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);
                }
@@ -488,7 +548,6 @@ 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;
@@ -496,6 +555,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        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"),
@@ -641,14 +702,17 @@ 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,
+                                  0, 1, &err) ||
+           ref_transaction_commit(transaction, NULL, &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;