diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 3d79de11ec..0b874e3d1a 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -107,6 +107,17 @@ git log --follow builtin-rev-list.c:: those commits that occurred before the file was given its present name. +git log --branches --not --glob=remotes/origin/*:: + + Shows all commits that are in any of local branches but not in + any of remote tracking branches for 'origin' (what you have that + origin doesn't). + +git log master --not --glob=remotes/*/master:: + + Shows all commits that are in local master but not in any remote + repository master branches. + Discussion ---------- diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 3341d1b62f..33122a3f33 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -24,6 +24,7 @@ SYNOPSIS [ \--branches ] [ \--tags ] [ \--remotes ] + [ \--glob=glob-pattern ] [ \--stdin ] [ \--quiet ] [ \--topo-order ] diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 82045a2522..6eb8c14f62 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -112,6 +112,11 @@ OPTIONS --remotes:: Show tag refs found in `$GIT_DIR/refs/remotes`. +--glob=glob-pattern:: + Show refs matching shell glob pattern `glob-pattern`. If pattern + specified lacks leading 'refs/', it is automatically prepended. + If pattern lacks '?', '*', or '[', '/*' at the end is impiled. + --show-prefix:: When the command is invoked from a subdirectory, show the path of the current directory relative to the top-level diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 1f57aed337..6d03c17a68 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -243,6 +243,13 @@ endif::git-rev-list[] Pretend as if all the refs in `$GIT_DIR/refs/remotes` are listed on the command line as ''. +--glob=glob-pattern:: + Pretend as if all the refs matching shell glob `glob-pattern` + are listed on the command line as ''. Leading 'refs/', + is automatically prepended if missing. If pattern lacks '?', '*', + or '[', '/*' at the end is impiled. + + ifndef::git-rev-list[] --bisect:: diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 37d0233521..a635dded65 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -52,6 +52,7 @@ static int is_rev_argument(const char *arg) "--parents", "--pretty", "--remotes", + "--glob=", "--sparse", "--tags", "--topo-order", @@ -577,6 +578,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for_each_tag_ref(show_reference, NULL); continue; } + if (!prefixcmp(arg, "--glob=")) { + for_each_glob_ref(show_reference, arg + 7, NULL); + continue; + } if (!strcmp(arg, "--remotes")) { for_each_remote_ref(show_reference, NULL); continue; diff --git a/refs.c b/refs.c index 3e73a0a36d..34fff75b0d 100644 --- a/refs.c +++ b/refs.c @@ -519,6 +519,13 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int * return ref; } +/* The argument to filter_refs */ +struct ref_filter { + const char *pattern; + each_ref_fn *fn; + void *cb_data; +}; + int read_ref(const char *ref, unsigned char *sha1) { if (resolve_ref(ref, sha1, 1, NULL)) @@ -545,6 +552,15 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim, return fn(entry->name + trim, entry->sha1, entry->flag, cb_data); } +static int filter_refs(const char *ref, const unsigned char *sha, int flags, + void *data) +{ + struct ref_filter *filter = (struct ref_filter *)data; + if (fnmatch(filter->pattern, ref, 0)) + return 0; + return filter->fn(ref, sha, flags, filter->cb_data); +} + int peel_ref(const char *ref, unsigned char *sha1) { int flag; @@ -674,6 +690,35 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data) return do_for_each_ref("refs/replace/", fn, 13, 0, cb_data); } +int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data) +{ + struct strbuf real_pattern = STRBUF_INIT; + struct ref_filter filter; + const char *has_glob_specials; + int ret; + + if (prefixcmp(pattern, "refs/")) + strbuf_addstr(&real_pattern, "refs/"); + strbuf_addstr(&real_pattern, pattern); + + has_glob_specials = strpbrk(pattern, "?*["); + if (!has_glob_specials) { + /* Append impiled '/' '*' if not present. */ + if (real_pattern.buf[real_pattern.len - 1] != '/') + strbuf_addch(&real_pattern, '/'); + /* No need to check for '*', there is none. */ + strbuf_addch(&real_pattern, '*'); + } + + filter.pattern = real_pattern.buf; + filter.fn = fn; + filter.cb_data = cb_data; + ret = for_each_ref(filter_refs, &filter); + + strbuf_release(&real_pattern); + return ret; +} + int for_each_rawref(each_ref_fn fn, void *cb_data) { return do_for_each_ref("refs/", fn, 0, diff --git a/refs.h b/refs.h index e141991851..78ad173f1b 100644 --- a/refs.h +++ b/refs.h @@ -25,6 +25,7 @@ extern int for_each_tag_ref(each_ref_fn, void *); extern int for_each_branch_ref(each_ref_fn, void *); extern int for_each_remote_ref(each_ref_fn, void *); extern int for_each_replace_ref(each_ref_fn, void *); +extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *); /* can be used to learn about broken ref and symref */ extern int for_each_rawref(each_ref_fn, void *); diff --git a/revision.c b/revision.c index 25fa14d93e..162b182914 100644 --- a/revision.c +++ b/revision.c @@ -699,12 +699,18 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, return 0; } +static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs, + unsigned flags) +{ + cb->all_revs = revs; + cb->all_flags = flags; +} + static void handle_refs(struct rev_info *revs, unsigned flags, int (*for_each)(each_ref_fn, void *)) { struct all_refs_cb cb; - cb.all_revs = revs; - cb.all_flags = flags; + init_all_refs_cb(&cb, revs, flags); for_each(handle_one_ref, &cb); } @@ -1352,6 +1358,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch handle_refs(revs, flags, for_each_remote_ref); continue; } + if (!prefixcmp(arg, "--glob=")) { + struct all_refs_cb cb; + init_all_refs_cb(&cb, revs, flags); + for_each_glob_ref(handle_one_ref, arg + 7, &cb); + continue; + } if (!strcmp(arg, "--reflog")) { handle_reflog(revs, flags); continue; diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh new file mode 100755 index 0000000000..1f251e1f43 --- /dev/null +++ b/t/t6018-rev-list-glob.sh @@ -0,0 +1,134 @@ +#!/bin/sh + +test_description='rev-list/rev-parse --glob' + +. ./test-lib.sh + +commit () { + test_tick && + echo $1 > foo && + git add foo && + git commit -m "$1" +} + +compare () { + # Split arguments on whitespace. + git $1 $2 >expected && + git $1 $3 >actual && + test_cmp expected actual +} + +test_expect_success 'setup' ' + + commit master && + git checkout -b subspace/one master && + commit one && + git checkout -b subspace/two master && + commit two && + git checkout -b subspace-x master && + commit subspace-x && + git checkout -b other/three master && + commit three && + git checkout -b someref master && + commit some && + git checkout master && + commit master2 +' + +test_expect_success 'rev-parse --glob=refs/heads/subspace/*' ' + + compare rev-parse "subspace/one subspace/two" "--glob=refs/heads/subspace/*" + +' + +test_expect_success 'rev-parse --glob=heads/subspace/*' ' + + compare rev-parse "subspace/one subspace/two" "--glob=heads/subspace/*" + +' + +test_expect_success 'rev-parse --glob=refs/heads/subspace/' ' + + compare rev-parse "subspace/one subspace/two" "--glob=refs/heads/subspace/" + +' + +test_expect_success 'rev-parse --glob=heads/subspace/' ' + + compare rev-parse "subspace/one subspace/two" "--glob=heads/subspace/" + +' + +test_expect_success 'rev-parse --glob=heads/subspace' ' + + compare rev-parse "subspace/one subspace/two" "--glob=heads/subspace" + +' + +test_expect_success 'rev-parse --glob=heads/subspace/* --glob=heads/other/*' ' + + compare rev-parse "subspace/one subspace/two other/three" "--glob=heads/subspace/* --glob=heads/other/*" + +' + +test_expect_success 'rev-parse --glob=heads/someref/* master' ' + + compare rev-parse "master" "--glob=heads/someref/* master" + +' + +test_expect_success 'rev-parse --glob=heads/*' ' + + compare rev-parse "master other/three someref subspace-x subspace/one subspace/two" "--glob=heads/*" + +' + +test_expect_success 'rev-list --glob=refs/heads/subspace/*' ' + + compare rev-list "subspace/one subspace/two" "--glob=refs/heads/subspace/*" + +' + +test_expect_success 'rev-list --glob=heads/subspace/*' ' + + compare rev-list "subspace/one subspace/two" "--glob=heads/subspace/*" + +' + +test_expect_success 'rev-list --glob=refs/heads/subspace/' ' + + compare rev-list "subspace/one subspace/two" "--glob=refs/heads/subspace/" + +' + +test_expect_success 'rev-list --glob=heads/subspace/' ' + + compare rev-list "subspace/one subspace/two" "--glob=heads/subspace/" + +' + +test_expect_success 'rev-list --glob=heads/subspace' ' + + compare rev-list "subspace/one subspace/two" "--glob=heads/subspace" + +' + +test_expect_success 'rev-list --glob=heads/someref/* master' ' + + compare rev-list "master" "--glob=heads/someref/* master" + +' + +test_expect_success 'rev-list --glob=heads/subspace/* --glob=heads/other/*' ' + + compare rev-list "subspace/one subspace/two other/three" "--glob=heads/subspace/* --glob=heads/other/*" + +' + +test_expect_success 'rev-list --glob=heads/*' ' + + compare rev-list "master other/three someref subspace-x subspace/one subspace/two" "--glob=heads/*" + +' + +test_done