Skip to content

Commit 7d66f21

Browse files
bertwesarggitster
authored andcommitted
for-each-ref: :short format for refname
Tries to shorten the refname to a non-ambiguous name. Szeder Gábor noticed that the git bash completion takes a tremendous amount of time to strip leading components from heads and tags refs (i.e. refs/heads, refs/tags, ...). He proposed a new atom called 'refbasename' which removes at most two leading components from the ref name. I myself, proposed a more dynamic solution, which strips off common leading components with the matched pattern. But the current bash solution and both proposals suffer from one mayor problem: ambiguous refs. A ref is ambiguous, if it resolves to more than one full refs. I.e. given the refs refs/heads/xyzzy and refs/tags/xyzzy. The (short) ref xyzzy can point to both refs. ( Note: Its irrelevant whether the referenced objects are the same or not. ) This proposal solves this by checking for ambiguity of the shorten ref name. The shortening is done with the same rules for resolving refs but in the reverse order. The short name is checked if it resolves to a different ref. To continue the above example, the output would be like this: heads/xyzzy xyzzy So, if you want just tags, xyzzy is not ambiguous, because it will resolve to a tag. If you need the heads you get a also a non-ambiguous short form of the ref. To integrate this new format into the bash completion to get only non-ambiguous refs is beyond the scope of this patch. Signed-off-by: Bert Wesarg <bert.wesarg@googlemail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 80d12c2 commit 7d66f21

File tree

3 files changed

+173
-7
lines changed

3 files changed

+173
-7
lines changed

Documentation/git-for-each-ref.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ For all objects, the following names can be used:
7474

7575
refname::
7676
The name of the ref (the part after $GIT_DIR/).
77+
For a non-ambiguous short name of the ref append `:short`.
7778

7879
objecttype::
7980
The type of the object (`blob`, `tree`, `commit`, `tag`).

builtin-for-each-ref.c

Lines changed: 128 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,107 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, v
545545
}
546546
}
547547

