ref-filter: modify the 'lstrip=<N>' option to work with negative '<N>'

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.188@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Karthik Nayak committed Jan 10, 2017 at 14:19 UTC 1a0ca5e358c1af14160e610e346c706e5dccf535
3 files changed +36 -13
Documentation/git-for-each-ref.txt
+8 -2
@@ -97,8 +97,14 @@ refname::
97 The option core.warnAmbiguousRefs is used to select the strict
98 abbreviation mode. If `lstrip=<N>` is appended, strips `<N>`
99 slash-separated path components from the front of the refname
100 - (e.g., `%(refname:lstrip=2)` turns `refs/tags/foo` into `foo`.
101 - `<N>` must be a positive integer.
100 + (e.g. `%(refname:lstrip=2)` turns `refs/tags/foo` into `foo`.
101 + If `<N>` is a negative number, strip as many path components as
102 + necessary from the left to leave `-<N>` path components
103 + (e.g. `%(refname:lstrip=-2)` turns
104 + `refs/tags/foo` into `tags/foo`). When the ref does not have
105 + enough components, the result becomes an empty string if
106 + stripping with positive <N>, or it becomes the full refname if
107 + stripping with negative <N>. Neither is an error.
108
109 objecttype::
110 The type of the object (`blob`, `tree`, `commit`, `tag`).
ref-filter.c
+22 -5
@@ -34,7 +34,7 @@ struct if_then_else {
34
35 struct refname_atom {
36 enum { R_NORMAL, R_SHORT, R_LSTRIP } option;
37 - unsigned int lstrip;
37 + int lstrip;
38 };
39
40 /*
@@ -93,8 +93,8 @@ static void refname_atom_parser_internal(struct refname_atom *atom,
93 atom->option = R_SHORT;
94 else if (skip_prefix(arg, "lstrip=", &arg)) {
95 atom->option = R_LSTRIP;
96 - if (strtoul_ui(arg, 10, &atom->lstrip) || atom->lstrip <= 0)
97 - die(_("positive value expected refname:lstrip=%s"), arg);
96 + if (strtol_i(arg, 10, &atom->lstrip))
97 + die(_("Integer value expected refname:lstrip=%s"), arg);
98 } else
99 die(_("unrecognized %%(%s) argument: %s"), name, arg);
100 }
@@ -1091,12 +1091,28 @@ static inline char *copy_advance(char *dst, const char *src)
1091 return dst;
1092 }
1093
1094 -static const char *lstrip_ref_components(const char *refname, unsigned int len)
1094 +static const char *lstrip_ref_components(const char *refname, int len)
1095 {
1096 long remaining = len;
1097 const char *start = refname;
1098
1099 - while (remaining) {
1099 + if (len < 0) {
1100 + int i;
1101 + const char *p = refname;
1102 +
1103 + /* Find total no of '/' separated path-components */
1104 + for (i = 0; p[i]; p[i] == '/' ? i++ : *p++)
1105 + ;
1106 + /*
1107 + * The number of components we need to strip is now
1108 + * the total minus the components to be left (Plus one
1109 + * because we count the number of '/', but the number
1110 + * of components is one more than the no of '/').
1111 + */
1112 + remaining = i + len + 1;
1113 + }
1114 +
1115 + while (remaining > 0) {
1116 switch (*start++) {
1117 case '\0':
1118 return "";
@@ -1105,6 +1121,7 @@ static const char *lstrip_ref_components(const char *refname, unsigned int len)
1121 break;
1122 }
1123 }
1124 +
1125 return start;
1126 }
1127
t/t6300-for-each-ref.sh
+6 -6
@@ -53,12 +53,16 @@ test_atom head refname refs/heads/master
53 test_atom head refname:short master
54 test_atom head refname:lstrip=1 heads/master
55 test_atom head refname:lstrip=2 master
56 +test_atom head refname:lstrip=-1 master
57 +test_atom head refname:lstrip=-2 heads/master
58 test_atom head upstream refs/remotes/origin/master
59 test_atom head upstream:short origin/master
60 test_atom head upstream:lstrip=2 origin/master
61 +test_atom head upstream:lstrip=-2 origin/master
62 test_atom head push refs/remotes/myfork/master
63 test_atom head push:short myfork/master
64 test_atom head push:lstrip=1 remotes/myfork/master
65 +test_atom head push:lstrip=-1 master
66 test_atom head objecttype commit
67 test_atom head objectsize 171
68 test_atom head objectname $(git rev-parse refs/heads/master)
@@ -141,12 +145,6 @@ test_expect_success 'Check invalid atoms names are errors' '
145 test_must_fail git for-each-ref --format="%(INVALID)" refs/heads
146 '
147
144 -test_expect_success 'arguments to :lstrip must be positive integers' '
145 - test_must_fail git for-each-ref --format="%(refname:lstrip=0)" &&
146 - test_must_fail git for-each-ref --format="%(refname:lstrip=-1)" &&
147 - test_must_fail git for-each-ref --format="%(refname:lstrip=foo)"
148 -'
149 -
148 test_expect_success 'Check format specifiers are ignored in naming date atoms' '
149 git for-each-ref --format="%(authordate)" refs/heads &&
150 git for-each-ref --format="%(authordate:default) %(authordate)" refs/heads &&
@@ -624,10 +622,12 @@ test_expect_success 'Verify usage of %(symref:short) atom' '
622
623 cat >expected <<EOF
624 master
625 +heads/master
626 EOF
627
628 test_expect_success 'Verify usage of %(symref:lstrip) atom' '
629 git for-each-ref --format="%(symref:lstrip=2)" refs/heads/sym > actual &&
630 + git for-each-ref --format="%(symref:lstrip=-2)" refs/heads/sym >> actual &&
631 test_cmp expected actual
632 '
633