mirror of
https://github.com/git/git
synced 2024-08-27 11:39:22 +00:00
![Jeff King](/assets/img/avatar_default.png)
The peel_ref() interface is confusing and error-prone: - it's typically used by ref iteration callbacks that have both a refname and oid. But since they pass only the refname, we may load the ref value from the filesystem again. This is inefficient, but also means we are open to a race if somebody simultaneously updates the ref. E.g., this: int some_ref_cb(const char *refname, const struct object_id *oid, ...) { if (!peel_ref(refname, &peeled)) printf("%s peels to %s", oid_to_hex(oid), oid_to_hex(&peeled); } could print nonsense. It is correct to say "refname peels to..." (you may see the "before" value or the "after" value, either of which is consistent), but mentioning both oids may be mixing before/after values. Worse, whether this is possible depends on whether the optimization to read from the current iterator value kicks in. So it is actually not possible with: for_each_ref(some_ref_cb); but it _is_ possible with: head_ref(some_ref_cb); which does not use the iterator mechanism (though in practice, HEAD should never peel to anything, so this may not be triggerable). - it must take a fully-qualified refname for the read_ref_full() code path to work. Yet we routinely pass it partial refnames from callbacks to for_each_tag_ref(), etc. This happens to work when iterating because there we do not call read_ref_full() at all, and only use the passed refname to check if it is the same as the iterator. But the requirements for the function parameters are quite unclear. Instead of taking a refname, let's instead take an oid. That fixes both problems. It's a little funny for a "ref" function not to involve refs at all. The key thing is that it's optimizing under the hood based on having access to the ref iterator. So let's change the name to make it clear why you'd want this function versus just peel_object(). There are two other directions I considered but rejected: - we could pass the peel information into the each_ref_fn callback. However, we don't know if the caller actually wants it or not. For packed-refs, providing it is essentially free. But for loose refs, we actually have to peel the object, which would be wasteful in most cases. We could likewise pass in a flag to the callback indicating whether the peeled information is known, but that complicates those callbacks, as they then have to decide whether to manually peel themselves. Plus it requires changing the interface of every callback, whether they care about peeling or not, and there are many of them. - we could make a function to return the peeled value of the current iterated ref (computing it if necessary), and BUG() otherwise. I.e.: int peel_current_iterated_ref(struct object_id *out); Each of the current callers is an each_ref_fn callback, so they'd mostly be happy. But: - we use those callbacks with functions like head_ref(), which do not use the iteration code. So we'd need to handle the fallback case there, anyway. - it's possible that a caller would want to call into generic code that sometimes is used during iteration and sometimes not. This encapsulates the logic to do the fast thing when possible, and fallback when necessary. The implementation is mostly obvious, but I want to call out a few things in the patch: - the test-tool coverage for peel_ref() is now meaningless, as it all collapses to a single peel_object() call (arguably they were pretty uninteresting before; the tricky part of that function is the fast-path we see during iteration, but these calls didn't trigger that). I've just dropped it entirely, though note that some other tests relied on the tags we created; I've moved that creation to the tests where it matters. - we no longer need to take a ref_store parameter, since we'd never look up a ref now. We do still rely on a global "current iterator" variable which _could_ be kept per-ref-store. But in practice this is only useful if there are multiple recursive iterations, at which point the more appropriate solution is probably a stack of iterators. No caller used the actual ref-store parameter anyway (they all call the wrapper that passes the_repository). - the original only kicked in the optimization when the "refname" pointer matched (i.e., not string comparison). We do likewise with the "oid" parameter here, but fall back to doing an actual oideq() call. This in theory lets us kick in the optimization more often, though in practice no current caller cares. It should never be wrong, though (peeling is a property of an object, so two refs pointing to the same object would peel identically). - the original took care not to touch the peeled out-parameter unless we found something to put in it. But no caller cares about this, and anyway, it is enforced by peel_object() itself (and even in the optimized iterator case, that's where we eventually end up). We can shorten the code and avoid an extra copy by just passing the out-parameter through the stack. Signed-off-by: Jeff King <peff@peff.net> Reviewed-by: Taylor Blau <me@ttaylorr.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
227 lines
5.6 KiB
C
227 lines
5.6 KiB
C
#include "builtin.h"
|
|
#include "cache.h"
|
|
#include "config.h"
|
|
#include "refs.h"
|
|
#include "object-store.h"
|
|
#include "object.h"
|
|
#include "tag.h"
|
|
#include "string-list.h"
|
|
#include "parse-options.h"
|
|
|
|
static const char * const show_ref_usage[] = {
|
|
N_("git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference] [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [<pattern>...]"),
|
|
N_("git show-ref --exclude-existing[=<pattern>]"),
|
|
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;
|
|
|
|
static void show_one(const char *refname, const struct object_id *oid)
|
|
{
|
|
const char *hex;
|
|
struct object_id peeled;
|
|
|
|
if (!has_object_file(oid))
|
|
die("git show-ref: bad ref %s (%s)", refname,
|
|
oid_to_hex(oid));
|
|
|
|
if (quiet)
|
|
return;
|
|
|
|
hex = find_unique_abbrev(oid, abbrev);
|
|
if (hash_only)
|
|
printf("%s\n", hex);
|
|
else
|
|
printf("%s %s\n", hex, refname);
|
|
|
|
if (!deref_tags)
|
|
return;
|
|
|
|
if (!peel_iterated_oid(oid, &peeled)) {
|
|
hex = find_unique_abbrev(&peeled, abbrev);
|
|
printf("%s %s^{}\n", hex, refname);
|
|
}
|
|
}
|
|
|
|
static int show_ref(const char *refname, const struct object_id *oid,
|
|
int flag, void *cbdata)
|
|
{
|
|
if (show_head && !strcmp(refname, "HEAD"))
|
|
goto match;
|
|
|
|
if (tags_only || heads_only) {
|
|
int match;
|
|
|
|
match = heads_only && starts_with(refname, "refs/heads/");
|
|
match |= tags_only && starts_with(refname, "refs/tags/");
|
|
if (!match)
|
|
return 0;
|
|
}
|
|
if (pattern) {
|
|
int reflen = strlen(refname);
|
|
const char **p = pattern, *m;
|
|
while ((m = *p++) != NULL) {
|
|
int len = strlen(m);
|
|
if (len > reflen)
|
|
continue;
|
|
if (memcmp(m, refname + reflen - len, len))
|
|
continue;
|
|
if (len == reflen)
|
|
goto match;
|
|
if (refname[reflen - len - 1] == '/')
|
|
goto match;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
match:
|
|
found_match++;
|
|
|
|
show_one(refname, oid);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int add_existing(const char *refname, const struct object_id *oid,
|
|
int flag, void *cbdata)
|
|
{
|
|
struct string_list *list = (struct string_list *)cbdata;
|
|
string_list_insert(list, refname);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* read "^(?:<anything>\s)?<refname>(?:\^\{\})?$" from the standard input,
|
|
* and
|
|
* (1) strip "^{}" at the end of line if any;
|
|
* (2) ignore if match is provided and does not head-match refname;
|
|
* (3) warn if refname is not a well-formed refname and skip;
|
|
* (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 struct string_list existing_refs = STRING_LIST_INIT_DUP;
|
|
char buf[1024];
|
|
int matchlen = match ? strlen(match) : 0;
|
|
|
|
for_each_ref(add_existing, &existing_refs);
|
|
while (fgets(buf, sizeof(buf), stdin)) {
|
|
char *ref;
|
|
int len = strlen(buf);
|
|
|
|
if (len > 0 && buf[len - 1] == '\n')
|
|
buf[--len] = '\0';
|
|
if (3 <= len && !strcmp(buf + len - 3, "^{}")) {
|
|
len -= 3;
|
|
buf[len] = '\0';
|
|
}
|
|
for (ref = buf + len; buf < ref; ref--)
|
|
if (isspace(ref[-1]))
|
|
break;
|
|
if (match) {
|
|
int reflen = buf + len - ref;
|
|
if (reflen < matchlen)
|
|
continue;
|
|
if (strncmp(ref, match, matchlen))
|
|
continue;
|
|
}
|
|
if (check_refname_format(ref, 0)) {
|
|
warning("ref '%s' ignored", ref);
|
|
continue;
|
|
}
|
|
if (!string_list_has_string(&existing_refs, ref)) {
|
|
printf("%s\n", buf);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int hash_callback(const struct option *opt, const char *arg, int unset)
|
|
{
|
|
hash_only = 1;
|
|
/* Use full length SHA1 if no argument */
|
|
if (!arg)
|
|
return 0;
|
|
return parse_opt_abbrev_cb(opt, arg, unset);
|
|
}
|
|
|
|
static int exclude_existing_callback(const struct option *opt, const char *arg,
|
|
int unset)
|
|
{
|
|
BUG_ON_OPT_NEG(unset);
|
|
exclude_arg = 1;
|
|
*(const char **)opt->value = 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)
|
|
{
|
|
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);
|
|
|
|
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);
|
|
for_each_ref(show_ref, NULL);
|
|
if (!found_match) {
|
|
if (verify && !quiet)
|
|
die("No match");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|