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 <jrnie...@gmail.com>
---
 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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to