Skip to content

Commit 1b32498

Browse files
committed
Merge branch 'jk/describe-omit-some-refs'
"git describe" and "git name-rev" have been taught to take more than one refname patterns to restrict the set of refs to base their naming output on, and also learned to take negative patterns to name refs not to be used for naming via their "--exclude" option. * jk/describe-omit-some-refs: describe: teach describe negative pattern matches describe: teach --match to accept multiple patterns name-rev: add support to exclude refs by pattern match name-rev: extend --refs to accept multiple patterns doc: add documentation for OPT_STRING_LIST
2 parents e7e07d5 + 77d21f2 commit 1b32498

File tree

8 files changed

+187
-21
lines changed

8 files changed

+187
-21
lines changed

Documentation/git-describe.txt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,20 @@ OPTIONS
8383
--match <pattern>::
8484
Only consider tags matching the given `glob(7)` pattern,
8585
excluding the "refs/tags/" prefix. This can be used to avoid
86-
leaking private tags from the repository.
86+
leaking private tags from the repository. If given multiple times, a
87+
list of patterns will be accumulated, and tags matching any of the
88+
patterns will be considered. Use `--no-match` to clear and reset the
89+
list of patterns.
90+
91+
--exclude <pattern>::
92+
Do not consider tags matching the given `glob(7)` pattern, excluding
93+
the "refs/tags/" prefix. This can be used to narrow the tag space and
94+
find only tags matching some meaningful criteria. If given multiple
95+
times, a list of patterns will be accumulated and tags matching any
96+
of the patterns will be excluded. When combined with --match a tag will
97+
be considered when it matches at least one --match pattern and does not
98+
match any of the --exclude patterns. Use `--no-exclude` to clear and
99+
reset the list of patterns.
87100

88101
--always::
89102
Show uniquely abbreviated commit object as fallback.

Documentation/git-name-rev.txt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,18 @@ OPTIONS
2626

2727
--refs=<pattern>::
2828
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+
--exclude=<pattern>::
34+
Do not use any ref whose name matches a given shell pattern. The
35+
pattern can be one of branch name, tag name or fully qualified ref
36+
name. If given multiple times, a ref will be excluded when it matches
37+
any of the given patterns. When used together with --refs, a ref will
38+
be used as a match only when it matches at least one --refs pattern and
39+
does not match any --exclude patterns. Use `--no-exclude` to clear the
40+
list of exclude patterns.
3041

3142
--all::
3243
List all commits reachable from all refs

Documentation/technical/api-parse-options.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,11 @@ There are some macros to easily define options:
168168
Introduce an option with string argument.
169169
The string argument is put into `str_var`.
170170

171+
`OPT_STRING_LIST(short, long, &struct string_list, arg_str, description)`::
172+
Introduce an option with string argument.
173+
The string argument is stored as an element in `string_list`.
174+
Use of `--no-option` will clear the list of preceding values.
175+
171176
`OPT_INTEGER(short, long, &int_var, description)`::
172177
Introduce an option with integer argument.
173178
The integer is put into `int_var`.

builtin/describe.c

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ static int abbrev = -1; /* unspecified */
2828
static int max_candidates = 10;
2929
static struct hashmap names;
3030
static int have_util;
31-
static const char *pattern;
31+
static struct string_list patterns = STRING_LIST_INIT_NODUP;
32+
static struct string_list exclude_patterns = STRING_LIST_INIT_NODUP;
3233
static int always;
3334
static const char *dirty;
3435

@@ -129,9 +130,40 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi
129130
if (!all && !is_tag)
130131
return 0;
131132

