Merge branch 'dt/pre-refs-backend'
Code restructuring around the "refs" area to prepare for pluggable refs backends. * dt/pre-refs-backend: (24 commits) refs: on symref reflog expire, lock symref not referrent refs: move resolve_ref_unsafe into common code show_head_ref(): check the result of resolve_ref_namespace() check_aliased_update(): check that dst_name is non-NULL checkout_paths(): remove unneeded flag variable cmd_merge(): remove unneeded flag variable fsck_head_link(): remove unneeded flag variable read_raw_ref(): change flags parameter to unsigned int files-backend: inline resolve_ref_1() into resolve_ref_unsafe() read_raw_ref(): manage own scratch space files-backend: break out ref reading resolve_ref_1(): eliminate local variable "bad_name" resolve_ref_1(): reorder code resolve_ref_1(): eliminate local variable resolve_ref_unsafe(): ensure flags is always set resolve_ref_unsafe(): use for loop to count up to MAXDEPTH resolve_missing_loose_ref(): simplify semantics t1430: improve test coverage of deletion of badly-named refs t1430: test for-each-ref in the presence of badly-named refs t1430: don't rely on symbolic-ref for creating broken symrefs ...
This commit is contained in:
commit
edc2f715bd
@ -242,7 +242,6 @@ static int checkout_paths(const struct checkout_opts *opts,
|
||||
struct checkout state;
|
||||
static char *ps_matched;
|
||||
unsigned char rev[20];
|
||||
int flag;
|
||||
struct commit *head;
|
||||
int errs = 0;
|
||||
struct lock_file *lock_file;
|
||||
@ -375,7 +374,7 @@ static int checkout_paths(const struct checkout_opts *opts,
|
||||
if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
|
||||
die(_("unable to write new index file"));
|
||||
|
||||
read_ref_full("HEAD", 0, rev, &flag);
|
||||
read_ref_full("HEAD", 0, rev, NULL);
|
||||
head = lookup_commit_reference_gently(rev, 1);
|
||||
|
||||
errs |= post_checkout_hook(head, head, 0);
|
||||
|
@ -493,13 +493,12 @@ static void fsck_object_dir(const char *path)
|
||||
|
||||
static int fsck_head_link(void)
|
||||
{
|
||||
int flag;
|
||||
int null_is_error = 0;
|
||||
|
||||
if (verbose)
|
||||
fprintf(stderr, "Checking HEAD link\n");
|
||||
|
||||
head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, &flag);
|
||||
head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, NULL);
|
||||
if (!head_points_at) {
|
||||
errors_found |= ERROR_REFS;
|
||||
return error("Invalid HEAD");
|
||||
|
@ -1168,7 +1168,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
|
||||
struct commit *head_commit;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
const char *head_arg;
|
||||
int flag, i, ret = 0, head_subsumed;
|
||||
int i, ret = 0, head_subsumed;
|
||||
int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
|
||||
struct commit_list *common = NULL;
|
||||
const char *best_strategy = NULL, *wt_strategy = NULL;
|
||||
@ -1182,7 +1182,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
|
||||
* Check if we are _not_ on a detached HEAD, i.e. if there is a
|
||||
* current branch.
|
||||
*/
|
||||
branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, &flag);
|
||||
branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, NULL);
|
||||
if (branch && starts_with(branch, "refs/heads/"))
|
||||
branch += 11;
|
||||
if (!branch || is_null_sha1(head_sha1))
|
||||
|
@ -1084,13 +1084,13 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
|
||||
if (!(flag & REF_ISSYMREF))
|
||||
return;
|
||||
|
||||
dst_name = strip_namespace(dst_name);
|
||||
if (!dst_name) {
|
||||
rp_error("refusing update to broken symref '%s'", cmd->ref_name);
|
||||
cmd->skip_update = 1;
|
||||
cmd->error_string = "broken symref";
|
||||
return;
|
||||
}
|
||||
dst_name = strip_namespace(dst_name);
|
||||
|
||||
if ((item = string_list_lookup(list, dst_name)) == NULL)
|
||||
return;
|
||||
|
@ -484,9 +484,9 @@ static int show_head_ref(const char *refname, const struct object_id *oid,
|
||||
const char *target = resolve_ref_unsafe(refname,
|
||||
RESOLVE_REF_READING,
|
||||
unused.hash, NULL);
|
||||
const char *target_nons = strip_namespace(target);
|
||||
|
||||
strbuf_addf(buf, "ref: %s\n", target_nons);
|
||||
if (target)
|
||||
strbuf_addf(buf, "ref: %s\n", strip_namespace(target));
|
||||
} else {
|
||||
strbuf_addf(buf, "%s\n", oid_to_hex(oid));
|
||||
}
|
||||
|
149
refs.c
149
refs.c
@ -1080,3 +1080,152 @@ int rename_ref_available(const char *oldname, const char *newname)
|
||||
strbuf_release(&err);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
struct object_id oid;
|
||||
int flag;
|
||||
|
||||
if (submodule) {
|
||||
if (resolve_gitlink_ref(submodule, "HEAD", oid.hash) == 0)
|
||||
return fn("HEAD", &oid, 0, cb_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!read_ref_full("HEAD", RESOLVE_REF_READING, oid.hash, &flag))
|
||||
return fn("HEAD", &oid, flag, cb_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int head_ref(each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return head_ref_submodule(NULL, fn, cb_data);
|
||||
}
|
||||
|
||||
int for_each_ref(each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(NULL, "", fn, 0, 0, cb_data);
|
||||
}
|
||||
|
||||
int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(submodule, "", fn, 0, 0, cb_data);
|
||||
}
|
||||
|
||||
int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data);
|
||||
}
|
||||
|
||||
int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
|
||||
{
|
||||
unsigned int flag = 0;
|
||||
|
||||
if (broken)
|
||||
flag = DO_FOR_EACH_INCLUDE_BROKEN;
|
||||
return do_for_each_ref(NULL, prefix, fn, 0, flag, cb_data);
|
||||
}
|
||||
|
||||
int for_each_ref_in_submodule(const char *submodule, const char *prefix,
|
||||
each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data);
|
||||
}
|
||||
|
||||
int for_each_replace_ref(each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(NULL, git_replace_ref_base, fn,
|
||||
strlen(git_replace_ref_base), 0, cb_data);
|
||||
}
|
||||
|
||||
int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int ret;
|
||||
strbuf_addf(&buf, "%srefs/", get_git_namespace());
|
||||
ret = do_for_each_ref(NULL, buf.buf, fn, 0, 0, cb_data);
|
||||
strbuf_release(&buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int for_each_rawref(each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(NULL, "", fn, 0,
|
||||
DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
|
||||
}
|
||||
|
||||
/* This function needs to return a meaningful errno on failure */
|
||||
const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
|
||||
unsigned char *sha1, int *flags)
|
||||
{
|
||||
static struct strbuf sb_refname = STRBUF_INIT;
|
||||
int unused_flags;
|
||||
int symref_count;
|
||||
|
||||
if (!flags)
|
||||
flags = &unused_flags;
|
||||
|
||||
*flags = 0;
|
||||
|
||||
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
|
||||
if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
|
||||
!refname_is_safe(refname)) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* dwim_ref() uses REF_ISBROKEN to distinguish between
|
||||
* missing refs and refs that were present but invalid,
|
||||
* to complain about the latter to stderr.
|
||||
*
|
||||
* We don't know whether the ref exists, so don't set
|
||||
* REF_ISBROKEN yet.
|
||||
*/
|
||||
*flags |= REF_BAD_NAME;
|
||||
}
|
||||
|
||||
for (symref_count = 0; symref_count < SYMREF_MAXDEPTH; symref_count++) {
|
||||
unsigned int read_flags = 0;
|
||||
|
||||
if (read_raw_ref(refname, sha1, &sb_refname, &read_flags)) {
|
||||
*flags |= read_flags;
|
||||
if (errno != ENOENT || (resolve_flags & RESOLVE_REF_READING))
|
||||
return NULL;
|
||||
hashclr(sha1);
|
||||
if (*flags & REF_BAD_NAME)
|
||||
*flags |= REF_ISBROKEN;
|
||||
return refname;
|
||||
}
|
||||
|
||||
*flags |= read_flags;
|
||||
|
||||
if (!(read_flags & REF_ISSYMREF)) {
|
||||
if (*flags & REF_BAD_NAME) {
|
||||
hashclr(sha1);
|
||||
*flags |= REF_ISBROKEN;
|
||||
}
|
||||
return refname;
|
||||
}
|
||||
|
||||
refname = sb_refname.buf;
|
||||
if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
|
||||
hashclr(sha1);
|
||||
return refname;
|
||||
}
|
||||
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
|
||||
if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
|
||||
!refname_is_safe(refname)) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*flags |= REF_ISBROKEN | REF_BAD_NAME;
|
||||
}
|
||||
}
|
||||
|
||||
errno = ELOOP;
|
||||
return NULL;
|
||||
}
|
||||
|
@ -513,9 +513,6 @@ static void sort_ref_dir(struct ref_dir *dir)
|
||||
dir->sorted = dir->nr = i;
|
||||
}
|
||||
|
||||
/* Include broken references in a do_for_each_ref*() iteration: */
|
||||
#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
|
||||
|
||||
/*
|
||||
* Return true iff the reference described by entry can be resolved to
|
||||
* an object in the database. Emit a warning if the referred-to
|
||||
@ -1272,8 +1269,6 @@ static struct ref_dir *get_loose_refs(struct ref_cache *refs)
|
||||
return get_ref_dir(refs->loose);
|
||||
}
|
||||
|
||||
/* We allow "recursive" symbolic refs. Only within reason, though */
|
||||
#define MAXDEPTH 5
|
||||
#define MAXREFLEN (1024)
|
||||
|
||||
/*
|
||||
@ -1303,7 +1298,7 @@ static int resolve_gitlink_ref_recursive(struct ref_cache *refs,
|
||||
char buffer[128], *p;
|
||||
char *path;
|
||||
|
||||
if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN)
|
||||
if (recursion > SYMREF_MAXDEPTH || strlen(refname) > MAXREFLEN)
|
||||
return -1;
|
||||
path = *refs->name
|
||||
? git_pathdup_submodule(refs->name, "%s", refname)
|
||||
@ -1371,13 +1366,11 @@ static struct ref_entry *get_packed_ref(const char *refname)
|
||||
}
|
||||
|
||||
/*
|
||||
* A loose ref file doesn't exist; check for a packed ref. The
|
||||
* options are forwarded from resolve_safe_unsafe().
|
||||
* A loose ref file doesn't exist; check for a packed ref.
|
||||
*/
|
||||
static int resolve_missing_loose_ref(const char *refname,
|
||||
int resolve_flags,
|
||||
unsigned char *sha1,
|
||||
int *flags)
|
||||
unsigned int *flags)
|
||||
{
|
||||
struct ref_entry *entry;
|
||||
|
||||
@ -1388,205 +1381,158 @@ static int resolve_missing_loose_ref(const char *refname,
|
||||
entry = get_packed_ref(refname);
|
||||
if (entry) {
|
||||
hashcpy(sha1, entry->u.value.oid.hash);
|
||||
if (flags)
|
||||
*flags |= REF_ISPACKED;
|
||||
return 0;
|
||||
}
|
||||
/* The reference is not a packed reference, either. */
|
||||
if (resolve_flags & RESOLVE_REF_READING) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
} else {
|
||||
hashclr(sha1);
|
||||
*flags |= REF_ISPACKED;
|
||||
return 0;
|
||||
}
|
||||
/* refname is not a packed reference. */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* This function needs to return a meaningful errno on failure */
|
||||
static const char *resolve_ref_1(const char *refname,
|
||||
int resolve_flags,
|
||||
unsigned char *sha1,
|
||||
int *flags,
|
||||
struct strbuf *sb_refname,
|
||||
struct strbuf *sb_path,
|
||||
struct strbuf *sb_contents)
|
||||
/*
|
||||
* Read a raw ref from the filesystem or packed refs file.
|
||||
*
|
||||
* If the ref is a sha1, fill in sha1 and return 0.
|
||||
*
|
||||
* If the ref is symbolic, fill in *symref with the referrent
|
||||
* (e.g. "refs/heads/master") and return 0. The caller is responsible
|
||||
* for validating the referrent. Set REF_ISSYMREF in flags.
|
||||
*
|
||||
* If the ref doesn't exist, set errno to ENOENT and return -1.
|
||||
*
|
||||
* If the ref exists but is neither a symbolic ref nor a sha1, it is
|
||||
* broken. Set REF_ISBROKEN in flags, set errno to EINVAL, and return
|
||||
* -1.
|
||||
*
|
||||
* If there is another error reading the ref, set errno appropriately and
|
||||
* return -1.
|
||||
*
|
||||
* Backend-specific flags might be set in flags as well, regardless of
|
||||
* outcome.
|
||||
*
|
||||
* sb_path is workspace: the caller should allocate and free it.
|
||||
*
|
||||
* It is OK for refname to point into symref. In this case:
|
||||
* - if the function succeeds with REF_ISSYMREF, symref will be
|
||||
* overwritten and the memory pointed to by refname might be changed
|
||||
* or even freed.
|
||||
* - in all other cases, symref will be untouched, and therefore
|
||||
* refname will still be valid and unchanged.
|
||||
*/
|
||||
int read_raw_ref(const char *refname, unsigned char *sha1,
|
||||
struct strbuf *symref, unsigned int *flags)
|
||||
{
|
||||
int depth = MAXDEPTH;
|
||||
int bad_name = 0;
|
||||
struct strbuf sb_contents = STRBUF_INIT;
|
||||
struct strbuf sb_path = STRBUF_INIT;
|
||||
const char *path;
|
||||
const char *buf;
|
||||
struct stat st;
|
||||
int fd;
|
||||
int ret = -1;
|
||||
int save_errno;
|
||||
|
||||
if (flags)
|
||||
*flags = 0;
|
||||
strbuf_reset(&sb_path);
|
||||
strbuf_git_path(&sb_path, "%s", refname);
|
||||
path = sb_path.buf;
|
||||
|
||||
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
|
||||
if (flags)
|
||||
*flags |= REF_BAD_NAME;
|
||||
stat_ref:
|
||||
/*
|
||||
* We might have to loop back here to avoid a race
|
||||
* condition: first we lstat() the file, then we try
|
||||
* to read it as a link or as a file. But if somebody
|
||||
* changes the type of the file (file <-> directory
|
||||
* <-> symlink) between the lstat() and reading, then
|
||||
* we don't want to report that as an error but rather
|
||||
* try again starting with the lstat().
|
||||
*/
|
||||
|
||||
if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
|
||||
!refname_is_safe(refname)) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
if (lstat(path, &st) < 0) {
|
||||
if (errno != ENOENT)
|
||||
goto out;
|
||||
if (resolve_missing_loose_ref(refname, sha1, flags)) {
|
||||
errno = ENOENT;
|
||||
goto out;
|
||||
}
|
||||
/*
|
||||
* dwim_ref() uses REF_ISBROKEN to distinguish between
|
||||
* missing refs and refs that were present but invalid,
|
||||
* to complain about the latter to stderr.
|
||||
*
|
||||
* We don't know whether the ref exists, so don't set
|
||||
* REF_ISBROKEN yet.
|
||||
*/
|
||||
bad_name = 1;
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
for (;;) {
|
||||
const char *path;
|
||||
struct stat st;
|
||||
char *buf;
|
||||
int fd;
|
||||
|
||||
if (--depth < 0) {
|
||||
errno = ELOOP;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
strbuf_reset(sb_path);
|
||||
strbuf_git_path(sb_path, "%s", refname);
|
||||
path = sb_path->buf;
|
||||
|
||||
/*
|
||||
* We might have to loop back here to avoid a race
|
||||
* condition: first we lstat() the file, then we try
|
||||
* to read it as a link or as a file. But if somebody
|
||||
* changes the type of the file (file <-> directory
|
||||
* <-> symlink) between the lstat() and reading, then
|
||||
* we don't want to report that as an error but rather
|
||||
* try again starting with the lstat().
|
||||
*/
|
||||
stat_ref:
|
||||
if (lstat(path, &st) < 0) {
|
||||
if (errno != ENOENT)
|
||||
return NULL;
|
||||
if (resolve_missing_loose_ref(refname, resolve_flags,
|
||||
sha1, flags))
|
||||
return NULL;
|
||||
if (bad_name) {
|
||||
hashclr(sha1);
|
||||
if (flags)
|
||||
*flags |= REF_ISBROKEN;
|
||||
}
|
||||
return refname;
|
||||
}
|
||||
|
||||
/* Follow "normalized" - ie "refs/.." symlinks by hand */
|
||||
if (S_ISLNK(st.st_mode)) {
|
||||
strbuf_reset(sb_contents);
|
||||
if (strbuf_readlink(sb_contents, path, 0) < 0) {
|
||||
if (errno == ENOENT || errno == EINVAL)
|
||||
/* inconsistent with lstat; retry */
|
||||
goto stat_ref;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
if (starts_with(sb_contents->buf, "refs/") &&
|
||||
!check_refname_format(sb_contents->buf, 0)) {
|
||||
strbuf_swap(sb_refname, sb_contents);
|
||||
refname = sb_refname->buf;
|
||||
if (flags)
|
||||
*flags |= REF_ISSYMREF;
|
||||
if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
|
||||
hashclr(sha1);
|
||||
return refname;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Is it a directory? */
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
errno = EISDIR;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Anything else, just open it and try to use it as
|
||||
* a ref
|
||||
*/
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
if (errno == ENOENT)
|
||||
/* Follow "normalized" - ie "refs/.." symlinks by hand */
|
||||
if (S_ISLNK(st.st_mode)) {
|
||||
strbuf_reset(&sb_contents);
|
||||
if (strbuf_readlink(&sb_contents, path, 0) < 0) {
|
||||
if (errno == ENOENT || errno == EINVAL)
|
||||
/* inconsistent with lstat; retry */
|
||||
goto stat_ref;
|
||||
else
|
||||
return NULL;
|
||||
goto out;
|
||||
}
|
||||
strbuf_reset(sb_contents);
|
||||
if (strbuf_read(sb_contents, fd, 256) < 0) {
|
||||
int save_errno = errno;
|
||||
close(fd);
|
||||
errno = save_errno;
|
||||
return NULL;
|
||||
}
|
||||
close(fd);
|
||||
strbuf_rtrim(sb_contents);
|
||||
|
||||
/*
|
||||
* Is it a symbolic ref?
|
||||
*/
|
||||
if (!starts_with(sb_contents->buf, "ref:")) {
|
||||
/*
|
||||
* Please note that FETCH_HEAD has a second
|
||||
* line containing other data.
|
||||
*/
|
||||
if (get_sha1_hex(sb_contents->buf, sha1) ||
|
||||
(sb_contents->buf[40] != '\0' && !isspace(sb_contents->buf[40]))) {
|
||||
if (flags)
|
||||
*flags |= REF_ISBROKEN;
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
if (bad_name) {
|
||||
hashclr(sha1);
|
||||
if (flags)
|
||||
*flags |= REF_ISBROKEN;
|
||||
}
|
||||
return refname;
|
||||
}
|
||||
if (flags)
|
||||
if (starts_with(sb_contents.buf, "refs/") &&
|
||||
!check_refname_format(sb_contents.buf, 0)) {
|
||||
strbuf_swap(&sb_contents, symref);
|
||||
*flags |= REF_ISSYMREF;
|
||||
buf = sb_contents->buf + 4;
|
||||
while (isspace(*buf))
|
||||
buf++;
|
||||
strbuf_reset(sb_refname);
|
||||
strbuf_addstr(sb_refname, buf);
|
||||
refname = sb_refname->buf;
|
||||
if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
|
||||
hashclr(sha1);
|
||||
return refname;
|
||||
}
|
||||
if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
|
||||
if (flags)
|
||||
*flags |= REF_ISBROKEN;
|
||||
|
||||
if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
|
||||
!refname_is_safe(buf)) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
bad_name = 1;
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
|
||||
unsigned char *sha1, int *flags)
|
||||
{
|
||||
static struct strbuf sb_refname = STRBUF_INIT;
|
||||
struct strbuf sb_contents = STRBUF_INIT;
|
||||
struct strbuf sb_path = STRBUF_INIT;
|
||||
const char *ret;
|
||||
/* Is it a directory? */
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
errno = EISDIR;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = resolve_ref_1(refname, resolve_flags, sha1, flags,
|
||||
&sb_refname, &sb_path, &sb_contents);
|
||||
/*
|
||||
* Anything else, just open it and try to use it as
|
||||
* a ref
|
||||
*/
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
if (errno == ENOENT)
|
||||
/* inconsistent with lstat; retry */
|
||||
goto stat_ref;
|
||||
else
|
||||
goto out;
|
||||
}
|
||||
strbuf_reset(&sb_contents);
|
||||
if (strbuf_read(&sb_contents, fd, 256) < 0) {
|
||||
int save_errno = errno;
|
||||
close(fd);
|
||||
errno = save_errno;
|
||||
goto out;
|
||||
}
|
||||
close(fd);
|
||||
strbuf_rtrim(&sb_contents);
|
||||
buf = sb_contents.buf;
|
||||
if (starts_with(buf, "ref:")) {
|
||||
buf += 4;
|
||||
while (isspace(*buf))
|
||||
buf++;
|
||||
|
||||
strbuf_reset(symref);
|
||||
strbuf_addstr(symref, buf);
|
||||
*flags |= REF_ISSYMREF;
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Please note that FETCH_HEAD has additional
|
||||
* data after the sha.
|
||||
*/
|
||||
if (get_sha1_hex(buf, sha1) ||
|
||||
(buf[40] != '\0' && !isspace(buf[40]))) {
|
||||
*flags |= REF_ISBROKEN;
|
||||
errno = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
save_errno = errno;
|
||||
strbuf_release(&sb_path);
|
||||
strbuf_release(&sb_contents);
|
||||
errno = save_errno;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -1727,10 +1673,13 @@ static int do_for_each_entry(struct ref_cache *refs, const char *base,
|
||||
* value, stop the iteration and return that value; otherwise, return
|
||||
* 0.
|
||||
*/
|
||||
static int do_for_each_ref(struct ref_cache *refs, const char *base,
|
||||
each_ref_fn fn, int trim, int flags, void *cb_data)
|
||||
int do_for_each_ref(const char *submodule, const char *base,
|
||||
each_ref_fn fn, int trim, int flags, void *cb_data)
|
||||
{
|
||||
struct ref_entry_cb data;
|
||||
struct ref_cache *refs;
|
||||
|
||||
refs = get_ref_cache(submodule);
|
||||
data.base = base;
|
||||
data.trim = trim;
|
||||
data.flags = flags;
|
||||
@ -1745,86 +1694,6 @@ static int do_for_each_ref(struct ref_cache *refs, const char *base,
|
||||
return do_for_each_entry(refs, base, do_one_ref, &data);
|
||||
}
|
||||
|
||||
static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
struct object_id oid;
|
||||
int flag;
|
||||
|
||||
if (submodule) {
|
||||
if (resolve_gitlink_ref(submodule, "HEAD", oid.hash) == 0)
|
||||
return fn("HEAD", &oid, 0, cb_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!read_ref_full("HEAD", RESOLVE_REF_READING, oid.hash, &flag))
|
||||
return fn("HEAD", &oid, flag, cb_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int head_ref(each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_head_ref(NULL, fn, cb_data);
|
||||
}
|
||||
|
||||
int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_head_ref(submodule, fn, cb_data);
|
||||
}
|
||||
|
||||
int for_each_ref(each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(&ref_cache, "", fn, 0, 0, cb_data);
|
||||
}
|
||||
|
||||
int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(get_ref_cache(submodule), "", fn, 0, 0, cb_data);
|
||||
}
|
||||
|
||||
int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data);
|
||||
}
|
||||
|
||||
int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
|
||||
{
|
||||
unsigned int flag = 0;
|
||||
|
||||
if (broken)
|
||||
flag = DO_FOR_EACH_INCLUDE_BROKEN;
|
||||
return do_for_each_ref(&ref_cache, prefix, fn, 0, flag, cb_data);
|
||||
}
|
||||
|
||||
int for_each_ref_in_submodule(const char *submodule, const char *prefix,
|
||||
each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(get_ref_cache(submodule), prefix, fn, strlen(prefix), 0, cb_data);
|
||||
}
|
||||
|
||||
int for_each_replace_ref(each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(&ref_cache, git_replace_ref_base, fn,
|
||||
strlen(git_replace_ref_base), 0, cb_data);
|
||||
}
|
||||
|
||||
int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int ret;
|
||||
strbuf_addf(&buf, "%srefs/", get_git_namespace());
|
||||
ret = do_for_each_ref(&ref_cache, buf.buf, fn, 0, 0, cb_data);
|
||||
strbuf_release(&buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int for_each_rawref(each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(&ref_cache, "", fn, 0,
|
||||
DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
|
||||
}
|
||||
|
||||
static void unlock_ref(struct ref_lock *lock)
|
||||
{
|
||||
/* Do not free lock->lk -- atexit() still looks at them */
|
||||
@ -3481,7 +3350,8 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
|
||||
* reference itself, plus we might need to update the
|
||||
* reference if --updateref was specified:
|
||||
*/
|
||||
lock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, 0, &type, &err);
|
||||
lock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, REF_NODEREF,
|
||||
&type, &err);
|
||||
if (!lock) {
|
||||
error("cannot lock ref '%s': %s", refname, err.buf);
|
||||
strbuf_release(&err);
|
||||
|
@ -197,4 +197,19 @@ const char *find_descendant_ref(const char *dirname,
|
||||
|
||||
int rename_ref_available(const char *oldname, const char *newname);
|
||||
|
||||
/* We allow "recursive" symbolic refs. Only within reason, though */
|
||||
#define SYMREF_MAXDEPTH 5
|
||||
|
||||
/* Include broken references in a do_for_each_ref*() iteration: */
|
||||
#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
|
||||
|
||||
/*
|
||||
* The common backend for the for_each_*ref* functions
|
||||
*/
|
||||
int do_for_each_ref(const char *submodule, const char *base,
|
||||
each_ref_fn fn, int trim, int flags, void *cb_data);
|
||||
|
||||
int read_raw_ref(const char *refname, unsigned char *sha1,
|
||||
struct strbuf *symref, unsigned int *flags);
|
||||
|
||||
#endif /* REFS_REFS_INTERNAL_H */
|
||||
|
@ -338,4 +338,14 @@ test_expect_failure 'reflog with non-commit entries displays all entries' '
|
||||
test_line_count = 3 actual
|
||||
'
|
||||
|
||||
test_expect_success 'reflog expire operates on symref not referrent' '
|
||||
git branch -l the_symref &&
|
||||
git branch -l referrent &&
|
||||
git update-ref referrent HEAD &&
|
||||
git symbolic-ref refs/heads/the_symref refs/heads/referrent &&
|
||||
test_when_finished "rm -f .git/refs/heads/referrent.lock" &&
|
||||
touch .git/refs/heads/referrent.lock &&
|
||||
git reflog expire --expire=all the_symref
|
||||
'
|
||||
|
||||
test_done
|
||||
|
@ -42,7 +42,7 @@ test_expect_success 'git branch shows badly named ref as warning' '
|
||||
cp .git/refs/heads/master .git/refs/heads/broken...ref &&
|
||||
test_when_finished "rm -f .git/refs/heads/broken...ref" &&
|
||||
git branch >output 2>error &&
|
||||
grep -e "broken\.\.\.ref" error &&
|
||||
test_i18ngrep -e "ignoring ref with broken name refs/heads/broken\.\.\.ref" error &&
|
||||
! grep -e "broken\.\.\.ref" output
|
||||
'
|
||||
|
||||
@ -147,35 +147,145 @@ test_expect_success 'rev-parse skips symref pointing to broken name' '
|
||||
test_when_finished "rm -f .git/refs/heads/broken...ref" &&
|
||||
git branch shadow one &&
|
||||
cp .git/refs/heads/master .git/refs/heads/broken...ref &&
|
||||
git symbolic-ref refs/tags/shadow refs/heads/broken...ref &&
|
||||
|
||||
printf "ref: refs/heads/broken...ref\n" >.git/refs/tags/shadow &&
|
||||
test_when_finished "rm -f .git/refs/tags/shadow" &&
|
||||
git rev-parse --verify one >expect &&
|
||||
git rev-parse --verify shadow >actual 2>err &&
|
||||
test_cmp expect actual &&
|
||||
test_i18ngrep "ignoring.*refs/tags/shadow" err
|
||||
test_i18ngrep "ignoring dangling symref refs/tags/shadow" err
|
||||
'
|
||||
|
||||
test_expect_success 'update-ref --no-deref -d can delete reference to broken name' '
|
||||
git symbolic-ref refs/heads/badname refs/heads/broken...ref &&
|
||||
test_expect_success 'for-each-ref emits warnings for broken names' '
|
||||
cp .git/refs/heads/master .git/refs/heads/broken...ref &&
|
||||
test_when_finished "rm -f .git/refs/heads/broken...ref" &&
|
||||
printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname &&
|
||||
test_when_finished "rm -f .git/refs/heads/badname" &&
|
||||
test_path_is_file .git/refs/heads/badname &&
|
||||
git update-ref --no-deref -d refs/heads/badname &&
|
||||
test_path_is_missing .git/refs/heads/badname
|
||||
printf "ref: refs/heads/master\n" >.git/refs/heads/broken...symref &&
|
||||
test_when_finished "rm -f .git/refs/heads/broken...symref" &&
|
||||
git for-each-ref >output 2>error &&
|
||||
! grep -e "broken\.\.\.ref" output &&
|
||||
! grep -e "badname" output &&
|
||||
! grep -e "broken\.\.\.symref" output &&
|
||||
test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.ref" error &&
|
||||
test_i18ngrep "ignoring broken ref refs/heads/badname" error &&
|
||||
test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.symref" error
|
||||
'
|
||||
|
||||
test_expect_success 'update-ref -d can delete broken name' '
|
||||
cp .git/refs/heads/master .git/refs/heads/broken...ref &&
|
||||
test_when_finished "rm -f .git/refs/heads/broken...ref" &&
|
||||
git update-ref -d refs/heads/broken...ref &&
|
||||
git update-ref -d refs/heads/broken...ref >output 2>error &&
|
||||
test_must_be_empty output &&
|
||||
test_must_be_empty error &&
|
||||
git branch >output 2>error &&
|
||||
! grep -e "broken\.\.\.ref" error &&
|
||||
! grep -e "broken\.\.\.ref" output
|
||||
'
|
||||
|
||||
test_expect_success 'branch -d can delete broken name' '
|
||||
cp .git/refs/heads/master .git/refs/heads/broken...ref &&
|
||||
test_when_finished "rm -f .git/refs/heads/broken...ref" &&
|
||||
git branch -d broken...ref >output 2>error &&
|
||||
test_i18ngrep "Deleted branch broken...ref (was broken)" output &&
|
||||
test_must_be_empty error &&
|
||||
git branch >output 2>error &&
|
||||
! grep -e "broken\.\.\.ref" error &&
|
||||
! grep -e "broken\.\.\.ref" output
|
||||
'
|
||||
|
||||
test_expect_success 'update-ref --no-deref -d can delete symref to broken name' '
|
||||
cp .git/refs/heads/master .git/refs/heads/broken...ref &&
|
||||
test_when_finished "rm -f .git/refs/heads/broken...ref" &&
|
||||
printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname &&
|
||||
test_when_finished "rm -f .git/refs/heads/badname" &&
|
||||
git update-ref --no-deref -d refs/heads/badname >output 2>error &&
|
||||
test_path_is_missing .git/refs/heads/badname &&
|
||||
test_must_be_empty output &&
|
||||
test_must_be_empty error
|
||||
'
|
||||
|
||||
test_expect_success 'branch -d can delete symref to broken name' '
|
||||
cp .git/refs/heads/master .git/refs/heads/broken...ref &&
|
||||
test_when_finished "rm -f .git/refs/heads/broken...ref" &&
|
||||
printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname &&
|
||||
test_when_finished "rm -f .git/refs/heads/badname" &&
|
||||
git branch -d badname >output 2>error &&
|
||||
test_path_is_missing .git/refs/heads/badname &&
|
||||
test_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output &&
|
||||
test_must_be_empty error
|
||||
'
|
||||
|
||||
test_expect_success 'update-ref --no-deref -d can delete dangling symref to broken name' '
|
||||
printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname &&
|
||||
test_when_finished "rm -f .git/refs/heads/badname" &&
|
||||
git update-ref --no-deref -d refs/heads/badname >output 2>error &&
|
||||
test_path_is_missing .git/refs/heads/badname &&
|
||||
test_must_be_empty output &&
|
||||
test_must_be_empty error
|
||||
'
|
||||
|
||||
test_expect_success 'branch -d can delete dangling symref to broken name' '
|
||||
printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname &&
|
||||
test_when_finished "rm -f .git/refs/heads/badname" &&
|
||||
git branch -d badname >output 2>error &&
|
||||
test_path_is_missing .git/refs/heads/badname &&
|
||||
test_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output &&
|
||||
test_must_be_empty error
|
||||
'
|
||||
|
||||
test_expect_success 'update-ref -d can delete broken name through symref' '
|
||||
cp .git/refs/heads/master .git/refs/heads/broken...ref &&
|
||||
test_when_finished "rm -f .git/refs/heads/broken...ref" &&
|
||||
printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname &&
|
||||
test_when_finished "rm -f .git/refs/heads/badname" &&
|
||||
git update-ref -d refs/heads/badname >output 2>error &&
|
||||
test_path_is_missing .git/refs/heads/broken...ref &&
|
||||
test_must_be_empty output &&
|
||||
test_must_be_empty error
|
||||
'
|
||||
|
||||
test_expect_success 'update-ref --no-deref -d can delete symref with broken name' '
|
||||
printf "ref: refs/heads/master\n" >.git/refs/heads/broken...symref &&
|
||||
test_when_finished "rm -f .git/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_must_be_empty output &&
|
||||
test_must_be_empty error
|
||||
'
|
||||
|
||||
test_expect_success 'branch -d can delete symref with broken name' '
|
||||
printf "ref: refs/heads/master\n" >.git/refs/heads/broken...symref &&
|
||||
test_when_finished "rm -f .git/refs/heads/broken...symref" &&
|
||||
git branch -d broken...symref >output 2>error &&
|
||||
test_path_is_missing .git/refs/heads/broken...symref &&
|
||||
test_i18ngrep "Deleted branch broken...symref (was refs/heads/master)" output &&
|
||||
test_must_be_empty error
|
||||
'
|
||||
|
||||
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 "rm -f .git/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_must_be_empty output &&
|
||||
test_must_be_empty error
|
||||
'
|
||||
|
||||
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 "rm -f .git/refs/heads/broken...symref" &&
|
||||
git branch -d broken...symref >output 2>error &&
|
||||
test_path_is_missing .git/refs/heads/broken...symref &&
|
||||
test_i18ngrep "Deleted branch broken...symref (was refs/heads/idonotexist)" output &&
|
||||
test_must_be_empty error
|
||||
'
|
||||
|
||||
test_expect_success 'update-ref -d cannot delete non-ref in .git dir' '
|
||||
echo precious >.git/my-private-file &&
|
||||
echo precious >expect &&
|
||||
test_must_fail git update-ref -d my-private-file &&
|
||||
test_must_fail git update-ref -d my-private-file >output 2>error &&
|
||||
test_must_be_empty output &&
|
||||
test_i18ngrep -e "cannot lock .*: unable to resolve reference" error &&
|
||||
test_cmp expect .git/my-private-file
|
||||
'
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user