From e7b432c521b0177e86eaecd2bd16906b9fc53e10 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 30 Aug 2013 16:37:55 -0700 Subject: revision: introduce --exclude= to tame wildcards People often find "git log --branches" etc. that includes _all_ branches is cumbersome to use when they want to grab most but except some. The same applies to --tags, --all and --glob. Teach the revision machinery to remember patterns, and then upon the next such a globbing option, exclude those that match the pattern. With this, I can view only my integration branches (e.g. maint, master, etc.) without topic branches, which are named after two letters from primary authors' names, slash and topic name. git rev-list --no-walk --exclude=??/* --branches | git name-rev --refs refs/heads/* --stdin This one shows things reachable from local and remote branches that have not been merged to the integration branches. git log --remotes --branches --not --exclude=??/* --branches It may be a bit rough around the edges, in that the pattern to give the exclude option depends on what globbing option follows. In these examples, the pattern "??/*" is used, not "refs/heads/??/*", because the globbing option that follows the -"-exclude=" is "--branches". As each use of globbing option resets previously set "--exclude", this may not be such a bad thing, though. Signed-off-by: Junio C Hamano --- revision.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- revision.h | 3 +++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/revision.c b/revision.c index 84ccc0529..3e8287425 100644 --- a/revision.c +++ b/revision.c @@ -1180,11 +1180,28 @@ struct all_refs_cb { const char *name_for_errormsg; }; +static int ref_excluded(struct rev_info *revs, const char *path) +{ + struct string_list_item *item; + + if (!revs->ref_excludes) + return 0; + for_each_string_list_item(item, revs->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) { struct all_refs_cb *cb = cb_data; - struct object *object = get_reference(cb->all_revs, path, sha1, - cb->all_flags); + struct object *object; + + if (ref_excluded(cb->all_revs, 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_pending_sha1(cb->all_revs, path, sha1, cb->all_flags); 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; } +static void clear_ref_exclusion(struct rev_info *revs) +{ + if (revs->ref_excludes) { + string_list_clear(revs->ref_excludes, 0); + free(revs->ref_excludes); + } + revs->ref_excludes = NULL; +} + +static void add_ref_exclusion(struct rev_info *revs, const char *exclude) +{ + if (!revs->ref_excludes) { + revs->ref_excludes = xcalloc(1, sizeof(*revs->ref_excludes)); + revs->ref_excludes->strdup_strings = 1; + } + string_list_append(revs->ref_excludes, exclude); +} + static void handle_refs(const char *submodule, struct rev_info *revs, unsigned flags, int (*for_each)(const char *, each_ref_fn, void *)) { @@ -1953,33 +1988,44 @@ static int handle_revision_pseudo_opt(const char *submodule, if (!strcmp(arg, "--all")) { handle_refs(submodule, revs, *flags, for_each_ref_submodule); handle_refs(submodule, revs, *flags, head_ref_submodule); + clear_ref_exclusion(revs); } else if (!strcmp(arg, "--branches")) { handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule); + clear_ref_exclusion(revs); } else if (!strcmp(arg, "--bisect")) { handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref); handle_refs(submodule, revs, *flags ^ (UNINTERESTING | BOTTOM), for_each_good_bisect_ref); revs->bisect = 1; } else if (!strcmp(arg, "--tags")) { handle_refs(submodule, revs, *flags, for_each_tag_ref_submodule); + clear_ref_exclusion(revs); } else if (!strcmp(arg, "--remotes")) { handle_refs(submodule, revs, *flags, for_each_remote_ref_submodule); + clear_ref_exclusion(revs); } else if ((argcount = parse_long_opt("glob", argv, &optarg))) { struct all_refs_cb cb; init_all_refs_cb(&cb, revs, *flags); for_each_glob_ref(handle_one_ref, optarg, &cb); + clear_ref_exclusion(revs); + return argcount; + } else if ((argcount = parse_long_opt("exclude", argv, &optarg))) { + add_ref_exclusion(revs, optarg); return argcount; } else if (!prefixcmp(arg, "--branches=")) { struct all_refs_cb cb; init_all_refs_cb(&cb, revs, *flags); for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb); + clear_ref_exclusion(revs); } else if (!prefixcmp(arg, "--tags=")) { struct all_refs_cb cb; init_all_refs_cb(&cb, revs, *flags); for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb); + clear_ref_exclusion(revs); } else if (!prefixcmp(arg, "--remotes=")) { struct all_refs_cb cb; init_all_refs_cb(&cb, revs, *flags); for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb); + clear_ref_exclusion(revs); } else if (!strcmp(arg, "--reflog")) { handle_reflog(revs, *flags); } else if (!strcmp(arg, "--not")) { diff --git a/revision.h b/revision.h index 95859ba11..b4dc56c3c 100644 --- a/revision.h +++ b/revision.h @@ -59,6 +59,9 @@ struct rev_info { /* The end-points specified by the end user */ struct rev_cmdline_info cmdline; + /* excluding from --branches, --refs, etc. expansion */ + struct string_list *ref_excludes; + /* Basic information */ const char *prefix; const char *def; -- cgit v1.2.1 From 574d370b0633a89f7d62ec4eac08228b26eea06f Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Mon, 2 Sep 2013 22:11:26 +0200 Subject: document --exclude option Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- Documentation/rev-list-options.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 5bdfb4285..7de3ad542 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -174,6 +174,21 @@ parents) and `--max-parents=-1` (negative numbers denote no upper limit). is automatically prepended if missing. If pattern lacks '?', '{asterisk}', or '[', '/{asterisk}' at the end is implied. +--exclude=:: + + Do not include refs matching '' 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:: Upon seeing an invalid object name in the input, pretend as if -- cgit v1.2.1 From 751a2ac6edfbc803b9ed7c48d2d1fc54c97cc64c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 1 Nov 2013 11:33:15 -0700 Subject: rev-list --exclude: tests Add tests for the --exclude= feature. A few tests are added for cases where use of globbing and "--exclude" results in no positive revisions: * "--exclude=" before "--all" etc. resulted in no results; * "--stdin" is used but no input was given; * "--all" etc. is used but no matching refs are found. Currently, we fail such a request with the same error message we would give to a command line that does not specify any positive revision (e.g. "git rev-list"). We may want to treat these cases differently and not error out, but the logic to detect that would be common to all of them, so I'd leave it outside this topic for now, and stop at adding these tests as food-for-thought. Signed-off-by: Junio C Hamano --- t/t6018-rev-list-glob.sh | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh index f00cebff3..77ef3234b 100755 --- a/t/t6018-rev-list-glob.sh +++ b/t/t6018-rev-list-glob.sh @@ -231,6 +231,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" 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 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' ' compare shortlog "subspace/one subspace/two" --branches=subspace && -- cgit v1.2.1 From ff32d3420a550d3811e745af7f2ccc77fb026b7b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 1 Nov 2013 12:02:45 -0700 Subject: rev-list --exclude: export add/clear-ref-exclusion and ref-excluded API ... while updating their function signature. To be squashed into the initial patch to rev-list. Signed-off-by: Junio C Hamano --- revision.c | 46 +++++++++++++++++++++++----------------------- revision.h | 5 +++++ 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/revision.c b/revision.c index 3e8287425..ddc71b9e0 100644 --- a/revision.c +++ b/revision.c @@ -1180,13 +1180,13 @@ struct all_refs_cb { const char *name_for_errormsg; }; -static int ref_excluded(struct rev_info *revs, const char *path) +int ref_excluded(struct string_list *ref_excludes, const char *path) { struct string_list_item *item; - if (!revs->ref_excludes) + if (!ref_excludes) return 0; - for_each_string_list_item(item, revs->ref_excludes) { + for_each_string_list_item(item, ref_excludes) { if (!fnmatch(item->string, path, 0)) return 1; } @@ -1198,7 +1198,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, struct all_refs_cb *cb = cb_data; struct object *object; - if (ref_excluded(cb->all_revs, path)) + if (ref_excluded(cb->all_revs->ref_excludes, path)) return 0; object = get_reference(cb->all_revs, path, sha1, cb->all_flags); @@ -1214,22 +1214,22 @@ static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs, cb->all_flags = flags; } -static void clear_ref_exclusion(struct rev_info *revs) +void clear_ref_exclusion(struct string_list **ref_excludes_p) { - if (revs->ref_excludes) { - string_list_clear(revs->ref_excludes, 0); - free(revs->ref_excludes); + if (*ref_excludes_p) { + string_list_clear(*ref_excludes_p, 0); + free(*ref_excludes_p); } - revs->ref_excludes = NULL; + *ref_excludes_p = NULL; } -static void add_ref_exclusion(struct rev_info *revs, const char *exclude) +void add_ref_exclusion(struct string_list **ref_excludes_p, const char *exclude) { - if (!revs->ref_excludes) { - revs->ref_excludes = xcalloc(1, sizeof(*revs->ref_excludes)); - revs->ref_excludes->strdup_strings = 1; + if (!*ref_excludes_p) { + *ref_excludes_p = xcalloc(1, sizeof(**ref_excludes_p)); + (*ref_excludes_p)->strdup_strings = 1; } - string_list_append(revs->ref_excludes, exclude); + string_list_append(*ref_excludes_p, exclude); } static void handle_refs(const char *submodule, struct rev_info *revs, unsigned flags, @@ -1988,44 +1988,44 @@ static int handle_revision_pseudo_opt(const char *submodule, if (!strcmp(arg, "--all")) { handle_refs(submodule, revs, *flags, for_each_ref_submodule); handle_refs(submodule, revs, *flags, head_ref_submodule); - clear_ref_exclusion(revs); + clear_ref_exclusion(&revs->ref_excludes); } else if (!strcmp(arg, "--branches")) { handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule); - clear_ref_exclusion(revs); + clear_ref_exclusion(&revs->ref_excludes); } else if (!strcmp(arg, "--bisect")) { handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref); handle_refs(submodule, revs, *flags ^ (UNINTERESTING | BOTTOM), for_each_good_bisect_ref); revs->bisect = 1; } else if (!strcmp(arg, "--tags")) { handle_refs(submodule, revs, *flags, for_each_tag_ref_submodule); - clear_ref_exclusion(revs); + clear_ref_exclusion(&revs->ref_excludes); } else if (!strcmp(arg, "--remotes")) { handle_refs(submodule, revs, *flags, for_each_remote_ref_submodule); - clear_ref_exclusion(revs); + clear_ref_exclusion(&revs->ref_excludes); } else if ((argcount = parse_long_opt("glob", argv, &optarg))) { struct all_refs_cb cb; init_all_refs_cb(&cb, revs, *flags); for_each_glob_ref(handle_one_ref, optarg, &cb); - clear_ref_exclusion(revs); + clear_ref_exclusion(&revs->ref_excludes); return argcount; } else if ((argcount = parse_long_opt("exclude", argv, &optarg))) { - add_ref_exclusion(revs, optarg); + add_ref_exclusion(&revs->ref_excludes, optarg); return argcount; } else if (!prefixcmp(arg, "--branches=")) { struct all_refs_cb cb; init_all_refs_cb(&cb, revs, *flags); for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb); - clear_ref_exclusion(revs); + clear_ref_exclusion(&revs->ref_excludes); } else if (!prefixcmp(arg, "--tags=")) { struct all_refs_cb cb; init_all_refs_cb(&cb, revs, *flags); for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb); - clear_ref_exclusion(revs); + clear_ref_exclusion(&revs->ref_excludes); } else if (!prefixcmp(arg, "--remotes=")) { struct all_refs_cb cb; init_all_refs_cb(&cb, revs, *flags); for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb); - clear_ref_exclusion(revs); + clear_ref_exclusion(&revs->ref_excludes); } else if (!strcmp(arg, "--reflog")) { handle_reflog(revs, *flags); } else if (!strcmp(arg, "--not")) { diff --git a/revision.h b/revision.h index b4dc56c3c..c67c46d71 100644 --- a/revision.h +++ b/revision.h @@ -192,6 +192,11 @@ struct rev_info { struct decoration line_log_data; }; +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_NEW 1 /* Only new files */ #define REV_TREE_OLD 2 /* Only files removed */ -- cgit v1.2.1 From 9dc01bf0631b51dfe497d57942a9085e0808e205 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 1 Nov 2013 12:13:01 -0700 Subject: rev-parse: introduce --exclude= to tame wildcards Teach "rev-parse" the same "I'm going to glob, but omit the ones that match these patterns" feature as "rev-list". Signed-off-by: Junio C Hamano --- Documentation/git-rev-parse.txt | 14 ++++++++++++++ builtin/rev-parse.c | 17 +++++++++++++++++ t/t6018-rev-list-glob.sh | 12 ++++++++++++ 3 files changed, 43 insertions(+) diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 2b126c0a7..4cba660bc 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -155,6 +155,20 @@ shown. If the pattern does not contain a globbing character (`?`, character (`?`, `*`, or `[`), it is turned into a prefix match by appending `/*`. +--exclude=:: + Do not include refs matching '' 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. + --show-toplevel:: Show the absolute path of the top-level directory. diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index de894c757..f52f8048b 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -9,6 +9,8 @@ #include "quote.h" #include "builtin.h" #include "parse-options.h" +#include "diff.h" +#include "revision.h" #define DO_REVS 1 #define DO_NOREV 2 @@ -30,6 +32,8 @@ static int abbrev_ref; static int abbrev_ref_strict; static int output_sq; +static struct string_list *ref_excludes; + /* * Some arguments are relevant "revision" arguments, * others are about output format or other details. @@ -185,6 +189,8 @@ static int show_default(void) 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); return 0; } @@ -633,32 +639,43 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (!prefixcmp(arg, "--branches=")) { for_each_glob_ref_in(show_reference, arg + 11, "refs/heads/", NULL); + clear_ref_exclusion(&ref_excludes); continue; } if (!strcmp(arg, "--branches")) { for_each_branch_ref(show_reference, NULL); + clear_ref_exclusion(&ref_excludes); continue; } if (!prefixcmp(arg, "--tags=")) { for_each_glob_ref_in(show_reference, arg + 7, "refs/tags/", NULL); + clear_ref_exclusion(&ref_excludes); continue; } if (!strcmp(arg, "--tags")) { for_each_tag_ref(show_reference, NULL); + clear_ref_exclusion(&ref_excludes); continue; } if (!prefixcmp(arg, "--glob=")) { for_each_glob_ref(show_reference, arg + 7, NULL); + clear_ref_exclusion(&ref_excludes); continue; } if (!prefixcmp(arg, "--remotes=")) { for_each_glob_ref_in(show_reference, arg + 10, "refs/remotes/", NULL); + clear_ref_exclusion(&ref_excludes); continue; } if (!strcmp(arg, "--remotes")) { 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; } if (!strcmp(arg, "--show-toplevel")) { diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh index 77ef3234b..d00f7db86 100755 --- a/t/t6018-rev-list-glob.sh +++ b/t/t6018-rev-list-glob.sh @@ -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/*' ' compare rev-list "subspace/one subspace/two" "--glob=refs/heads/subspace/*" -- cgit v1.2.1