Merge branch 'jc/ref-excludes'

People often wished a way to tell "git log --branches" (and "git
log --remotes --not --branches") to exclude some local branches
from the expansion of "--branches" (similarly for "--tags", "--all"
and "--glob=<pattern>").  Now they have one.

* jc/ref-excludes:
  rev-parse: introduce --exclude=<glob> to tame wildcards
  rev-list --exclude: export add/clear-ref-exclusion and ref-excluded API
  rev-list --exclude: tests
  document --exclude option
  revision: introduce --exclude=<glob> to tame wildcards
This commit is contained in:
Junio C Hamano 2013-12-05 12:59:09 -08:00
commit 10167eb251
6 changed files with 155 additions and 2 deletions

View File

@ -177,6 +177,20 @@ shown. If the pattern does not contain a globbing character (`?`,
character (`?`, `*`, or `[`), it is turned into a prefix character (`?`, `*`, or `[`), it is turned into a prefix
match by appending `/*`. match by appending `/*`.
--exclude=<glob-pattern>::
Do not include refs matching '<glob-pattern>' that the next `--all`,
`--branches`, `--tags`, `--remotes`, or `--glob` would otherwise
consider. Repetitions of this option accumulate exclusion patterns
up to the next `--all`, `--branches`, `--tags`, `--remotes`, or
`--glob` option (other options or arguments do not clear
accumlated patterns).
+
The patterns given should not begin with `refs/heads`, `refs/tags`, or
`refs/remotes` when applied to `--branches`, `--tags`, or `--remotes`,
respectively, and they must begin with `refs/` when applied to `--glob`
or `--all`. If a trailing '/{asterisk}' is intended, it must be given
explicitly.
--disambiguate=<prefix>:: --disambiguate=<prefix>::
Show every object whose name begins with the given prefix. Show every object whose name begins with the given prefix.
The <prefix> must be at least 4 hexadecimal digits long to The <prefix> must be at least 4 hexadecimal digits long to

View File

@ -153,6 +153,21 @@ parents) and `--max-parents=-1` (negative numbers denote no upper limit).
is automatically prepended if missing. If pattern lacks '?', '{asterisk}', is automatically prepended if missing. If pattern lacks '?', '{asterisk}',
or '[', '/{asterisk}' at the end is implied. or '[', '/{asterisk}' at the end is implied.
--exclude=<glob-pattern>::
Do not include refs matching '<glob-pattern>' that the next `--all`,
`--branches`, `--tags`, `--remotes`, or `--glob` would otherwise
consider. Repetitions of this option accumulate exclusion patterns
up to the next `--all`, `--branches`, `--tags`, `--remotes`, or
`--glob` option (other options or arguments do not clear
accumlated patterns).
+
The patterns given should not begin with `refs/heads`, `refs/tags`, or
`refs/remotes` when applied to `--branches`, `--tags`, or `--remotes`,
respectively, and they must begin with `refs/` when applied to `--glob`
or `--all`. If a trailing '/{asterisk}' is intended, it must be given
explicitly.
--ignore-missing:: --ignore-missing::
Upon seeing an invalid object name in the input, pretend as if Upon seeing an invalid object name in the input, pretend as if
the bad input was not given. the bad input was not given.

View File

@ -9,6 +9,8 @@
#include "quote.h" #include "quote.h"
#include "builtin.h" #include "builtin.h"
#include "parse-options.h" #include "parse-options.h"
#include "diff.h"
#include "revision.h"
#define DO_REVS 1 #define DO_REVS 1
#define DO_NOREV 2 #define DO_NOREV 2
@ -31,6 +33,7 @@ static int abbrev_ref_strict;
static int output_sq; static int output_sq;
static int stuck_long; static int stuck_long;
static struct string_list *ref_excludes;
/* /*
* Some arguments are relevant "revision" arguments, * Some arguments are relevant "revision" arguments,
@ -187,6 +190,8 @@ static int show_default(void)
static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data) static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{ {
if (ref_excluded(ref_excludes, refname))
return 0;
show_rev(NORMAL, sha1, refname); show_rev(NORMAL, sha1, refname);
return 0; return 0;
} }
@ -625,32 +630,43 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (!prefixcmp(arg, "--branches=")) { if (!prefixcmp(arg, "--branches=")) {
for_each_glob_ref_in(show_reference, arg + 11, for_each_glob_ref_in(show_reference, arg + 11,
"refs/heads/", NULL); "refs/heads/", NULL);
clear_ref_exclusion(&ref_excludes);
continue; continue;
} }
if (!strcmp(arg, "--branches")) { if (!strcmp(arg, "--branches")) {
for_each_branch_ref(show_reference, NULL); for_each_branch_ref(show_reference, NULL);
clear_ref_exclusion(&ref_excludes);
continue; continue;
} }
if (!prefixcmp(arg, "--tags=")) { if (!prefixcmp(arg, "--tags=")) {
for_each_glob_ref_in(show_reference, arg + 7, for_each_glob_ref_in(show_reference, arg + 7,
"refs/tags/", NULL); "refs/tags/", NULL);
clear_ref_exclusion(&ref_excludes);
continue; continue;
} }
if (!strcmp(arg, "--tags")) { if (!strcmp(arg, "--tags")) {
for_each_tag_ref(show_reference, NULL); for_each_tag_ref(show_reference, NULL);
clear_ref_exclusion(&ref_excludes);
continue; continue;
} }
if (!prefixcmp(arg, "--glob=")) { if (!prefixcmp(arg, "--glob=")) {
for_each_glob_ref(show_reference, arg + 7, NULL); for_each_glob_ref(show_reference, arg + 7, NULL);
clear_ref_exclusion(&ref_excludes);
continue; continue;
} }
if (!prefixcmp(arg, "--remotes=")) { if (!prefixcmp(arg, "--remotes=")) {
for_each_glob_ref_in(show_reference, arg + 10, for_each_glob_ref_in(show_reference, arg + 10,
"refs/remotes/", NULL); "refs/remotes/", NULL);
clear_ref_exclusion(&ref_excludes);
continue; continue;
} }
if (!strcmp(arg, "--remotes")) { if (!strcmp(arg, "--remotes")) {
for_each_remote_ref(show_reference, NULL); for_each_remote_ref(show_reference, NULL);
clear_ref_exclusion(&ref_excludes);
continue;
}
if (!prefixcmp(arg, "--exclude=")) {
add_ref_exclusion(&ref_excludes, arg + 10);
continue; continue;
} }
if (!strcmp(arg, "--local-env-vars")) { if (!strcmp(arg, "--local-env-vars")) {

View File

@ -1180,11 +1180,28 @@ struct all_refs_cb {
const char *name_for_errormsg; const char *name_for_errormsg;
}; };
int ref_excluded(struct string_list *ref_excludes, const char *path)
{
struct string_list_item *item;
if (!ref_excludes)
return 0;
for_each_string_list_item(item, ref_excludes) {
if (!fnmatch(item->string, path, 0))
return 1;
}
return 0;
}
static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{ {
struct all_refs_cb *cb = cb_data; struct all_refs_cb *cb = cb_data;
struct object *object = get_reference(cb->all_revs, path, sha1, struct object *object;
cb->all_flags);
if (ref_excluded(cb->all_revs->ref_excludes, path))
return 0;
object = get_reference(cb->all_revs, path, sha1, cb->all_flags);
add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags); add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);
add_pending_sha1(cb->all_revs, path, sha1, cb->all_flags); add_pending_sha1(cb->all_revs, path, sha1, cb->all_flags);
return 0; return 0;
@ -1197,6 +1214,24 @@ static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs,
cb->all_flags = flags; cb->all_flags = flags;
} }
void clear_ref_exclusion(struct string_list **ref_excludes_p)
{
if (*ref_excludes_p) {
string_list_clear(*ref_excludes_p, 0);
free(*ref_excludes_p);
}
*ref_excludes_p = NULL;
}
void add_ref_exclusion(struct string_list **ref_excludes_p, const char *exclude)
{
if (!*ref_excludes_p) {
*ref_excludes_p = xcalloc(1, sizeof(**ref_excludes_p));
(*ref_excludes_p)->strdup_strings = 1;
}
string_list_append(*ref_excludes_p, exclude);
}
static void handle_refs(const char *submodule, struct rev_info *revs, unsigned flags, static void handle_refs(const char *submodule, struct rev_info *revs, unsigned flags,
int (*for_each)(const char *, each_ref_fn, void *)) int (*for_each)(const char *, each_ref_fn, void *))
{ {
@ -1969,33 +2004,44 @@ static int handle_revision_pseudo_opt(const char *submodule,
if (!strcmp(arg, "--all")) { if (!strcmp(arg, "--all")) {
handle_refs(submodule, revs, *flags, for_each_ref_submodule); handle_refs(submodule, revs, *flags, for_each_ref_submodule);
handle_refs(submodule, revs, *flags, head_ref_submodule); handle_refs(submodule, revs, *flags, head_ref_submodule);
clear_ref_exclusion(&revs->ref_excludes);
} else if (!strcmp(arg, "--branches")) { } else if (!strcmp(arg, "--branches")) {
handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule); handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule);
clear_ref_exclusion(&revs->ref_excludes);
} else if (!strcmp(arg, "--bisect")) { } else if (!strcmp(arg, "--bisect")) {
handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref); handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref);
handle_refs(submodule, revs, *flags ^ (UNINTERESTING | BOTTOM), for_each_good_bisect_ref); handle_refs(submodule, revs, *flags ^ (UNINTERESTING | BOTTOM), for_each_good_bisect_ref);
revs->bisect = 1; revs->bisect = 1;
} else if (!strcmp(arg, "--tags")) { } else if (!strcmp(arg, "--tags")) {
handle_refs(submodule, revs, *flags, for_each_tag_ref_submodule); handle_refs(submodule, revs, *flags, for_each_tag_ref_submodule);
clear_ref_exclusion(&revs->ref_excludes);
} else if (!strcmp(arg, "--remotes")) { } else if (!strcmp(arg, "--remotes")) {
handle_refs(submodule, revs, *flags, for_each_remote_ref_submodule); handle_refs(submodule, revs, *flags, for_each_remote_ref_submodule);
clear_ref_exclusion(&revs->ref_excludes);
} else if ((argcount = parse_long_opt("glob", argv, &optarg))) { } else if ((argcount = parse_long_opt("glob", argv, &optarg))) {
struct all_refs_cb cb; struct all_refs_cb cb;
init_all_refs_cb(&cb, revs, *flags); init_all_refs_cb(&cb, revs, *flags);
for_each_glob_ref(handle_one_ref, optarg, &cb); for_each_glob_ref(handle_one_ref, optarg, &cb);
clear_ref_exclusion(&revs->ref_excludes);
return argcount;
} else if ((argcount = parse_long_opt("exclude", argv, &optarg))) {
add_ref_exclusion(&revs->ref_excludes, optarg);
return argcount; return argcount;
} else if (!prefixcmp(arg, "--branches=")) { } else if (!prefixcmp(arg, "--branches=")) {
struct all_refs_cb cb; struct all_refs_cb cb;
init_all_refs_cb(&cb, revs, *flags); init_all_refs_cb(&cb, revs, *flags);
for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb); for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb);
clear_ref_exclusion(&revs->ref_excludes);
} else if (!prefixcmp(arg, "--tags=")) { } else if (!prefixcmp(arg, "--tags=")) {
struct all_refs_cb cb; struct all_refs_cb cb;
init_all_refs_cb(&cb, revs, *flags); init_all_refs_cb(&cb, revs, *flags);
for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb); for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb);
clear_ref_exclusion(&revs->ref_excludes);
} else if (!prefixcmp(arg, "--remotes=")) { } else if (!prefixcmp(arg, "--remotes=")) {
struct all_refs_cb cb; struct all_refs_cb cb;
init_all_refs_cb(&cb, revs, *flags); init_all_refs_cb(&cb, revs, *flags);
for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb); for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb);
clear_ref_exclusion(&revs->ref_excludes);
} else if (!strcmp(arg, "--reflog")) { } else if (!strcmp(arg, "--reflog")) {
handle_reflog(revs, *flags); handle_reflog(revs, *flags);
} else if (!strcmp(arg, "--not")) { } else if (!strcmp(arg, "--not")) {

View File

@ -61,6 +61,9 @@ struct rev_info {
/* The end-points specified by the end user */ /* The end-points specified by the end user */
struct rev_cmdline_info cmdline; struct rev_cmdline_info cmdline;
/* excluding from --branches, --refs, etc. expansion */
struct string_list *ref_excludes;
/* Basic information */ /* Basic information */
const char *prefix; const char *prefix;
const char *def; const char *def;
@ -194,6 +197,11 @@ struct rev_info {
struct saved_parents *saved_parents_slab; struct saved_parents *saved_parents_slab;
}; };
extern int ref_excluded(struct string_list *, const char *path);
void clear_ref_exclusion(struct string_list **);
void add_ref_exclusion(struct string_list **, const char *exclude);
#define REV_TREE_SAME 0 #define REV_TREE_SAME 0
#define REV_TREE_NEW 1 /* Only new files */ #define REV_TREE_NEW 1 /* Only new files */
#define REV_TREE_OLD 2 /* Only files removed */ #define REV_TREE_OLD 2 /* Only files removed */

