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
This commit is contained in:
commit
1b324988ac
@ -83,7 +83,20 @@ OPTIONS
|
||||
--match <pattern>::
|
||||
Only consider tags matching the given `glob(7)` pattern,
|
||||
excluding the "refs/tags/" prefix. This can be used to avoid
|
||||
leaking private tags from the repository.
|
||||
leaking private tags from the repository. If given multiple times, a
|
||||
list of patterns will be accumulated, and tags matching any of the
|
||||
patterns will be considered. Use `--no-match` to clear and reset the
|
||||
list of patterns.
|
||||
|
||||
--exclude <pattern>::
|
||||
Do not consider tags matching the given `glob(7)` pattern, excluding
|
||||
the "refs/tags/" prefix. This can be used to narrow the tag space and
|
||||
find only tags matching some meaningful criteria. If given multiple
|
||||
times, a list of patterns will be accumulated and tags matching any
|
||||
of the patterns will be excluded. When combined with --match a tag will
|
||||
be considered when it matches at least one --match pattern and does not
|
||||
match any of the --exclude patterns. Use `--no-exclude` to clear and
|
||||
reset the list of patterns.
|
||||
|
||||
--always::
|
||||
Show uniquely abbreviated commit object as fallback.
|
||||
|
@ -26,7 +26,18 @@ OPTIONS
|
||||
|
||||
--refs=<pattern>::
|
||||
Only use refs whose names match a given shell pattern. The pattern
|
||||
can be one of branch name, tag name or fully qualified ref name.
|
||||
can be one of branch name, tag name or fully qualified ref name. If
|
||||
given multiple times, use refs whose names match any of the given shell
|
||||
patterns. Use `--no-refs` to clear any previous ref patterns given.
|
||||
|
||||
--exclude=<pattern>::
|
||||
Do not use any ref whose name matches a given shell pattern. The
|
||||
pattern can be one of branch name, tag name or fully qualified ref
|
||||
name. If given multiple times, a ref will be excluded when it matches
|
||||
any of the given patterns. When used together with --refs, a ref will
|
||||
be used as a match only when it matches at least one --refs pattern and
|
||||
does not match any --exclude patterns. Use `--no-exclude` to clear the
|
||||
list of exclude patterns.
|
||||
|
||||
--all::
|
||||
List all commits reachable from all refs
|
||||
|
@ -168,6 +168,11 @@ There are some macros to easily define options:
|
||||
Introduce an option with string argument.
|
||||
The string argument is put into `str_var`.
|
||||
|
||||
`OPT_STRING_LIST(short, long, &struct string_list, arg_str, description)`::
|
||||
Introduce an option with string argument.
|
||||
The string argument is stored as an element in `string_list`.
|
||||
Use of `--no-option` will clear the list of preceding values.
|
||||
|
||||
`OPT_INTEGER(short, long, &int_var, description)`::
|
||||
Introduce an option with integer argument.
|
||||
The integer is put into `int_var`.
|
||||
|
@ -28,7 +28,8 @@ static int abbrev = -1; /* unspecified */
|
||||
static int max_candidates = 10;
|
||||
static struct hashmap names;
|
||||
static int have_util;
|
||||
static const char *pattern;
|
||||
static struct string_list patterns = STRING_LIST_INIT_NODUP;
|
||||
static struct string_list exclude_patterns = STRING_LIST_INIT_NODUP;
|
||||
static int always;
|
||||
static const char *dirty;
|
||||
|
||||
@ -129,9 +130,40 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi
|
||||
if (!all && !is_tag)
|
||||
return 0;
|
||||
|
||||
/* Accept only tags that match the pattern, if given */
|
||||
if (pattern && (!is_tag || wildmatch(pattern, path + 10, 0, NULL)))
|
||||
return 0;
|
||||
/*
|
||||
* If we're given exclude patterns, first exclude any tag which match
|
||||
* any of the exclude pattern.
|
||||
*/
|
||||
if (exclude_patterns.nr) {
|
||||
struct string_list_item *item;
|
||||
|
||||
if (!is_tag)
|
||||
return 0;
|
||||
|
||||
for_each_string_list_item(item, &exclude_patterns) {
|
||||
if (!wildmatch(item->string, path + 10, 0, NULL))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If we're given patterns, accept only tags which match at least one
|
||||
* pattern.
|
||||
*/
|
||||
if (patterns.nr) {
|
||||
struct string_list_item *item;
|
||||
|
||||
if (!is_tag)
|
||||
return 0;
|
||||
|
||||
for_each_string_list_item(item, &patterns) {
|
||||
if (!wildmatch(item->string, path + 10, 0, NULL))
|
||||
break;
|
||||
|
||||
/* If we get here, no pattern matched. */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Is it annotated? */
|
||||
if (!peel_ref(path, peeled.hash)) {
|
||||
@ -404,8 +436,10 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
|
||||
N_("only output exact matches"), 0),
|
||||
OPT_INTEGER(0, "candidates", &max_candidates,
|
||||
N_("consider <n> most recent tags (default: 10)")),
|
||||
OPT_STRING(0, "match", &pattern, N_("pattern"),
|
||||
OPT_STRING_LIST(0, "match", &patterns, N_("pattern"),
|
||||
N_("only consider tags matching <pattern>")),
|
||||
OPT_STRING_LIST(0, "exclude", &exclude_patterns, N_("pattern"),
|
||||
N_("do not consider tags matching <pattern>")),
|
||||
OPT_BOOL(0, "always", &always,
|
||||
N_("show abbreviated commit object as fallback")),
|
||||
{OPTION_STRING, 0, "dirty", &dirty, N_("mark"),
|
||||
@ -430,6 +464,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
|
||||
die(_("--long is incompatible with --abbrev=0"));
|
||||
|
||||
if (contains) {
|
||||
struct string_list_item *item;
|
||||
struct argv_array args;
|
||||
|
||||
argv_array_init(&args);
|
||||
@ -440,8 +475,10 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
|
||||
argv_array_push(&args, "--always");
|
||||
if (!all) {
|
||||
argv_array_push(&args, "--tags");
|
||||
if (pattern)
|
||||
argv_array_pushf(&args, "--refs=refs/tags/%s", pattern);
|
||||
for_each_string_list_item(item, &patterns)
|
||||
argv_array_pushf(&args, "--refs=refs/tags/%s", item->string);
|
||||
for_each_string_list_item(item, &exclude_patterns)
|
||||
argv_array_pushf(&args, "--exclude=refs/tags/%s", item->string);
|
||||
}
|
||||
if (argc)
|
||||
argv_array_pushv(&args, argv);
|
||||
|
@ -108,7 +108,8 @@ static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous)
|
||||
struct name_ref_data {
|
||||
int tags_only;
|
||||
int name_only;
|
||||
const char *ref_filter;
|
||||
struct string_list ref_filters;
|
||||
struct string_list exclude_filters;
|
||||
};
|
||||
|
||||
static struct tip_table {
|
||||
@ -150,18 +151,49 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo
|
||||
if (data->tags_only && !starts_with(path, "refs/tags/"))
|
||||
return 0;
|
||||
|
||||
if (data->ref_filter) {
|
||||
switch (subpath_matches(path, data->ref_filter)) {
|
||||
case -1: /* did not match */
|
||||
return 0;
|
||||
case 0: /* matched fully */
|
||||
break;
|
||||
default: /* matched subpath */
|
||||
can_abbreviate_output = 1;
|
||||
break;
|
||||
if (data->exclude_filters.nr) {
|
||||
struct string_list_item *item;
|
||||
|
||||
for_each_string_list_item(item, &data->exclude_filters) {
|
||||
if (subpath_matches(path, item->string) >= 0)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (data->ref_filters.nr) {
|
||||
struct string_list_item *item;
|
||||
int matched = 0;
|
||||
|
||||
/* See if any of the patterns match. */
|
||||
for_each_string_list_item(item, &data->ref_filters) {
|
||||
/*
|
||||
* Check all patterns even after finding a match, so
|
||||
* that we can see if a match with a subpath exists.
|
||||
* When a user asked for 'refs/tags/v*' and 'v1.*',
|
||||
* both of which match, the user is showing her
|
||||
* willingness to accept a shortened output by having
|
||||
* the 'v1.*' in the acceptable refnames, so we
|
||||
* shouldn't stop when seeing 'refs/tags/v1.4' matches
|
||||
* 'refs/tags/v*'. We should show it as 'v1.4'.
|
||||
*/
|
||||
switch (subpath_matches(path, item->string)) {
|
||||
case -1: /* did not match */
|
||||
break;
|
||||
case 0: /* matched fully */
|
||||
matched = 1;
|
||||
break;
|
||||
default: /* matched subpath */
|
||||
matched = 1;
|
||||
can_abbreviate_output = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* If none of the patterns matched, stop now */
|
||||
if (!matched)
|
||||
return 0;
|
||||
}
|
||||
|
||||
add_to_tip_table(oid->hash, path, can_abbreviate_output);
|
||||
|
||||
while (o && o->type == OBJ_TAG) {
|
||||
@ -306,12 +338,14 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
struct object_array revs = OBJECT_ARRAY_INIT;
|
||||
int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;
|
||||
struct name_ref_data data = { 0, 0, NULL };
|
||||
struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP };
|
||||
struct option opts[] = {
|
||||
OPT_BOOL(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")),
|
||||
OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")),
|
||||
OPT_STRING(0, "refs", &data.ref_filter, N_("pattern"),
|
||||
OPT_STRING_LIST(0, "refs", &data.ref_filters, N_("pattern"),
|
||||
N_("only use refs matching <pattern>")),
|
||||
OPT_STRING_LIST(0, "exclude", &data.exclude_filters, N_("pattern"),
|
||||
N_("ignore refs matching <pattern>")),
|
||||
OPT_GROUP(""),
|
||||
OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")),
|
||||
OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")),
|
||||
|
@ -1207,6 +1207,7 @@ _git_describe ()
|
||||
__gitcomp "
|
||||
--all --tags --contains --abbrev= --candidates=
|
||||
--exact-match --debug --long --match --always --first-parent
|
||||
--exclude
|
||||
"
|
||||
return
|
||||
esac
|
||||
|
@ -99,6 +99,44 @@ test_expect_success '--cherry-pick bar does not come up empty (II)' '
|
||||
test_cmp actual.named expect
|
||||
'
|
||||
|
||||
test_expect_success 'name-rev multiple --refs combine inclusive' '
|
||||
git rev-list --left-right --cherry-pick F...E -- bar >actual &&
|
||||
git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" \
|
||||
<actual >actual.named &&
|
||||
test_cmp actual.named expect
|
||||
'
|
||||
|
||||
cat >expect <<EOF
|
||||
<tags/F
|
||||
EOF
|
||||
|
||||
test_expect_success 'name-rev --refs excludes non-matched patterns' '
|
||||
git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect &&
|
||||
git rev-list --left-right --cherry-pick F...E -- bar >actual &&
|
||||
git name-rev --stdin --name-only --refs="*tags/F" \
|
||||
<actual >actual.named &&
|
||||
test_cmp actual.named expect
|
||||
'
|
||||
|
||||
cat >expect <<EOF
|
||||
<tags/F
|
||||
EOF
|
||||
|
||||
test_expect_success 'name-rev --exclude excludes matched patterns' '
|
||||
git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect &&
|
||||
git rev-list --left-right --cherry-pick F...E -- bar >actual &&
|
||||
git name-rev --stdin --name-only --refs="*tags/*" --exclude="*E" \
|
||||
<actual >actual.named &&
|
||||
test_cmp actual.named expect
|
||||
'
|
||||
|
||||
test_expect_success 'name-rev --no-refs clears the refs list' '
|
||||
git rev-list --left-right --cherry-pick F...E -- bar >expect &&
|
||||
git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" --no-refs --refs="*tags/G" \
|
||||
<expect >actual &&
|
||||
test_cmp actual expect
|
||||
'
|
||||
|
||||
cat >expect <<EOF
|
||||
+tags/F
|
||||
=tags/D
|
||||
|
@ -182,6 +182,10 @@ check_describe "test2-lightweight-*" --tags --match="test2-*"
|
||||
|
||||
check_describe "test2-lightweight-*" --long --tags --match="test2-*" HEAD^
|
||||
|
||||
check_describe "test1-lightweight-*" --long --tags --match="test1-*" --match="test2-*" HEAD^
|
||||
|
||||
check_describe "test2-lightweight-*" --long --tags --match="test1-*" --no-match --match="test2-*" HEAD^
|
||||
|
||||
test_expect_success 'name-rev with exact tags' '
|
||||
echo A >expect &&
|
||||
tag_object=$(git rev-parse refs/tags/A) &&
|
||||
@ -206,4 +210,27 @@ test_expect_success 'describe --contains with the exact tags' '
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'describe --contains and --match' '
|
||||
echo "A^0" >expect &&
|
||||
tagged_commit=$(git rev-parse "refs/tags/A^0") &&
|
||||
test_must_fail git describe --contains --match="B" $tagged_commit &&
|
||||
git describe --contains --match="B" --match="A" $tagged_commit >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'describe --exclude' '
|
||||
echo "c~1" >expect &&
|
||||
tagged_commit=$(git rev-parse "refs/tags/A^0") &&
|
||||
test_must_fail git describe --contains --match="B" $tagged_commit &&
|
||||
git describe --contains --match="?" --exclude="A" $tagged_commit >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'describe --contains and --no-match' '
|
||||
echo "A^0" >expect &&
|
||||
tagged_commit=$(git rev-parse "refs/tags/A^0") &&
|
||||
git describe --contains --match="B" --no-match $tagged_commit >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_done
|
||||
|
Loading…
Reference in New Issue
Block a user