summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/git-log.txt11
-rw-r--r--Documentation/git-rev-list.txt7
-rw-r--r--Documentation/git-rev-parse.txt26
-rw-r--r--Documentation/rev-list-options.txt25
-rw-r--r--builtin-rev-parse.c23
-rw-r--r--refs.c53
-rw-r--r--refs.h2
-rw-r--r--revision.c34
-rwxr-xr-xt/t6018-rev-list-glob.sh195
9 files changed, 357 insertions, 19 deletions
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 818eb48d67..e7845d4055 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`.
-
---tags::
- Show tag refs found in `$GIT_DIR/refs/tags`.
-
---remotes::
- Show tag refs found in `$GIT_DIR/refs/remotes`.
+--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 `/\*`.
+
+--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 '<commit>'.
---branches::
+--branches[=pattern]::
Pretend as if all the refs in `$GIT_DIR/refs/heads` are listed
- on the command line as '<commit>'.
+ on the command line as '<commit>'. 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 '<commit>'.
+ on the command line as '<commit>'. 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 '<commit>'.
+ on the command line as '<commit>'. 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 '<commit>'. 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 4e1a299bfb..f54d43ffb3 100644
--- a/revision.c
+++ b/revision.c
@@ -700,12 +700,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);
}
@@ -1362,6 +1368,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