diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt index 36e81b9dec..ba75747005 100644 --- a/Documentation/git-show-ref.txt +++ b/Documentation/git-show-ref.txt @@ -8,10 +8,14 @@ git-show-ref - List references in a local repository SYNOPSIS -------- [verse] -'git show-ref' [-q | --quiet] [--verify] [--head] [-d | --dereference] +'git show-ref' [--head] [-d | --dereference] [-s | --hash[=]] [--abbrev[=]] [--tags] [--heads] [--] [...] +'git show-ref' --verify [-q | --quiet] [-d | --dereference] + [-s | --hash[=]] [--abbrev[=]] + [--] [...] 'git show-ref' --exclude-existing[=] +'git show-ref' --exists DESCRIPTION ----------- @@ -27,6 +31,10 @@ The `--exclude-existing` form is a filter that does the inverse. It reads refs from stdin, one ref per line, and shows those that don't exist in the local repository. +The `--exists` form can be used to check for the existence of a single +references. This form does not verify whether the reference resolves to an +actual object. + Use of this utility is encouraged in favor of directly accessing files under the `.git` directory. @@ -62,6 +70,12 @@ OPTIONS Aside from returning an error code of 1, it will also print an error message if `--quiet` was not specified. +--exists:: + + Check whether the given reference exists. Returns an exit code of 0 if + it does, 2 if it is missing, and 1 in case looking up the reference + failed with an error other than the reference being missing. + --abbrev[=]:: Abbreviate the object name. When using `--hash`, you do @@ -70,8 +84,8 @@ OPTIONS -q:: --quiet:: - Do not print any results to stdout. When combined with `--verify`, this - can be used to silently check if a reference exists. + Do not print any results to stdout. Can be used with `--verify` to + silently check if a reference exists. --exclude-existing[=]:: diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 5110814f79..7aac525a87 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -2,7 +2,7 @@ #include "config.h" #include "gettext.h" #include "hex.h" -#include "refs.h" +#include "refs/refs-internal.h" #include "object-name.h" #include "object-store-ll.h" #include "object.h" @@ -11,19 +11,26 @@ #include "parse-options.h" static const char * const show_ref_usage[] = { - N_("git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference]\n" + N_("git show-ref [--head] [-d | --dereference]\n" " [-s | --hash[=]] [--abbrev[=]] [--tags]\n" " [--heads] [--] [...]"), + N_("git show-ref --verify [-q | --quiet] [-d | --dereference]\n" + " [-s | --hash[=]] [--abbrev[=]]\n" + " [--] [...]"), N_("git show-ref --exclude-existing[=]"), + N_("git show-ref --exists "), NULL }; -static int deref_tags, show_head, tags_only, heads_only, found_match, verify, - quiet, hash_only, abbrev, exclude_arg; -static const char **pattern; -static const char *exclude_existing_arg; +struct show_one_options { + int quiet; + int hash_only; + int abbrev; + int deref_tags; +}; -static void show_one(const char *refname, const struct object_id *oid) +static void show_one(const struct show_one_options *opts, + const char *refname, const struct object_id *oid) { const char *hex; struct object_id peeled; @@ -32,33 +39,42 @@ static void show_one(const char *refname, const struct object_id *oid) die("git show-ref: bad ref %s (%s)", refname, oid_to_hex(oid)); - if (quiet) + if (opts->quiet) return; - hex = repo_find_unique_abbrev(the_repository, oid, abbrev); - if (hash_only) + hex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev); + if (opts->hash_only) printf("%s\n", hex); else printf("%s %s\n", hex, refname); - if (!deref_tags) + if (!opts->deref_tags) return; if (!peel_iterated_oid(oid, &peeled)) { - hex = repo_find_unique_abbrev(the_repository, &peeled, abbrev); + hex = repo_find_unique_abbrev(the_repository, &peeled, opts->abbrev); printf("%s %s^{}\n", hex, refname); } } +struct show_ref_data { + const struct show_one_options *show_one_opts; + const char **patterns; + int found_match; + int show_head; +}; + static int show_ref(const char *refname, const struct object_id *oid, - int flag UNUSED, void *cbdata UNUSED) + int flag UNUSED, void *cbdata) { - if (show_head && !strcmp(refname, "HEAD")) + struct show_ref_data *data = cbdata; + + if (data->show_head && !strcmp(refname, "HEAD")) goto match; - if (pattern) { + if (data->patterns) { int reflen = strlen(refname); - const char **p = pattern, *m; + const char **p = data->patterns, *m; while ((m = *p++) != NULL) { int len = strlen(m); if (len > reflen) @@ -74,9 +90,9 @@ static int show_ref(const char *refname, const struct object_id *oid, } match: - found_match++; + data->found_match++; - show_one(refname, oid); + show_one(data->show_one_opts, refname, oid); return 0; } @@ -90,6 +106,15 @@ static int add_existing(const char *refname, return 0; } +struct exclude_existing_options { + /* + * We need an explicit `enabled` field because it is perfectly valid + * for `pattern` to be `NULL` even if `--exclude-existing` was given. + */ + int enabled; + const char *pattern; +}; + /* * read "^(?:\s)?(?:\^\{\})?$" from the standard input, * and @@ -99,11 +124,11 @@ static int add_existing(const char *refname, * (4) ignore if refname is a ref that exists in the local repository; * (5) otherwise output the line. */ -static int exclude_existing(const char *match) +static int cmd_show_ref__exclude_existing(const struct exclude_existing_options *opts) { - static struct string_list existing_refs = STRING_LIST_INIT_DUP; + struct string_list existing_refs = STRING_LIST_INIT_DUP; char buf[1024]; - int matchlen = match ? strlen(match) : 0; + int patternlen = opts->pattern ? strlen(opts->pattern) : 0; for_each_ref(add_existing, &existing_refs); while (fgets(buf, sizeof(buf), stdin)) { @@ -119,11 +144,11 @@ static int exclude_existing(const char *match) for (ref = buf + len; buf < ref; ref--) if (isspace(ref[-1])) break; - if (match) { + if (opts->pattern) { int reflen = buf + len - ref; - if (reflen < matchlen) + if (reflen < patternlen) continue; - if (strncmp(ref, match, matchlen)) + if (strncmp(ref, opts->pattern, patternlen)) continue; } if (check_refname_format(ref, 0)) { @@ -134,97 +159,172 @@ static int exclude_existing(const char *match) printf("%s\n", buf); } } + + string_list_clear(&existing_refs, 0); return 0; } +static int cmd_show_ref__verify(const struct show_one_options *show_one_opts, + const char **refs) +{ + if (!refs || !*refs) + die("--verify requires a reference"); + + while (*refs) { + struct object_id oid; + + if ((starts_with(*refs, "refs/") || !strcmp(*refs, "HEAD")) && + !read_ref(*refs, &oid)) { + show_one(show_one_opts, *refs, &oid); + } + else if (!show_one_opts->quiet) + die("'%s' - not a valid ref", *refs); + else + return 1; + refs++; + } + + return 0; +} + +struct patterns_options { + int show_head; + int heads_only; + int tags_only; +}; + +static int cmd_show_ref__patterns(const struct patterns_options *opts, + const struct show_one_options *show_one_opts, + const char **patterns) +{ + struct show_ref_data show_ref_data = { + .show_one_opts = show_one_opts, + .show_head = opts->show_head, + }; + + if (patterns && *patterns) + show_ref_data.patterns = patterns; + + if (opts->show_head) + head_ref(show_ref, &show_ref_data); + if (opts->heads_only || opts->tags_only) { + if (opts->heads_only) + for_each_fullref_in("refs/heads/", show_ref, &show_ref_data); + if (opts->tags_only) + for_each_fullref_in("refs/tags/", show_ref, &show_ref_data); + } else { + for_each_ref(show_ref, &show_ref_data); + } + if (!show_ref_data.found_match) + return 1; + + return 0; +} + +static int cmd_show_ref__exists(const char **refs) +{ + struct strbuf unused_referent = STRBUF_INIT; + struct object_id unused_oid; + unsigned int unused_type; + int failure_errno = 0; + const char *ref; + int ret = 0; + + if (!refs || !*refs) + die("--exists requires a reference"); + ref = *refs++; + if (*refs) + die("--exists requires exactly one reference"); + + if (refs_read_raw_ref(get_main_ref_store(the_repository), ref, + &unused_oid, &unused_referent, &unused_type, + &failure_errno)) { + if (failure_errno == ENOENT) { + error(_("reference does not exist")); + ret = 2; + } else { + errno = failure_errno; + error_errno(_("failed to look up reference")); + ret = 1; + } + + goto out; + } + +out: + strbuf_release(&unused_referent); + return ret; +} + static int hash_callback(const struct option *opt, const char *arg, int unset) { - hash_only = 1; + struct show_one_options *opts = opt->value; + struct option abbrev_opt = *opt; + + opts->hash_only = 1; /* Use full length SHA1 if no argument */ if (!arg) return 0; - return parse_opt_abbrev_cb(opt, arg, unset); + + abbrev_opt.value = &opts->abbrev; + return parse_opt_abbrev_cb(&abbrev_opt, arg, unset); } static int exclude_existing_callback(const struct option *opt, const char *arg, int unset) { + struct exclude_existing_options *opts = opt->value; BUG_ON_OPT_NEG(unset); - exclude_arg = 1; - *(const char **)opt->value = arg; + opts->enabled = 1; + opts->pattern = arg; return 0; } -static const struct option show_ref_options[] = { - OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")), - OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")), - OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, " - "requires exact ref path")), - OPT_HIDDEN_BOOL('h', NULL, &show_head, - N_("show the HEAD reference, even if it would be filtered out")), - OPT_BOOL(0, "head", &show_head, - N_("show the HEAD reference, even if it would be filtered out")), - OPT_BOOL('d', "dereference", &deref_tags, - N_("dereference tags into object IDs")), - OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"), - N_("only show SHA1 hash using digits"), - PARSE_OPT_OPTARG, &hash_callback), - OPT__ABBREV(&abbrev), - OPT__QUIET(&quiet, - N_("do not print results to stdout (useful with --verify)")), - OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_arg, - N_("pattern"), N_("show refs from stdin that aren't in local repository"), - PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback), - OPT_END() -}; - int cmd_show_ref(int argc, const char **argv, const char *prefix) { + struct exclude_existing_options exclude_existing_opts = {0}; + struct patterns_options patterns_opts = {0}; + struct show_one_options show_one_opts = {0}; + int verify = 0, exists = 0; + const struct option show_ref_options[] = { + OPT_BOOL(0, "tags", &patterns_opts.tags_only, N_("only show tags (can be combined with heads)")), + OPT_BOOL(0, "heads", &patterns_opts.heads_only, N_("only show heads (can be combined with tags)")), + OPT_BOOL(0, "exists", &exists, N_("check for reference existence without resolving")), + OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, " + "requires exact ref path")), + OPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head, + N_("show the HEAD reference, even if it would be filtered out")), + OPT_BOOL(0, "head", &patterns_opts.show_head, + N_("show the HEAD reference, even if it would be filtered out")), + OPT_BOOL('d', "dereference", &show_one_opts.deref_tags, + N_("dereference tags into object IDs")), + OPT_CALLBACK_F('s', "hash", &show_one_opts, N_("n"), + N_("only show SHA1 hash using digits"), + PARSE_OPT_OPTARG, &hash_callback), + OPT__ABBREV(&show_one_opts.abbrev), + OPT__QUIET(&show_one_opts.quiet, + N_("do not print results to stdout (useful with --verify)")), + OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_opts, + N_("pattern"), N_("show refs from stdin that aren't in local repository"), + PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback), + OPT_END() + }; + git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, show_ref_options, show_ref_usage, 0); - if (exclude_arg) - return exclude_existing(exclude_existing_arg); + if ((!!exclude_existing_opts.enabled + !!verify + !!exists) > 1) + die(_("only one of '%s', '%s' or '%s' can be given"), + "--exclude-existing", "--verify", "--exists"); - pattern = argv; - if (!*pattern) - pattern = NULL; - - if (verify) { - if (!pattern) - die("--verify requires a reference"); - while (*pattern) { - struct object_id oid; - - if ((starts_with(*pattern, "refs/") || !strcmp(*pattern, "HEAD")) && - !read_ref(*pattern, &oid)) { - show_one(*pattern, &oid); - } - else if (!quiet) - die("'%s' - not a valid ref", *pattern); - else - return 1; - pattern++; - } - return 0; - } - - if (show_head) - head_ref(show_ref, NULL); - if (heads_only || tags_only) { - if (heads_only) - for_each_fullref_in("refs/heads/", show_ref, NULL); - if (tags_only) - for_each_fullref_in("refs/tags/", show_ref, NULL); - } else { - for_each_ref(show_ref, NULL); - } - if (!found_match) { - if (verify && !quiet) - die("No match"); - return 1; - } - return 0; + if (exclude_existing_opts.enabled) + return cmd_show_ref__exclude_existing(&exclude_existing_opts); + else if (verify) + return cmd_show_ref__verify(&show_one_opts, argv); + else if (exists) + return cmd_show_ref__exists(argv); + else + return cmd_show_ref__patterns(&patterns_opts, &show_one_opts, argv); } diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh index 9252a581ab..b50ae6fcf1 100755 --- a/t/t1403-show-ref.sh +++ b/t/t1403-show-ref.sh @@ -196,4 +196,74 @@ test_expect_success 'show-ref --verify with dangling ref' ' ) ' +test_expect_success 'show-ref sub-modes are mutually exclusive' ' + cat >expect <<-EOF && + fatal: only one of ${SQ}--exclude-existing${SQ}, ${SQ}--verify${SQ} or ${SQ}--exists${SQ} can be given + EOF + + test_must_fail git show-ref --verify --exclude-existing 2>err && + test_cmp expect err && + + test_must_fail git show-ref --verify --exists 2>err && + test_cmp expect err && + + test_must_fail git show-ref --exclude-existing --exists 2>err && + test_cmp expect err +' + +test_expect_success '--exists with existing reference' ' + git show-ref --exists refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +' + +test_expect_success '--exists with missing reference' ' + test_expect_code 2 git show-ref --exists refs/heads/does-not-exist +' + +test_expect_success '--exists does not use DWIM' ' + test_expect_code 2 git show-ref --exists $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 2>err && + grep "reference does not exist" err +' + +test_expect_success '--exists with HEAD' ' + git show-ref --exists HEAD +' + +test_expect_success '--exists with bad reference name' ' + test_when_finished "git update-ref -d refs/heads/bad...name" && + new_oid=$(git rev-parse HEAD) && + test-tool ref-store main update-ref msg refs/heads/bad...name $new_oid $ZERO_OID REF_SKIP_REFNAME_VERIFICATION && + git show-ref --exists refs/heads/bad...name +' + +test_expect_success '--exists with arbitrary symref' ' + test_when_finished "git symbolic-ref -d refs/symref" && + git symbolic-ref refs/symref refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME && + git show-ref --exists refs/symref +' + +test_expect_success '--exists with dangling symref' ' + test_when_finished "git symbolic-ref -d refs/heads/dangling" && + git symbolic-ref refs/heads/dangling refs/heads/does-not-exist && + git show-ref --exists refs/heads/dangling +' + +test_expect_success '--exists with nonexistent object ID' ' + test-tool ref-store main update-ref msg refs/heads/missing-oid $(test_oid 001) $ZERO_OID REF_SKIP_OID_VERIFICATION && + git show-ref --exists refs/heads/missing-oid +' + +test_expect_success '--exists with non-commit object' ' + tree_oid=$(git rev-parse HEAD^{tree}) && + test-tool ref-store main update-ref msg refs/heads/tree ${tree_oid} $ZERO_OID REF_SKIP_OID_VERIFICATION && + git show-ref --exists refs/heads/tree +' + +test_expect_success '--exists with directory fails with generic error' ' + cat >expect <<-EOF && + error: failed to look up reference: Is a directory + EOF + test_expect_code 1 git show-ref --exists refs/heads 2>err && + test_cmp expect err +' + test_done diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh index ff1c967d55..7b7d6953c6 100755 --- a/t/t1430-bad-ref-name.sh +++ b/t/t1430-bad-ref-name.sh @@ -205,8 +205,9 @@ test_expect_success 'update-ref --no-deref -d can delete symref to broken name' test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref" && test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg && test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" && + test_ref_exists refs/heads/badname && git update-ref --no-deref -d refs/heads/badname >output 2>error && - test_path_is_missing .git/refs/heads/badname && + test_ref_missing refs/heads/badname && test_must_be_empty output && test_must_be_empty error ' @@ -216,8 +217,9 @@ test_expect_success 'branch -d can delete symref to broken name' ' test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref" && test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg && test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" && + test_ref_exists refs/heads/badname && git branch -d badname >output 2>error && - test_path_is_missing .git/refs/heads/badname && + test_ref_missing refs/heads/badname && test_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output && test_must_be_empty error ' @@ -225,8 +227,9 @@ test_expect_success 'branch -d can delete symref to broken name' ' test_expect_success 'update-ref --no-deref -d can delete dangling symref to broken name' ' test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg && test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" && + test_ref_exists refs/heads/badname && git update-ref --no-deref -d refs/heads/badname >output 2>error && - test_path_is_missing .git/refs/heads/badname && + test_ref_missing refs/heads/badname && test_must_be_empty output && test_must_be_empty error ' @@ -234,8 +237,9 @@ test_expect_success 'update-ref --no-deref -d can delete dangling symref to brok test_expect_success 'branch -d can delete dangling symref to broken name' ' test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg && test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" && + test_ref_exists refs/heads/badname && git branch -d badname >output 2>error && - test_path_is_missing .git/refs/heads/badname && + test_ref_missing refs/heads/badname && test_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output && test_must_be_empty error ' @@ -245,8 +249,9 @@ test_expect_success 'update-ref -d can delete broken name through symref' ' test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref" && test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg && test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" && + test_ref_exists refs/heads/broken...ref && git update-ref -d refs/heads/badname >output 2>error && - test_path_is_missing .git/refs/heads/broken...ref && + test_ref_missing refs/heads/broken...ref && test_must_be_empty output && test_must_be_empty error ' @@ -254,8 +259,9 @@ test_expect_success 'update-ref -d can delete broken name through symref' ' test_expect_success 'update-ref --no-deref -d can delete symref with broken name' ' printf "ref: refs/heads/main\n" >.git/refs/heads/broken...symref && test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" && + test_ref_exists refs/heads/broken...symref && git update-ref --no-deref -d refs/heads/broken...symref >output 2>error && - test_path_is_missing .git/refs/heads/broken...symref && + test_ref_missing refs/heads/broken...symref && test_must_be_empty output && test_must_be_empty error ' @@ -263,8 +269,9 @@ test_expect_success 'update-ref --no-deref -d can delete symref with broken name test_expect_success 'branch -d can delete symref with broken name' ' printf "ref: refs/heads/main\n" >.git/refs/heads/broken...symref && test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" && + test_ref_exists refs/heads/broken...symref && git branch -d broken...symref >output 2>error && - test_path_is_missing .git/refs/heads/broken...symref && + test_ref_missing refs/heads/broken...symref && test_i18ngrep "Deleted branch broken...symref (was refs/heads/main)" output && test_must_be_empty error ' @@ -272,8 +279,9 @@ test_expect_success 'branch -d can delete symref with broken name' ' test_expect_success 'update-ref --no-deref -d can delete dangling symref with broken name' ' printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref && test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" && + test_ref_exists refs/heads/broken...symref && git update-ref --no-deref -d refs/heads/broken...symref >output 2>error && - test_path_is_missing .git/refs/heads/broken...symref && + test_ref_missing refs/heads/broken...symref && test_must_be_empty output && test_must_be_empty error ' @@ -281,8 +289,9 @@ test_expect_success 'update-ref --no-deref -d can delete dangling symref with br test_expect_success 'branch -d can delete dangling symref with broken name' ' printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref && test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" && + test_ref_exists refs/heads/broken...symref && git branch -d broken...symref >output 2>error && - test_path_is_missing .git/refs/heads/broken...symref && + test_ref_missing refs/heads/broken...symref && test_i18ngrep "Deleted branch broken...symref (was refs/heads/idonotexist)" output && test_must_be_empty error ' diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 3182abde27..964bc36514 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -25,7 +25,7 @@ test_expect_success 'prepare a trivial repository' ' test_expect_success 'git branch --help should not have created a bogus branch' ' test_might_fail git branch --man --help /dev/null 2>&1 && - test_path_is_missing .git/refs/heads/--help + test_ref_missing refs/heads/--help ' test_expect_success 'branch -h in broken repository' ' @@ -40,7 +40,8 @@ test_expect_success 'branch -h in broken repository' ' ' test_expect_success 'git branch abc should create a branch' ' - git branch abc && test_path_is_file .git/refs/heads/abc + git branch abc && + test_ref_exists refs/heads/abc ' test_expect_success 'git branch abc should fail when abc exists' ' @@ -61,11 +62,13 @@ test_expect_success 'git branch --force abc should succeed when abc exists' ' ' test_expect_success 'git branch a/b/c should create a branch' ' - git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c + git branch a/b/c && + test_ref_exists refs/heads/a/b/c ' test_expect_success 'git branch mb main... should create a branch' ' - git branch mb main... && test_path_is_file .git/refs/heads/mb + git branch mb main... && + test_ref_exists refs/heads/mb ' test_expect_success 'git branch HEAD should fail' ' @@ -78,14 +81,14 @@ EOF test_expect_success 'git branch --create-reflog d/e/f should create a branch and a log' ' GIT_COMMITTER_DATE="2005-05-26 23:30" \ git -c core.logallrefupdates=false branch --create-reflog d/e/f && - test_path_is_file .git/refs/heads/d/e/f && + test_ref_exists refs/heads/d/e/f && test_path_is_file .git/logs/refs/heads/d/e/f && test_cmp expect .git/logs/refs/heads/d/e/f ' test_expect_success 'git branch -d d/e/f should delete a branch and a log' ' git branch -d d/e/f && - test_path_is_missing .git/refs/heads/d/e/f && + test_ref_missing refs/heads/d/e/f && test_must_fail git reflog exists refs/heads/d/e/f ' @@ -213,7 +216,7 @@ test_expect_success 'git branch -M should leave orphaned HEAD alone' ' test_commit initial && git checkout --orphan lonely && grep lonely .git/HEAD && - test_path_is_missing .git/refs/head/lonely && + test_ref_missing refs/head/lonely && git branch -M main mistress && grep lonely .git/HEAD ) @@ -799,8 +802,8 @@ test_expect_success 'deleting a symref' ' git symbolic-ref refs/heads/symref refs/heads/target && echo "Deleted branch symref (was refs/heads/target)." >expect && git branch -d symref >actual && - test_path_is_file .git/refs/heads/target && - test_path_is_missing .git/refs/heads/symref && + test_ref_exists refs/heads/target && + test_ref_missing refs/heads/symref && test_cmp expect actual ' @@ -809,16 +812,16 @@ test_expect_success 'deleting a dangling symref' ' test_path_is_file .git/refs/heads/dangling-symref && echo "Deleted branch dangling-symref (was nowhere)." >expect && git branch -d dangling-symref >actual && - test_path_is_missing .git/refs/heads/dangling-symref && + test_ref_missing refs/heads/dangling-symref && test_cmp expect actual ' test_expect_success 'deleting a self-referential symref' ' git symbolic-ref refs/heads/self-reference refs/heads/self-reference && - test_path_is_file .git/refs/heads/self-reference && + test_ref_exists refs/heads/self-reference && echo "Deleted branch self-reference (was refs/heads/self-reference)." >expect && git branch -d self-reference >actual && - test_path_is_missing .git/refs/heads/self-reference && + test_ref_missing refs/heads/self-reference && test_cmp expect actual ' @@ -826,8 +829,8 @@ test_expect_success 'renaming a symref is not allowed' ' git symbolic-ref refs/heads/topic refs/heads/main && test_must_fail git branch -m topic new-topic && git symbolic-ref refs/heads/topic && - test_path_is_file .git/refs/heads/main && - test_path_is_missing .git/refs/heads/new-topic + test_ref_exists refs/heads/main && + test_ref_missing refs/heads/new-topic ' test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog for u is a symlink' ' @@ -1142,7 +1145,7 @@ EOF test_expect_success 'git checkout -b g/h/i -l should create a branch and a log' ' GIT_COMMITTER_DATE="2005-05-26 23:30" \ git checkout -b g/h/i -l main && - test_path_is_file .git/refs/heads/g/h/i && + test_ref_exists refs/heads/g/h/i && test_path_is_file .git/logs/refs/heads/g/h/i && test_cmp expect .git/logs/refs/heads/g/h/i ' diff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh index 079b2f2536..3681859f98 100755 --- a/t/t5521-pull-options.sh +++ b/t/t5521-pull-options.sh @@ -143,7 +143,7 @@ test_expect_success 'git pull --dry-run' ' cd clonedry && git pull --dry-run ../parent && test_path_is_missing .git/FETCH_HEAD && - test_path_is_missing .git/refs/heads/main && + test_ref_missing refs/heads/main && test_path_is_missing .git/index && test_path_is_missing file ) @@ -157,7 +157,7 @@ test_expect_success 'git pull --all --dry-run' ' git remote add origin ../parent && git pull --all --dry-run && test_path_is_missing .git/FETCH_HEAD && - test_path_is_missing .git/refs/remotes/origin/main && + test_ref_missing refs/remotes/origin/main && test_path_is_missing .git/index && test_path_is_missing file ) diff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh index 1d7b1abda1..946c575188 100755 --- a/t/t5605-clone-local.sh +++ b/t/t5605-clone-local.sh @@ -69,7 +69,7 @@ test_expect_success 'local clone of repo with nonexistent ref in HEAD' ' git clone a d && (cd d && git fetch && - test ! -e .git/refs/remotes/origin/HEAD) + test_ref_missing refs/remotes/origin/HEAD) ' test_expect_success 'bundle clone without .bundle suffix' ' diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 2f8868caa1..56b33536ed 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -251,6 +251,61 @@ debug () { done } +# Usage: test_ref_exists [options] +# +# -C : +# Run all git commands in directory +# +# This helper function checks whether a reference exists. Symrefs or object IDs +# will not be resolved. Can be used to check references with bad names. +test_ref_exists () { + local indir= + + while test $# != 0 + do + case "$1" in + -C) + indir="$2" + shift + ;; + *) + break + ;; + esac + shift + done && + + indir=${indir:+"$indir"/} && + + if test "$#" != 1 + then + BUG "expected exactly one reference" + fi && + + git ${indir:+ -C "$indir"} show-ref --exists "$1" +} + +# Behaves the same as test_ref_exists, except that it checks for the absence of +# a reference. This is preferable to `! test_ref_exists` as this function is +# able to distinguish actually-missing references from other, generic errors. +test_ref_missing () { + test_ref_exists "$@" + case "$?" in + 2) + # This is the good case. + return 0 + ;; + 0) + echo >&4 "test_ref_missing: reference exists" + return 1 + ;; + *) + echo >&4 "test_ref_missing: generic error" + return 1 + ;; + esac +} + # Usage: test_commit [options] [ [ []]] # -C : # Run all git commands in directory