name-rev: extend --refs to accept multiple patterns

Teach git name-rev to take multiple --refs stored as a string list of patterns. The list of patterns will be matched inclusively, and each ref only needs to match one pattern to be included. A ref will only be excluded if it does not match any of the given patterns. Additionally, if any of the patterns would allow abbreviation, then we will abbreviate the ref, even if another pattern is more strict and would not have allowed abbreviation on its own. Add tests and documentation for this change. The tests expected output is dynamically generated. This is in order to avoid hard-coding a commit object name in the test results (as the expected output is to simply leave the commit object unnamed). Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jacob Keller committed Jan 18, 2017 at 15:06 UTC 290be6674a4c89594105faa19061523e7380d586
3 files changed +63 -13
Documentation/git-name-rev.txt
+3 -1
@@ -26,7 +26,9 @@ OPTIONS
26
27 --refs=<pattern>::
28 Only use refs whose names match a given shell pattern. The pattern
29 - can be one of branch name, tag name or fully qualified ref name.
29 + can be one of branch name, tag name or fully qualified ref name. If
30 + given multiple times, use refs whose names match any of the given shell
31 + patterns. Use `--no-refs` to clear any previous ref patterns given.
32
33 --all::
34 List all commits reachable from all refs
builtin/name-rev.c
+34 -12
@@ -108,7 +108,7 @@ static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous)
108 struct name_ref_data {
109 int tags_only;
110 int name_only;
111 - const char *ref_filter;
111 + struct string_list ref_filters;
112 };
113
114 static struct tip_table {
@@ -150,16 +150,38 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo
150 if (data->tags_only && !starts_with(path, "refs/tags/"))
151 return 0;
152
153 - if (data->ref_filter) {
154 - switch (subpath_matches(path, data->ref_filter)) {
155 - case -1: /* did not match */
156 - return 0;
157 - case 0: /* matched fully */
158 - break;
159 - default: /* matched subpath */
160 - can_abbreviate_output = 1;
161 - break;
153 + if (data->ref_filters.nr) {
154 + struct string_list_item *item;
155 + int matched = 0;
156 +
157 + /* See if any of the patterns match. */
158 + for_each_string_list_item(item, &data->ref_filters) {
159 + /*
160 + * Check all patterns even after finding a match, so
161 + * that we can see if a match with a subpath exists.
162 + * When a user asked for 'refs/tags/v*' and 'v1.*',
163 + * both of which match, the user is showing her
164 + * willingness to accept a shortened output by having
165 + * the 'v1.*' in the acceptable refnames, so we
166 + * shouldn't stop when seeing 'refs/tags/v1.4' matches
167 + * 'refs/tags/v*'. We should show it as 'v1.4'.
168 + */
169 + switch (subpath_matches(path, item->string)) {
170 + case -1: /* did not match */
171 + break;
172 + case 0: /* matched fully */
173 + matched = 1;
174 + break;
175 + default: /* matched subpath */
176 + matched = 1;
177 + can_abbreviate_output = 1;
178 + break;
179 + }
180 }
181 +
182 + /* If none of the patterns matched, stop now */
183 + if (!matched)
184 + return 0;
185 }
186
187 add_to_tip_table(oid->hash, path, can_abbreviate_output);
@@ -306,11 +328,11 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
328 {
329 struct object_array revs = OBJECT_ARRAY_INIT;
330 int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;
309 - struct name_ref_data data = { 0, 0, NULL };
331 + struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP };
332 struct option opts[] = {
333 OPT_BOOL(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")),
334 OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")),
313 - OPT_STRING(0, "refs", &data.ref_filter, N_("pattern"),
335 + OPT_STRING_LIST(0, "refs", &data.ref_filters, N_("pattern"),
336 N_("only use refs matching <pattern>")),
337 OPT_GROUP(""),
338 OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")),
t/t6007-rev-list-cherry-pick-file.sh
+26
@@ -99,6 +99,32 @@ test_expect_success '--cherry-pick bar does not come up empty (II)' '
99 test_cmp actual.named expect
100 '
101
102 +test_expect_success 'name-rev multiple --refs combine inclusive' '
103 + git rev-list --left-right --cherry-pick F...E -- bar >actual &&
104 + git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" \
105 + <actual >actual.named &&
106 + test_cmp actual.named expect
107 +'
108 +
109 +cat >expect <<EOF
110 +<tags/F
111 +EOF
112 +
113 +test_expect_success 'name-rev --refs excludes non-matched patterns' '
114 + git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect &&
115 + git rev-list --left-right --cherry-pick F...E -- bar >actual &&
116 + git name-rev --stdin --name-only --refs="*tags/F" \
117 + <actual >actual.named &&
118 + test_cmp actual.named expect
119 +'
120 +
121 +test_expect_success 'name-rev --no-refs clears the refs list' '
122 + git rev-list --left-right --cherry-pick F...E -- bar >expect &&
123 + git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" --no-refs --refs="*tags/G" \
124 + <expect >actual &&
125 + test_cmp actual expect
126 +'
127 +
128 cat >expect <<EOF
129 +tags/F
130 =tags/D