If a repository gets in a broken state with too much symref nesting, it cannot be repaired with "git branch -d": $ git symbolic-ref refs/heads/nonsense refs/heads/nonsense $ git branch -d nonsense error: branch 'nonsense' not found. Worse, "git update-ref --no-deref -d" doesn't work for such repairs either: $ git update-ref -d refs/heads/nonsense error: unable to resolve reference refs/heads/nonsense: Too many levels of symbolic links Fix both by teaching resolve_ref_unsafe a new RESOLVE_REF_NODEREF flag and passing it when appropriate. Callers can still read the value of a symref (for example to print a message about it) with that flag set --- resolve_ref_unsafe will resolve one level of symrefs and stop there. Signed-off-by: Jonathan Nieder <jrnieder@xxxxxxxxx> --- builtin/branch.c | 3 ++- cache.h | 1 + refs.c | 10 ++++++++++ t/t1400-update-ref.sh | 16 ++++++++++++++++ t/t3200-branch.sh | 9 +++++++++ 5 files changed, 38 insertions(+), 1 deletion(-) diff --git a/builtin/branch.c b/builtin/branch.c index f144808..7925660 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -234,7 +234,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, free(name); name = mkpathdup(fmt, bname.buf); - target = resolve_ref_unsafe(name, sha1, &flags, 0); + target = resolve_ref_unsafe(name, sha1, + &flags, RESOLVE_REF_NODEREF); if (!target || (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) { error(remote_branch diff --git a/cache.h b/cache.h index 2d3c5ed..03ade12 100644 --- a/cache.h +++ b/cache.h @@ -982,6 +982,7 @@ extern int read_ref(const char *refname, unsigned char *sha1); * errno is set to something meaningful on error. */ #define RESOLVE_REF_READING 0x01 +#define RESOLVE_REF_NODEREF 0x02 extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int *flags, int resolve_flags); extern char *resolve_refdup(const char *ref, unsigned char *sha1, int *flags, int resolve_flags); diff --git a/refs.c b/refs.c index a5f9734..f11df33 100644 --- a/refs.c +++ b/refs.c @@ -1408,6 +1408,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int *fl refname = refname_buffer; if (flags) *flags |= REF_ISSYMREF; + if (resolve_flags & RESOLVE_REF_NODEREF) { + hashclr(sha1); + return refname; + } continue; } } @@ -1471,6 +1475,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int *fl return NULL; } refname = strcpy(refname_buffer, buf); + if (resolve_flags & RESOLVE_REF_NODEREF) { + hashclr(sha1); + return refname; + } } } @@ -2110,6 +2118,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, if (mustexist) resolve_flags |= RESOLVE_REF_READING; + if (flags & REF_NODEREF) + resolve_flags |= RESOLVE_REF_NODEREF; refname = resolve_ref_unsafe(refname, lock->old_sha1, &type, resolve_flags); diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 0218e96..ff4607b 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -110,6 +110,22 @@ test_expect_success "delete symref without dereference when the referred ref is cp -f .git/HEAD.orig .git/HEAD git update-ref -d $m +test_expect_success 'update-ref -d is not confused by self-reference' ' + git symbolic-ref refs/heads/self refs/heads/self && + test_when_finished "rm -f .git/refs/heads/self" && + test_path_is_file .git/refs/heads/self && + test_must_fail git update-ref -d refs/heads/self && + test_path_is_file .git/refs/heads/self +' + +test_expect_success 'update-ref --no-deref -d can delete self-reference' ' + git symbolic-ref refs/heads/self refs/heads/self && + test_when_finished "rm -f .git/refs/heads/self" && + test_path_is_file .git/refs/heads/self && + git update-ref --no-deref -d refs/heads/self && + test_path_is_missing .git/refs/heads/self +' + test_expect_success '(not) create HEAD with old sha1' " test_must_fail git update-ref HEAD $A $B " diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index ac31b71..432921b 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -285,6 +285,15 @@ test_expect_success 'deleting a dangling symref' ' test_i18ncmp expect actual ' +test_expect_success 'deleting a self-referential symref' ' + git symbolic-ref refs/heads/self-reference refs/heads/self-reference && + test_path_is_file .git/refs/heads/self-reference && + echo "Deleted branch self-reference (was refs/heads/self-reference)." >expect && + git branch -d self-reference >actual && + test_path_is_missing .git/refs/heads/self-reference && + test_i18ncmp expect actual +' + test_expect_success 'renaming a symref is not allowed' ' git symbolic-ref refs/heads/master2 refs/heads/master && test_must_fail git branch -m master2 master3 && -- 2.1.0.rc2.206.gedb03e5 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html