提交 ac3f5a34 编写于 作者: Æ Ævar Arnfjörð Bjarmason 提交者: Junio C Hamano

ref-filter: add --no-contains option to tag/branch/for-each-ref

Change the tag, branch & for-each-ref commands to have a --no-contains
option in addition to their longstanding --contains options.

This allows for finding the last-good rollout tag given a known-bad
<commit>. Given a hypothetically bad commit cf5c7253, the git
version to revert to can be found with this hacky two-liner:

    (git tag -l 'v[0-9]*'; git tag -l --contains cf5c7253 'v[0-9]*') |
        sort | uniq -c | grep -E '^ *1 ' | awk '{print $2}' | tail -n 10

With this new --no-contains option the same can be achieved with:

    git tag -l --no-contains cf5c7253 'v[0-9]*' | sort | tail -n 10

As the filtering machinery is shared between the tag, branch &
for-each-ref commands, implement this for those commands too. A
practical use for this with "branch" is e.g. finding branches which
were branched off between v2.8.0 and v2.10.0:

    git branch --contains v2.8.0 --no-contains v2.10.0

The "describe" command also has a --contains option, but its semantics
are unrelated to what tag/branch/for-each-ref use --contains for. A
--no-contains option for "describe" wouldn't make any sense, other
than being exactly equivalent to not supplying --contains at all,
which would be confusing at best.

Add a --without option to "tag" as an alias for --no-contains, for
consistency with --with and --contains.  The --with option is
undocumented, and possibly the only user of it is
Junio (<xmqqefy71iej.fsf@gitster.mtv.corp.google.com>). But it's
trivial to support, so let's do that.

The additions to the the test suite are inverse copies of the
corresponding --contains tests. With this change --no-contains for
tag, branch & for-each-ref is just as well tested as the existing
--contains option.

