tag.c: use 'ref-filter' APIs

Make 'tag.c' use 'ref-filter' APIs for iterating through refs, sorting and printing of refs. This removes most of the code used in 'tag.c' replacing it with calls to the 'ref-filter' library. Make 'tag.c' use the 'filter_refs()' function provided by 'ref-filter' to filter out tags based on the options set. For printing tags we use 'show_ref_array_item()' function provided by 'ref-filter'. We improve the sorting option provided by 'tag.c' by using the sorting options provided by 'ref-filter'. This causes the test 'invalid sort parameter on command line' in t7004 to fail, as 'ref-filter' throws an error for all sorting fields which are incorrect. The test is changed to reflect the same. Modify documentation for the same. Mentored-by: Christian Couder <christian.couder@gmail.com> Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr> Signed-off-by: Karthik Nayak <karthik.188@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Karthik Nayak committed Sep 11, 2015 at 20:36 UTC b7cc53e92c806b73e14b03f60c17b7c29e52b4a4
3 files changed +53 -316
Documentation/git-tag.txt
+9 -7
@@ -13,7 +13,7 @@ SYNOPSIS
13 <tagname> [<commit> | <object>]
14 'git tag' -d <tagname>...
15 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
16 - [--column[=<options>] | --no-column] [--create-reflog] [<pattern>...]
16 + [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>] [<pattern>...]
17 'git tag' -v <tagname>...
18
19 DESCRIPTION
@@ -94,14 +94,16 @@ OPTIONS
94 using fnmatch(3)). Multiple patterns may be given; if any of
95 them matches, the tag is shown.
96
97 ---sort=<type>::
98 - Sort in a specific order. Supported type is "refname"
99 - (lexicographic order), "version:refname" or "v:refname" (tag
97 +--sort=<key>::
98 + Sort based on the key given. Prefix `-` to sort in
99 + descending order of the value. You may use the --sort=<key> option
100 + multiple times, in which case the last key becomes the primary
101 + key. Also supports "version:refname" or "v:refname" (tag
102 names are treated as versions). The "version:refname" sort
103 order can also be affected by the
102 - "versionsort.prereleaseSuffix" configuration variable. Prepend
103 - "-" to reverse sort order. When this option is not given, the
104 - sort order defaults to the value configured for the 'tag.sort'
104 + "versionsort.prereleaseSuffix" configuration variable.
105 + The keys supported are the same as those in `git for-each-ref`.
106 + Sort order defaults to the value configured for the 'tag.sort'
107 variable if it exists, or lexicographic order otherwise. See
108 linkgit:git-config[1].
109
builtin/tag.c
+43 -302
@@ -28,278 +28,35 @@ static const char * const git_tag_usage[] = {
28 NULL
29 };
30
31 -#define STRCMP_SORT 0 /* must be zero */
32 -#define VERCMP_SORT 1
33 -#define SORT_MASK 0x7fff
34 -#define REVERSE_SORT 0x8000
35 -
36 -static int tag_sort;
37 -
31 static unsigned int colopts;
32
40 -static int match_pattern(const char **patterns, const char *ref)
41 -{
42 - /* no pattern means match everything */
43 - if (!*patterns)
44 - return 1;
45 - for (; *patterns; patterns++)
46 - if (!wildmatch(*patterns, ref, 0, NULL))
47 - return 1;
48 - return 0;
49 -}
50 -
51 -/*
52 - * This is currently duplicated in ref-filter.c, and will eventually be
53 - * removed as we port tag.c to use the ref-filter APIs.
54 - */
55 -static const unsigned char *match_points_at(const char *refname,
56 - const unsigned char *sha1,
57 - struct sha1_array *points_at)
58 -{
59 - const unsigned char *tagged_sha1 = NULL;
60 - struct object *obj;
61 -
62 - if (sha1_array_lookup(points_at, sha1) >= 0)
63 - return sha1;
64 - obj = parse_object(sha1);
65 - if (!obj)
66 - die(_("malformed object at '%s'"), refname);
67 - if (obj->type == OBJ_TAG)
68 - tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
69 - if (tagged_sha1 && sha1_array_lookup(points_at, tagged_sha1) >= 0)
70 - return tagged_sha1;
71 - return NULL;
72 -}
73 -
74 -static int in_commit_list(const struct commit_list *want, struct commit *c)
75 -{
76 - for (; want; want = want->next)
77 - if (!hashcmp(want->item->object.sha1, c->object.sha1))
78 - return 1;
79 - return 0;
80 -}
81 -
82 -/*
83 - * The entire code segment for supporting the --contains option has been
84 - * copied over to ref-filter.{c,h}. This will be deleted evetually when
85 - * we port tag.c to use ref-filter APIs.
86 - */
87 -enum contains_result {
88 - CONTAINS_UNKNOWN = -1,
89 - CONTAINS_NO = 0,
90 - CONTAINS_YES = 1
91 -};
92 -
93 -/*
94 - * Test whether the candidate or one of its parents is contained in the list.
95 - * Do not recurse to find out, though, but return -1 if inconclusive.
96 - */
97 -static enum contains_result contains_test(struct commit *candidate,
98 - const struct commit_list *want)
99 -{
100 - /* was it previously marked as containing a want commit? */
101 - if (candidate->object.flags & TMP_MARK)
102 - return 1;
103 - /* or marked as not possibly containing a want commit? */
104 - if (candidate->object.flags & UNINTERESTING)
105 - return 0;
106 - /* or are we it? */
107 - if (in_commit_list(want, candidate)) {
108 - candidate->object.flags |= TMP_MARK;
109 - return 1;
110 - }
111 -
112 - if (parse_commit(candidate) < 0)
113 - return 0;
114 -
115 - return -1;
116 -}
117 -
118 -/*
119 - * Mimicking the real stack, this stack lives on the heap, avoiding stack
120 - * overflows.
121 - *
122 - * At each recursion step, the stack items points to the commits whose
123 - * ancestors are to be inspected.
124 - */
125 -struct stack {
126 - int nr, alloc;
127 - struct stack_entry {
128 - struct commit *commit;
129 - struct commit_list *parents;
130 - } *stack;
131 -};
132 -
133 -static void push_to_stack(struct commit *candidate, struct stack *stack)
134 -{
135 - int index = stack->nr++;
136 - ALLOC_GROW(stack->stack, stack->nr, stack->alloc);
137 - stack->stack[index].commit = candidate;
138 - stack->stack[index].parents = candidate->parents;
139 -}
140 -
141 -static enum contains_result contains(struct commit *candidate,
142 - const struct commit_list *want)
143 -{
144 - struct stack stack = { 0, 0, NULL };
145 - int result = contains_test(candidate, want);
146 -
147 - if (result != CONTAINS_UNKNOWN)
148 - return result;
149 -
150 - push_to_stack(candidate, &stack);
151 - while (stack.nr) {
152 - struct stack_entry *entry = &stack.stack[stack.nr - 1];
153 - struct commit *commit = entry->commit;
154 - struct commit_list *parents = entry->parents;
155 -
156 - if (!parents) {
157 - commit->object.flags |= UNINTERESTING;
158 - stack.nr--;
159 - }
160 - /*
161 - * If we just popped the stack, parents->item has been marked,
162 - * therefore contains_test will return a meaningful 0 or 1.
163 - */
164 - else switch (contains_test(parents->item, want)) {
165 - case CONTAINS_YES:
166 - commit->object.flags |= TMP_MARK;
167 - stack.nr--;
168 - break;
169 - case CONTAINS_NO:
170 - entry->parents = parents->next;
171 - break;
172 - case CONTAINS_UNKNOWN:
173 - push_to_stack(parents->item, &stack);
174 - break;
175 - }
176 - }
177 - free(stack.stack);
178 - return contains_test(candidate, want);
179 -}
180 -
181 -/*
182 - * Currently modified and used in ref-filter as append_lines(), will
183 - * eventually be removed as we port tag.c to use ref-filter APIs.
184 - */
185 -static void show_tag_lines(const struct object_id *oid, int lines)
186 -{
187 - int i;
188 - unsigned long size;
189 - enum object_type type;
190 - char *buf, *sp, *eol;
191 - size_t len;
192 -
193 - buf = read_sha1_file(oid->hash, &type, &size);
194 - if (!buf)
195 - die_errno("unable to read object %s", oid_to_hex(oid));
196 - if (type != OBJ_COMMIT && type != OBJ_TAG)
197 - goto free_return;
198 - if (!size)
199 - die("an empty %s object %s?",
200 - typename(type), oid_to_hex(oid));
201 -
202 - /* skip header */
203 - sp = strstr(buf, "\n\n");
204 - if (!sp)
205 - goto free_return;
206 -
207 - /* only take up to "lines" lines, and strip the signature from a tag */
208 - if (type == OBJ_TAG)
209 - size = parse_signature(buf, size);
210 - for (i = 0, sp += 2; i < lines && sp < buf + size; i++) {
211 - if (i)
212 - printf("\n ");
213 - eol = memchr(sp, '\n', size - (sp - buf));
214 - len = eol ? eol - sp : size - (sp - buf);
215 - fwrite(sp, len, 1, stdout);
216 - if (!eol)
217 - break;
218 - sp = eol + 1;
219 - }
220 -free_return:
221 - free(buf);
222 -}
223 -
224 -static void ref_array_append(struct ref_array *array, const char *refname)
225 -{
226 - size_t len = strlen(refname);
227 - struct ref_array_item *ref = xcalloc(1, sizeof(struct ref_array_item) + len + 1);
228 - memcpy(ref->refname, refname, len);
229 - ref->refname[len] = '\0';
230 - REALLOC_ARRAY(array->items, array->nr + 1);
231 - array->items[array->nr++] = ref;
232 -}
233 -
234 -static int show_reference(const char *refname, const struct object_id *oid,
235 - int flag, void *cb_data)
236 -{
237 - struct ref_filter_cbdata *data = cb_data;
238 - struct ref_array *array = data->array;
239 - struct ref_filter *filter = data->filter;
240 -
241 - if (match_pattern(filter->name_patterns, refname)) {
242 - if (filter->with_commit) {
243 - struct commit *commit;
244 -
245 - commit = lookup_commit_reference_gently(oid->hash, 1);
246 - if (!commit)
247 - return 0;
248 - if (!contains(commit, filter->with_commit))
249 - return 0;
250 - }
251 -
252 - if (filter->points_at.nr && !match_points_at(refname, oid->hash, &filter->points_at))
253 - return 0;
254 -
255 - if (!filter->lines) {
256 - if (tag_sort)
257 - ref_array_append(array, refname);
258 - else
259 - printf("%s\n", refname);
260 - return 0;
261 - }
262 - printf("%-15s ", refname);
263 - show_tag_lines(oid, filter->lines);
264 - putchar('\n');
265 - }
266 -
267 - return 0;
268 -}
269 -
270 -static int sort_by_version(const void *a_, const void *b_)
271 -{
272 - const struct ref_array_item *a = *((struct ref_array_item **)a_);
273 - const struct ref_array_item *b = *((struct ref_array_item **)b_);
274 - return versioncmp(a->refname, b->refname);
275 -}
276 -
277 -static int list_tags(struct ref_filter *filter, int sort)
33 +static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting)
34 {
35 struct ref_array array;
280 - struct ref_filter_cbdata data;
36 + char *format, *to_free = NULL;
37 + int i;
38
39 memset(&array, 0, sizeof(array));
283 - data.array = &array;
284 - data.filter = filter;
40
41 if (filter->lines == -1)
42 filter->lines = 0;
43
289 - for_each_tag_ref(show_reference, &data);
290 - if (sort) {
291 - int i;
292 - if ((sort & SORT_MASK) == VERCMP_SORT)
293 - qsort(array.items, array.nr,
294 - sizeof(struct ref_array_item *), sort_by_version);
295 - if (sort & REVERSE_SORT)
296 - for (i = array.nr - 1; i >= 0; i--)
297 - printf("%s\n", array.items[i]->refname);
298 - else
299 - for (i = 0; i < array.nr; i++)
300 - printf("%s\n", array.items[i]->refname);
301 - ref_array_clear(&array);
302 - }
44 + if (filter->lines) {
45 + to_free = xstrfmt("%s %%(contents:lines=%d)",
46 + "%(align:15)%(refname:short)%(end)", filter->lines);
47 + format = to_free;
48 + } else
49 + format = "%(refname:short)";
50 +
51 + verify_ref_format(format);
52 + filter_refs(&array, filter, FILTER_REFS_TAGS);
53 + ref_array_sort(sorting, &array);
54 +
55 + for (i = 0; i < array.nr; i++)
56 + show_ref_array_item(array.items[i], format, 0);
57 + ref_array_clear(&array);
58 + free(to_free);
59 +
60 return 0;
61 }
62
@@ -366,35 +123,26 @@ static const char tag_template_nocleanup[] =
123 "Lines starting with '%c' will be kept; you may remove them"
124 " yourself if you want to.\n");
125
369 -/*
370 - * Parse a sort string, and return 0 if parsed successfully. Will return
371 - * non-zero when the sort string does not parse into a known type. If var is
372 - * given, the error message becomes a warning and includes information about
373 - * the configuration value.
374 - */
375 -static int parse_sort_string(const char *var, const char *arg, int *sort)
126 +/* Parse arg given and add it the ref_sorting array */
127 +static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail)
128 {
377 - int type = 0, flags = 0;
378 -
379 - if (skip_prefix(arg, "-", &arg))
380 - flags |= REVERSE_SORT;
129 + struct ref_sorting *s;
130 + int len;
131
382 - if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg))
383 - type = VERCMP_SORT;
384 - else
385 - type = STRCMP_SORT;
132 + s = xcalloc(1, sizeof(*s));
133 + s->next = *sorting_tail;
134 + *sorting_tail = s;
135
387 - if (strcmp(arg, "refname")) {
388 - if (!var)
389 - return error(_("unsupported sort specification '%s'"), arg);
390 - else {
391 - warning(_("unsupported sort specification '%s' in variable '%s'"),
392 - var, arg);
393 - return -1;
394 - }
136 + if (*arg == '-') {
137 + s->reverse = 1;
138 + arg++;
139 }
140 + if (skip_prefix(arg, "version:", &arg) ||
141 + skip_prefix(arg, "v:", &arg))
142 + s->version = 1;
143
397 - *sort = (type | flags);
144 + len = strlen(arg);
145 + s->atom = parse_ref_filter_atom(arg, arg+len);
146
147 return 0;
148 }
@@ -402,11 +150,12 @@ static int parse_sort_string(const char *var, const char *arg, int *sort)
150 static int git_tag_config(const char *var, const char *value, void *cb)
151 {
152 int status;
153 + struct ref_sorting **sorting_tail = (struct ref_sorting **)cb;
154
155 if (!strcmp(var, "tag.sort")) {
156 if (!value)
157 return config_error_nonbool(var);
409 - parse_sort_string(var, value, &tag_sort);
158 + parse_sorting_string(value, sorting_tail);
159 return 0;
160 }
161
@@ -564,13 +313,6 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
313 return check_refname_format(sb->buf, 0);
314 }
315
567 -static int parse_opt_sort(const struct option *opt, const char *arg, int unset)
568 -{
569 - int *sort = opt->value;
570 -
571 - return parse_sort_string(NULL, arg, sort);
572 -}
573 -
316 int cmd_tag(int argc, const char **argv, const char *prefix)
317 {
318 struct strbuf buf = STRBUF_INIT;
@@ -587,6 +329,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
329 struct ref_transaction *transaction;
330 struct strbuf err = STRBUF_INIT;
331 struct ref_filter filter;
332 + static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
333 struct option options[] = {
334 OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
335 { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
@@ -613,10 +356,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
356 OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
357 OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")),
358 OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")),
616 - {
617 - OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"),
618 - PARSE_OPT_NONEG, parse_opt_sort
619 - },
359 + OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
360 + N_("field name to sort on"), &parse_opt_ref_sorting),
361 {
362 OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
363 N_("print only tags of the object"), 0, parse_opt_object_name
@@ -624,7 +365,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
365 OPT_END()
366 };
367
627 - git_config(git_tag_config, NULL);
368 + git_config(git_tag_config, sorting_tail);
369
370 memset(&opt, 0, sizeof(opt));
371 memset(&filter, 0, sizeof(filter));
@@ -650,6 +391,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
391 die(_("--column and -n are incompatible"));
392 colopts = 0;
393 }
394 + if (!sorting)
395 + sorting = ref_default_sorting();
396 if (cmdmode == 'l') {
397 int ret;
398 if (column_active(colopts)) {
@@ -658,10 +401,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
401 copts.padding = 2;
402 run_column_filter(colopts, &copts);
403 }
661 - if (filter.lines != -1 && tag_sort)
662 - die(_("--sort and -n are incompatible"));
404 filter.name_patterns = argv;
664 - ret = list_tags(&filter, tag_sort);
405 + ret = list_tags(&filter, sorting);
406 if (column_active(colopts))
407 stop_column_filter();
408 return ret;
t/t7004-tag.sh
+1 -7
@@ -1462,13 +1462,7 @@ test_expect_success 'invalid sort parameter on command line' '
1462
1463 test_expect_success 'invalid sort parameter in configuratoin' '
1464 git config tag.sort "v:notvalid" &&
1465 - git tag -l "foo*" >actual &&
1466 - cat >expect <<-\EOF &&
1467 - foo1.10
1468 - foo1.3
1469 - foo1.6
1470 - EOF
1471 - test_cmp expect actual
1465 + test_must_fail git tag -l "foo*"
1466 '
1467
1468 test_expect_success 'version sort with prerelease reordering' '