Merge branch 'tg/tag-state-tag-name-in-editor-hints'
authorJunio C Hamano <gitster@pobox.com>
Fri, 6 Jun 2014 18:22:24 +0000 (11:22 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 6 Jun 2014 18:22:25 +0000 (11:22 -0700)
* tg/tag-state-tag-name-in-editor-hints:
builtin/tag.c: show tag name to hint in the message editor

1  2 
builtin/tag.c
diff --combined builtin/tag.c
index f3440023abecc672386381749d5e5855f3673ecf,8f1a0c170c092be1c364fffe92469938cdf0703e..c6e8a7112700c03b2615f9c99c9858884128ab25
@@@ -27,16 -27,9 +27,16 @@@ static const char * const git_tag_usage
        NULL
  };
  
 +#define STRCMP_SORT     0     /* must be zero */
 +#define VERCMP_SORT     1
 +#define SORT_MASK       0x7fff
 +#define REVERSE_SORT    0x8000
 +
  struct tag_filter {
        const char **patterns;
        int lines;
 +      int sort;
 +      struct string_list tags;
        struct commit_list *with_commit;
  };
  
@@@ -49,7 -42,7 +49,7 @@@ static int match_pattern(const char **p
        if (!*patterns)
                return 1;
        for (; *patterns; patterns++)
 -              if (!fnmatch(*patterns, ref, 0))
 +              if (!wildmatch(*patterns, ref, 0, NULL))
                        return 1;
        return 0;
  }
@@@ -80,19 -73,11 +80,19 @@@ static int in_commit_list(const struct 
        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;
        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)
  {
 -      return contains_recurse(candidate, want);
 +      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)
 +{
 +      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)
@@@ -233,10 -166,7 +233,10 @@@ static int show_reference(const char *r
                        return 0;
  
                if (!filter->lines) {
 -                      printf("%s\n", refname);
 +                      if (filter->sort)
 +                              string_list_append(&filter->tags, refname);
 +                      else
 +                              printf("%s\n", refname);
                        return 0;
                }
                printf("%-15s ", refname);
        return 0;
  }
  
 +static int sort_by_version(const void *a_, const void *b_)
 +{
 +      const struct string_list_item *a = a_;
 +      const struct string_list_item *b = b_;
 +      return versioncmp(a->string, b->string);
 +}
 +
  static int list_tags(const char **patterns, int lines,
 -                      struct commit_list *with_commit)
 +                   struct commit_list *with_commit, int sort)
  {
        struct tag_filter filter;
  
        filter.patterns = patterns;
        filter.lines = lines;
 +      filter.sort = sort;
        filter.with_commit = with_commit;
 +      memset(&filter.tags, 0, sizeof(filter.tags));
 +      filter.tags.strdup_strings = 1;
  
        for_each_tag_ref(show_reference, (void *) &filter);
 -
 +      if (sort) {
 +              int i;
 +              if ((sort & SORT_MASK) == VERCMP_SORT)
 +                      qsort(filter.tags.items, filter.tags.nr,
 +                            sizeof(struct string_list_item), sort_by_version);
 +              if (sort & REVERSE_SORT)
 +                      for (i = filter.tags.nr - 1; i >= 0; i--)
 +                              printf("%s\n", filter.tags.items[i].string);
 +              else
 +                      for (i = 0; i < filter.tags.nr; i++)
 +                              printf("%s\n", filter.tags.items[i].string);
 +              string_list_clear(&filter.tags, 0);
 +      }
        return 0;
  }
  
@@@ -338,11 -246,11 +338,11 @@@ static int do_sign(struct strbuf *buffe
  }
  
  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");
  
@@@ -438,9 -346,9 +438,9 @@@ static void create_tag(const unsigned c
                        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);
                }
@@@ -519,29 -427,6 +519,29 @@@ static int parse_opt_points_at(const st
        return 0;
  }
  
 +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;
 +}
 +
  int cmd_tag(int argc, const char **argv, const char *prefix)
  {
        struct strbuf buf = STRBUF_INIT;
        struct create_tag_options opt;
        char *cleanup_arg = NULL;
        int annotate = 0, force = 0, lines = -1;
 -      int cmdmode = 0;
 +      int cmdmode = 0, sort = 0;
        const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
        struct commit_list *with_commit = NULL;
                OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
                OPT_STRING(0, "cleanup", &cleanup_arg, N_("mode"),
                        N_("how to strip spaces and #comments from message")),
 -              OPT_STRING('u', "local-user", &keyid, N_("key id"),
 +              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_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
 +              {
 +                      OPTION_CALLBACK, 0, "sort", &sort, N_("type"), N_("sort tags"),
 +                      PARSE_OPT_NONEG, parse_opt_sort
 +              },
  
                OPT_GROUP(N_("Tag listing options")),
                {
                        PARSE_OPT_LASTARG_DEFAULT,
                        parse_opt_with_commit, (intptr_t)"HEAD",
                },
 +              {
 +                      OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
 +                      N_("print only tags that contain the commit"),
 +                      PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
 +                      parse_opt_with_commit, (intptr_t)"HEAD",
 +              },
                {
                        OPTION_CALLBACK, 0, "points-at", NULL, N_("object"),
                        N_("print only tags of the object"), 0, parse_opt_points_at
                        copts.padding = 2;
                        run_column_filter(colopts, &copts);
                }
 -              ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit);
 +              if (lines != -1 && sort)
 +                      die(_("--sort and -n are incompatible"));
 +              ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, sort);
                if (column_active(colopts))
                        stop_column_filter();
                return ret;