Merge branch 'ps/show-ref' into ps/ref-tests-update

* ps/show-ref:
  t: use git-show-ref(1) to check for ref existence
  builtin/show-ref: add new mode to check for reference existence
  builtin/show-ref: explicitly spell out different modes in synopsis
  builtin/show-ref: ensure mutual exclusiveness of subcommands
  builtin/show-ref: refactor options for patterns subcommand
  builtin/show-ref: stop using global vars for `show_one()`
  builtin/show-ref: stop using global variable to count matches
  builtin/show-ref: refactor `--exclude-existing` options
  builtin/show-ref: fix dead code when passing patterns
  builtin/show-ref: fix leaking string buffer
  builtin/show-ref: split up different subcommands
  builtin/show-ref: convert pattern to a local variable
This commit is contained in:
Junio C Hamano 2023-11-03 08:36:54 +09:00
commit d5dbad0c76
8 changed files with 373 additions and 122 deletions

View file

@ -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[=<n>]] [--abbrev[=<n>]] [--tags]
[--heads] [--] [<pattern>...]
'git show-ref' --verify [-q | --quiet] [-d | --dereference]
[-s | --hash[=<n>]] [--abbrev[=<n>]]
[--] [<ref>...]
'git show-ref' --exclude-existing[=<pattern>]
'git show-ref' --exists <ref>
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[=<n>]::
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[=<pattern>]::

View file

@ -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[=<n>]] [--abbrev[=<n>]] [--tags]\n"
" [--heads] [--] [<pattern>...]"),
N_("git show-ref --verify [-q | --quiet] [-d | --dereference]\n"
" [-s | --hash[=<n>]] [--abbrev[=<n>]]\n"
" [--] [<ref>...]"),
N_("git show-ref --exclude-existing[=<pattern>]"),
N_("git show-ref --exists <ref>"),
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 "^(?:<anything>\s)?<refname>(?:\^\{\})?$" 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 <n> 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 <n> 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);
}

View file

@ -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

View file

@ -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
'

View file

@ -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 >/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
'

View file

@ -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
)

View file

@ -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' '

View file

@ -251,6 +251,61 @@ debug () {
done
}
# Usage: test_ref_exists [options] <ref>
#
# -C <dir>:
# Run all git commands in directory <dir>
#
# 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] <message> [<file> [<contents> [<tag>]]]
# -C <dir>:
# Run all git commands in directory <dir>