Skip to content

Commit 62a2d52

Browse files
jrngitster
authored andcommitted
branch -d: avoid repeated symref resolution
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_NO_RECURSE 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 <[email protected]> Reviewed-by: Ronnie Sahlberg <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 014e7db commit 62a2d52

File tree

6 files changed

+68
-3
lines changed

6 files changed

+68
-3
lines changed

builtin/branch.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
236236
free(name);
237237

238238
name = mkpathdup(fmt, bname.buf);
239-
target = resolve_ref_unsafe(name, 0, sha1, &flags);
239+
target = resolve_ref_unsafe(name, RESOLVE_REF_NO_RECURSE,
240+
sha1, &flags);
240241
if (!target ||
241242
(!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) {
242243
error(remote_branch

cache.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,11 @@ extern int read_ref(const char *refname, unsigned char *sha1);
973973
* "writing" to the ref, the return value is the name of the ref
974974
* that will actually be created or changed.
975975
*
976+
* If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one
977+
* level of symbolic reference. The value stored in sha1 for a symbolic
978+
* reference will always be null_sha1 in this case, and the return
979+
* value is the reference that the symref refers to directly.
980+
*
976981
* If flags is non-NULL, set the value that it points to the
977982
* combination of REF_ISPACKED (if the reference was found among the
978983
* packed references), REF_ISSYMREF (if the initial reference was a
@@ -985,6 +990,7 @@ extern int read_ref(const char *refname, unsigned char *sha1);
985990
* errno is set to something meaningful on error.
986991
*/
987992
#define RESOLVE_REF_READING 0x01
993+
#define RESOLVE_REF_NO_RECURSE 0x02
988994
extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);
989995
extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);
990996

refs.c

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,6 +1467,10 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned
14671467
refname = refname_buffer;
14681468
if (flags)
14691469
*flags |= REF_ISSYMREF;
1470+
if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
1471+
hashclr(sha1);
1472+
return refname;
1473+
}
14701474
continue;
14711475
}
14721476
}
@@ -1523,13 +1527,17 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned
15231527
buf = buffer + 4;
15241528
while (isspace(*buf))
15251529
buf++;
1530+
refname = strcpy(refname_buffer, buf);
1531+
if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
1532+
hashclr(sha1);
1533+
return refname;
1534+
}
15261535
if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
15271536
if (flags)
15281537
*flags |= REF_ISBROKEN;
15291538
errno = EINVAL;
15301539
return NULL;
15311540
}
1532-
refname = strcpy(refname_buffer, buf);
15331541
}
15341542
}
15351543

@@ -2170,6 +2178,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
21702178

21712179
if (mustexist)
21722180
resolve_flags |= RESOLVE_REF_READING;
2181+
if (flags & REF_NODEREF && flags & REF_DELETING)
2182+
resolve_flags |= RESOLVE_REF_NO_RECURSE;
21732183

21742184
refname = resolve_ref_unsafe(refname, resolve_flags,
21752185
lock->old_sha1, &type);
@@ -3664,13 +3674,16 @@ int ref_transaction_commit(struct ref_transaction *transaction,
36643674
/* Acquire all locks while verifying old values */
36653675
for (i = 0; i < n; i++) {
36663676
struct ref_update *update = updates[i];
3677+
int flags = update->flags;
36673678

3679+
if (is_null_sha1(update->new_sha1))
3680+
flags |= REF_DELETING;
36683681
update->lock = lock_ref_sha1_basic(update->refname,
36693682
(update->have_old ?
36703683
update->old_sha1 :
36713684
NULL),
36723685
NULL,
3673-
update->flags,
3686+
flags,
36743687
&update->type);
36753688
if (!update->lock) {
36763689
ret = (errno == ENOTDIR)

refs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,12 @@ extern int peel_ref(const char *refname, unsigned char *sha1);
177177
* ref_transaction_create(), etc.
178178
* REF_NODEREF: act on the ref directly, instead of dereferencing
179179
* symbolic references.
180+
* REF_DELETING: tolerate broken refs
180181
*
181182
* Flags >= 0x100 are reserved for internal use.
182183
*/
183184
#define REF_NODEREF 0x01
185+
#define REF_DELETING 0x02
184186
/*
185187
* This function sets errno to something meaningful on failure.
186188
*/

t/t1400-update-ref.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,40 @@ test_expect_success "delete symref without dereference when the referred ref is
110110
cp -f .git/HEAD.orig .git/HEAD
111111
git update-ref -d $m
112112

113+
test_expect_success 'update-ref -d is not confused by self-reference' '
114+
git symbolic-ref refs/heads/self refs/heads/self &&
115+
test_when_finished "rm -f .git/refs/heads/self" &&
116+
test_path_is_file .git/refs/heads/self &&
117+
test_must_fail git update-ref -d refs/heads/self &&
118+
test_path_is_file .git/refs/heads/self
119+
'
120+
121+
test_expect_success 'update-ref --no-deref -d can delete self-reference' '
122+
git symbolic-ref refs/heads/self refs/heads/self &&
123+
test_when_finished "rm -f .git/refs/heads/self" &&
124+
test_path_is_file .git/refs/heads/self &&
125+
git update-ref --no-deref -d refs/heads/self &&
126+
test_path_is_missing .git/refs/heads/self
127+
'
128+
129+
test_expect_success 'update-ref --no-deref -d can delete reference to bad ref' '
130+
>.git/refs/heads/bad &&
131+
test_when_finished "rm -f .git/refs/heads/bad" &&
132+
git symbolic-ref refs/heads/ref-to-bad refs/heads/bad &&
133+
test_when_finished "rm -f .git/refs/heads/ref-to-bad" &&
134+
test_path_is_file .git/refs/heads/ref-to-bad &&
135+
git update-ref --no-deref -d refs/heads/ref-to-bad &&
136+
test_path_is_missing .git/refs/heads/ref-to-bad
137+
'
138+
139+
test_expect_success 'update-ref --no-deref -d can delete reference to broken name' '
140+
git symbolic-ref refs/heads/badname refs/heads/broken...ref &&
141+
test_when_finished "rm -f .git/refs/heads/badname" &&
142+
test_path_is_file .git/refs/heads/badname &&
143+
git update-ref --no-deref -d refs/heads/badname &&
144+
test_path_is_missing .git/refs/heads/badname
145+
'
146+
113147
test_expect_success '(not) create HEAD with old sha1' "
114148
test_must_fail git update-ref HEAD $A $B
115149
"

t/t3200-branch.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,15 @@ test_expect_success 'deleting a dangling symref' '
285285
test_i18ncmp expect actual
286286
'
287287

288+
test_expect_success 'deleting a self-referential symref' '
289+
git symbolic-ref refs/heads/self-reference refs/heads/self-reference &&
290+
test_path_is_file .git/refs/heads/self-reference &&
291+
echo "Deleted branch self-reference (was refs/heads/self-reference)." >expect &&
292+
git branch -d self-reference >actual &&
293+
test_path_is_missing .git/refs/heads/self-reference &&
294+
test_i18ncmp expect actual
295+
'
296+
288297
test_expect_success 'renaming a symref is not allowed' '
289298
git symbolic-ref refs/heads/master2 refs/heads/master &&
290299
test_must_fail git branch -m master2 master3 &&

0 commit comments

Comments
 (0)