Currently the 'lstrip=<N>' option only takes a positive value '<N>'
and strips '<N>' slash-separated path components from the left. Modify
the 'lstrip' option to also take a negative number '<N>' which would
strip from the left as necessary and _leave_ behind only 'N'
slash-separated path components from the right-most end.

For e.g. %(refname:lstrip=-1) would make 'foo/goo/abc' into 'abc'.

Add documentation and tests for the same.

Signed-off-by: Karthik Nayak <karthik....@gmail.com>
---
 Documentation/git-for-each-ref.txt |  7 ++++++-
 ref-filter.c                       | 27 ++++++++++++++++++++++-----
 t/t6300-for-each-ref.sh            | 12 ++++++------
 3 files changed, 34 insertions(+), 12 deletions(-)

diff --git a/Documentation/git-for-each-ref.txt 
b/Documentation/git-for-each-ref.txt
index 04ffc3552..331e1fef8 100644
--- a/Documentation/git-for-each-ref.txt
+++ b/Documentation/git-for-each-ref.txt
@@ -98,7 +98,12 @@ refname::
        abbreviation mode. If `lstrip=<N>` is appended, strips `<N>`
        slash-separated path components from the front of the refname
        (e.g., `%(refname:lstrip=2)` turns `refs/tags/foo` into `foo`.
-       `<N>` must be a positive integer.
+       if `<N>` is a negative number, then only `<N>` path components
+       are left behind. (e.g., `%(refname:lstrip=-2)` turns
+       `refs/tags/foo` into `tags/foo`). When the ref does not have
+       enough components, the result becomes an empty string if
+       stripping with positive <N>, or it becomes the full refname if
+       stripping with negative <N>.  Neither is an error.
 
 objecttype::
        The type of the object (`blob`, `tree`, `commit`, `tag`).
diff --git a/ref-filter.c b/ref-filter.c
index 76553ebc4..1cd92ea4e 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -34,7 +34,7 @@ struct if_then_else {
 
 struct refname_atom {
        enum { R_NORMAL, R_SHORT, R_LSTRIP } option;
-       unsigned int lstrip;
+       int lstrip;
 };
 
 /*
@@ -93,8 +93,8 @@ static void refname_atom_parser_internal(struct refname_atom 
*atom,
                atom->option = R_SHORT;
        else if (skip_prefix(arg, "lstrip=", &arg)) {
                atom->option = R_LSTRIP;
-               if (strtoul_ui(arg, 10, &atom->lstrip) || atom->lstrip <= 0)
-                       die(_("positive value expected refname:lstrip=%s"), 
arg);
+               if (strtol_i(arg, 10, &atom->lstrip))
+                       die(_("Integer value expected refname:lstrip=%s"), arg);
        } else
                die(_("unrecognized %%(%s) argument: %s"), name, arg);
 }
@@ -1091,12 +1091,28 @@ static inline char *copy_advance(char *dst, const char 
*src)
        return dst;
 }
 
-static const char *lstrip_ref_components(const char *refname, unsigned int len)
+static const char *lstrip_ref_components(const char *refname, int len)
 {
        long remaining = len;
        const char *start = refname;
 
-       while (remaining) {
+       if (len < 0) {
+               int i;
+               const char *p = refname;
+
+               /* Find total no of '/' separated path-components */
+               for (i = 0; p[i]; p[i] == '/' ? i++ : *p++)
+                       ;
+               /*
+                * The number of components we need to strip is now
+                * the total minus the components to be left (Plus one
+                * because we count the number of '/', but the number
+                * of components is one more than the no of '/').
+                */
+               remaining = i + len + 1;
+       }
+
+       while (remaining > 0) {
                switch (*start++) {
                case '\0':
                        return "";
@@ -1105,6 +1121,7 @@ static const char *lstrip_ref_components(const char 
*refname, unsigned int len)
                        break;
                }
        }
+
        return start;
 }
 
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index d3d1a97db..203cfaa15 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -53,12 +53,16 @@ test_atom head refname refs/heads/master
 test_atom head refname:short master
 test_atom head refname:lstrip=1 heads/master
 test_atom head refname:lstrip=2 master
+test_atom head refname:lstrip=-1 master
+test_atom head refname:lstrip=-2 heads/master
 test_atom head upstream refs/remotes/origin/master
 test_atom head upstream:short origin/master
 test_atom head upstream:lstrip=2 origin/master
+test_atom head upstream:lstrip=-2 origin/master
 test_atom head push refs/remotes/myfork/master
 test_atom head push:short myfork/master
 test_atom head push:lstrip=1 remotes/myfork/master
+test_atom head push:lstrip=-1 master
 test_atom head objecttype commit
 test_atom head objectsize 171
 test_atom head objectname $(git rev-parse refs/heads/master)
@@ -141,12 +145,6 @@ 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 :lstrip must be positive integers' '
-       test_must_fail git for-each-ref --format="%(refname:lstrip=0)" &&
-       test_must_fail git for-each-ref --format="%(refname:lstrip=-1)" &&
-       test_must_fail git for-each-ref --format="%(refname:lstrip=foo)"
-'
-
 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 &&
@@ -624,10 +622,12 @@ test_expect_success 'Verify usage of %(symref:short) 
atom' '
 
 cat >expected <<EOF
 master
+heads/master
 EOF
 
 test_expect_success 'Verify usage of %(symref:lstrip) atom' '
        git for-each-ref --format="%(symref:lstrip=2)" refs/heads/sym > actual 
&&
+       git for-each-ref --format="%(symref:lstrip=-2)" refs/heads/sym >> 
actual &&
        test_cmp expected actual
 '
 
-- 
2.11.0

Reply via email to