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
This commit is contained in:
commit
90ce285a42
@ -661,7 +661,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
|
||||
describe_detached_head(_("HEAD is now at"), new->commit);
|
||||
}
|
||||
} else if (new->path) { /* Switch branches. */
|
||||
create_symref("HEAD", new->path, msg.buf);
|
||||
if (create_symref("HEAD", new->path, msg.buf) < 0)
|
||||
die("unable to update HEAD");
|
||||
if (!opts->quiet) {
|
||||
if (old->path && !strcmp(new->path, old->path)) {
|
||||
if (opts->new_branch_force)
|
||||
|
@ -636,9 +636,11 @@ static void update_remote_refs(const struct ref *refs,
|
||||
struct strbuf head_ref = STRBUF_INIT;
|
||||
strbuf_addstr(&head_ref, branch_top);
|
||||
strbuf_addstr(&head_ref, "HEAD");
|
||||
create_symref(head_ref.buf,
|
||||
remote_head_points_at->peer_ref->name,
|
||||
msg);
|
||||
if (create_symref(head_ref.buf,
|
||||
remote_head_points_at->peer_ref->name,
|
||||
msg) < 0)
|
||||
die("unable to update %s", head_ref.buf);
|
||||
strbuf_release(&head_ref);
|
||||
}
|
||||
}
|
||||
|
||||
@ -648,7 +650,8 @@ static void update_head(const struct ref *our, const struct ref *remote,
|
||||
const char *head;
|
||||
if (our && skip_prefix(our->name, "refs/heads/", &head)) {
|
||||
/* Local default branch link */
|
||||
create_symref("HEAD", our->name, NULL);
|
||||
if (create_symref("HEAD", our->name, NULL) < 0)
|
||||
die("unable to update HEAD");
|
||||
if (!option_bare) {
|
||||
update_ref(msg, "HEAD", our->old_oid.hash, NULL, 0,
|
||||
UPDATE_REFS_DIE_ON_ERR);
|
||||
|
2
refs.h
2
refs.h
@ -292,7 +292,7 @@ extern char *shorten_unambiguous_ref(const char *refname, int strict);
|
||||
/** rename ref, return 0 on success **/
|
||||
extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);
|
||||
|
||||
extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
|
||||
extern int create_symref(const char *refname, const char *target, const char *logmsg);
|
||||
|
||||
enum action_on_err {
|
||||
UPDATE_REFS_MSG_ON_ERR,
|
||||
|
@ -1840,12 +1840,17 @@ static int verify_lock(struct ref_lock *lock,
|
||||
if (read_ref_full(lock->ref_name,
|
||||
mustexist ? RESOLVE_REF_READING : 0,
|
||||
lock->old_oid.hash, NULL)) {
|
||||
int save_errno = errno;
|
||||
strbuf_addf(err, "can't verify ref %s", lock->ref_name);
|
||||
errno = save_errno;
|
||||
return -1;
|
||||
if (old_sha1) {
|
||||
int save_errno = errno;
|
||||
strbuf_addf(err, "can't verify ref %s", lock->ref_name);
|
||||
errno = save_errno;
|
||||
return -1;
|
||||
} else {
|
||||
hashclr(lock->old_oid.hash);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (hashcmp(lock->old_oid.hash, old_sha1)) {
|
||||
if (old_sha1 && hashcmp(lock->old_oid.hash, old_sha1)) {
|
||||
strbuf_addf(err, "ref %s is at %s but expected %s",
|
||||
lock->ref_name,
|
||||
sha1_to_hex(lock->old_oid.hash),
|
||||
@ -1882,7 +1887,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
|
||||
const char *orig_refname = refname;
|
||||
struct ref_lock *lock;
|
||||
int last_errno = 0;
|
||||
int type, lflags;
|
||||
int type;
|
||||
int lflags = 0;
|
||||
int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
|
||||
int resolve_flags = 0;
|
||||
int attempts_remaining = 3;
|
||||
@ -1893,10 +1899,11 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
|
||||
|
||||
if (mustexist)
|
||||
resolve_flags |= RESOLVE_REF_READING;
|
||||
if (flags & REF_DELETING) {
|
||||
if (flags & REF_DELETING)
|
||||
resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;
|
||||
if (flags & REF_NODEREF)
|
||||
resolve_flags |= RESOLVE_REF_NO_RECURSE;
|
||||
if (flags & REF_NODEREF) {
|
||||
resolve_flags |= RESOLVE_REF_NO_RECURSE;
|
||||
lflags |= LOCK_NO_DEREF;
|
||||
}
|
||||
|
||||
refname = resolve_ref_unsafe(refname, resolve_flags,
|
||||
@ -1932,6 +1939,10 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
|
||||
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
if (flags & REF_NODEREF)
|
||||
refname = orig_refname;
|
||||
|
||||
/*
|
||||
* If the ref did not exist and we are creating it, make sure
|
||||
* 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,
|
||||
|
||||
lock->lk = xcalloc(1, sizeof(struct lock_file));
|
||||
|
||||
lflags = 0;
|
||||
if (flags & REF_NODEREF) {
|
||||
refname = orig_refname;
|
||||
lflags |= LOCK_NO_DEREF;
|
||||
}
|
||||
lock->ref_name = xstrdup(refname);
|
||||
lock->orig_ref_name = xstrdup(orig_refname);
|
||||
strbuf_git_path(&ref_file, "%s", refname);
|
||||
@ -1985,7 +1991,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
|
||||
goto error_return;
|
||||
}
|
||||
}
|
||||
if (old_sha1 && verify_lock(lock, old_sha1, mustexist, err)) {
|
||||
if (verify_lock(lock, old_sha1, mustexist, err)) {
|
||||
last_errno = errno;
|
||||
goto error_return;
|
||||
}
|
||||
@ -2811,75 +2817,74 @@ static int commit_ref_update(struct ref_lock *lock,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int create_symref(const char *ref_target, const char *refs_heads_master,
|
||||
const char *logmsg)
|
||||
static int create_ref_symlink(struct ref_lock *lock, const char *target)
|
||||
{
|
||||
char *lockpath = NULL;
|
||||
char ref[1000];
|
||||
int fd, len, written;
|
||||
char *git_HEAD = git_pathdup("%s", ref_target);
|
||||
unsigned char old_sha1[20], new_sha1[20];
|
||||
struct strbuf err = STRBUF_INIT;
|
||||
|
||||
if (logmsg && read_ref(ref_target, old_sha1))
|
||||
hashclr(old_sha1);
|
||||
|
||||
if (safe_create_leading_directories(git_HEAD) < 0)
|
||||
return error("unable to create directory for %s", git_HEAD);
|
||||
|
||||
int ret = -1;
|
||||
#ifndef NO_SYMLINK_HEAD
|
||||
if (prefer_symlink_refs) {
|
||||
unlink(git_HEAD);
|
||||
if (!symlink(refs_heads_master, git_HEAD))
|
||||
goto done;
|
||||
char *ref_path = get_locked_file_path(lock->lk);
|
||||
unlink(ref_path);
|
||||
ret = symlink(target, ref_path);
|
||||
free(ref_path);
|
||||
|
||||
if (ret)
|
||||
fprintf(stderr, "no symlink - falling back to symbolic ref\n");
|
||||
}
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master);
|
||||
if (sizeof(ref) <= len) {
|
||||
error("refname too long: %s", refs_heads_master);
|
||||
goto error_free_return;
|
||||
}
|
||||
lockpath = mkpathdup("%s.lock", git_HEAD);
|
||||
fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
|
||||
if (fd < 0) {
|
||||
error("Unable to open %s for writing", lockpath);
|
||||
goto error_free_return;
|
||||
}
|
||||
written = write_in_full(fd, ref, len);
|
||||
if (close(fd) != 0 || written != len) {
|
||||
error("Unable to write to %s", lockpath);
|
||||
goto error_unlink_return;
|
||||
}
|
||||
if (rename(lockpath, git_HEAD) < 0) {
|
||||
error("Unable to create %s", git_HEAD);
|
||||
goto error_unlink_return;
|
||||
}
|
||||
if (adjust_shared_perm(git_HEAD)) {
|
||||
error("Unable to fix permissions on %s", lockpath);
|
||||
error_unlink_return:
|
||||
unlink_or_warn(lockpath);
|
||||
error_free_return:
|
||||
free(lockpath);
|
||||
free(git_HEAD);
|
||||
return -1;
|
||||
}
|
||||
free(lockpath);
|
||||
|
||||
#ifndef NO_SYMLINK_HEAD
|
||||
done:
|
||||
#endif
|
||||
if (logmsg && !read_ref(refs_heads_master, new_sha1) &&
|
||||
log_ref_write(ref_target, old_sha1, new_sha1, logmsg, 0, &err)) {
|
||||
static void update_symref_reflog(struct ref_lock *lock, const char *refname,
|
||||
const char *target, const char *logmsg)
|
||||
{
|
||||
struct strbuf err = STRBUF_INIT;
|
||||
unsigned char new_sha1[20];
|
||||
if (logmsg && !read_ref(target, new_sha1) &&
|
||||
log_ref_write(refname, lock->old_oid.hash, new_sha1, logmsg, 0, &err)) {
|
||||
error("%s", err.buf);
|
||||
strbuf_release(&err);
|
||||
}
|
||||
}
|
||||
|
||||
free(git_HEAD);
|
||||
static int create_symref_locked(struct ref_lock *lock, const char *refname,
|
||||
const char *target, const char *logmsg)
|
||||
{
|
||||
if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
|
||||
update_symref_reflog(lock, refname, target, logmsg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!fdopen_lock_file(lock->lk, "w"))
|
||||
return error("unable to fdopen %s: %s",
|
||||
lock->lk->tempfile.filename.buf, strerror(errno));
|
||||
|
||||
update_symref_reflog(lock, refname, target, logmsg);
|
||||
|
||||
/* no error check; commit_ref will check ferror */
|
||||
fprintf(lock->lk->tempfile.fp, "ref: %s\n", target);
|
||||
if (commit_ref(lock) < 0)
|
||||
return error("unable to write symref for %s: %s", refname,
|
||||
strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int create_symref(const char *refname, const char *target, const char *logmsg)
|
||||
{
|
||||
struct strbuf err = STRBUF_INIT;
|
||||
struct ref_lock *lock;
|
||||
int ret;
|
||||
|
||||
lock = lock_ref_sha1_basic(refname, NULL, NULL, NULL, REF_NODEREF, NULL,
|
||||
&err);
|
||||
if (!lock) {
|
||||
error("%s", err.buf);
|
||||
strbuf_release(&err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = create_symref_locked(lock, refname, target, logmsg);
|
||||
unlock_ref(lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int reflog_exists(const char *refname)
|
||||
{
|
||||
struct stat st;
|
||||
|
@ -114,4 +114,19 @@ test_expect_success 'symbolic-ref writes reflog entry' '
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'symbolic-ref does not create ref d/f conflicts' '
|
||||
git checkout -b df &&
|
||||
test_commit df &&
|
||||
test_must_fail git symbolic-ref refs/heads/df/conflict refs/heads/df &&
|
||||
git pack-refs --all --prune &&
|
||||
test_must_fail git symbolic-ref refs/heads/df/conflict refs/heads/df
|
||||
'
|
||||
|
||||
test_expect_success 'symbolic-ref handles existing pointer to invalid name' '
|
||||
head=$(git rev-parse HEAD) &&
|
||||
git symbolic-ref HEAD refs/heads/outer &&
|
||||
git update-ref refs/heads/outer/inner $head &&
|
||||
git symbolic-ref HEAD refs/heads/unrelated
|
||||
'
|
||||
|
||||
test_done
|
||||
|
@ -19,4 +19,43 @@ test_expect_success 'checkout master from invalid HEAD' '
|
||||
git checkout master --
|
||||
'
|
||||
|
||||
test_expect_success 'checkout notices failure to lock HEAD' '
|
||||
test_when_finished "rm -f .git/HEAD.lock" &&
|
||||
>.git/HEAD.lock &&
|
||||
test_must_fail git checkout -b other
|
||||
'
|
||||
|
||||
test_expect_success 'create ref directory/file conflict scenario' '
|
||||
git update-ref refs/heads/outer/inner master &&
|
||||
|
||||
# do not rely on symbolic-ref to get a known state,
|
||||
# as it may use the same code we are testing
|
||||
reset_to_df () {
|
||||
echo "ref: refs/heads/outer" >.git/HEAD
|
||||
}
|
||||
'
|
||||
|
||||
test_expect_success 'checkout away from d/f HEAD (unpacked, to branch)' '
|
||||
reset_to_df &&
|
||||
git checkout master
|
||||
'
|
||||
|
||||
test_expect_success 'checkout away from d/f HEAD (unpacked, to detached)' '
|
||||
reset_to_df &&
|
||||
git checkout --detach master
|
||||
'
|
||||
|
||||
test_expect_success 'pack refs' '
|
||||
git pack-refs --all --prune
|
||||
'
|
||||
|
||||
test_expect_success 'checkout away from d/f HEAD (packed, to branch)' '
|
||||
reset_to_df &&
|
||||
git checkout master
|
||||
'
|
||||
|
||||
test_expect_success 'checkout away from d/f HEAD (packed, to detached)' '
|
||||
reset_to_df &&
|
||||
git checkout --detach master
|
||||
'
|
||||
test_done
|
||||
|
Loading…
Reference in New Issue
Block a user