From: David Turner <dturner@twopensource.com>
To: git@vger.kernel.org, peff@peff.net, mhagger@alum.mit.edu,
pclouds@gmail.com
Cc: David Turner <dturner@twopensource.com>,
Junio C Hamano <gitster@pobox.com>
Subject: [PATCH v7 22/33] refs: resolve symbolic refs first
Date: Mon, 29 Feb 2016 19:52:55 -0500 [thread overview]
Message-ID: <1456793586-22082-23-git-send-email-dturner@twopensource.com> (raw)
In-Reply-To: <1456793586-22082-1-git-send-email-dturner@twopensource.com>
Before committing ref updates, split symbolic ref updates into two
parts: an update to the underlying ref, and a log-only update to the
symbolic ref. This ensures that both references are locked correctly
while their reflogs are updated.
It is still possible to confuse git by concurrent updates, since the
splitting of symbolic refs does not happen under lock. So a symbolic ref
could be replaced by a plain ref in the middle of this operation, which
would lead to reflog discontinuities and missed old-ref checks.
All callers to lock_ref_sha1_basic outside of ref_transaction_commit
now set REF_NODEREF. And ref_transaction_commit only happens on
already-derefernced refs. So we can assume REF_NODEREF when resolving
inside this function.
Signed-off-by: David Turner <dturner@twopensource.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
refs.c | 69 ++++++++++++++++++++++++++
refs/files-backend.c | 137 ++++++++++++++++++++++++++-------------------------
refs/refs-internal.h | 8 +++
3 files changed, 148 insertions(+), 66 deletions(-)
diff --git a/refs.c b/refs.c
index 9f695cd..eee8c37 100644
--- a/refs.c
+++ b/refs.c
@@ -1354,6 +1354,71 @@ int refs_init_db(int shared, struct strbuf *err)
return the_refs_backend->init_db(shared, err);
}
+/*
+ * Special case for symbolic refs when REF_NODEREF is not turned on.
+ * Dereference them here, mark them REF_LOG_ONLY, and add an update
+ * for the underlying ref.
+ */
+static int dereference_symrefs(struct ref_transaction *transaction,
+ struct strbuf *err)
+{
+ int i;
+ int nr = transaction->nr;
+
+ for (i = 0; i < nr; i++) {
+ struct ref_update *update = transaction->updates[i];
+ const char *resolved;
+ unsigned char sha1[20];
+ int resolve_flags = 0;
+ int mustexist = update->flags & REF_HAVE_OLD &&
+ !is_null_sha1(update->old_sha1);
+ int deleting = (update->flags & REF_HAVE_NEW) &&
+ is_null_sha1(update->new_sha1);
+
+ if (mustexist)
+ resolve_flags |= RESOLVE_REF_READING;
+ if (deleting)
+ resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME |
+ RESOLVE_REF_NO_RECURSE;
+
+ if (strcmp(update->refname, "HEAD"))
+ update->flags |= REF_IS_NOT_HEAD;
+
+ resolved = resolve_ref_unsafe(update->refname, resolve_flags,
+ sha1, &update->type);
+ if (!resolved) {
+ /*
+ * We may notice this breakage later and die
+ * with a sensible error message
+ */
+ update->type |= REF_ISBROKEN;
+ continue;
+ }
+
+ hashcpy(update->read_sha1, sha1);
+
+ if (update->flags & REF_NODEREF ||
+ !(update->type & REF_ISSYMREF))
+ continue;
+
+ /* Create a new transaction for the underlying ref */
+ if (ref_transaction_update(transaction,
+ resolved,
+ update->new_sha1,
+ (update->flags & REF_HAVE_OLD) ?
+ update->old_sha1 : NULL,
+ update->flags & ~REF_IS_NOT_HEAD,
+ update->msg, err))
+ return -1;
+
+ /* Make the symbolic ref update non-recursive */
+ update->flags |= REF_LOG_ONLY | REF_NODEREF;
+ update->flags &= ~REF_HAVE_OLD;
+ }
+
+ return 0;
+}
+
int ref_transaction_commit(struct ref_transaction *transaction,
struct strbuf *err)
{
@@ -1370,6 +1435,10 @@ int ref_transaction_commit(struct ref_transaction *transaction,
return 0;
}
+ ret = dereference_symrefs(transaction, err);
+ if (ret)
+ goto done;
+
if (get_affected_refnames(transaction, &affected_refnames, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto done;
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 1f36dde..7ccaf88 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -7,7 +7,6 @@
struct ref_lock {
char *ref_name;
- char *orig_ref_name;
struct lock_file *lk;
struct object_id old_oid;
};
@@ -1715,7 +1714,6 @@ static void unlock_ref(struct ref_lock *lock)
if (lock->lk)
rollback_lock_file(lock->lk);
free(lock->ref_name);
- free(lock->orig_ref_name);
free(lock);
}
@@ -1771,6 +1769,7 @@ static int remove_empty_directories(struct strbuf *path)
*/
static struct ref_lock *lock_ref_sha1_basic(const char *refname,
const unsigned char *old_sha1,
+ const unsigned char *read_sha1,
const struct string_list *extras,
const struct string_list *skip,
unsigned int flags, int *type_p,
@@ -1778,14 +1777,14 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
{
struct strbuf ref_file = STRBUF_INIT;
struct strbuf orig_ref_file = STRBUF_INIT;
- const char *orig_refname = refname;
struct ref_lock *lock;
int last_errno = 0;
int type;
int lflags = 0;
int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
- int resolve_flags = 0;
+ int resolve_flags = RESOLVE_REF_NO_RECURSE;
int attempts_remaining = 3;
+ int resolved;
assert(err);
@@ -1795,65 +1794,65 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
resolve_flags |= RESOLVE_REF_READING;
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)
lflags |= LOCK_NO_DEREF;
- }
- refname = resolve_ref_unsafe(refname, resolve_flags,
- lock->old_oid.hash, &type);
- if (!refname && errno == EISDIR) {
- /*
- * we are trying to lock foo but we used to
- * have foo/bar which now does not exist;
- * it is normal for the empty directory 'foo'
- * to remain.
- */
- strbuf_git_path(&orig_ref_file, "%s", orig_refname);
- if (remove_empty_directories(&orig_ref_file)) {
+ if (type_p && *type_p & REF_ISSYMREF) {
+ hashcpy(lock->old_oid.hash, read_sha1);
+ } else {
+ resolved = !!resolve_ref_unsafe(refname, resolve_flags,
+ lock->old_oid.hash, &type);
+ if (!resolved && errno == EISDIR) {
+ /*
+ * we are trying to lock foo but we used to
+ * have foo/bar which now does not exist;
+ * it is normal for the empty directory 'foo'
+ * to remain.
+ */
+ strbuf_git_path(&orig_ref_file, "%s", refname);
+ if (remove_empty_directories(&orig_ref_file)) {
+ struct ref_dir *loose_refs;
+ loose_refs = get_loose_refs(&ref_cache);
+ last_errno = errno;
+ if (!verify_refname_available_dir(refname, extras, skip,
+ loose_refs, err))
+ strbuf_addf(err, "there are still refs under '%s'",
+ refname);
+ goto error_return;
+ }
+ resolved = !!resolve_ref_unsafe(refname, resolve_flags,
+ lock->old_oid.hash, &type);
+ }
+
+ if (type_p)
+ *type_p = type;
+ if (!resolved) {
last_errno = errno;
- if (!verify_refname_available_dir(orig_refname, extras, skip,
+ if (last_errno != ENOTDIR ||
+ !verify_refname_available_dir(refname, extras, skip,
get_loose_refs(&ref_cache), err))
- strbuf_addf(err, "there are still refs under '%s'",
- orig_refname);
+ strbuf_addf(err,
+ "unable to resolve reference %s: %s",
+ refname, strerror(last_errno));
+
+ goto error_return;
+ }
+ /*
+ * If the ref did not exist and we are creating it, make sure
+ * there is no existing packed ref whose name begins with our
+ * refname, nor a packed ref whose name is a proper prefix of
+ * our refname.
+ */
+ if (is_null_oid(&lock->old_oid) &&
+ verify_refname_available_dir(refname, extras, skip,
+ get_packed_refs(&ref_cache), err)) {
+ last_errno = ENOTDIR;
goto error_return;
}
- refname = resolve_ref_unsafe(orig_refname, resolve_flags,
- lock->old_oid.hash, &type);
- }
- if (type_p)
- *type_p = type;
- if (!refname) {
- last_errno = errno;
- if (last_errno != ENOTDIR ||
- !verify_refname_available_dir(orig_refname, extras, skip,
- get_loose_refs(&ref_cache), err))
- strbuf_addf(err, "unable to resolve reference %s: %s",
- orig_refname, strerror(last_errno));
-
- 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
- * refname, nor a packed ref whose name is a proper prefix of
- * our refname.
- */
- if (is_null_oid(&lock->old_oid) &&
- verify_refname_available_dir(refname, extras, skip,
- get_packed_refs(&ref_cache), err)) {
- last_errno = ENOTDIR;
- goto error_return;
}
-
lock->lk = xcalloc(1, sizeof(struct lock_file));
lock->ref_name = xstrdup(refname);
- lock->orig_ref_name = xstrdup(orig_refname);
strbuf_git_path(&ref_file, "%s", refname);
retry:
@@ -1885,7 +1884,13 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
goto error_return;
}
}
- if (verify_lock(lock, old_sha1, mustexist, err)) {
+
+ if (type_p && *type_p & REF_ISSYMREF && !(*type_p & REF_ISBROKEN)) {
+ /*
+ * Old hash verification for symrefs happens on their
+ * base ref.
+ */
+ } else if (verify_lock(lock, old_sha1, mustexist, err)) {
last_errno = errno;
goto error_return;
}
@@ -2419,8 +2424,8 @@ static int files_rename_ref(const char *oldrefname, const char *newrefname,
logmoved = log;
- lock = lock_ref_sha1_basic(newrefname, NULL, NULL, NULL, REF_NODEREF,
- NULL, &err);
+ lock = lock_ref_sha1_basic(newrefname, NULL, NULL, NULL, NULL,
+ REF_NODEREF, NULL, &err);
if (!lock) {
error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf);
strbuf_release(&err);
@@ -2438,8 +2443,9 @@ static int files_rename_ref(const char *oldrefname, const char *newrefname,
return 0;
rollback:
- lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, NULL, REF_NODEREF,
- NULL, &err);
+ lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, NULL, NULL,
+ REF_NODEREF, NULL, &err);
+
if (!lock) {
error("unable to lock %s for rollback: %s", oldrefname, err.buf);
strbuf_release(&err);
@@ -2667,9 +2673,7 @@ static int commit_ref_update(struct ref_lock *lock,
int flags, struct strbuf *err)
{
clear_loose_ref_cache(&ref_cache);
- if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0 ||
- (strcmp(lock->ref_name, lock->orig_ref_name) &&
- log_ref_write(lock->orig_ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0)) {
+ if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0) {
char *old_msg = strbuf_detach(err, NULL);
strbuf_addf(err, "Cannot update the ref '%s': %s",
lock->ref_name, old_msg);
@@ -2677,7 +2681,7 @@ static int commit_ref_update(struct ref_lock *lock,
unlock_ref(lock);
return -1;
}
- if (strcmp(lock->orig_ref_name, "HEAD") != 0) {
+ if (flags & REF_IS_NOT_HEAD) {
/*
* Special hack: If a branch is updated directly and HEAD
* points to it (may happen on the remote side of a push
@@ -2772,8 +2776,8 @@ static int files_create_symref(const char *refname,
struct ref_lock *lock;
int ret;
- lock = lock_ref_sha1_basic(refname, NULL, NULL, NULL, REF_NODEREF, NULL,
- &err);
+ lock = lock_ref_sha1_basic(refname, NULL, NULL, NULL, NULL, REF_NODEREF,
+ NULL, &err);
if (!lock) {
error("%s", err.buf);
strbuf_release(&err);
@@ -3041,6 +3045,7 @@ static int files_transaction_commit(struct ref_transaction *transaction,
update->refname,
((update->flags & REF_HAVE_OLD) ?
update->old_sha1 : NULL),
+ update->read_sha1,
affected_refnames, NULL,
update->flags,
&update->type,
@@ -3287,7 +3292,7 @@ static int files_reflog_expire(const char *refname, const unsigned char *sha1,
struct ref_lock *lock;
char *log_file;
int status = 0;
- int type;
+ int type = 0;
struct strbuf err = STRBUF_INIT;
memset(&cb, 0, sizeof(cb));
@@ -3300,7 +3305,7 @@ static int files_reflog_expire(const char *refname, const unsigned char *sha1,
* reference itself, plus we might need to update the
* reference if --updateref was specified:
*/
- lock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, REF_NODEREF,
+ lock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, NULL, REF_NODEREF,
&type, &err);
if (!lock) {
error("cannot lock ref '%s': %s", refname, err.buf);
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index dd76246..0d3f9e7 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -49,6 +49,8 @@
*/
#define REF_LOG_ONLY 0x80
+#define REF_IS_NOT_HEAD 0x100
+
/*
* Return true iff refname is minimally safe. "Safe" here means that
* deleting a loose reference by this name will not do any damage, for
@@ -147,6 +149,12 @@ struct ref_update {
*/
unsigned char old_sha1[20];
/*
+ * During the symbolic ref split stage, we resolve refs.
+ * We'll re-resolve non-symbolic refs once they are locked,
+ * but we store this to avoid re-resolving symbolic refs.
+ */
+ unsigned char read_sha1[20];
+ /*
* One or more of REF_HAVE_NEW, REF_HAVE_OLD, REF_NODEREF,
* REF_DELETING, and REF_ISPRUNING:
*/
--
2.4.2.767.g62658d5-twtrsrc
next prev parent reply other threads:[~2016-03-01 0:54 UTC|newest]
Thread overview: 57+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-03-01 0:52 [PATCH v7 00/33] refs backend David Turner
2016-03-01 0:52 ` [PATCH v7 01/33] setup: call setup_git_directory_gently before accessing refs David Turner
2016-03-01 8:35 ` Jeff King
2016-03-01 23:47 ` David Turner
2016-03-02 0:33 ` David Turner
2016-03-02 2:45 ` Jeff King
2016-03-01 0:52 ` [PATCH v7 02/33] refs: move head_ref{,_submodule} to the common code David Turner
2016-03-01 0:52 ` [PATCH v7 03/33] refs: move for_each_*ref* functions into " David Turner
2016-03-01 0:52 ` [PATCH v7 04/33] files-backend: break out ref reading David Turner
2016-03-20 5:03 ` Michael Haggerty
2016-03-22 8:33 ` Michael Haggerty
2016-03-23 10:19 ` Michael Haggerty
2016-03-01 0:52 ` [PATCH v7 05/33] refs: move resolve_ref_unsafe into common code David Turner
2016-03-01 0:52 ` [PATCH v7 06/33] refs: add a backend method structure with transaction functions David Turner
2016-03-01 0:52 ` [PATCH v7 07/33] refs: add methods for misc ref operations David Turner
2016-03-01 0:52 ` [PATCH v7 08/33] refs: add method for do_for_each_ref David Turner
2016-03-01 0:52 ` [PATCH v7 09/33] refs: reduce the visibility of do_for_each_ref() David Turner
2016-03-24 7:07 ` Michael Haggerty
2016-03-24 18:56 ` David Turner
2016-03-01 0:52 ` [PATCH v7 10/33] refs: add do_for_each_per_worktree_ref David Turner
2016-03-01 0:52 ` [PATCH v7 11/33] refs: add methods for reflog David Turner
2016-03-01 0:52 ` [PATCH v7 12/33] refs: add method for initial ref transaction commit David Turner
2016-03-01 0:52 ` [PATCH v7 13/33] refs: add method for delete_refs David Turner
2016-03-01 0:52 ` [PATCH v7 14/33] refs: add methods to init refs db David Turner
2016-03-24 7:28 ` Michael Haggerty
2016-03-24 18:04 ` David Turner
2016-03-01 0:52 ` [PATCH v7 15/33] refs: add method to rename refs David Turner
2016-03-01 0:52 ` [PATCH v7 16/33] refs: handle non-normal ref renames David Turner
2016-03-01 0:52 ` [PATCH v7 17/33] refs: make lock generic David Turner
2016-03-24 19:45 ` Michael Haggerty
2016-03-01 0:52 ` [PATCH v7 18/33] refs: move duplicate check to common code David Turner
2016-03-01 0:52 ` [PATCH v7 19/33] refs: allow log-only updates David Turner
2016-04-21 14:17 ` Michael Haggerty
2016-04-25 16:46 ` David Turner
2016-03-01 0:52 ` [PATCH v7 20/33] refs: don't dereference on rename David Turner
2016-03-01 0:52 ` [PATCH v7 21/33] refs: on symref reflog expire, lock symref not referrent David Turner
2016-03-01 0:52 ` David Turner [this message]
2016-03-01 0:52 ` [PATCH v7 23/33] refs: always handle non-normal refs in files backend David Turner
2016-03-01 0:52 ` [PATCH v7 24/33] init: allow alternate ref strorage to be set for new repos David Turner
2016-03-01 0:52 ` [PATCH v7 25/33] refs: check submodules' ref storage config David Turner
2016-03-01 0:52 ` [PATCH v7 26/33] clone: allow ref storage backend to be set for clone David Turner
2016-03-01 0:53 ` [PATCH v7 27/33] svn: learn ref-storage argument David Turner
2016-03-01 0:53 ` [PATCH v7 28/33] refs: register ref storage backends David Turner
2016-03-01 0:53 ` [PATCH v7 29/33] setup: configure ref storage on setup David Turner
2016-03-01 8:48 ` Jeff King
2016-03-01 14:50 ` Jeff King
2016-03-01 17:18 ` Ramsay Jones
2016-03-01 19:16 ` David Turner
2016-03-01 0:53 ` [PATCH v7 30/33] refs: break out resolve_ref_unsafe_submodule David Turner
2016-03-01 17:21 ` Ramsay Jones
2016-03-01 19:17 ` David Turner
2016-03-01 0:53 ` [PATCH v7 31/33] refs: add LMDB refs storage backend David Turner
2016-03-01 1:31 ` Duy Nguyen
2016-03-01 1:35 ` David Turner
2016-03-01 1:45 ` Duy Nguyen
2016-03-01 0:53 ` [PATCH v7 32/33] refs: tests for lmdb backend David Turner
2016-03-01 0:53 ` [PATCH v7 33/33] tests: add ref-storage argument David Turner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1456793586-22082-23-git-send-email-dturner@twopensource.com \
--to=dturner@twopensource.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=mhagger@alum.mit.edu \
--cc=pclouds@gmail.com \
--cc=peff@peff.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).