diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 06208c4990..2e3e96f663 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -92,7 +92,11 @@ refname:: The name of the ref (the part after $GIT_DIR/). For a non-ambiguous short name of the ref append `:short`. The option core.warnAmbiguousRefs is used to select the strict - abbreviation mode. + abbreviation mode. If `strip=` is appended, strips `` + slash-separated path components from the front of the refname + (e.g., `%(refname:strip=2)` turns `refs/tags/foo` into `foo`. + `` must be a positive integer. If a displayed ref has fewer + components than ``, the command aborts with an error. objecttype:: The type of the object (`blob`, `tree`, `commit`, `tag`). diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 7220e5eca1..abab4814ec 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -163,7 +163,7 @@ This option is only applicable when listing tags without annotation lines. A string that interpolates `%(fieldname)` from the object pointed at by a ref being shown. The format is the same as that of linkgit:git-for-each-ref[1]. When unspecified, - defaults to `%(refname:short)`. + defaults to `%(refname:strip=2)`. --[no-]merged []:: Only list tags whose tips are reachable, or not reachable diff --git a/builtin/tag.c b/builtin/tag.c index 8db8c87e57..1705c94665 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -44,11 +44,11 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con if (!format) { if (filter->lines) { to_free = xstrfmt("%s %%(contents:lines=%d)", - "%(align:15)%(refname:short)%(end)", + "%(align:15)%(refname:strip=2)%(end)", filter->lines); format = to_free; } else - format = "%(refname:short)"; + format = "%(refname:strip=2)"; } verify_ref_format(format); diff --git a/ref-filter.c b/ref-filter.c index 7bef7f8dac..f097176ed9 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -763,6 +763,29 @@ static inline char *copy_advance(char *dst, const char *src) return dst; } +static const char *strip_ref_components(const char *refname, const char *nr_arg) +{ + char *end; + long nr = strtol(nr_arg, &end, 10); + long remaining = nr; + const char *start = refname; + + if (nr < 1 || *end != '\0') + die(":strip= requires a positive integer argument"); + + while (remaining) { + switch (*start++) { + case '\0': + die("ref '%s' does not have %ld components to :strip", + refname, nr); + case '/': + remaining--; + break; + } + } + return start; +} + /* * Parse the object referred by ref, and grab needed value. */ @@ -909,11 +932,14 @@ static void populate_value(struct ref_array_item *ref) formatp = strchr(name, ':'); if (formatp) { int num_ours, num_theirs; + const char *arg; formatp++; if (!strcmp(formatp, "short")) refname = shorten_unambiguous_ref(refname, warn_ambiguous_refs); + else if (skip_prefix(formatp, "strip=", &arg)) + refname = strip_ref_components(refname, arg); else if (!strcmp(formatp, "track") && (starts_with(name, "upstream") || starts_with(name, "push"))) { diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index d3913f9088..4261403cf6 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -176,4 +176,12 @@ test_expect_success 'git branch --points-at option' ' test_cmp expect actual ' +test_expect_success 'ambiguous branch/tag not marked' ' + git tag ambiguous && + git branch ambiguous && + echo " ambiguous" >expect && + git branch --list ambiguous >actual && + test_cmp expect actual +' + test_done diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 03873b09d1..19a2823025 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -49,11 +49,17 @@ test_atom() { } test_atom head refname refs/heads/master +test_atom head refname:short master +test_atom head refname:strip=1 heads/master +test_atom head refname:strip=2 master test_atom head upstream refs/remotes/origin/master +test_atom head upstream:short origin/master test_atom head push refs/remotes/myfork/master +test_atom head push:short myfork/master test_atom head objecttype commit test_atom head objectsize 171 test_atom head objectname $(git rev-parse refs/heads/master) +test_atom head objectname:short $(git rev-parse --short refs/heads/master) test_atom head tree $(git rev-parse refs/heads/master^{tree}) test_atom head parent '' test_atom head numparent 0 @@ -86,11 +92,13 @@ test_atom head contents 'Initial test_atom head HEAD '*' test_atom tag refname refs/tags/testtag +test_atom tag refname:short testtag test_atom tag upstream '' test_atom tag push '' test_atom tag objecttype tag test_atom tag objectsize 154 test_atom tag objectname $(git rev-parse refs/tags/testtag) +test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag) test_atom tag tree '' test_atom tag parent '' test_atom tag numparent '' @@ -126,6 +134,16 @@ test_expect_success 'Check invalid atoms names are errors' ' test_must_fail git for-each-ref --format="%(INVALID)" refs/heads ' +test_expect_success 'arguments to :strip must be positive integers' ' + test_must_fail git for-each-ref --format="%(refname:strip=0)" && + test_must_fail git for-each-ref --format="%(refname:strip=-1)" && + test_must_fail git for-each-ref --format="%(refname:strip=foo)" +' + +test_expect_success 'stripping refnames too far gives an error' ' + test_must_fail git for-each-ref --format="%(refname:strip=3)" +' + test_expect_success 'Check format specifiers are ignored in naming date atoms' ' git for-each-ref --format="%(authordate)" refs/heads && git for-each-ref --format="%(authordate:default) %(authordate)" refs/heads && @@ -338,47 +356,14 @@ for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do " done -cat >expected <<\EOF -master -testtag -EOF - -test_expect_success 'Check short refname format' ' - (git for-each-ref --format="%(refname:short)" refs/heads && - git for-each-ref --format="%(refname:short)" refs/tags) >actual && - test_cmp expected actual -' - -cat >expected <actual && - test_cmp expected actual -' - test_expect_success 'setup for upstream:track[short]' ' test_commit two ' -cat >expected <actual && - test_cmp expected actual -' - -cat >expected < -EOF - -test_expect_success 'Check upstream:trackshort format' ' - git for-each-ref --format="%(upstream:trackshort)" refs/heads >actual && - test_cmp expected actual -' +test_atom head upstream:track '[ahead 1]' +test_atom head upstream:trackshort '>' +test_atom head push:track '[ahead 1]' +test_atom head push:trackshort '>' test_expect_success 'Check that :track[short] cannot be used with other atoms' ' test_must_fail git for-each-ref --format="%(refname:track)" 2>/dev/null && @@ -398,21 +383,6 @@ test_expect_success 'Check that :track[short] works when upstream is invalid' ' test_cmp expected actual ' -test_expect_success '%(push) supports tracking specifiers, too' ' - echo "[ahead 1]" >expected && - git for-each-ref --format="%(push:track)" refs/heads >actual && - test_cmp expected actual -' - -cat >expected <actual && - test_cmp expected actual -' - test_expect_success 'Check for invalid refname format' ' test_must_fail git for-each-ref --format="%(refname:INVALID)" ' diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 2797f22530..cf3469b142 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1558,4 +1558,12 @@ test_expect_success '--no-merged show unmerged tags' ' test_cmp expect actual ' +test_expect_success 'ambiguous branch/tags not marked' ' + git tag ambiguous && + git branch ambiguous && + echo ambiguous >expect && + git tag -l ambiguous >actual && + test_cmp expect actual +' + test_done