Merge branch 'ps/show-ref'

Teach "git show-ref" a mode to check the existence of a ref.

* 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
maint
Junio C Hamano 2023-11-08 11:03:59 +09:00
commit d8972a5abd
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 SYNOPSIS
-------- --------
[verse] [verse]
'git show-ref' [-q | --quiet] [--verify] [--head] [-d | --dereference] 'git show-ref' [--head] [-d | --dereference]
[-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags] [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]
[--heads] [--] [<pattern>...] [--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' --exclude-existing[=<pattern>]
'git show-ref' --exists <ref>


DESCRIPTION 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 refs from stdin, one ref per line, and shows those that don't exist in
the local repository. 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 Use of this utility is encouraged in favor of directly accessing files under
the `.git` directory. the `.git` directory.


@ -62,6 +70,12 @@ OPTIONS
Aside from returning an error code of 1, it will also print an error Aside from returning an error code of 1, it will also print an error
message if `--quiet` was not specified. 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>]:: --abbrev[=<n>]::


Abbreviate the object name. When using `--hash`, you do Abbreviate the object name. When using `--hash`, you do
@ -70,8 +84,8 @@ OPTIONS
-q:: -q::
--quiet:: --quiet::


Do not print any results to stdout. When combined with `--verify`, this Do not print any results to stdout. Can be used with `--verify` to
can be used to silently check if a reference exists. silently check if a reference exists.


--exclude-existing[=<pattern>]:: --exclude-existing[=<pattern>]::



View File

@ -2,7 +2,7 @@
#include "config.h" #include "config.h"
#include "gettext.h" #include "gettext.h"
#include "hex.h" #include "hex.h"
#include "refs.h" #include "refs/refs-internal.h"
#include "object-name.h" #include "object-name.h"
#include "object-store-ll.h" #include "object-store-ll.h"
#include "object.h" #include "object.h"
@ -11,19 +11,26 @@
#include "parse-options.h" #include "parse-options.h"


static const char * const show_ref_usage[] = { 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" " [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\n"
" [--heads] [--] [<pattern>...]"), " [--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 --exclude-existing[=<pattern>]"),
N_("git show-ref --exists <ref>"),
NULL NULL
}; };


static int deref_tags, show_head, tags_only, heads_only, found_match, verify, struct show_one_options {
quiet, hash_only, abbrev, exclude_arg; int quiet;
static const char **pattern; int hash_only;
static const char *exclude_existing_arg; 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; const char *hex;
struct object_id peeled; 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, die("git show-ref: bad ref %s (%s)", refname,
oid_to_hex(oid)); oid_to_hex(oid));


if (quiet) if (opts->quiet)
return; return;


hex = repo_find_unique_abbrev(the_repository, oid, abbrev); hex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);
if (hash_only) if (opts->hash_only)
printf("%s\n", hex); printf("%s\n", hex);
else else
printf("%s %s\n", hex, refname); printf("%s %s\n", hex, refname);


if (!deref_tags) if (!opts->deref_tags)
return; return;


if (!peel_iterated_oid(oid, &peeled)) { 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); 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, 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; goto match;


if (pattern) { if (data->patterns) {
int reflen = strlen(refname); int reflen = strlen(refname);
const char **p = pattern, *m; const char **p = data->patterns, *m;
while ((m = *p++) != NULL) { while ((m = *p++) != NULL) {
int len = strlen(m); int len = strlen(m);
if (len > reflen) if (len > reflen)
@ -74,9 +90,9 @@ static int show_ref(const char *refname, const struct object_id *oid,
} }


match: match:
found_match++; data->found_match++;


show_one(refname, oid); show_one(data->show_one_opts, refname, oid);


return 0; return 0;
} }
@ -90,6 +106,15 @@ static int add_existing(const char *refname,
return 0; 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, * read "^(?:<anything>\s)?<refname>(?:\^\{\})?$" from the standard input,
* and * 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; * (4) ignore if refname is a ref that exists in the local repository;
* (5) otherwise output the line. * (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]; char buf[1024];
int matchlen = match ? strlen(match) : 0; int patternlen = opts->pattern ? strlen(opts->pattern) : 0;


for_each_ref(add_existing, &existing_refs); for_each_ref(add_existing, &existing_refs);
while (fgets(buf, sizeof(buf), stdin)) { while (fgets(buf, sizeof(buf), stdin)) {
@ -119,11 +144,11 @@ static int exclude_existing(const char *match)
for (ref = buf + len; buf < ref; ref--) for (ref = buf + len; buf < ref; ref--)
if (isspace(ref[-1])) if (isspace(ref[-1]))
break; break;
if (match) { if (opts->pattern) {
int reflen = buf + len - ref; int reflen = buf + len - ref;
if (reflen < matchlen) if (reflen < patternlen)
continue; continue;
if (strncmp(ref, match, matchlen)) if (strncmp(ref, opts->pattern, patternlen))
continue; continue;
} }
if (check_refname_format(ref, 0)) { if (check_refname_format(ref, 0)) {
@ -134,97 +159,172 @@ static int exclude_existing(const char *match)
printf("%s\n", buf); printf("%s\n", buf);
} }
} }

string_list_clear(&existing_refs, 0);
return 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) 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 */ /* Use full length SHA1 if no argument */
if (!arg) if (!arg)
return 0; 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, static int exclude_existing_callback(const struct option *opt, const char *arg,
int unset) int unset)
{ {
struct exclude_existing_options *opts = opt->value;
BUG_ON_OPT_NEG(unset); BUG_ON_OPT_NEG(unset);
exclude_arg = 1; opts->enabled = 1;
*(const char **)opt->value = arg; opts->pattern = arg;
return 0; 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) 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); git_config(git_default_config, NULL);


