Skip to content

Commit 9080a7f

Browse files
pks-tgitster
authored andcommitted
builtin/show-ref: add new mode to check for reference existence
While we have multiple ways to show the value of a given reference, we do not have any way to check whether a reference exists at all. While commands like git-rev-parse(1) or git-show-ref(1) can be used to check for reference existence in case the reference resolves to something sane, neither of them can be used to check for existence in some other scenarios where the reference does not resolve cleanly: - References which have an invalid name cannot be resolved. - References to nonexistent objects cannot be resolved. - Dangling symrefs can be resolved via git-symbolic-ref(1), but this requires the caller to special case existence checks depending on whether or not a reference is symbolic or direct. Furthermore, git-rev-list(1) and other commands do not let the caller distinguish easily between an actually missing reference and a generic error. Taken together, this seems like sufficient motivation to introduce a separate plumbing command to explicitly check for the existence of a reference without trying to resolve its contents. This new command comes in the form of `git show-ref --exists`. This new mode will exit successfully when the reference exists, with a specific exit code of 2 when it does not exist, or with 1 when there has been a generic error. Note that the only way to properly implement this command is by using the internal `refs_read_raw_ref()` function. While the public function `refs_resolve_ref_unsafe()` can be made to behave in the same way by passing various flags, it does not provide any way to obtain the errno with which the reference backend failed when reading the reference. As such, it becomes impossible for us to distinguish generic errors from the explicit case where the reference wasn't found. Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 1307d5e commit 9080a7f

File tree

3 files changed

+117
-6
lines changed

3 files changed

+117
-6
lines changed

Documentation/git-show-ref.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ SYNOPSIS
1515
[-s | --hash[=<n>]] [--abbrev[=<n>]]
1616
[--] [<ref>...]
1717
'git show-ref' --exclude-existing[=<pattern>]
18+
'git show-ref' --exists <ref>
1819

1920
DESCRIPTION
2021
-----------
@@ -30,6 +31,10 @@ The `--exclude-existing` form is a filter that does the inverse. It reads
3031
refs from stdin, one ref per line, and shows those that don't exist in
3132
the local repository.
3233

34+
The `--exists` form can be used to check for the existence of a single
35+
references. This form does not verify whether the reference resolves to an
36+
actual object.
37+
3338
Use of this utility is encouraged in favor of directly accessing files under
3439
the `.git` directory.
3540

@@ -65,6 +70,12 @@ OPTIONS
6570
Aside from returning an error code of 1, it will also print an error
6671
message if `--quiet` was not specified.
6772

73+
--exists::
74+
75+
Check whether the given reference exists. Returns an exit code of 0 if
76+
it does, 2 if it is missing, and 1 in case looking up the reference
77+
failed with an error other than the reference being missing.
78+
6879
--abbrev[=<n>]::
6980

7081
Abbreviate the object name. When using `--hash`, you do

builtin/show-ref.c

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#include "config.h"
33
#include "gettext.h"
44
#include "hex.h"
5-
#include "refs.h"
5+
#include "refs/refs-internal.h"
66
#include "object-name.h"
77
#include "object-store-ll.h"
88
#include "object.h"
@@ -18,6 +18,7 @@ static const char * const show_ref_usage[] = {
1818
" [-s | --hash[=<n>]] [--abbrev[=<n>]]\n"
1919
" [--] [<ref>...]"),
2020
N_("git show-ref --exclude-existing[=<pattern>]"),
21+
N_("git show-ref --exists <ref>"),
2122
NULL
2223
};
2324

@@ -220,6 +221,41 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,
220221
return 0;
221222
}
222223

224+
static int cmd_show_ref__exists(const char **refs)
225+
{
226+
struct strbuf unused_referent = STRBUF_INIT;
227+
struct object_id unused_oid;
228+
unsigned int unused_type;
229+
int failure_errno = 0;
230+
const char *ref;
231+
int ret = 0;
232+
233+
if (!refs || !*refs)
234+
die("--exists requires a reference");
235+
ref = *refs++;
236+
if (*refs)
237+
die("--exists requires exactly one reference");
238+
239+
if (refs_read_raw_ref(get_main_ref_store(the_repository), ref,
240+
&unused_oid, &unused_referent, &unused_type,
241+
&failure_errno)) {
242+
if (failure_errno == ENOENT) {
243+
error(_("reference does not exist"));
244+
ret = 2;
245+
} else {
246+
errno = failure_errno;
247+
error_errno(_("failed to look up reference"));
248+
ret = 1;
249+
}
250+
251+
goto out;
252+
}
253+
254+
out:
255+
strbuf_release(&unused_referent);
256+
return ret;
257+
}
258+
223259
static int hash_callback(const struct option *opt, const char *arg, int unset)
224260
{
225261
struct show_one_options *opts = opt->value;
@@ -249,10 +285,11 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
249285
struct exclude_existing_options exclude_existing_opts = {0};
250286
struct patterns_options patterns_opts = {0};
251287
struct show_one_options show_one_opts = {0};
252-
int verify = 0;
288+
int verify = 0, exists = 0;
253289
const struct option show_ref_options[] = {
254290
OPT_BOOL(0, "tags", &patterns_opts.tags_only, N_("only show tags (can be combined with heads)")),
255291
OPT_BOOL(0, "heads", &patterns_opts.heads_only, N_("only show heads (can be combined with tags)")),
292+
OPT_BOOL(0, "exists", &exists, N_("check for reference existence without resolving")),
256293
OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
257294
"requires exact ref path")),
258295
OPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,
@@ -278,14 +315,16 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
278315
argc = parse_options(argc, argv, prefix, show_ref_options,
279316
show_ref_usage, 0);
280317