View File

@ -129,6 +129,18 @@ test_expect_success 'rev-parse --remotes=foo' '
' '
test_expect_success 'rev-parse --exclude with --branches' '
compare rev-parse "--exclude=*/* --branches" "master someref subspace-x"
'
test_expect_success 'rev-parse --exclude with --all' '
compare rev-parse "--exclude=refs/remotes/* --all" "--branches --tags"
'
test_expect_success 'rev-parse accumulates multiple --exclude' '
compare rev-parse "--exclude=refs/remotes/* --exclude=refs/tags/* --all" --branches
'
test_expect_success 'rev-list --glob=refs/heads/subspace/*' ' test_expect_success 'rev-list --glob=refs/heads/subspace/*' '
compare rev-list "subspace/one subspace/two" "--glob=refs/heads/subspace/*" compare rev-list "subspace/one subspace/two" "--glob=refs/heads/subspace/*"
@ -231,6 +243,48 @@ test_expect_success 'rev-list --remotes=foo' '
' '
test_expect_success 'rev-list --exclude with --branches' '
compare rev-list "--exclude=*/* --branches" "master someref subspace-x"
'
test_expect_success 'rev-list --exclude with --all' '
compare rev-list "--exclude=refs/remotes/* --all" "--branches --tags"
'
test_expect_success 'rev-list accumulates multiple --exclude' '
compare rev-list "--exclude=refs/remotes/* --exclude=refs/tags/* --all" --branches
'
# "git rev-list<ENTER>" is likely to be a bug in the calling script and may
# deserve an error message, but do cases where set of refs programatically
# given using globbing and/or --stdin need to fail with the same error, or
# are we better off reporting a success with no output? The following few
# tests document the current behaviour to remind us that we might want to
# think about this issue.
test_expect_failure 'rev-list may want to succeed with empty output on no input (1)' '
>expect &&
git rev-list --stdin <expect >actual &&
test_cmp expect actual
'
test_expect_failure 'rev-list may want to succeed with empty output on no input (2)' '
>expect &&
git rev-list --exclude=* --all >actual &&
test_cmp expect actual
'
test_expect_failure 'rev-list may want to succeed with empty output on no input (3)' '
(
test_create_repo empty &&
cd empty &&
>expect &&
git rev-list --all >actual &&
test_cmp expect actual
)
'
test_expect_success 'shortlog accepts --glob/--tags/--remotes' ' test_expect_success 'shortlog accepts --glob/--tags/--remotes' '
compare shortlog "subspace/one subspace/two" --branches=subspace && compare shortlog "subspace/one subspace/two" --branches=subspace &&