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
only _leave_ behind 'N' slash-separated path components from the left.

Add documentation and tests for the same.

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

diff --git a/Documentation/git-for-each-ref.txt 
b/Documentation/git-for-each-ref.txt
index f85ccff..ad9b243 100644
--- a/Documentation/git-for-each-ref.txt
+++ b/Documentation/git-for-each-ref.txt
@@ -95,8 +95,9 @@ 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 a displayed ref has fewer
-       components than `<N>`, the command aborts with an error.
+       if `<N>` is a negative number, then only `<N>` path components
+       are left behind.  If a displayed ref has fewer components than
+       `<N>`, the command aborts with an error.
 
 objecttype::
        The type of the object (`blob`, `tree`, `commit`, `tag`).
diff --git a/ref-filter.c b/ref-filter.c
index c74b08d..deb2b29 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -33,7 +33,7 @@ struct if_then_else {
 
 struct refname_atom {
        enum { R_NORMAL, R_SHORT, R_LSTRIP } option;
-       unsigned int lstrip;
+       int lstrip;
 };
 
 /*
@@ -92,8 +92,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);
 }
@@ -1071,21 +1071,37 @@ 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;
 
+       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) {
                switch (*start++) {
                case '\0':
-                       die(_("ref '%s' does not have %ud components to 
:lstrip"),
+                       die(_("ref '%s' does not have %d components to 
:lstrip"),
                            refname, len);
                case '/':
                        remaining--;
                        break;
                }
        }
+
        return start;
 }
 
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index 2b1af9c..26adca8 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,14 +145,9 @@ 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 'stripping refnames too far gives an error' '
-       test_must_fail git for-each-ref --format="%(refname:lstrip=3)"
+       test_must_fail git for-each-ref --format="%(refname:lstrip=3)" &&
+       test_must_fail git for-each-ref --format="%(refname:lstrip=-4)"
 '
 
 test_expect_success 'Check format specifiers are ignored in naming date atoms' 
'
@@ -603,10 +602,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.10.2

Reply via email to