diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index ff4063b020..0e39bb61ee 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 --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 --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 78d3f48dfb..ae17c8a6fb 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -21,9 +21,10 @@ SYNOPSIS [ \--full-history ] [ \--not ] [ \--all ] - [ \--branches ] - [ \--tags ] - [ \--remotes ] + [ \--branches[=pattern] ] + [ \--tags=[pattern] ] + [ \--remotes=[pattern] ] + [ \--glob=glob-pattern ] [ \--stdin ] [ \--quiet ] [ \--topo-order ] diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index d375f1af10..c9184fcc76 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -103,14 +103,24 @@ OPTIONS --all:: Show all refs found in `$GIT_DIR/refs`. ---branches:: - Show branch refs found in `$GIT_DIR/refs/heads`. +--branches[=pattern]:: +--tags[=pattern]:: +--remotes[=pattern]:: + Show all branches, tags, or remote-tracking branches, + respectively (i.e., refs found in `$GIT_DIR/refs/heads`, + `$GIT_DIR/refs/tags`, or `$GIT_DIR/refs/remotes`, + respectively). ++ +If a `pattern` is given, only refs matching the given shell glob are +shown. If the pattern does not contain a globbing character (`?`, +`\*`, or `[`), it is turned into a prefix match by appending `/\*`. ---tags:: - Show tag refs found in `$GIT_DIR/refs/tags`. - ---remotes:: - Show tag refs found in `$GIT_DIR/refs/remotes`. +--glob=pattern:: + Show all refs matching the shell glob pattern `pattern`. If + the pattern does not start with `refs/`, this is automatically + prepended. If the pattern does not contain a globbing + character (`?`, `\*`, or `[`), it is turned into a prefix + match by appending `/\*`. --show-toplevel:: Show the absolute path of the top-level directory. diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 1f57aed337..3ef71179d9 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -228,20 +228,33 @@ endif::git-rev-list[] Pretend as if all the refs in `$GIT_DIR/refs/` are listed on the command line as ''. ---branches:: +--branches[=pattern]:: Pretend as if all the refs in `$GIT_DIR/refs/heads` are listed - on the command line as ''. + on the command line as ''. If `pattern` is given, limit + branches to ones matching given shell glob. If pattern lacks '?', + '*', or '[', '/*' at the end is impiled. ---tags:: +--tags[=pattern]:: Pretend as if all the refs in `$GIT_DIR/refs/tags` are listed - on the command line as ''. + on the command line as ''. If `pattern` is given, limit + tags to ones matching given shell glob. If pattern lacks '?', '*', + or '[', '/*' at the end is impiled. ---remotes:: +--remotes[=pattern]:: Pretend as if all the refs in `$GIT_DIR/refs/remotes` are listed - on the command line as ''. + on the command line as ''. If `pattern`is given, limit + remote tracking branches to ones matching given shell glob. + If pattern lacks '?', '*', or '[', '/*' at the end is impiled. + +--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 cbe5b428ad..a8c5043ded 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -41,6 +41,7 @@ static int is_rev_argument(const char *arg) "--all", "--bisect", "--dense", + "--branches=", "--branches", "--header", "--max-age=", @@ -51,8 +52,11 @@ static int is_rev_argument(const char *arg) "--objects-edge", "--parents", "--pretty", + "--remotes=", "--remotes", + "--glob=", "--sparse", + "--tags=", "--tags", "--topo-order", "--date-order", @@ -569,14 +573,33 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for_each_ref_in("refs/bisect/good", anti_reference, NULL); continue; } + if (!prefixcmp(arg, "--branches=")) { + for_each_glob_ref_in(show_reference, arg + 11, + "refs/heads/", NULL); + continue; + } if (!strcmp(arg, "--branches")) { for_each_branch_ref(show_reference, NULL); continue; } + if (!prefixcmp(arg, "--tags=")) { + for_each_glob_ref_in(show_reference, arg + 7, + "refs/tags/", NULL); + continue; + } if (!strcmp(arg, "--tags")) { for_each_tag_ref(show_reference, NULL); continue; } + if (!prefixcmp(arg, "--glob=")) { + for_each_glob_ref(show_reference, arg + 7, NULL); + continue; + } + if (!prefixcmp(arg, "--remotes=")) { + for_each_glob_ref_in(show_reference, arg + 10, + "refs/remotes/", NULL); + continue; + } if (!strcmp(arg, "--remotes")) { for_each_remote_ref(show_reference, NULL); continue; diff --git a/refs.c b/refs.c index 3e73a0a36d..503a8c2bd0 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,43 @@ 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_in(each_ref_fn fn, const char *pattern, + const char *prefix, void *cb_data) +{ + struct strbuf real_pattern = STRBUF_INIT; + struct ref_filter filter; + const char *has_glob_specials; + int ret; + + if (!prefix && prefixcmp(pattern, "refs/")) + strbuf_addstr(&real_pattern, "refs/"); + else if (prefix) + strbuf_addstr(&real_pattern, prefix); + 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_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data) +{ + return for_each_glob_ref_in(fn, pattern, NULL, cb_data); +} + 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..f7648b9bd3 100644 --- a/refs.h +++ b/refs.h @@ -25,6 +25,8 @@ 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 *); +extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, 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 f3b82d97bc..1d3457cb6a 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); } @@ -1361,6 +1367,30 @@ 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 (!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); + continue; + } + 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); + continue; + } + 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); + 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..8d3fa7d014 --- /dev/null +++ b/t/t6018-rev-list-glob.sh @@ -0,0 +1,195 @@ +#!/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 && + git tag foo/bar master && + git update-ref refs/remotes/foo/baz master +' + +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 --branches=subspace/*' ' + + compare rev-parse "subspace/one subspace/two" "--branches=subspace/*" + +' + +test_expect_success 'rev-parse --branches=subspace/' ' + + compare rev-parse "subspace/one subspace/two" "--branches=subspace/" + +' + +test_expect_success 'rev-parse --branches=subspace' ' + + compare rev-parse "subspace/one subspace/two" "--branches=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-parse --tags=foo' ' + + compare rev-parse "foo/bar" "--tags=foo" + +' + +test_expect_success 'rev-parse --remotes=foo' ' + + compare rev-parse "foo/baz" "--remotes=foo" + +' + +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 --branches=subspace/*' ' + + compare rev-list "subspace/one subspace/two" "--branches=subspace/*" + +' + +test_expect_success 'rev-list --branches=subspace/' ' + + compare rev-list "subspace/one subspace/two" "--branches=subspace/" + +' + +test_expect_success 'rev-list --branches=subspace' ' + + compare rev-list "subspace/one subspace/two" "--branches=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_expect_success 'rev-list --tags=foo' ' + + compare rev-list "foo/bar" "--tags=foo" + +' + +test_expect_success 'rev-list --remotes=foo' ' + + compare rev-list "foo/baz" "--remotes=foo" + +' + +test_done