548+
/*
549+
* generate a format suitable for scanf from a ref_rev_parse_rules
550+
* rule, that is replace the "%.*s" spec with a "%s" spec
551+
*/
552+
static void gen_scanf_fmt(char *scanf_fmt, const char *rule)
553+
{
554+
char *spec;
555+
556+
spec = strstr(rule, "%.*s");
557+
if (!spec || strstr(spec + 4, "%.*s"))
558+
die("invalid rule in ref_rev_parse_rules: %s", rule);
559+
560+
/* copy all until spec */
561+
strncpy(scanf_fmt, rule, spec - rule);
562+
scanf_fmt[spec - rule] = '\0';
563+
/* copy new spec */
564+
strcat(scanf_fmt, "%s");
565+
/* copy remaining rule */
566+
strcat(scanf_fmt, spec + 4);
567+
568+
return;
569+
}
570+
571+
/*
572+
* Shorten the refname to an non-ambiguous form
573+
*/
574+
static char *get_short_ref(struct refinfo *ref)
575+
{
576+
int i;
577+
static char **scanf_fmts;
578+
static int nr_rules;
579+
char *short_name;
580+
581+
/* pre generate scanf formats from ref_rev_parse_rules[] */
582+
if (!nr_rules) {
583+
size_t total_len = 0;
584+
585+
/* the rule list is NULL terminated, count them first */
586+
for (; ref_rev_parse_rules[nr_rules]; nr_rules++)
587+
/* no +1 because strlen("%s") < strlen("%.*s") */
588+
total_len += strlen(ref_rev_parse_rules[nr_rules]);
589+
590+
scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);
591+
592+
total_len = 0;
593+
for (i = 0; i < nr_rules; i++) {
594+
scanf_fmts[i] = (char *)&scanf_fmts[nr_rules]
595+
+ total_len;
596+
gen_scanf_fmt(scanf_fmts[i], ref_rev_parse_rules[i]);
597+
total_len += strlen(ref_rev_parse_rules[i]);
598+
}
599+
}
600+
601+
/* bail out if there are no rules */
602+
if (!nr_rules)
603+
return ref->refname;
604+
605+
/* buffer for scanf result, at most ref->refname must fit */
606+
short_name = xstrdup(ref->refname);
607+
608+
/* skip first rule, it will always match */
609+
for (i = nr_rules - 1; i > 0 ; --i) {
610+
int j;
611+
int short_name_len;
612+
613+
if (1 != sscanf(ref->refname, scanf_fmts[i], short_name))
614+
continue;
615+
616+
short_name_len = strlen(short_name);
617+
618+
/*
619+
* check if the short name resolves to a valid ref,
620+
* but use only rules prior to the matched one
621+
*/
622+
for (j = 0; j < i; j++) {
623+
const char *rule = ref_rev_parse_rules[j];
624+
unsigned char short_objectname[20];
625+
626+
/*
627+
* the short name is ambiguous, if it resolves
628+
* (with this previous rule) to a valid ref
629+
* read_ref() returns 0 on success
630+
*/
631+
if (!read_ref(mkpath(rule, short_name_len, short_name),
632+
short_objectname))
633+
break;
634+
}
635+
636+
/*
637+
* short name is non-ambiguous if all previous rules
638+
* haven't resolved to a valid ref
639+
*/
640+
if (j == i)
641+
return short_name;
642+
}
643+
644+
free(short_name);
645+
return ref->refname;
646+
}
647+
648+
548649
/*
549650
* Parse the object referred by ref, and grab needed value.
550651
*/
@@ -570,13 +671,33 @@ static void populate_value(struct refinfo *ref)
570671
for (i = 0; i < used_atom_cnt; i++) {
571672
const char *name = used_atom[i];
572673
struct atom_value *v = &ref->value[i];
573-
if (!strcmp(name, "refname"))
574-
v->s = ref->refname;
575-
else if (!strcmp(name, "*refname")) {
576-
int len = strlen(ref->refname);
577-
char *s = xmalloc(len + 4);
578-
sprintf(s, "%s^{}", ref->refname);
579-
v->s = s;
674+
int deref = 0;
675+
if (*name == '*') {
676+
deref = 1;
677+
name++;
678+
}
679+
if (!prefixcmp(name, "refname")) {
680+
const char *formatp = strchr(name, ':');
681+
const char *refname = ref->refname;
682+
683+
/* look for "short" refname format */
684+
if (formatp) {
685+
formatp++;
686+
if (!strcmp(formatp, "short"))
687+
refname = get_short_ref(ref);
688+
else
689+
die("unknown refname format %s",
690+
formatp);
691+
}
692+
693+
if (!deref)
694+
v->s = refname;
695+
else {
696+
int len = strlen(refname);
697+
char *s = xmalloc(len + 4);
698+
sprintf(s, "%s^{}", refname);
699+
v->s = s;
700+
}
580701
}
581702
}
582703

t/t6300-for-each-ref.sh

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,50 @@ for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
262262
"
263263
done
264264

265+
cat >expected <<\EOF
266+
master
267+
testtag
268+
EOF
269+
270+
test_expect_success 'Check short refname format' '
271+
(git for-each-ref --format="%(refname:short)" refs/heads &&
272+
git for-each-ref --format="%(refname:short)" refs/tags) >actual &&
273+
test_cmp expected actual
274+
'
275+
276+
test_expect_success 'Check for invalid refname format' '
277+
test_must_fail git for-each-ref --format="%(refname:INVALID)"
278+
'
279+
280+
cat >expected <<\EOF
281+
heads/master
282+
master
283+
EOF
284+
285+
test_expect_success 'Check ambiguous head and tag refs' '
286+
git checkout -b newtag &&
287+
echo "Using $datestamp" > one &&
288+
git add one &&
289+
git commit -m "Branch" &&
290+
setdate_and_increment &&
291+
git tag -m "Tagging at $datestamp" master &&
292+
git for-each-ref --format "%(refname:short)" refs/heads/master refs/tags/master >actual &&
293+
test_cmp expected actual
294+
'
295+
296+
cat >expected <<\EOF
297+
heads/ambiguous
298+
ambiguous
299+
EOF
300+
301+
test_expect_success 'Check ambiguous head and tag refs II' '
302+
git checkout master &&
303+
git tag ambiguous testtag^0 &&
304+
git branch ambiguous testtag^0 &&
305+
git for-each-ref --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual &&
306+
test_cmp expected actual
307+
'
308+
265309
test_expect_success 'an unusual tag with an incomplete line' '
266310
267311
git tag -m "bogo" bogo &&

0 commit comments

Comments
 (0)