Skip to content

Commit 8435bdf

Browse files
committed
Merge branch 'bw/shortref'
* bw/shortref: for-each-ref: `:short` format for `refname`
2 parents a1e3c2c + 7d66f21 commit 8435bdf

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)