From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS31976 209.132.180.0/23 X-Spam-Status: No, score=-3.5 required=3.0 tests=AWL,BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,RCVD_IN_DNSWL_HI,RCVD_IN_SORBS_SPAM, RP_MATCHES_RCVD shortcircuit=no autolearn=no autolearn_force=no version=3.4.0 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by dcvr.yhbt.net (Postfix) with ESMTP id 5FDE1202F8 for ; Thu, 9 Mar 2017 20:03:32 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1750945AbdCIUDa (ORCPT ); Thu, 9 Mar 2017 15:03:30 -0500 Received: from mail-wm0-f46.google.com ([74.125.82.46]:37867 "EHLO mail-wm0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750819AbdCIUD3 (ORCPT ); Thu, 9 Mar 2017 15:03:29 -0500 Received: by mail-wm0-f46.google.com with SMTP id n11so64670014wma.0 for ; Thu, 09 Mar 2017 12:03:27 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=zs0R+sLp/tIzQikn0027NhjmlzRuHDPaPR3CbgM/mGY=; b=s9H/Jtn5HHWQeG8qWA/MmcYm51NLOXaa+LUBfurxrQsyvOML/dTByaHfDCsYYmtgtK 6hva35CGfZOx1ugwNkeNFxZNFL+eyAvYPaw39G90PNe8RK9eUuN2DZk9WwBriWKHUHuT wEfSb2A0daTDZUyGryzK4ZIyBwjVRJGsPUKtPm8+8Rs08pWR2ibrd9H44JcVpALD6ynf dfeBtbHFoMMVvTO3ju239QbP1dvKryMbohPdmoZewCDHJVEqUhBP3uUo8uHyloV/rEd8 T/hR5phRaZfGmMGCcrtegehQgaBNbUhw1ZxMhUuojzCJtgb2FXXb1epovby2awtoVVbx MVeA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=zs0R+sLp/tIzQikn0027NhjmlzRuHDPaPR3CbgM/mGY=; b=SLCoH5QMKcx+ByXzaYAX3a4u7jOlyeM+T9pTeVx7r52qxGEDNjghDbaVU03t0B3eav 4KyzWmNYeKj6pJrFP93NhRzF0qaqkUl7au6+CXW5+yXdDTpK5fWLH3bfJ+kFuQ3kZM0v /nVJ8y3u2tuH8PPvLKl+668K18IAiCUKORXSbgzep0nAXJTkNgSK21OxrUe73mOWUXzd xrXureI6Enier7aMjv/x6huu45UC6o0fTMiW9gm6tl5bGsN6QaAIkxq9OLISsRKt9Hvt X3peOl1Sbio1wRFRTesw6Dq/CC7rlHuVDfpPDdrTh4kvjsXUf27LPC74Z9Sub0ex07qM PQxw== X-Gm-Message-State: AMke39kucCcLgLqP3bx0/+9IMZuY8Pj4mwrtQKhcMC7+G282xLCuV2ip8gD+cP589ZkNRw== X-Received: by 10.28.227.213 with SMTP id a204mr11246155wmh.120.1489089806663; Thu, 09 Mar 2017 12:03:26 -0800 (PST) Received: from u.nix.is ([2a01:4f8:190:5095::2]) by smtp.gmail.com with ESMTPSA id 94sm9421190wrl.50.2017.03.09.12.03.25 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Thu, 09 Mar 2017 12:03:25 -0800 (PST) From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= To: git@vger.kernel.org Cc: Junio C Hamano , Lars Hjemli , Jeff King , Christian Couder , =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Subject: [PATCH v2] ref-filter: Add --no-contains option to tag/branch/for-each-ref Date: Thu, 9 Mar 2017 20:02:43 +0000 Message-Id: <20170309200243.2203-1-avarab@gmail.com> X-Mailer: git-send-email 2.11.0 In-Reply-To: <20170309125132.tubwxtneffok4nrc@sigill.intra.peff.net> References: <20170309125132.tubwxtneffok4nrc@sigill.intra.peff.net> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org Change the tag, branch & for-each-ref commands to have a --no-contains option in addition to their longstanding --contains options. The use-case I have for this is to find the last-good rollout tag given a known-bad . Right now, given a hypothetically bad commit v2.10.1-3-gcf5c7253e0, you can find which git version to revert to with this hacky two-liner: (./git tag -l 'v[0-9]*'; ./git tag -l 'v[0-9]*' --contains v2.10.1-3-gcf5c7253e0) \ |sort|uniq -c|grep -E '^ *1 '|awk '{print $2}' | tail -n 10 But with the --no-contains option you can now get the exact same output with: ./git tag -l 'v[0-9]*' --no-contains v2.10.1-3-gcf5c7253e0|sort|tail -n 10 The filtering machinery is generic between the tag, branch & for-each-ref commands, so once I'd implemented it for tag it was trivial to add support for this to the other two. A practical use for this with "branch" is e.g. finding branches which diverged between 2.8 & 2.10: ./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. I don't see how a --no-contains option for "describe" would make any sense, other than being exactly equivalent to not supplying --contains at all, which would be confusing at best. I'm adding a --without option to "tag" as an alias for --no-contains for consistency with --with and --contains. Since we don't even document --with anymore (or test it). The --with option is undocumented, and possibly the only user of it is Junio[1]. But it's trivial to support, so let's do that. Where I'm changing existing documentation lines I'm mainly word wrapping at 75 columns to be consistent with the existing style. The changes to Documentation/ are much smaller with: git show --word-diff, same for the minor change to git-completion.bash. Most of the test changes I've made are just doing the inverse of the existing --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 I've added tests for --contains in combination with --no-contains for all three commands, and 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. When --contains and --no-contains are provided to "tag" we disable the optimized code to find tags added in v1.7.2-rc1-1-gffc4b8012d. That code changes flags on commit objects as an optimization, and thus can't be called twice. Jeff King has a WIP patch to fix that[2], but rather than try to incorporate that into this already big patch I'm just disabling the optimized codepath. Using both --contains and --no-contains will most likely be rare, and we can get an initial correct version working & optimize it later. It's possible that the --merge & --no-merge codepaths for "tag" have a similar bug, but as of writing I can't produce any evidence of that via a brute-force test script[3]. 1. 2. <20170309125132.tubwxtneffok4nrc@sigill.intra.peff.net> 3. Signed-off-by: Ævar Arnfjörð Bjarmason --- Changes since v1: * Now git-for-each-ref has --no-contains * Doc fixes, happy with the NOTES section in git-branch.txt now. * Now tag --contains works with --no-contains, but falls back on the much slower pre-1.8 era code pending efforts to make commit_contains() re-callable. * Now `git tag --no-contains HEAD hi` dies, just like `git tag --contains HEAD hi` does. * Fixed up my shitty C in ref-filter.c as Peff suggested. * Tests for --contains combined with --no-contains for all the commands. * Rewrote the very verbose t7004-tag.sh tag test Peff complied about. Documentation/git-branch.txt | 24 ++++--- Documentation/git-for-each-ref.txt | 6 +- Documentation/git-tag.txt | 6 +- builtin/branch.c | 4 +- builtin/for-each-ref.c | 1 + builtin/tag.c | 15 +++- contrib/completion/git-completion.bash | 9 +-- parse-options.h | 4 +- ref-filter.c | 16 +++-- ref-filter.h | 1 + t/t3201-branch-contains.sh | 51 +++++++++++++- t/t6302-for-each-ref-filter.sh | 16 +++++ t/t7004-tag.sh | 123 ++++++++++++++++++++++++++++++++- 13 files changed, 249 insertions(+), 27 deletions(-) diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 092f1bcf9f..28a36a8a0a 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -11,7 +11,7 @@ SYNOPSIS 'git branch' [--color[=] | --no-color] [-r | -a] [--list] [-v [--abbrev= | --no-abbrev]] [--column[=] | --no-column] - [(--merged | --no-merged | --contains) []] [--sort=] + [(--merged | --no-merged | --contains | --no-contains) []] [--sort=] [--points-at ] [--format=] [...] 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] [] 'git branch' (--set-upstream-to= | -u ) [] @@ -35,11 +35,12 @@ as branch creation. With `--contains`, shows only the branches that contain the named commit (in other words, the branches whose tip commits are descendants of the -named commit). With `--merged`, only branches merged into 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 -the named commit will be listed. If the argument is missing it -defaults to `HEAD` (i.e. the tip of the current branch). +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) will be listed. With `--no-merged` only +branches not merged into the named commit will be listed. If the +argument is missing it defaults to `HEAD` (i.e. the tip of the current +branch). The command's second form creates a new branch head named which points to the current `HEAD`, or if given. @@ -213,6 +214,10 @@ start-point is either a local or remote-tracking branch. Only list branches which contain the specified commit (HEAD if not specified). Implies `--list`. +--no-contains []:: + Only list branches which don't contain the specified commit + (HEAD if not specified). Implies `--list`. + --merged []:: Only list branches whose tips are reachable from the specified commit (HEAD if not specified). Implies `--list`. @@ -296,13 +301,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 a branch and check it out with a single command. -The options `--contains`, `--merged` and `--no-merged` serve three related -but different purposes: +The options `--contains`, `--no-contains`, `--merged` and `--no-merged` +serve four related but different purposes: - `--contains ` is used to find all branches which will need special attention if were to be rebased or amended, since those branches contain the specified . +- `--no-contains ` is the inverse of that, i.e. branches that don't + contain the specified . + - `--merged` is used to find all branches which can be safely deleted, since those branches are fully contained by HEAD. diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 111e1be6f5..83b93c75a8 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -11,7 +11,7 @@ SYNOPSIS 'git for-each-ref' [--count=] [--shell|--perl|--python|--tcl] [(--sort=)...] [--format=] [...] [--points-at ] [(--merged | --no-merged) []] - [--contains []] + [(--contains | --no-contains) []] DESCRIPTION ----------- @@ -79,6 +79,10 @@ OPTIONS Only list refs which contain the specified commit (HEAD if not specified). +--no-contains []:: + Only list refs which don't contain the specified commit (HEAD + if not specified). + --ignore-case:: Sorting and filtering refs are case insensitive. diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 525737a5d8..4938496194 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -12,7 +12,7 @@ SYNOPSIS 'git tag' [-a | -s | -u ] [-f] [-m | -F ] [ | ] 'git tag' -d ... -'git tag' [-n[]] -l [--contains ] [--points-at ] +'git tag' [-n[]] -l [--[no-]contains ] [--points-at ] [--column[=] | --no-column] [--create-reflog] [--sort=] [--format=] [--[no-]merged []] [...] 'git tag' -v [--format=] ... @@ -124,6 +124,10 @@ This option is only applicable when listing tags without annotation lines. Only list tags which contain the specified commit (HEAD if not specified). +--no-contains []:: + Only list tags which don't contain the specified commit (HEAD if + not secified). + --points-at :: Only list tags of the given object. diff --git a/builtin/branch.c b/builtin/branch.c index 94f7de7fa5..e8d534604c 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -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"), FILTER_REFS_REMOTES), 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_WITHOUT(&filter.with_commit, N_("print only branches that don't contain the commit")), OPT__ABBREV(&filter.abbrev), OPT_GROUP(N_("Specific git-branch actions:")), @@ -604,7 +606,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0) list = 1; - if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr) + if (filter.with_commit || filter.no_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr) list = 1; if (!!delete + !!rename + !!new_upstream + diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index df41fa0350..b1ae2388e6 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -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_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_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_END(), }; diff --git a/builtin/tag.c b/builtin/tag.c index ad29be6923..d83674e3e6 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -53,7 +53,16 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con } verify_ref_format(format); - filter->with_commit_tag_algo = 1; + if (filter->with_commit && filter->no_commit) + /* Due to the way the contains_tag_algo() function + touches object.flags we can only call it once + per-process. + + For now sacrifice performance for correctness when + both --contains and --no-contains are provided */ + filter->with_commit_tag_algo = 0; + else + filter->with_commit_tag_algo = 1; filter_refs(&array, filter, FILTER_REFS_TAGS); ref_array_sort(sorting, &array); @@ -424,7 +433,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_GROUP(N_("Tag listing options")), 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_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_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_NO_MERGED(&filter, N_("print only tags that are not merged")), OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), @@ -488,6 +499,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) die(_("-n option is only allowed with -l.")); if (filter.with_commit) die(_("--contains option is only allowed with -l.")); + if (filter.no_commit) + die(_("--no-contains option is only allowed with -l.")); if (filter.points_at.nr) die(_("--points-at option is only allowed with -l.")); if (filter.merge_commit) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index fc32286a43..fa3da49478 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1093,9 +1093,9 @@ _git_branch () --*) __gitcomp " --color --no-color --verbose --abbrev= --no-abbrev - --track --no-track --contains --merged --no-merged - --set-upstream-to= --edit-description --list - --unset-upstream --delete --move --remotes + --track --no-track --contains --no-contains --merged + --no-merged --set-upstream-to= --edit-description + --list --unset-upstream --delete --move --remotes --column --no-column --sort= --points-at " ;; @@ -2862,7 +2862,8 @@ _git_tag () __gitcomp " --list --delete --verify --annotate --message --file --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 diff --git a/parse-options.h b/parse-options.h index dcd8a0926c..0eac90b510 100644 --- a/parse-options.h +++ b/parse-options.h @@ -258,7 +258,9 @@ extern int parse_opt_passthru_argv(const struct option *, const char *, int); PARSE_OPT_LASTARG_DEFAULT | flag, \ parse_opt_commits, (intptr_t) "HEAD" \ } -#define OPT_CONTAINS(v, h) _OPT_CONTAINS_OR_WITH("contains", v, h, 0) +#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) +#define OPT_WITHOUT(v, h) _OPT_CONTAINS_OR_WITH("without", v, h, PARSE_OPT_HIDDEN) #endif diff --git a/ref-filter.c b/ref-filter.c index 1ec0fb8391..b6dab75729 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -1571,11 +1571,11 @@ static enum contains_result contains_tag_algo(struct commit *candidate, return contains_test(candidate, want); } -static int commit_contains(struct ref_filter *filter, struct commit *commit) +static int commit_contains(struct ref_filter *filter, struct commit_list *list, struct commit *commit) { if (filter->with_commit_tag_algo) - return contains_tag_algo(commit, filter->with_commit); - return is_descendant_of(commit, filter->with_commit); + return contains_tag_algo(commit, list); + return is_descendant_of(commit, list); } /* @@ -1765,13 +1765,17 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, * obtain the commit using the 'oid' available and discard all * 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); if (!commit) return 0; - /* We perform the filtering for the '--contains' option */ + /* We perform the filtering for the '--contains' option... */ if (filter->with_commit && - !commit_contains(filter, commit)) + !commit_contains(filter, filter->with_commit, commit)) + return 0; + /* ...or for the `--no-contains' option */ + if (filter->no_commit && + commit_contains(filter, filter->no_commit, commit)) return 0; } diff --git a/ref-filter.h b/ref-filter.h index 154e24c405..af85eb4592 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -53,6 +53,7 @@ struct ref_filter { const char **name_patterns; struct sha1_array points_at; struct commit_list *with_commit; + struct commit_list *no_commit; enum { REF_FILTER_MERGED_NONE = 0, diff --git a/t/t3201-branch-contains.sh b/t/t3201-branch-contains.sh index 7f3ec47241..506415dbd3 100755 --- a/t/t3201-branch-contains.sh +++ b/t/t3201-branch-contains.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='branch --contains , --merged, and --no-merged' +test_description='branch --contains , --no-contains --merged, and --no-merged' . ./test-lib.sh @@ -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' ' git branch --contains=side >actual && @@ -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' ' git branch --contains=master master >actual && @@ -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' ' git branch --merged >actual && @@ -126,7 +160,9 @@ test_expect_success 'branch --no-merged with pattern implies --list' ' test_expect_success 'implicit --list conflicts with modification options' ' 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 ' @@ -159,4 +195,15 @@ test_expect_success 'branch --merged with --verbose' ' 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 diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index a09a1a46ef..4902ba5f16 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -93,6 +93,22 @@ test_expect_success 'filtering with --contains' ' 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_must_fail git for-each-ref --format="%(color)%(refname)" ' diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index b4698ab5f5..8ad5719962 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1385,6 +1385,23 @@ test_expect_success 'checking that first commit is in all tags (relative)' " 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 < expected <actual && + test_cmp expected actual +" cat > expected < expected <actual && + test_cmp expected actual +" + # how about a simple merge? test_expect_success 'creating simple branch' ' @@ -1424,6 +1465,19 @@ test_expect_success 'checking that branch head only has one tag' " test_cmp expected actual " +cat > expected <actual && + test_cmp expected actual +" + test_expect_success 'merging original branch into this branch' ' git merge --strategy=ours master && git tag v4.0 @@ -1445,6 +1499,20 @@ 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 <expected && + git tag -l --no-contains $hash1 v* >actual && + test_cmp expected actual +" + # mixing modes and options: test_expect_success 'mixing incompatibles modes and options is forbidden' ' @@ -1709,7 +1783,7 @@ run_with_limited_stack () { test_lazy_prereq ULIMIT_STACK_SIZE 'run_with_limited_stack true' # 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 && i=1 && while test $i -lt 8000 @@ -1725,7 +1799,9 @@ EOF" git checkout master && git tag far-far-away HEAD^ && 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 ">" 70 actual ' test_expect_success '--format should list tags as per format given' ' @@ -1773,4 +1849,47 @@ test_expect_success 'ambiguous branch/tags not marked' ' 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 -- 2.11.0