Skip to content

Commit 90ce285

Browse files
committed
Merge branch 'jk/symbolic-ref'
The low-level code that is used to create symbolic references has been updated to share more code with the code that deals with normal references. * jk/symbolic-ref: lock_ref_sha1_basic: handle REF_NODEREF with invalid refs lock_ref_sha1_basic: always fill old_oid while holding lock checkout,clone: check return value of create_symref create_symref: write reflog while holding lock create_symref: use existing ref-lock code create_symref: modernize variable names
2 parents b2ed5ae + 2859dcd commit 90ce285

File tree

6 files changed

+140
-77
lines changed

6 files changed

+140
-77
lines changed

builtin/checkout.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
661661
describe_detached_head(_("HEAD is now at"), new->commit);
662662
}
663663
} else if (new->path) { /* Switch branches. */
664-
create_symref("HEAD", new->path, msg.buf);
664+
if (create_symref("HEAD", new->path, msg.buf) < 0)
665+
die("unable to update HEAD");
665666
if (!opts->quiet) {
666667
if (old->path && !strcmp(new->path, old->path)) {
667668
if (opts->new_branch_force)

builtin/clone.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -636,9 +636,11 @@ static void update_remote_refs(const struct ref *refs,
636636
struct strbuf head_ref = STRBUF_INIT;
637637
strbuf_addstr(&head_ref, branch_top);
638638
strbuf_addstr(&head_ref, "HEAD");
639-
create_symref(head_ref.buf,
640-
remote_head_points_at->peer_ref->name,
641-
msg);
639+
if (create_symref(head_ref.buf,
640+
remote_head_points_at->peer_ref->name,
641+
msg) < 0)
642+
die("unable to update %s", head_ref.buf);
643+
strbuf_release(&head_ref);
642644
}
643645
}
644646

@@ -648,7 +650,8 @@ static void update_head(const struct ref *our, const struct ref *remote,
648650
const char *head;
649651
if (our && skip_prefix(our->name, "refs/heads/", &head)) {
650652
/* Local default branch link */
651-
create_symref("HEAD", our->name, NULL);
653+
if (create_symref("HEAD", our->name, NULL) < 0)
654+
die("unable to update HEAD");
652655
if (!option_bare) {
653656
update_ref(msg, "HEAD", our->old_oid.hash, NULL, 0,
654657
UPDATE_REFS_DIE_ON_ERR);

refs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ extern char *shorten_unambiguous_ref(const char *refname, int strict);
292292
/** rename ref, return 0 on success **/
293293
extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);
294294

295-
extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
295+
extern int create_symref(const char *refname, const char *target, const char *logmsg);
296296

297297
enum action_on_err {
298298
UPDATE_REFS_MSG_ON_ERR,

refs/files-backend.c

Lines changed: 76 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1840,12 +1840,17 @@ static int verify_lock(struct ref_lock *lock,
18401840
if (read_ref_full(lock->ref_name,
18411841
mustexist ? RESOLVE_REF_READING : 0,
18421842
lock->old_oid.hash, NULL)) {
1843-
int save_errno = errno;
1844-
strbuf_addf(err, "can't verify ref %s", lock->ref_name);
1845-
errno = save_errno;
1846-
return -1;
1843+
if (old_sha1) {
1844+
int save_errno = errno;
1845+
strbuf_addf(err, "can't verify ref %s", lock->ref_name);
1846+
errno = save_errno;
1847+
return -1;
1848+
} else {
1849+
hashclr(lock->old_oid.hash);
1850+
return 0;
1851+
}
18471852
}
1848-
if (hashcmp(lock->old_oid.hash, old_sha1)) {
1853+
if (old_sha1 && hashcmp(lock->old_oid.hash, old_sha1)) {
18491854
strbuf_addf(err, "ref %s is at %s but expected %s",
18501855
lock->ref_name,
18511856
sha1_to_hex(lock->old_oid.hash),
@@ -1882,7 +1887,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
18821887
const char *orig_refname = refname;
18831888
struct ref_lock *lock;
18841889
int last_errno = 0;
1885-
int type, lflags;
1890+
int type;
1891+
int lflags = 0;
18861892
int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
18871893
int resolve_flags = 0;
18881894
int attempts_remaining = 3;
@@ -1893,10 +1899,11 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
18931899

18941900
if (mustexist)
18951901
resolve_flags |= RESOLVE_REF_READING;
1896-
if (flags & REF_DELETING) {
1902+
if (flags & REF_DELETING)
18971903
resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;
1898-
if (flags & REF_NODEREF)
1899-
resolve_flags |= RESOLVE_REF_NO_RECURSE;
1904+
if (flags & REF_NODEREF) {
1905+
resolve_flags |= RESOLVE_REF_NO_RECURSE;
1906+
lflags |= LOCK_NO_DEREF;
19001907
}
19011908

19021909
refname = resolve_ref_unsafe(refname, resolve_flags,
@@ -1932,6 +1939,10 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
19321939

19331940
goto error_return;
19341941
}
1942+
1943+
if (flags & REF_NODEREF)
1944+
refname = orig_refname;
1945+
19351946
/*
19361947
* If the ref did not exist and we are creating it, make sure
19371948
* there is no existing packed ref whose name begins with our
@@ -1947,11 +1958,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
19471958

19481959
lock->lk = xcalloc(1, sizeof(struct lock_file));
19491960

1950-
lflags = 0;
1951-
if (flags & REF_NODEREF) {
1952-
refname = orig_refname;
1953-
lflags |= LOCK_NO_DEREF;
1954-
}
19551961
lock->ref_name = xstrdup(refname);
19561962
lock->orig_ref_name = xstrdup(orig_refname);
19571963
strbuf_git_path(&ref_file, "%s", refname);
@@ -1985,7 +1991,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
19851991
goto error_return;
19861992
}
19871993
}
1988-
if (old_sha1 && verify_lock(lock, old_sha1, mustexist, err)) {
1994+
if (verify_lock(lock, old_sha1, mustexist, err)) {
19891995
last_errno = errno;
19901996
goto error_return;
19911997
}
@@ -2811,73 +2817,72 @@ static int commit_ref_update(struct ref_lock *lock,
28112817
return 0;
28122818
}
28132819

2814-
int create_symref(const char *ref_target, const char *refs_heads_master,
2815-
const char *logmsg)
2820+
static int create_ref_symlink(struct ref_lock *lock, const char *target)
28162821
{
2817-
char *lockpath = NULL;
2818-
char ref[1000];
2819-
int fd, len, written;
2820-
char *git_HEAD = git_pathdup("%s", ref_target);
2821-
unsigned char old_sha1[20], new_sha1[20];
2822-
struct strbuf err = STRBUF_INIT;
2823-
2824-
if (logmsg && read_ref(ref_target, old_sha1))
2825-
hashclr(old_sha1);
2826-
2827-
if (safe_create_leading_directories(git_HEAD) < 0)
2828-
return error("unable to create directory for %s", git_HEAD);
2829-
2822+
int ret = -1;
28302823
#ifndef NO_SYMLINK_HEAD
2831-
if (prefer_symlink_refs) {
2832-
unlink(git_HEAD);
2833-
if (!symlink(refs_heads_master, git_HEAD))
2834-
goto done;
2824+
char *ref_path = get_locked_file_path(lock->lk);
2825+
unlink(ref_path);
2826+
ret = symlink(target, ref_path);
2827+
free(ref_path);
2828+
2829+
if (ret)
28352830
fprintf(stderr, "no symlink - falling back to symbolic ref\n");
2836-
}
28372831
#endif
2832+
return ret;
2833+
}
28382834

2839-
len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master);
2840-
if (sizeof(ref) <= len) {
2841-
error("refname too long: %s", refs_heads_master);
2842-
goto error_free_return;
2843-
}
2844-
lockpath = mkpathdup("%s.lock", git_HEAD);
2845-
fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
2846-
if (fd < 0) {
2847-
error("Unable to open %s for writing", lockpath);
2848-
goto error_free_return;
2849-
}
2850-
written = write_in_full(fd, ref, len);
2851-
if (close(fd) != 0 || written != len) {
2852-
error("Unable to write to %s", lockpath);
2853-
goto error_unlink_return;
2854-
}
2855-
if (rename(lockpath, git_HEAD) < 0) {
2856-
error("Unable to create %s", git_HEAD);
2857-
goto error_unlink_return;
2858-
}
2859-
if (adjust_shared_perm(git_HEAD)) {
2860-
error("Unable to fix permissions on %s", lockpath);
2861-
error_unlink_return:
2862-
unlink_or_warn(lockpath);
2863-
error_free_return:
2864-
free(lockpath);
2865-
free(git_HEAD);
2866-
return -1;
2835+
static void update_symref_reflog(struct ref_lock *lock, const char *refname,
2836+
const char *target, const char *logmsg)
2837+
{
2838+
struct strbuf err = STRBUF_INIT;
2839+
unsigned char new_sha1[20];
2840+
if (logmsg && !read_ref(target, new_sha1) &&
2841+
log_ref_write(refname, lock->old_oid.hash, new_sha1, logmsg, 0, &err)) {
2842+
error("%s", err.buf);
2843+
strbuf_release(&err);
28672844
}
2868-
free(lockpath);
2845+
}
28692846

2870-
#ifndef NO_SYMLINK_HEAD
2871-
done:
2872-
#endif
2873-
if (logmsg && !read_ref(refs_heads_master, new_sha1) &&
2874-
log_ref_write(ref_target, old_sha1, new_sha1, logmsg, 0, &err)) {
2847+
static int create_symref_locked(struct ref_lock *lock, const char *refname,
2848+
const char *target, const char *logmsg)
2849+
{
2850+
if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
2851+
update_symref_reflog(lock, refname, target, logmsg);
2852+
return 0;
2853+
}
2854+
2855+
if (!fdopen_lock_file(lock->lk, "w"))
2856+
return error("unable to fdopen %s: %s",
2857+
lock->lk->tempfile.filename.buf, strerror(errno));
2858+
2859+
update_symref_reflog(lock, refname, target, logmsg);
2860+
2861+
/* no error check; commit_ref will check ferror */
2862+
fprintf(lock->lk->tempfile.fp, "ref: %s\n", target);
2863+
if (commit_ref(lock) < 0)
2864+
return error("unable to write symref for %s: %s", refname,
2865+
strerror(errno));
2866+
return 0;
2867+
}
2868+
2869+
int create_symref(const char *refname, const char *target, const char *logmsg)
2870+
{
2871+
struct strbuf err = STRBUF_INIT;
2872+
struct ref_lock *lock;
2873+
int ret;
2874+
2875+
lock = lock_ref_sha1_basic(refname, NULL, NULL, NULL, REF_NODEREF, NULL,
2876+
&err);
2877+
if (!lock) {
28752878
error("%s", err.buf);
28762879
strbuf_release(&err);
2880+
return -1;
28772881
}
28782882

2879-
free(git_HEAD);
2880-
return 0;
2883+
ret = create_symref_locked(lock, refname, target, logmsg);
2884+
unlock_ref(lock);
2885+
return ret;
28812886
}
28822887

28832888
int reflog_exists(const char *refname)

t/t1401-symbolic-ref.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,19 @@ test_expect_success 'symbolic-ref writes reflog entry' '
114114
test_cmp expect actual
115115
'
116116

117+
test_expect_success 'symbolic-ref does not create ref d/f conflicts' '
118+
git checkout -b df &&
119+
test_commit df &&
120+
test_must_fail git symbolic-ref refs/heads/df/conflict refs/heads/df &&
121+
git pack-refs --all --prune &&
122+
test_must_fail git symbolic-ref refs/heads/df/conflict refs/heads/df
123+
'
124+
125+
test_expect_success 'symbolic-ref handles existing pointer to invalid name' '
126+
head=$(git rev-parse HEAD) &&
127+
git symbolic-ref HEAD refs/heads/outer &&
128+
git update-ref refs/heads/outer/inner $head &&
129+
git symbolic-ref HEAD refs/heads/unrelated
130+
'
131+
117132
test_done

t/t2011-checkout-invalid-head.sh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,43 @@ test_expect_success 'checkout master from invalid HEAD' '
1919
git checkout master --
2020
'
2121

22+
test_expect_success 'checkout notices failure to lock HEAD' '
23+
test_when_finished "rm -f .git/HEAD.lock" &&
24+
>.git/HEAD.lock &&
25+
test_must_fail git checkout -b other
26+
'
27+
28+
test_expect_success 'create ref directory/file conflict scenario' '
29+
git update-ref refs/heads/outer/inner master &&
30+
31+
# do not rely on symbolic-ref to get a known state,
32+
# as it may use the same code we are testing
33+
reset_to_df () {
34+
echo "ref: refs/heads/outer" >.git/HEAD
35+
}
36+
'
37+
38+
test_expect_success 'checkout away from d/f HEAD (unpacked, to branch)' '
39+
reset_to_df &&
40+
git checkout master
41+
'
42+
43+
test_expect_success 'checkout away from d/f HEAD (unpacked, to detached)' '
44+
reset_to_df &&
45+
git checkout --detach master
46+
'
47+
48+
test_expect_success 'pack refs' '
49+
git pack-refs --all --prune
50+
'
51+
52+
test_expect_success 'checkout away from d/f HEAD (packed, to branch)' '
53+
reset_to_df &&
54+
git checkout master
55+
'
56+
57+
test_expect_success 'checkout away from d/f HEAD (packed, to detached)' '
58+
reset_to_df &&
59+
git checkout --detach master
60+
'
2261
test_done

0 commit comments

Comments
 (0)