In addition to those tests, add a test for "tag" which asserts that
--no-contains won't find tree/blob tags, which is slightly
unintuitive, but consistent with how --contains works & is documented.
Signed-off-by: NÆvar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: NJunio C Hamano <gitster@pobox.com>
上级 1e0c3b68
...@@ -11,7 +11,8 @@ SYNOPSIS ...@@ -11,7 +11,8 @@ SYNOPSIS
'git branch' [--color[=<when>] | --no-color] [-r | -a] 'git branch' [--color[=<when>] | --no-color] [-r | -a]
[--list] [-v [--abbrev=<length> | --no-abbrev]] [--list] [-v [--abbrev=<length> | --no-abbrev]]
[--column[=<options>] | --no-column] [--column[=<options>] | --no-column]
[(--merged | --no-merged | --contains) [<commit>]] [--sort=<key>] [(--merged | --no-merged) [<commit>]]
[--contains [<commit]] [--no-contains [<commit>]] [--sort=<key>]
[--points-at <object>] [--format=<format>] [<pattern>...] [--points-at <object>] [--format=<format>] [<pattern>...]
'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>] 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>] 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
...@@ -35,7 +36,7 @@ as branch creation. ...@@ -35,7 +36,7 @@ as branch creation.
With `--contains`, shows only the branches that contain the named commit With `--contains`, shows only the branches that contain the named commit
(in other words, the branches whose tip commits are descendants of the (in other words, the branches whose tip commits are descendants of the
named commit). With `--merged`, only branches merged into the named named commit), `--no-contains` inverts it. With `--merged`, only branches merged into the named
commit (i.e. the branches whose tip commits are reachable from the named commit (i.e. the branches whose tip commits are reachable from the named
commit) will be listed. With `--no-merged` only branches not merged into commit) will be listed. With `--no-merged` only branches not merged into
the named commit will be listed. If the <commit> argument is missing it the named commit will be listed. If the <commit> argument is missing it
...@@ -213,6 +214,10 @@ start-point is either a local or remote-tracking branch. ...@@ -213,6 +214,10 @@ start-point is either a local or remote-tracking branch.
Only list branches which contain the specified commit (HEAD Only list branches which contain the specified commit (HEAD
if not specified). Implies `--list`. if not specified). Implies `--list`.
--no-contains [<commit>]::
Only list branches which don't contain the specified commit
(HEAD if not specified). Implies `--list`.
--merged [<commit>]:: --merged [<commit>]::
Only list branches whose tips are reachable from the Only list branches whose tips are reachable from the
specified commit (HEAD if not specified). Implies `--list`, specified commit (HEAD if not specified). Implies `--list`,
...@@ -298,13 +303,16 @@ If you are creating a branch that you want to checkout immediately, it is ...@@ -298,13 +303,16 @@ If you are creating a branch that you want to checkout immediately, it is
easier to use the git checkout command with its `-b` option to create easier to use the git checkout command with its `-b` option to create
a branch and check it out with a single command. a branch and check it out with a single command.
The options `--contains`, `--merged` and `--no-merged` serve three related The options `--contains`, `--no-contains`, `--merged` and `--no-merged`
but different purposes: serve four related but different purposes:
- `--contains <commit>` is used to find all branches which will need - `--contains <commit>` is used to find all branches which will need
special attention if <commit> were to be rebased or amended, since those special attention if <commit> were to be rebased or amended, since those
branches contain the specified <commit>. branches contain the specified <commit>.
- `--no-contains <commit>` is the inverse of that, i.e. branches that don't
contain the specified <commit>.
- `--merged` is used to find all branches which can be safely deleted, - `--merged` is used to find all branches which can be safely deleted,
since those branches are fully contained by HEAD. since those branches are fully contained by HEAD.
......
...@@ -11,7 +11,7 @@ SYNOPSIS ...@@ -11,7 +11,7 @@ SYNOPSIS
'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl] 'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl]
[(--sort=<key>)...] [--format=<format>] [<pattern>...] [(--sort=<key>)...] [--format=<format>] [<pattern>...]
[--points-at <object>] [(--merged | --no-merged) [<object>]] [--points-at <object>] [(--merged | --no-merged) [<object>]]
[--contains [<object>]] [--contains [<object>]] [--no-contains [<object>]]
DESCRIPTION DESCRIPTION
----------- -----------
...@@ -81,6 +81,10 @@ OPTIONS ...@@ -81,6 +81,10 @@ OPTIONS
Only list refs which contain the specified commit (HEAD if not Only list refs which contain the specified commit (HEAD if not
specified). specified).
--no-contains [<object>]::
Only list refs which don't contain the specified commit (HEAD
if not specified).
--ignore-case:: --ignore-case::
Sorting and filtering refs are case insensitive. Sorting and filtering refs are case insensitive.
......
...@@ -12,7 +12,7 @@ SYNOPSIS ...@@ -12,7 +12,7 @@ SYNOPSIS
'git tag' [-a | -s | -u <keyid>] [-f] [-m <msg> | -F <file>] 'git tag' [-a | -s | -u <keyid>] [-f] [-m <msg> | -F <file>]
<tagname> [<commit> | <object>] <tagname> [<commit> | <object>]
'git tag' -d <tagname>... 'git tag' -d <tagname>...
'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>] 'git tag' [-n[<num>]] -l [--contains <commit>] [--contains <commit>] [--points-at <object>]
[--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>] [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]
[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...] [--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]
'git tag' -v [--format=<format>] <tagname>... 'git tag' -v [--format=<format>] <tagname>...
...@@ -130,6 +130,10 @@ This option is only applicable when listing tags without annotation lines. ...@@ -130,6 +130,10 @@ This option is only applicable when listing tags without annotation lines.
Only list tags which contain the specified commit (HEAD if not Only list tags which contain the specified commit (HEAD if not
specified). Implies `--list`. specified). Implies `--list`.
--no-contains [<commit>]::
Only list tags which don't contain the specified commit (HEAD if
not specified). Implies `--list`.
--merged [<commit>]:: --merged [<commit>]::
Only list tags whose commits are reachable from the specified Only list tags whose commits are reachable from the specified
commit (`HEAD` if not specified), incompatible with `--no-merged`. commit (`HEAD` if not specified), incompatible with `--no-merged`.
......
...@@ -548,7 +548,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) ...@@ -548,7 +548,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_SET_INT('r', "remotes", &filter.kind, N_("act on remote-tracking branches"), OPT_SET_INT('r', "remotes", &filter.kind, N_("act on remote-tracking branches"),
FILTER_REFS_REMOTES), FILTER_REFS_REMOTES),
OPT_CONTAINS(&filter.with_commit, N_("print only branches that contain the commit")), OPT_CONTAINS(&filter.with_commit, N_("print only branches that contain the commit")),
OPT_NO_CONTAINS(&filter.no_commit, N_("print only branches that don't contain the commit")),
OPT_WITH(&filter.with_commit, N_("print only branches that contain the commit")), OPT_WITH(&filter.with_commit, N_("print only branches that contain the commit")),
OPT_WITHOUT(&filter.no_commit, N_("print only branches that don't contain the commit")),
OPT__ABBREV(&filter.abbrev), OPT__ABBREV(&filter.abbrev),
OPT_GROUP(N_("Specific git-branch actions:")), OPT_GROUP(N_("Specific git-branch actions:")),
...@@ -604,7 +606,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) ...@@ -604,7 +606,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0) if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0)
list = 1; list = 1;
if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr) if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr ||
filter.no_commit)
list = 1; list = 1;
if (!!delete + !!rename + !!new_upstream + if (!!delete + !!rename + !!new_upstream +
......
...@@ -9,7 +9,7 @@ static char const * const for_each_ref_usage[] = { ...@@ -9,7 +9,7 @@ static char const * const for_each_ref_usage[] = {
N_("git for-each-ref [<options>] [<pattern>]"), N_("git for-each-ref [<options>] [<pattern>]"),
N_("git for-each-ref [--points-at <object>]"), N_("git for-each-ref [--points-at <object>]"),
N_("git for-each-ref [(--merged | --no-merged) [<commit>]]"), N_("git for-each-ref [(--merged | --no-merged) [<commit>]]"),
N_("git for-each-ref [--contains [<commit>]]"), N_("git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"),
NULL NULL
}; };
...@@ -43,6 +43,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) ...@@ -43,6 +43,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
OPT_MERGED(&filter, N_("print only refs that are merged")), OPT_MERGED(&filter, N_("print only refs that are merged")),
OPT_NO_MERGED(&filter, N_("print only refs that are not merged")), OPT_NO_MERGED(&filter, N_("print only refs that are not merged")),
OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")), OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")),
OPT_NO_CONTAINS(&filter.no_commit, N_("print only refs which don't contain the commit")),
OPT_BOOL(0, "ignore-case", &icase, N_("sorting and filtering are case insensitive")), OPT_BOOL(0, "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
OPT_END(), OPT_END(),
}; };
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
static const char * const git_tag_usage[] = { static const char * const git_tag_usage[] = {
N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"), N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"),
N_("git tag -d <tagname>..."), N_("git tag -d <tagname>..."),
N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]" N_("git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--points-at <object>]"
"\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"), "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
N_("git tag -v [--format=<format>] <tagname>..."), N_("git tag -v [--format=<format>] <tagname>..."),
NULL NULL
...@@ -424,7 +424,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix) ...@@ -424,7 +424,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
OPT_GROUP(N_("Tag listing options")), OPT_GROUP(N_("Tag listing options")),
OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")), OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")), OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")),
OPT_NO_CONTAINS(&filter.no_commit, N_("print only tags that don't contain the commit")),
OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")), OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")),
OPT_WITHOUT(&filter.no_commit, N_("print only tags that don't contain the commit")),
OPT_MERGED(&filter, N_("print only tags that are merged")), OPT_MERGED(&filter, N_("print only tags that are merged")),
OPT_NO_MERGED(&filter, N_("print only tags that are not merged")), OPT_NO_MERGED(&filter, N_("print only tags that are not merged")),
OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
...@@ -458,7 +460,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) ...@@ -458,7 +460,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (!cmdmode) { if (!cmdmode) {
if (argc == 0) if (argc == 0)
cmdmode = 'l'; cmdmode = 'l';
else if (filter.with_commit || else if (filter.with_commit || filter.no_commit ||
filter.points_at.nr || filter.merge_commit || filter.points_at.nr || filter.merge_commit ||
filter.lines != -1) filter.lines != -1)
cmdmode = 'l'; cmdmode = 'l';
...@@ -495,6 +497,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) ...@@ -495,6 +497,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
die(_("-n option is only allowed in list mode")); die(_("-n option is only allowed in list mode"));
if (filter.with_commit) if (filter.with_commit)
die(_("--contains option is only allowed in list mode")); die(_("--contains option is only allowed in list mode"));
if (filter.no_commit)
die(_("--no-contains option is only allowed in list mode"));
if (filter.points_at.nr) if (filter.points_at.nr)
die(_("--points-at option is only allowed in list mode")); die(_("--points-at option is only allowed in list mode"));
if (filter.merge_commit) if (filter.merge_commit)
......
...@@ -1093,7 +1093,7 @@ _git_branch () ...@@ -1093,7 +1093,7 @@ _git_branch ()
--*) --*)
__gitcomp " __gitcomp "
--color --no-color --verbose --abbrev= --no-abbrev --color --no-color --verbose --abbrev= --no-abbrev
--track --no-track --contains --merged --no-merged --track --no-track --contains --no-contains --merged --no-merged
--set-upstream-to= --edit-description --list --set-upstream-to= --edit-description --list
--unset-upstream --delete --move --remotes --unset-upstream --delete --move --remotes
--column --no-column --sort= --points-at --column --no-column --sort= --points-at
...@@ -2862,7 +2862,7 @@ _git_tag () ...@@ -2862,7 +2862,7 @@ _git_tag ()
__gitcomp " __gitcomp "
--list --delete --verify --annotate --message --file --list --delete --verify --annotate --message --file
--sign --cleanup --local-user --force --column --sort= --sign --cleanup --local-user --force --column --sort=
--contains --points-at --merged --no-merged --create-reflog --contains --no-contains --points-at --merged --no-merged --create-reflog
" "
;; ;;
esac esac
......
...@@ -259,6 +259,8 @@ extern int parse_opt_passthru_argv(const struct option *, const char *, int); ...@@ -259,6 +259,8 @@ extern int parse_opt_passthru_argv(const struct option *, const char *, int);
parse_opt_commits, (intptr_t) "HEAD" \ parse_opt_commits, (intptr_t) "HEAD" \
} }
#define OPT_CONTAINS(v, h) _OPT_CONTAINS_OR_WITH("contains", v, h, PARSE_OPT_NONEG) #define OPT_CONTAINS(v, h) _OPT_CONTAINS_OR_WITH("contains", v, h, PARSE_OPT_NONEG)
#define OPT_NO_CONTAINS(v, h) _OPT_CONTAINS_OR_WITH("no-contains", v, h, PARSE_OPT_NONEG)
#define OPT_WITH(v, h) _OPT_CONTAINS_OR_WITH("with", v, h, PARSE_OPT_HIDDEN | PARSE_OPT_NONEG) #define OPT_WITH(v, h) _OPT_CONTAINS_OR_WITH("with", v, h, PARSE_OPT_HIDDEN | PARSE_OPT_NONEG)
#define OPT_WITHOUT(v, h) _OPT_CONTAINS_OR_WITH("without", v, h, PARSE_OPT_HIDDEN | PARSE_OPT_NONEG)
#endif #endif
...@@ -1487,6 +1487,7 @@ struct ref_filter_cbdata { ...@@ -1487,6 +1487,7 @@ struct ref_filter_cbdata {
struct ref_array *array; struct ref_array *array;
struct ref_filter *filter; struct ref_filter *filter;
struct contains_cache contains_cache; struct contains_cache contains_cache;
struct contains_cache no_contains_cache;
}; };
/* /*
...@@ -1586,11 +1587,11 @@ static enum contains_result contains_tag_algo(struct commit *candidate, ...@@ -1586,11 +1587,11 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
} }
static int commit_contains(struct ref_filter *filter, struct commit *commit, static int commit_contains(struct ref_filter *filter, struct commit *commit,
struct contains_cache *cache) struct commit_list *list, struct contains_cache *cache)
{ {
if (filter->with_commit_tag_algo) if (filter->with_commit_tag_algo)
return contains_tag_algo(commit, filter->with_commit, cache) == CONTAINS_YES; return contains_tag_algo(commit, list, cache) == CONTAINS_YES;
return is_descendant_of(commit, filter->with_commit); return is_descendant_of(commit, list);
} }
/* /*
...@@ -1780,13 +1781,17 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, ...@@ -1780,13 +1781,17 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
* obtain the commit using the 'oid' available and discard all * obtain the commit using the 'oid' available and discard all
* non-commits early. The actual filtering is done later. * non-commits early. The actual filtering is done later.
*/ */
if (filter->merge_commit || filter->with_commit || filter->verbose) { if (filter->merge_commit || filter->with_commit || filter->no_commit || filter->verbose) {
commit = lookup_commit_reference_gently(oid->hash, 1); commit = lookup_commit_reference_gently(oid->hash, 1);
if (!commit) if (!commit)
return 0; return 0;
/* We perform the filtering for the '--contains' option */ /* We perform the filtering for the '--contains' option... */
if (filter->with_commit && if (filter->with_commit &&
!commit_contains(filter, commit, &ref_cbdata->contains_cache)) !commit_contains(filter, commit, filter->with_commit, &ref_cbdata->contains_cache))
return 0;
/* ...or for the `--no-contains' option */
if (filter->no_commit &&
commit_contains(filter, commit, filter->no_commit, &ref_cbdata->no_contains_cache))
return 0; return 0;
} }
...@@ -1887,6 +1892,7 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int ...@@ -1887,6 +1892,7 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
filter->kind = type & FILTER_REFS_KIND_MASK; filter->kind = type & FILTER_REFS_KIND_MASK;
init_contains_cache(&ref_cbdata.contains_cache); init_contains_cache(&ref_cbdata.contains_cache);
init_contains_cache(&ref_cbdata.no_contains_cache);
/* Simple per-ref filtering */ /* Simple per-ref filtering */
if (!filter->kind) if (!filter->kind)
...@@ -1911,6 +1917,7 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int ...@@ -1911,6 +1917,7 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
} }
clear_contains_cache(&ref_cbdata.contains_cache); clear_contains_cache(&ref_cbdata.contains_cache);
clear_contains_cache(&ref_cbdata.no_contains_cache);
/* Filters that need revision walking */ /* Filters that need revision walking */
if (filter->merge_commit) if (filter->merge_commit)
......
...@@ -53,6 +53,7 @@ struct ref_filter { ...@@ -53,6 +53,7 @@ struct ref_filter {
const char **name_patterns; const char **name_patterns;
struct sha1_array points_at; struct sha1_array points_at;
struct commit_list *with_commit; struct commit_list *with_commit;
struct commit_list *no_commit;
enum { enum {
REF_FILTER_MERGED_NONE = 0, REF_FILTER_MERGED_NONE = 0,
......
#!/bin/sh #!/bin/sh
test_description='branch --contains <commit>, --merged, and --no-merged' test_description='branch --contains <commit>, --no-contains <commit> --merged, and --no-merged'
. ./test-lib.sh . ./test-lib.sh
...@@ -45,6 +45,22 @@ test_expect_success 'branch --contains master' ' ...@@ -45,6 +45,22 @@ test_expect_success 'branch --contains master' '
' '
test_expect_success 'branch --no-contains=master' '
git branch --no-contains=master >actual &&
>expect &&
test_cmp expect actual
'
test_expect_success 'branch --no-contains master' '
git branch --no-contains master >actual &&
>expect &&
test_cmp expect actual
'
test_expect_success 'branch --contains=side' ' test_expect_success 'branch --contains=side' '
git branch --contains=side >actual && git branch --contains=side >actual &&
...@@ -55,6 +71,16 @@ test_expect_success 'branch --contains=side' ' ...@@ -55,6 +71,16 @@ test_expect_success 'branch --contains=side' '
' '
test_expect_success 'branch --no-contains=side' '
git branch --no-contains=side >actual &&
{
echo " master"
} >expect &&
test_cmp expect actual
'
test_expect_success 'branch --contains with pattern implies --list' ' test_expect_success 'branch --contains with pattern implies --list' '
git branch --contains=master master >actual && git branch --contains=master master >actual &&
...@@ -65,6 +91,14 @@ test_expect_success 'branch --contains with pattern implies --list' ' ...@@ -65,6 +91,14 @@ test_expect_success 'branch --contains with pattern implies --list' '
' '
test_expect_success 'branch --no-contains with pattern implies --list' '
git branch --no-contains=master master >actual &&
>expect &&
test_cmp expect actual
'
test_expect_success 'side: branch --merged' ' test_expect_success 'side: branch --merged' '
git branch --merged >actual && git branch --merged >actual &&
...@@ -126,7 +160,9 @@ test_expect_success 'branch --no-merged with pattern implies --list' ' ...@@ -126,7 +160,9 @@ test_expect_success 'branch --no-merged with pattern implies --list' '
test_expect_success 'implicit --list conflicts with modification options' ' test_expect_success 'implicit --list conflicts with modification options' '
test_must_fail git branch --contains=master -d && test_must_fail git branch --contains=master -d &&
test_must_fail git branch --contains=master -m foo test_must_fail git branch --contains=master -m foo &&
test_must_fail git branch --no-contains=master -d &&
test_must_fail git branch --no-contains=master -m foo
' '
...@@ -136,7 +172,8 @@ test_expect_success 'Assert that --contains only works on commits, not trees & b ...@@ -136,7 +172,8 @@ test_expect_success 'Assert that --contains only works on commits, not trees & b
Some blob Some blob
EOF EOF
) && ) &&
test_must_fail git branch --contains $blob test_must_fail git branch --contains $blob &&
test_must_fail git branch --no-contains $blob
' '
# We want to set up a case where the walk for the tracking info # We want to set up a case where the walk for the tracking info
...@@ -168,4 +205,15 @@ test_expect_success 'branch --merged with --verbose' ' ...@@ -168,4 +205,15 @@ test_expect_success 'branch --merged with --verbose' '
test_i18ncmp expect actual test_i18ncmp expect actual
' '
test_expect_success 'branch --contains combined with --no-contains' '
git branch --contains zzz --no-contains topic >actual &&
cat >expect <<-\EOF &&
master
side
zzz
EOF
test_cmp expect actual
'
test_done test_done
...@@ -93,6 +93,22 @@ test_expect_success 'filtering with --contains' ' ...@@ -93,6 +93,22 @@ test_expect_success 'filtering with --contains' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_success 'filtering with --no-contains' '
cat >expect <<-\EOF &&
refs/tags/one
EOF
git for-each-ref --format="%(refname)" --no-contains=two >actual &&
test_cmp expect actual
'
test_expect_success 'filtering with --contains and --no-contains' '
cat >expect <<-\EOF &&
refs/tags/two
EOF
git for-each-ref --format="%(refname)" --contains=two --no-contains=three >actual &&
test_cmp expect actual
'
test_expect_success '%(color) must fail' ' test_expect_success '%(color) must fail' '
test_must_fail git for-each-ref --format="%(color)%(refname)" test_must_fail git for-each-ref --format="%(color)%(refname)"
' '
......
...@@ -1424,6 +1424,23 @@ test_expect_success 'checking that first commit is in all tags (relative)' " ...@@ -1424,6 +1424,23 @@ test_expect_success 'checking that first commit is in all tags (relative)' "
test_cmp expected actual test_cmp expected actual
" "
# All the --contains tests above, but with --no-contains
test_expect_success 'checking that first commit is not listed in any tag with --no-contains (hash)' "
>expected &&
git tag -l --no-contains $hash1 v* >actual &&
test_cmp expected actual
"
test_expect_success 'checking that first commit is in all tags (tag)' "
git tag -l --no-contains v1.0 v* >actual &&
test_cmp expected actual
"
test_expect_success 'checking that first commit is in all tags (relative)' "
git tag -l --no-contains HEAD~2 v* >actual &&
test_cmp expected actual
"
cat > expected <<EOF cat > expected <<EOF
v2.0 v2.0
EOF EOF
...@@ -1433,6 +1450,17 @@ test_expect_success 'checking that second commit only has one tag' " ...@@ -1433,6 +1450,17 @@ test_expect_success 'checking that second commit only has one tag' "
test_cmp expected actual test_cmp expected actual
" "
cat > expected <<EOF
v0.2.1
v1.0
v1.0.1
v1.1.3
EOF
test_expect_success 'inverse of the last test, with --no-contains' "
git tag -l --no-contains $hash2 v* >actual &&
test_cmp expected actual
"
cat > expected <<EOF cat > expected <<EOF
EOF EOF
...@@ -1442,6 +1470,19 @@ test_expect_success 'checking that third commit has no tags' " ...@@ -1442,6 +1470,19 @@ test_expect_success 'checking that third commit has no tags' "
test_cmp expected actual test_cmp expected actual
" "
cat > expected <<EOF
v0.2.1
v1.0
v1.0.1
v1.1.3
v2.0
EOF
test_expect_success 'conversely --no-contains on the third commit lists all tags' "
git tag -l --no-contains $hash3 v* >actual &&
test_cmp expected actual
"
# how about a simple merge? # how about a simple merge?
test_expect_success 'creating simple branch' ' test_expect_success 'creating simple branch' '
...@@ -1463,6 +1504,19 @@ test_expect_success 'checking that branch head only has one tag' " ...@@ -1463,6 +1504,19 @@ test_expect_success 'checking that branch head only has one tag' "
test_cmp expected actual test_cmp expected actual
" "
cat > expected <<EOF
v0.2.1
v1.0
v1.0.1
v1.1.3
v2.0
EOF
test_expect_success 'checking that branch head with --no-contains lists all but one tag' "
git tag -l --no-contains $hash4 v* >actual &&
test_cmp expected actual
"
test_expect_success 'merging original branch into this branch' ' test_expect_success 'merging original branch into this branch' '
git merge --strategy=ours master && git merge --strategy=ours master &&
git tag v4.0 git tag v4.0
...@@ -1477,6 +1531,20 @@ test_expect_success 'checking that original branch head has one tag now' " ...@@ -1477,6 +1531,20 @@ test_expect_success 'checking that original branch head has one tag now' "
test_cmp expected actual test_cmp expected actual
" "
cat > expected <<EOF
v0.2.1
v1.0
v1.0.1
v1.1.3
v2.0
v3.0
EOF
test_expect_success 'checking that original branch head with --no-contains lists all but one tag now' "
git tag -l --no-contains $hash3 v* >actual &&
test_cmp expected actual
"
cat > expected <<EOF cat > expected <<EOF
v0.2.1 v0.2.1
v1.0 v1.0
...@@ -1497,6 +1565,12 @@ test_expect_success 'checking that --contains can be used in non-list mode' ' ...@@ -1497,6 +1565,12 @@ test_expect_success 'checking that --contains can be used in non-list mode' '
test_cmp expected actual test_cmp expected actual
' '
test_expect_success 'checking that initial commit is in all tags with --no-contains' "
>expected &&
git tag -l --no-contains $hash1 v* >actual &&
test_cmp expected actual
"
# mixing modes and options: # mixing modes and options:
test_expect_success 'mixing incompatibles modes and options is forbidden' ' test_expect_success 'mixing incompatibles modes and options is forbidden' '
...@@ -1522,10 +1596,13 @@ test_expect_success 'mixing incompatibles modes and options is forbidden' ' ...@@ -1522,10 +1596,13 @@ test_expect_success 'mixing incompatibles modes and options is forbidden' '
test_must_fail git tag -l -F some file && test_must_fail git tag -l -F some file &&
test_must_fail git tag -v -s && test_must_fail git tag -v -s &&
test_must_fail git tag --contains tag-tree && test_must_fail git tag --contains tag-tree &&
test_must_fail git tag --contains tag-blob test_must_fail git tag --contains tag-blob &&
test_must_fail git tag --no-contains tag-tree &&
test_must_fail git tag --no-contains tag-blob &&
test_must_fail git tag --contains --no-contains
' '
for option in --contains --merged --no-merged --points-at for option in --contains --no-contains --merged --no-merged --points-at
do do
test_expect_success "mixing incompatible modes with $option is forbidden" " test_expect_success "mixing incompatible modes with $option is forbidden" "
test_must_fail git tag -d $option HEAD && test_must_fail git tag -d $option HEAD &&
...@@ -1792,7 +1869,7 @@ run_with_limited_stack () { ...@@ -1792,7 +1869,7 @@ run_with_limited_stack () {
test_lazy_prereq ULIMIT_STACK_SIZE 'run_with_limited_stack true' test_lazy_prereq ULIMIT_STACK_SIZE 'run_with_limited_stack true'
# we require ulimit, this excludes Windows # we require ulimit, this excludes Windows
test_expect_success ULIMIT_STACK_SIZE '--contains works in a deep repo' ' test_expect_success ULIMIT_STACK_SIZE '--contains and --no-contains work in a deep repo' '
>expect && >expect &&
i=1 && i=1 &&
while test $i -lt 8000 while test $i -lt 8000
...@@ -1808,7 +1885,9 @@ EOF" ...@@ -1808,7 +1885,9 @@ EOF"
git checkout master && git checkout master &&
git tag far-far-away HEAD^ && git tag far-far-away HEAD^ &&
run_with_limited_stack git tag --contains HEAD >actual && run_with_limited_stack git tag --contains HEAD >actual &&
test_cmp expect actual test_cmp expect actual &&
run_with_limited_stack git tag --no-contains HEAD >actual &&
test_line_count ">" 10 actual
' '
test_expect_success '--format should list tags as per format given' ' test_expect_success '--format should list tags as per format given' '
...@@ -1870,4 +1949,47 @@ test_expect_success 'ambiguous branch/tags not marked' ' ...@@ -1870,4 +1949,47 @@ test_expect_success 'ambiguous branch/tags not marked' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_success '--contains combined with --no-contains' '
(
git init no-contains &&
cd no-contains &&
test_commit v0.1 &&
test_commit v0.2 &&
test_commit v0.3 &&
test_commit v0.4 &&
test_commit v0.5 &&
cat >expected <<-\EOF &&
v0.2
v0.3
v0.4
EOF
git tag --contains v0.2 --no-contains v0.5 >actual &&
test_cmp expected actual
)
'
# As the docs say, list tags which contain a specified *commit*. We
# don't recurse down to tags for trees or blobs pointed to by *those*
# commits.
test_expect_success 'Does --[no-]contains stop at commits? Yes!' '
cd no-contains &&
blob=$(git rev-parse v0.3:v0.3.t) &&
tree=$(git rev-parse v0.3^{tree}) &&
git tag tag-blob $blob &&
git tag tag-tree $tree &&
git tag --contains v0.3 >actual &&
cat >expected <<-\EOF &&
v0.3
v0.4
v0.5
EOF
test_cmp expected actual &&
git tag --no-contains v0.3 >actual &&
cat >expected <<-\EOF &&
v0.1
v0.2
EOF
test_cmp expected actual
'
test_done test_done
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册