281-
if ((!!exclude_existing_opts.enabled + !!verify) > 1)
282-
die(_("only one of '%s' or '%s' can be given"),
283-
"--exclude-existing", "--verify");
318+
if ((!!exclude_existing_opts.enabled + !!verify + !!exists) > 1)
319+
die(_("only one of '%s', '%s' or '%s' can be given"),
320+
"--exclude-existing", "--verify", "--exists");
284321

285322
if (exclude_existing_opts.enabled)
286323
return cmd_show_ref__exclude_existing(&exclude_existing_opts);
287324
else if (verify)
288325
return cmd_show_ref__verify(&show_one_opts, argv);
326+
else if (exists)
327+
return cmd_show_ref__exists(argv);
289328
else
290329
return cmd_show_ref__patterns(&patterns_opts, &show_one_opts, argv);
291330
}

t/t1403-show-ref.sh

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,71 @@ test_expect_success 'show-ref --verify with dangling ref' '
198198

199199
test_expect_success 'show-ref sub-modes are mutually exclusive' '
200200
cat >expect <<-EOF &&
201-
fatal: only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given
201+
fatal: only one of ${SQ}--exclude-existing${SQ}, ${SQ}--verify${SQ} or ${SQ}--exists${SQ} can be given
202202
EOF
203203
204204
test_must_fail git show-ref --verify --exclude-existing 2>err &&
205+
test_cmp expect err &&
206+
207+
test_must_fail git show-ref --verify --exists 2>err &&
208+
test_cmp expect err &&
209+
210+
test_must_fail git show-ref --exclude-existing --exists 2>err &&
211+
test_cmp expect err
212+
'
213+
214+
test_expect_success '--exists with existing reference' '
215+
git show-ref --exists refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
216+
'
217+
218+
test_expect_success '--exists with missing reference' '
219+
test_expect_code 2 git show-ref --exists refs/heads/does-not-exist
220+
'
221+
222+
test_expect_success '--exists does not use DWIM' '
223+
test_expect_code 2 git show-ref --exists $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 2>err &&
224+
grep "reference does not exist" err
225+
'
226+
227+
test_expect_success '--exists with HEAD' '
228+
git show-ref --exists HEAD
229+
'
230+
231+
test_expect_success '--exists with bad reference name' '
232+
test_when_finished "git update-ref -d refs/heads/bad...name" &&
233+
new_oid=$(git rev-parse HEAD) &&
234+
test-tool ref-store main update-ref msg refs/heads/bad...name $new_oid $ZERO_OID REF_SKIP_REFNAME_VERIFICATION &&
235+
git show-ref --exists refs/heads/bad...name
236+
'
237+
238+
test_expect_success '--exists with arbitrary symref' '
239+
test_when_finished "git symbolic-ref -d refs/symref" &&
240+
git symbolic-ref refs/symref refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
241+
git show-ref --exists refs/symref
242+
'
243+
244+
test_expect_success '--exists with dangling symref' '
245+
test_when_finished "git symbolic-ref -d refs/heads/dangling" &&
246+
git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
247+
git show-ref --exists refs/heads/dangling
248+
'
249+
250+
test_expect_success '--exists with nonexistent object ID' '
251+
test-tool ref-store main update-ref msg refs/heads/missing-oid $(test_oid 001) $ZERO_OID REF_SKIP_OID_VERIFICATION &&
252+
git show-ref --exists refs/heads/missing-oid
253+
'
254+
255+
test_expect_success '--exists with non-commit object' '
256+
tree_oid=$(git rev-parse HEAD^{tree}) &&
257+
test-tool ref-store main update-ref msg refs/heads/tree ${tree_oid} $ZERO_OID REF_SKIP_OID_VERIFICATION &&
258+
git show-ref --exists refs/heads/tree
259+
'
260+
261+
test_expect_success '--exists with directory fails with generic error' '
262+
cat >expect <<-EOF &&
263+
error: failed to look up reference: Is a directory
264+
EOF
265+
test_expect_code 1 git show-ref --exists refs/heads 2>err &&
205266
test_cmp expect err
206267
'
207268

0 commit comments

Comments
 (0)