132-
/* Accept only tags that match the pattern, if given */
133-
if (pattern && (!is_tag || wildmatch(pattern, path + 10, 0, NULL)))
134-
return 0;
133+
/*
134+
* If we're given exclude patterns, first exclude any tag which match
135+
* any of the exclude pattern.
136+
*/
137+
if (exclude_patterns.nr) {
138+
struct string_list_item *item;
139+
140+
if (!is_tag)
141+
return 0;
142+
143+
for_each_string_list_item(item, &exclude_patterns) {
144+
if (!wildmatch(item->string, path + 10, 0, NULL))
145+
return 0;
146+
}
147+
}
148+
149+
/*
150+
* If we're given patterns, accept only tags which match at least one
151+
* pattern.
152+
*/
153+
if (patterns.nr) {
154+
struct string_list_item *item;
155+
156+
if (!is_tag)
157+
return 0;
158+
159+
for_each_string_list_item(item, &patterns) {
160+
if (!wildmatch(item->string, path + 10, 0, NULL))
161+
break;
162+
163+
/* If we get here, no pattern matched. */
164+
return 0;
165+
}
166+
}
135167

136168
/* Is it annotated? */
137169
if (!peel_ref(path, peeled.hash)) {
@@ -404,8 +436,10 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
404436
N_("only output exact matches"), 0),
405437
OPT_INTEGER(0, "candidates", &max_candidates,
406438
N_("consider <n> most recent tags (default: 10)")),
407-
OPT_STRING(0, "match", &pattern, N_("pattern"),
439+
OPT_STRING_LIST(0, "match", &patterns, N_("pattern"),
408440
N_("only consider tags matching <pattern>")),
441+
OPT_STRING_LIST(0, "exclude", &exclude_patterns, N_("pattern"),
442+
N_("do not consider tags matching <pattern>")),
409443
OPT_BOOL(0, "always", &always,
410444
N_("show abbreviated commit object as fallback")),
411445
{OPTION_STRING, 0, "dirty", &dirty, N_("mark"),
@@ -430,6 +464,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
430464
die(_("--long is incompatible with --abbrev=0"));
431465

432466
if (contains) {
467+
struct string_list_item *item;
433468
struct argv_array args;
434469

435470
argv_array_init(&args);
@@ -440,8 +475,10 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
440475
argv_array_push(&args, "--always");
441476
if (!all) {
442477
argv_array_push(&args, "--tags");
443-
if (pattern)
444-
argv_array_pushf(&args, "--refs=refs/tags/%s", pattern);
478+
for_each_string_list_item(item, &patterns)
479+
argv_array_pushf(&args, "--refs=refs/tags/%s", item->string);
480+
for_each_string_list_item(item, &exclude_patterns)
481+
argv_array_pushf(&args, "--exclude=refs/tags/%s", item->string);
445482
}
446483
if (argc)
447484
argv_array_pushv(&args, argv);

builtin/name-rev.c

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous)
108108
struct name_ref_data {
109109
int tags_only;
110110
int name_only;
111-
const char *ref_filter;
111+
struct string_list ref_filters;
112+
struct string_list exclude_filters;
112113
};
113114

114115
static struct tip_table {
@@ -150,18 +151,49 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo
150151
if (data->tags_only && !starts_with(path, "refs/tags/"))
151152
return 0;
152153

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;
154+
if (data->exclude_filters.nr) {
155+
struct string_list_item *item;
156+
157+
for_each_string_list_item(item, &data->exclude_filters) {
158+
if (subpath_matches(path, item->string) >= 0)
159+
return 0;
162160
}
163161
}
164162

163+
if (data->ref_filters.nr) {
164+
struct string_list_item *item;
165+
int matched = 0;
166+
167+
/* See if any of the patterns match. */
168+
for_each_string_list_item(item, &data->ref_filters) {
169+
/*
170+
* Check all patterns even after finding a match, so
171+
* that we can see if a match with a subpath exists.
172+
* When a user asked for 'refs/tags/v*' and 'v1.*',
173+
* both of which match, the user is showing her
174+
* willingness to accept a shortened output by having
175+
* the 'v1.*' in the acceptable refnames, so we
176+
* shouldn't stop when seeing 'refs/tags/v1.4' matches
177+
* 'refs/tags/v*'. We should show it as 'v1.4'.
178+
*/
179+
switch (subpath_matches(path, item->string)) {
180+
case -1: /* did not match */
181+
break;
182+
case 0: /* matched fully */
183+
matched = 1;
184+
break;
185+
default: /* matched subpath */
186+
matched = 1;
187+
can_abbreviate_output = 1;
188+
break;
189+
}
190+
}
191+
192+
/* If none of the patterns matched, stop now */
193+
if (!matched)
194+
return 0;
195+
}
196+
165197
add_to_tip_table(oid->hash, path, can_abbreviate_output);
166198

167199
while (o && o->type == OBJ_TAG) {
@@ -306,12 +338,14 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
306338
{
307339
struct object_array revs = OBJECT_ARRAY_INIT;
308340
int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;
309-
struct name_ref_data data = { 0, 0, NULL };
341+
struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP };
310342
struct option opts[] = {
311343
OPT_BOOL(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")),
312344
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"),
345+
OPT_STRING_LIST(0, "refs", &data.ref_filters, N_("pattern"),
314346
N_("only use refs matching <pattern>")),
347+
OPT_STRING_LIST(0, "exclude", &data.exclude_filters, N_("pattern"),
348+
N_("ignore refs matching <pattern>")),
315349
OPT_GROUP(""),
316350
OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")),
317351
OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")),

contrib/completion/git-completion.bash

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,7 @@ _git_describe ()
12071207
__gitcomp "
12081208
--all --tags --contains --abbrev= --candidates=
12091209
--exact-match --debug --long --match --always --first-parent
1210+
--exclude
12101211
"
12111212
return
12121213
esac

t/t6007-rev-list-cherry-pick-file.sh

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,44 @@ test_expect_success '--cherry-pick bar does not come up empty (II)' '
9999
test_cmp actual.named expect
100100
'
101101

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+
cat >expect <<EOF
122+
<tags/F
123+
EOF
124+
125+
test_expect_success 'name-rev --exclude excludes matched patterns' '
126+
git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect &&
127+
git rev-list --left-right --cherry-pick F...E -- bar >actual &&
128+
git name-rev --stdin --name-only --refs="*tags/*" --exclude="*E" \
129+
<actual >actual.named &&
130+
test_cmp actual.named expect
131+
'
132+
133+
test_expect_success 'name-rev --no-refs clears the refs list' '
134+
git rev-list --left-right --cherry-pick F...E -- bar >expect &&
135+
git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" --no-refs --refs="*tags/G" \
136+
<expect >actual &&
137+
test_cmp actual expect
138+
'
139+
102140
cat >expect <<EOF
103141
+tags/F
104142
=tags/D

t/t6120-describe.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ check_describe "test2-lightweight-*" --tags --match="test2-*"
182182

183183
check_describe "test2-lightweight-*" --long --tags --match="test2-*" HEAD^
184184

185+
check_describe "test1-lightweight-*" --long --tags --match="test1-*" --match="test2-*" HEAD^
186+
187+
check_describe "test2-lightweight-*" --long --tags --match="test1-*" --no-match --match="test2-*" HEAD^
188+
185189
test_expect_success 'name-rev with exact tags' '
186190
echo A >expect &&
187191
tag_object=$(git rev-parse refs/tags/A) &&
@@ -206,4 +210,27 @@ test_expect_success 'describe --contains with the exact tags' '
206210
test_cmp expect actual
207211
'
208212

213+
test_expect_success 'describe --contains and --match' '
214+
echo "A^0" >expect &&
215+
tagged_commit=$(git rev-parse "refs/tags/A^0") &&
216+
test_must_fail git describe --contains --match="B" $tagged_commit &&
217+
git describe --contains --match="B" --match="A" $tagged_commit >actual &&
218+
test_cmp expect actual
219+
'
220+
221+
test_expect_success 'describe --exclude' '
222+
echo "c~1" >expect &&
223+
tagged_commit=$(git rev-parse "refs/tags/A^0") &&
224+
test_must_fail git describe --contains --match="B" $tagged_commit &&
225+
git describe --contains --match="?" --exclude="A" $tagged_commit >actual &&
226+
test_cmp expect actual
227+
'
228+
229+
test_expect_success 'describe --contains and --no-match' '
230+
echo "A^0" >expect &&
231+
tagged_commit=$(git rev-parse "refs/tags/A^0") &&
232+
git describe --contains --match="B" --no-match $tagged_commit >actual &&
233+
test_cmp expect actual
234+
'
235+
209236
test_done

0 commit comments

Comments
 (0)