branch.c: use 'ref-filter' APIs

Make 'branch.c' use 'ref-filter' APIs for iterating through refs sorting. This removes most of the code used in 'branch.c' replacing it with calls to the 'ref-filter' library. Make 'branch.c' use the 'filter_refs()' function provided by 'ref-filter' to filter out tags based on the options set. We provide a sorting option provided for 'branch.c' by using the sorting options provided by 'ref-filter'. Also by default, we sort by 'refname'. Since 'HEAD' is alphabatically before 'refs/...' we end up with an array consisting of the 'HEAD' ref then the local branches and finally the remote-tracking branches. Also remove the 'ignore' variable from ref_array_item as it was previously used for the '--merged' option and now that is handled by ref-filter. Modify some of the tests in t1430 to check the stderr for a warning regarding the broken ref. This is done as ref-filter throws a warning for broken refs rather than directly printing them. Add tests and 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 23, 2015 at 23:41 UTC aedcb7dc75e5c260f20bebe14925f3ac4841b03d
6 files changed +78 -192
Documentation/git-branch.txt
+11 -1
@@ -11,7 +11,7 @@ SYNOPSIS
11 'git branch' [--color[=<when>] | --no-color] [-r | -a]
12 [--list] [-v [--abbrev=<length> | --no-abbrev]]
13 [--column[=<options>] | --no-column]
14 - [(--merged | --no-merged | --contains) [<commit>]] [<pattern>...]
14 + [(--merged | --no-merged | --contains) [<commit>]] [--sort=<key>] [<pattern>...]
15 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
16 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
17 'git branch' --unset-upstream [<branchname>]
@@ -229,6 +229,16 @@ start-point is either a local or remote-tracking branch.
229 The new name for an existing branch. The same restrictions as for
230 <branchname> apply.
231
232 +--sort=<key>::
233 + Sort based on the key given. Prefix `-` to sort in descending
234 + order of the value. You may use the --sort=<key> option
235 + multiple times, in which case the last key becomes the primary
236 + key. The keys supported are the same as those in `git
237 + for-each-ref`. Sort order defaults to sorting based on the
238 + full refname (including `refs/...` prefix). This lists
239 + detached HEAD (if present) first, then local branches and
240 + finally remote-tracking branches.
241 +
242
243 Examples
244 --------
builtin/branch.c
+35 -178
@@ -270,125 +270,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
270 return(ret);
271 }
272
273 -static char *resolve_symref(const char *src, const char *prefix)
274 -{
275 - unsigned char sha1[20];
276 - int flag;
277 - const char *dst;
278 -
279 - dst = resolve_ref_unsafe(src, 0, sha1, &flag);
280 - if (!(dst && (flag & REF_ISSYMREF)))
281 - return NULL;
282 - if (prefix)
283 - skip_prefix(dst, prefix, &dst);
284 - return xstrdup(dst);
285 -}
286 -
287 -static int match_patterns(const char **pattern, const char *refname)
288 -{
289 - if (!*pattern)
290 - return 1; /* no pattern always matches */
291 - while (*pattern) {
292 - if (!wildmatch(*pattern, refname, 0, NULL))
293 - return 1;
294 - pattern++;
295 - }
296 - return 0;
297 -}
298 -
299 -/*
300 - * Allocate memory for a new ref_array_item and insert that into the
301 - * given ref_array. Doesn't take the objectname unlike
302 - * new_ref_array_item(). This is a temporary function which will be
303 - * removed when we port branch.c to use ref-filter APIs.
304 - */
305 -static struct ref_array_item *ref_array_append(struct ref_array *array, const char *refname)
306 -{
307 - size_t len = strlen(refname);
308 - struct ref_array_item *ref = xcalloc(1, sizeof(struct ref_array_item) + len + 1);
309 - memcpy(ref->refname, refname, len);
310 - ref->refname[len] = '\0';
311 - REALLOC_ARRAY(array->items, array->nr + 1);
312 - array->items[array->nr++] = ref;
313 - return ref;
314 -}
315 -
316 -static int append_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data)
317 -{
318 - struct ref_filter_cbdata *cb = (struct ref_filter_cbdata *)(cb_data);
319 - struct ref_filter *filter = cb->filter;
320 - struct ref_array *array = cb->array;
321 - struct ref_array_item *item;
322 - struct commit *commit;
323 - int kind, i;
324 - const char *prefix, *orig_refname = refname;
325 -
326 - static struct {
327 - int kind;
328 - const char *prefix;
329 - } ref_kind[] = {
330 - { FILTER_REFS_BRANCHES, "refs/heads/" },
331 - { FILTER_REFS_REMOTES, "refs/remotes/" },
332 - };
333 -
334 - /* Detect kind */
335 - for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
336 - prefix = ref_kind[i].prefix;
337 - if (skip_prefix(refname, prefix, &refname)) {
338 - kind = ref_kind[i].kind;
339 - break;
340 - }
341 - }
342 - if (ARRAY_SIZE(ref_kind) <= i) {
343 - if (!strcmp(refname, "HEAD"))
344 - kind = FILTER_REFS_DETACHED_HEAD;
345 - else
346 - return 0;
347 - }
348 -
349 - /* Don't add types the caller doesn't want */
350 - if ((kind & filter->kind) == 0)
351 - return 0;
352 -
353 - if (!match_patterns(filter->name_patterns, refname))
354 - return 0;
355 -
356 - commit = NULL;
357 - if (filter->verbose || filter->with_commit || filter->merge != REF_FILTER_MERGED_NONE) {
358 - commit = lookup_commit_reference_gently(oid->hash, 1);
359 - if (!commit)
360 - return 0;
361 -
362 - /* Filter with with_commit if specified */
363 - if (!is_descendant_of(commit, filter->with_commit))
364 - return 0;
365 -
366 - if (filter->merge != REF_FILTER_MERGED_NONE)
367 - add_pending_object(array->revs,
368 - (struct object *)commit, refname);
369 - }
370 -
371 - item = ref_array_append(array, refname);
372 -
373 - /* Record the new item */
374 - item->kind = kind;
375 - item->commit = commit;
376 - item->symref = resolve_symref(orig_refname, prefix);
377 - item->ignore = 0;
378 -
379 - return 0;
380 -}
381 -
382 -static int ref_cmp(const void *r1, const void *r2)
383 -{
384 - struct ref_array_item *c1 = *((struct ref_array_item **)r1);
385 - struct ref_array_item *c2 = *((struct ref_array_item **)r2);
386 -
387 - if (c1->kind != c2->kind)
388 - return c1->kind - c2->kind;
389 - return strcmp(c1->refname, c2->refname);
390 -}
391 -
273 static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
274 int show_upstream_ref)
275 {
@@ -452,7 +333,7 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
333 }
334
335 static void add_verbose_info(struct strbuf *out, struct ref_array_item *item,
455 - struct ref_filter *filter)
336 + struct ref_filter *filter, const char *refname)
337 {
338 struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
339 const char *sub = _(" **** invalid ref ****");
@@ -464,7 +345,7 @@ static void add_verbose_info(struct strbuf *out, struct ref_array_item *item,
345 }
346
347 if (item->kind == FILTER_REFS_BRANCHES)
467 - fill_tracking_info(&stat, item->refname, filter->verbose > 1);
348 + fill_tracking_info(&stat, refname, filter->verbose > 1);
349
350 strbuf_addf(out, " %s %s%s",
351 find_unique_abbrev(item->commit->object.sha1, filter->abbrev),
@@ -504,8 +385,8 @@ static char *get_head_description(void)
385 return strbuf_detach(&desc, NULL);
386 }
387
507 -static void print_ref_item(struct ref_array_item *item, int maxwidth,
508 - struct ref_filter *filter, const char *remote_prefix)
388 +static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth,
389 + struct ref_filter *filter, const char *remote_prefix)
390 {
391 char c;
392 int current = 0;
@@ -515,17 +396,16 @@ static void print_ref_item(struct ref_array_item *item, int maxwidth,
396 const char *desc = item->refname;
397 char *to_free = NULL;
398
518 - if (item->ignore)
519 - return;
520 -
399 switch (item->kind) {
400 case FILTER_REFS_BRANCHES:
523 - if (!filter->detached && !strcmp(item->refname, head))
401 + skip_prefix(desc, "refs/heads/", &desc);
402 + if (!filter->detached && !strcmp(desc, head))
403 current = 1;
404 else
405 color = BRANCH_COLOR_LOCAL;
406 break;
407 case FILTER_REFS_REMOTES:
408 + skip_prefix(desc, "refs/remotes/", &desc);
409 color = BRANCH_COLOR_REMOTE;
410 prefix = remote_prefix;
411 break;
@@ -554,11 +434,13 @@ static void print_ref_item(struct ref_array_item *item, int maxwidth,
434 strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
435 name.buf, branch_get_color(BRANCH_COLOR_RESET));
436
557 - if (item->symref)
558 - strbuf_addf(&out, " -> %s", item->symref);
437 + if (item->symref) {
438 + skip_prefix(item->symref, "refs/remotes/", &desc);
439 + strbuf_addf(&out, " -> %s", desc);
440 + }
441 else if (filter->verbose)
442 /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
561 - add_verbose_info(&out, item, filter);
443 + add_verbose_info(&out, item, filter, desc);
444 if (column_active(colopts)) {
445 assert(!filter->verbose && "--column and --verbose are incompatible");
446 string_list_append(&output, out.buf);
@@ -575,11 +457,13 @@ static int calc_maxwidth(struct ref_array *refs, int remote_bonus)
457 int i, max = 0;
458 for (i = 0; i < refs->nr; i++) {
459 struct ref_array_item *it = refs->items[i];
460 + const char *desc = it->refname;
461 int w;
462
580 - if (it->ignore)
581 - continue;
582 - w = utf8_strwidth(it->refname);
463 + skip_prefix(it->refname, "refs/heads/", &desc);
464 + skip_prefix(it->refname, "refs/remotes/", &desc);
465 + w = utf8_strwidth(desc);
466 +
467 if (it->kind == FILTER_REFS_REMOTES)
468 w += remote_bonus;
469 if (w > max)
@@ -588,14 +472,12 @@ static int calc_maxwidth(struct ref_array *refs, int remote_bonus)
472 return max;
473 }
474
591 -static void print_ref_list(struct ref_filter *filter)
475 +static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting)
476 {
477 int i;
478 struct ref_array array;
595 - struct ref_filter_cbdata data;
479 int maxwidth = 0;
480 const char *remote_prefix = "";
598 - struct rev_info revs;
481
482 /*
483 * If we are listing more than just remote branches,
@@ -606,54 +488,26 @@ static void print_ref_list(struct ref_filter *filter)
488 remote_prefix = "remotes/";
489
490 memset(&array, 0, sizeof(array));
609 - if (filter->merge != REF_FILTER_MERGED_NONE)
610 - init_revisions(&revs, NULL);
611 -
612 - data.array = &array;
613 - data.filter = filter;
614 - array.revs = &revs;
615 -
616 - /*
617 - * First we obtain all regular branch refs and if the HEAD is
618 - * detached then we insert that ref to the end of the ref_fist
619 - * so that it can be printed and removed first.
620 - */
621 - for_each_rawref(append_ref, &data);
622 - if (filter->detached)
623 - head_ref(append_ref, &data);
624 - /*
625 - * The following implementation is currently duplicated in ref-filter. It
626 - * will eventually be removed when we port branch.c to use ref-filter APIs.
627 - */
628 - if (filter->merge != REF_FILTER_MERGED_NONE) {
629 - filter->merge_commit->object.flags |= UNINTERESTING;
630 - add_pending_object(&revs, &filter->merge_commit->object, "");
631 - revs.limited = 1;
632 -
633 - if (prepare_revision_walk(&revs))
634 - die(_("revision walk setup failed"));
635 -
636 - for (i = 0; i < array.nr; i++) {
637 - struct ref_array_item *item = array.items[i];
638 - struct commit *commit = item->commit;
639 - int is_merged = !!(commit->object.flags & UNINTERESTING);
640 - item->ignore = is_merged != (filter->merge == REF_FILTER_MERGED_INCLUDE);
641 - }
491
643 - for (i = 0; i < array.nr; i++) {
644 - struct ref_array_item *item = array.items[i];
645 - clear_commit_marks(item->commit, ALL_REV_FLAGS);
646 - }
647 - clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS);
648 - }
492 + verify_ref_format("%(refname)%(symref)");
493 + filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN);
494
495 if (filter->verbose)
496 maxwidth = calc_maxwidth(&array, strlen(remote_prefix));
497
653 - qsort(array.items, array.nr, sizeof(struct ref_array_item *), ref_cmp);
498 + /*
499 + * If no sorting parameter is given then we default to sorting
500 + * by 'refname'. This would give us an alphabetically sorted
501 + * array with the 'HEAD' ref at the beginning followed by
502 + * local branches 'refs/heads/...' and finally remote-tacking
503 + * branches 'refs/remotes/...'.
504 + */
505 + if (!sorting)
506 + sorting = ref_default_sorting();
507 + ref_array_sort(sorting, &array);
508
509 for (i = 0; i < array.nr; i++)
656 - print_ref_item(array.items[i], maxwidth, filter, remote_prefix);
510 + format_and_print_ref_item(array.items[i], maxwidth, filter, remote_prefix);
511
512 ref_array_clear(&array);
513 }
@@ -755,6 +609,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
609 const char *new_upstream = NULL;
610 enum branch_track track;
611 struct ref_filter filter;
612 + static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
613
614 struct option options[] = {
615 OPT_GROUP(N_("Generic options")),
@@ -789,6 +644,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
644 OPT_MERGED(&filter, N_("print only branches that are merged")),
645 OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
646 OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
647 + OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
648 + N_("field name to sort on"), &parse_opt_ref_sorting),
649 OPT_END(),
650 };
651
@@ -847,7 +704,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
704 if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached)
705 filter.kind |= FILTER_REFS_DETACHED_HEAD;
706 filter.name_patterns = argv;
850 - print_ref_list(&filter);
707 + print_ref_list(&filter, sorting);
708 print_columns(&output, colopts, NULL);
709 string_list_clear(&output, 0);
710 return 0;
ref-filter.c
+1 -1
@@ -1331,7 +1331,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
1331 * obtain the commit using the 'oid' available and discard all
1332 * non-commits early. The actual filtering is done later.
1333 */
1334 - if (filter->merge_commit || filter->with_commit) {
1334 + if (filter->merge_commit || filter->with_commit || filter->verbose) {
1335 commit = lookup_commit_reference_gently(oid->hash, 1);
1336 if (!commit)
1337 return 0;
ref-filter.h
-1
@@ -36,7 +36,6 @@ struct ref_array_item {
36 unsigned char objectname[20];
37 int flag;
38 unsigned int kind;
39 - int ignore : 1; /* To be removed in the next patch */
39 const char *symref;
40 struct commit *commit;
41 struct atom_value *value;
t/t1430-bad-ref-name.sh
+20 -11
@@ -38,18 +38,20 @@ test_expect_success 'fast-import: fail on invalid branch name "bad[branch]name"'
38 test_must_fail git fast-import <input
39 '
40
41 -test_expect_success 'git branch shows badly named ref' '
41 +test_expect_success 'git branch shows badly named ref as warning' '
42 cp .git/refs/heads/master .git/refs/heads/broken...ref &&
43 test_when_finished "rm -f .git/refs/heads/broken...ref" &&
44 - git branch >output &&
45 - grep -e "broken\.\.\.ref" output
44 + git branch >output 2>error &&
45 + grep -e "broken\.\.\.ref" error &&
46 + ! grep -e "broken\.\.\.ref" output
47 '
48
49 test_expect_success 'branch -d can delete badly named ref' '
50 cp .git/refs/heads/master .git/refs/heads/broken...ref &&
51 test_when_finished "rm -f .git/refs/heads/broken...ref" &&
52 git branch -d broken...ref &&
52 - git branch >output &&
53 + git branch >output 2>error &&
54 + ! grep -e "broken\.\.\.ref" error &&
55 ! grep -e "broken\.\.\.ref" output
56 '
57
@@ -57,7 +59,8 @@ test_expect_success 'branch -D can delete badly named ref' '
59 cp .git/refs/heads/master .git/refs/heads/broken...ref &&
60 test_when_finished "rm -f .git/refs/heads/broken...ref" &&
61 git branch -D broken...ref &&
60 - git branch >output &&
62 + git branch >output 2>error &&
63 + ! grep -e "broken\.\.\.ref" error &&
64 ! grep -e "broken\.\.\.ref" output
65 '
66
@@ -85,7 +88,8 @@ test_expect_success 'branch -D cannot delete absolute path' '
88 test_expect_success 'git branch cannot create a badly named ref' '
89 test_when_finished "rm -f .git/refs/heads/broken...ref" &&
90 test_must_fail git branch broken...ref &&
88 - git branch >output &&
91 + git branch >output 2>error &&
92 + ! grep -e "broken\.\.\.ref" error &&
93 ! grep -e "broken\.\.\.ref" output
94 '
95
@@ -95,7 +99,8 @@ test_expect_success 'branch -m cannot rename to a bad ref name' '
99 git branch goodref &&
100 test_must_fail git branch -m goodref broken...ref &&
101 test_cmp_rev master goodref &&
98 - git branch >output &&
102 + git branch >output 2>error &&
103 + ! grep -e "broken\.\.\.ref" error &&
104 ! grep -e "broken\.\.\.ref" output
105 '
106
@@ -104,14 +109,16 @@ test_expect_failure 'branch -m can rename from a bad ref name' '
109 test_when_finished "rm -f .git/refs/heads/broken...ref" &&
110 git branch -m broken...ref renamed &&
111 test_cmp_rev master renamed &&
107 - git branch >output &&
112 + git branch >output 2>error &&
113 + ! grep -e "broken\.\.\.ref" error &&
114 ! grep -e "broken\.\.\.ref" output
115 '
116
117 test_expect_success 'push cannot create a badly named ref' '
118 test_when_finished "rm -f .git/refs/heads/broken...ref" &&
119 test_must_fail git push "file://$(pwd)" HEAD:refs/heads/broken...ref &&
114 - git branch >output &&
120 + git branch >output 2>error &&
121 + ! grep -e "broken\.\.\.ref" error &&
122 ! grep -e "broken\.\.\.ref" output
123 '
124
@@ -131,7 +138,8 @@ test_expect_failure 'push --mirror can delete badly named ref' '
138 cp .git/refs/heads/master .git/refs/heads/broken...ref
139 ) &&
140 git -C src push --mirror "file://$top/dest" &&
134 - git -C dest branch >output &&
141 + git -C dest branch >output 2>error &&
142 + ! grep -e "broken\.\.\.ref" error &&
143 ! grep -e "broken\.\.\.ref" output
144 '
145
@@ -159,7 +167,8 @@ test_expect_success 'update-ref -d can delete broken name' '
167 cp .git/refs/heads/master .git/refs/heads/broken...ref &&
168 test_when_finished "rm -f .git/refs/heads/broken...ref" &&
169 git update-ref -d refs/heads/broken...ref &&
162 - git branch >output &&
170 + git branch >output 2>error &&
171 + ! grep -e "broken\.\.\.ref" error &&
172 ! grep -e "broken\.\.\.ref" output
173 '
174
t/t3203-branch-output.sh
+11
@@ -143,4 +143,15 @@ EOF
143 test_i18ncmp expect actual
144 '
145
146 +test_expect_success 'git branch `--sort` option' '
147 + cat >expect <<-\EOF &&
148 + branch-two
149 + * (HEAD detached from fromtag)
150 + branch-one
151 + master
152 + EOF
153 + git branch --sort=objectsize >actual &&
154 + test_i18ncmp expect actual
155 +'
156 +
157 test_done