argc = parse_options(argc, argv, prefix, show_ref_options, argc = parse_options(argc, argv, prefix, show_ref_options,
show_ref_usage, 0); show_ref_usage, 0);


if (exclude_arg) if ((!!exclude_existing_opts.enabled + !!verify + !!exists) > 1)
return exclude_existing(exclude_existing_arg); die(_("only one of '%s', '%s' or '%s' can be given"),
"--exclude-existing", "--verify", "--exists");


pattern = argv; if (exclude_existing_opts.enabled)
if (!*pattern) return cmd_show_ref__exclude_existing(&exclude_existing_opts);
pattern = NULL; else if (verify)

return cmd_show_ref__verify(&show_one_opts, argv);
if (verify) { else if (exists)
if (!pattern) return cmd_show_ref__exists(argv);
die("--verify requires a reference"); else
while (*pattern) { return cmd_show_ref__patterns(&patterns_opts, &show_one_opts, argv);
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;
} }

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 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_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-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_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 && 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 output &&
test_must_be_empty error 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_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-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_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 && 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_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output &&
test_must_be_empty error 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_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-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_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 && 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 output &&
test_must_be_empty error 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_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-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_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 && 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_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output &&
test_must_be_empty error 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_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-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_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 && 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 output &&
test_must_be_empty error 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' ' 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 && 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_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 && 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 output &&
test_must_be_empty error 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' ' test_expect_success 'branch -d can delete symref with broken name' '
printf "ref: refs/heads/main\n" >.git/refs/heads/broken...symref && 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_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 && 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_i18ngrep "Deleted branch broken...symref (was refs/heads/main)" output &&
test_must_be_empty error 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' ' 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 && 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_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 && 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 output &&
test_must_be_empty error 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' ' test_expect_success 'branch -d can delete dangling symref with broken name' '
printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref && 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_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 && 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_i18ngrep "Deleted branch broken...symref (was refs/heads/idonotexist)" output &&
test_must_be_empty error 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_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_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' ' 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' ' 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' ' 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' ' 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' ' 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' ' 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' ' 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_COMMITTER_DATE="2005-05-26 23:30" \
git -c core.logallrefupdates=false branch --create-reflog d/e/f && 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_path_is_file .git/logs/refs/heads/d/e/f &&
test_cmp expect .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' ' test_expect_success 'git branch -d d/e/f should delete a branch and a log' '
git branch -d d/e/f && 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 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 && test_commit initial &&
git checkout --orphan lonely && git checkout --orphan lonely &&
grep lonely .git/HEAD && grep lonely .git/HEAD &&
test_path_is_missing .git/refs/head/lonely && test_ref_missing refs/head/lonely &&
git branch -M main mistress && git branch -M main mistress &&
grep lonely .git/HEAD grep lonely .git/HEAD
) )
@ -799,8 +802,8 @@ test_expect_success 'deleting a symref' '
git symbolic-ref refs/heads/symref refs/heads/target && git symbolic-ref refs/heads/symref refs/heads/target &&
echo "Deleted branch symref (was refs/heads/target)." >expect && echo "Deleted branch symref (was refs/heads/target)." >expect &&
git branch -d symref >actual && git branch -d symref >actual &&
test_path_is_file .git/refs/heads/target && test_ref_exists refs/heads/target &&
test_path_is_missing .git/refs/heads/symref && test_ref_missing refs/heads/symref &&
test_cmp expect actual test_cmp expect actual
' '


@ -809,16 +812,16 @@ test_expect_success 'deleting a dangling symref' '
test_path_is_file .git/refs/heads/dangling-symref && test_path_is_file .git/refs/heads/dangling-symref &&
echo "Deleted branch dangling-symref (was nowhere)." >expect && echo "Deleted branch dangling-symref (was nowhere)." >expect &&
git branch -d dangling-symref >actual && 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_cmp expect actual
' '


test_expect_success 'deleting a self-referential symref' ' test_expect_success 'deleting a self-referential symref' '
git symbolic-ref refs/heads/self-reference refs/heads/self-reference && 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 && echo "Deleted branch self-reference (was refs/heads/self-reference)." >expect &&
git branch -d self-reference >actual && 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 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 && git symbolic-ref refs/heads/topic refs/heads/main &&
test_must_fail git branch -m topic new-topic && test_must_fail git branch -m topic new-topic &&
git symbolic-ref refs/heads/topic && git symbolic-ref refs/heads/topic &&
test_path_is_file .git/refs/heads/main && test_ref_exists refs/heads/main &&
test_path_is_missing .git/refs/heads/new-topic 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' ' 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' ' 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_COMMITTER_DATE="2005-05-26 23:30" \
git checkout -b g/h/i -l main && 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_path_is_file .git/logs/refs/heads/g/h/i &&
test_cmp expect .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 && cd clonedry &&
git pull --dry-run ../parent && git pull --dry-run ../parent &&
test_path_is_missing .git/FETCH_HEAD && 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 .git/index &&
test_path_is_missing file test_path_is_missing file
) )
@ -157,7 +157,7 @@ test_expect_success 'git pull --all --dry-run' '
git remote add origin ../parent && git remote add origin ../parent &&
git pull --all --dry-run && git pull --all --dry-run &&
test_path_is_missing .git/FETCH_HEAD && 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 .git/index &&
test_path_is_missing file 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 && git clone a d &&
(cd d && (cd d &&
git fetch && git fetch &&
test ! -e .git/refs/remotes/origin/HEAD) test_ref_missing refs/remotes/origin/HEAD)
' '


test_expect_success 'bundle clone without .bundle suffix' ' test_expect_success 'bundle clone without .bundle suffix' '

View File

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