{"thread":{"id":"36849","subject":"[PATCH 0/4] Use transactions for renames","startedAt":"2014-06-05T23:17:10Z","lastAt":"2014-06-05T23:17:14Z","messageCount":5,"participants":["Ronnie Sahlberg"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"243431","messageId":"1402010234-7628-1-git-send-email-sahlberg@google.com","threadId":"36849","inReplyTo":null,"subject":"[PATCH 0/4] Use transactions for renames","fromName":"Ronnie Sahlberg","fromEmail":"sahlberg@google.com","sentAt":"2014-06-05T23:17:10Z","receivedAt":"2014-06-05T23:17:10Z","isPatch":true,"sender":{"key":"sahlberg@google.com","avatar":"https://avatars.githubusercontent.com/u/7320636?v=4"},"body":"This series is based on ref-transactions-reflog\nIt is also available at\nhttps://github.com/rsahlberg/git/tree/ref-transactions-rename\n\nThis series adds support to perform rename_ref as a single transaction for\nboth deleting/re-creating the ref and updating the reflog.\n\nSince we no longer use rename() for the reflog changes we can now support\nrenames even if the reflogs are symlinks.\n\nIn order to make the delete-then-create fully atomic and also to ensure that\nat no point in time is the object unreferenced we add support to do\ndeletes via the packed refs file to transaction_commit. For any refs that are\nto be deleted, we first copy these refs to the packed refs file.\nThis allows us to immediately delete the loose ref files for those refs and\nstill being able to cancel/rollback the transaction.\nOnce all the changes are successfully updated we finally commit the packed\nrefs file a second time at which stage all the refs-to-be-deleted all\ndisappear in one atomic rename().\n\nThis allows us to perform transactions that delete multiple refs and have the\ndelete appear as one atomic transaction to any external observer.\n\n\nRonnie Sahlberg (4):\n  refs.c: allow passing raw git_committer_info as email to\n    _update_reflog\n  refs.c: return error instead of dying when locking fails during\n    transaction\n  refs.c: use packed refs when deleting refs during a transaction\n  refs.c: update rename_ref to use a transaction\n\n refs.c            | 308 +++++++++++++++++++++++++++++++++---------------------\n refs.h            |   1 +\n t/t3200-branch.sh |   7 --\n 3 files changed, 192 insertions(+), 124 deletions(-)\n\n-- \n2.0.0.583.g402232d\n"},{"id":"243436","messageId":"1402010234-7628-2-git-send-email-sahlberg@google.com","threadId":"36849","inReplyTo":"1402010234-7628-1-git-send-email-sahlberg@google.com","subject":"[PATCH 1/4] refs.c: allow passing raw git_committer_info as email to _update_reflog","fromName":"Ronnie Sahlberg","fromEmail":"sahlberg@google.com","sentAt":"2014-06-05T23:17:11Z","receivedAt":"2014-06-05T23:17:11Z","isPatch":true,"sender":{"key":"sahlberg@google.com","avatar":"https://avatars.githubusercontent.com/u/7320636?v=4"},"body":"In many places in the code we do not have access to the individual fields\nin the committer data. Instead we might only have access to prebaked data\nsuch as what is returned by git_committer_info() containing a string\nthat consists of email, timestamp, zone etc.\n\nThis makes it inconvenient to use transaction_update_reflog since it means you\nwould have to first parse git_committer_info before you can call update_reflog.\n\nAdd a new flag REFLOG_EMAIL_IS_COMMITTER to _update_reflog to tell it\nthat what we pass in as email is already the fully baked committer string\nwe can use as-is.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\n\nConflicts:\n\trefs.c\n---\n refs.c | 20 ++++++++++++--------\n refs.h |  1 +\n 2 files changed, 13 insertions(+), 8 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 9689592..e3a0383 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3489,14 +3489,18 @@ int transaction_update_reflog(struct ref_transaction *transaction,\n \thashcpy(update->old_sha1, old_sha1);\n \tupdate->reflog_fd = -1;\n \tif (email) {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tchar sign = (tz < 0) ? '-' : '+';\n-\t\tint zone = (tz < 0) ? (-tz) : tz;\n-\n-\t\tstrbuf_addf(&buf, \"%s %lu %c%04d\", email, timestamp, sign,\n-\t\t\t    zone);\n-\t\tupdate->committer = xstrdup(buf.buf);\n-\t\tstrbuf_release(&buf);\n+\t\tif (flags & REFLOG_EMAIL_IS_COMMITTER)\n+\t\t\tupdate->committer = xstrdup(email);\n+\t\telse {\n+\t\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\t\tchar sign = (tz < 0) ? '-' : '+';\n+\t\t\tint zone = (tz < 0) ? (-tz) : tz;\n+\n+\t\t\tstrbuf_addf(&buf, \"%s %lu %c%04d\", email, timestamp,\n+\t\t\t\t    sign, zone);\n+\t\t\tupdate->committer = xstrdup(buf.buf);\n+\t\t\tstrbuf_release(&buf);\n+\t\t}\n \t}\n \tif (msg)\n \t\tupdate->msg = xstrdup(msg);\ndiff --git a/refs.h b/refs.h\nindex 1b6a055..3c99619 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -318,6 +318,7 @@ int transaction_delete_sha1(struct ref_transaction *transaction,\n \t\t\t    struct strbuf *err);\n \n #define REFLOG_TRUNCATE 0x01\n+#define REFLOG_EMAIL_IS_COMMITTER 0x02\n /*\n  * Append a reflog entry for refname. If the REFLOG_TRUNCATE flag is set\n  * this update will first truncate the reflog before writing the entry.\n-- \n2.0.0.583.g402232d\n"},{"id":"243446","messageId":"1402010234-7628-3-git-send-email-sahlberg@google.com","threadId":"36849","inReplyTo":"1402010234-7628-1-git-send-email-sahlberg@google.com","subject":"[PATCH 2/4] refs.c: return error instead of dying when locking fails during transaction","fromName":"Ronnie Sahlberg","fromEmail":"sahlberg@google.com","sentAt":"2014-06-05T23:17:12Z","receivedAt":"2014-06-05T23:17:12Z","isPatch":true,"sender":{"key":"sahlberg@google.com","avatar":"https://avatars.githubusercontent.com/u/7320636?v=4"},"body":"Change lock_ref_sha1_basic to return an error instead of dying when\nwe fail to lock a file during a transaction.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\n---\n refs.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex e3a0383..ab0b629 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2153,7 +2153,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\t */\n \t\t\tgoto retry;\n \t\telse\n-\t\t\tunable_to_lock_index_die(ref_file, errno);\n+\t\t\tgoto error_return;\n \t}\n \treturn old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;\n \n-- \n2.0.0.583.g402232d\n"},{"id":"243433","messageId":"1402010234-7628-4-git-send-email-sahlberg@google.com","threadId":"36849","inReplyTo":"1402010234-7628-1-git-send-email-sahlberg@google.com","subject":"[PATCH 3/4] refs.c: use packed refs when deleting refs during a transaction","fromName":"Ronnie Sahlberg","fromEmail":"sahlberg@google.com","sentAt":"2014-06-05T23:17:13Z","receivedAt":"2014-06-05T23:17:13Z","isPatch":true,"sender":{"key":"sahlberg@google.com","avatar":"https://avatars.githubusercontent.com/u/7320636?v=4"},"body":"Make the deletion of refs during a transaction more atomic.\nStart by first copying all loose refs we will be deleting to the packed\nrefs file and then commit the packed refs file. Then re-lock the packed refs\nfile to avoid anyone else from modifying the refs we are to delete during this\ntransaction and keep it locked until we are finished.\nSince all refs we are about to delete are now safely held in the packed refs\nfile we can proceed to immediately unlink any loose refs for those refs\nand still be fully rollback-able.\n\nThe exception is for refs that can not be resolved. Those refs are never\nadded to the packed refs and will just be un-rollback-ably deleted during\ncommit.\n\nBy deleting all the loose refs at the start of the transaction we make make it\npossible to both delete one ref and then re-create a different ref in the\nsame transaction, even if both the old-to-be-deleted and the new-to-be-created\nrefs would otherwise conflict in the file-system.\n\nExample: rename m->m/m\n\nIn that example we want to delete the file 'm' so that we make room so\nthat we can create a directory with the same name in order to lock and\nwrite to the ref m/m and its lock-file m/m.lock.\n\nIf there is a failure during the commit phase we can rollback without losing\nany refs since we have so far only deleted the loose refs but we still have\nthe refs stored in the packed refs file.\nOnce we have finished all updates for refs and their reflogs we can repack\nthe packed refs file and remove the to-be-deleted refs from the packed refs,\nat which point all the deleted refs will disappear in one atomic rename\noperation.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\n---\n refs.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++---------------\n 1 file changed, 114 insertions(+), 32 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex ab0b629..3692a4d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1311,7 +1311,7 @@ static struct ref_entry *get_packed_ref(const char *refname)\n \n /*\n  * A loose ref file doesn't exist; check for a packed ref.  The\n- * options are forwarded from resolve_safe_unsafe().\n+ * options are forwarded from resolve_ref_unsafe().\n  */\n static const char *handle_missing_loose_ref(const char *refname,\n \t\t\t\t\t    unsigned char *sha1,\n@@ -1367,7 +1367,6 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\t}\n \n \t\tgit_snpath(path, sizeof(path), \"%s\", refname);\n-\n \t\t/*\n \t\t * We might have to loop back here to avoid a race\n \t\t * condition: first we lstat() the file, then we try\n@@ -2474,6 +2473,9 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)\n \treturn 0;\n }\n \n+/*\n+ * Must be called with packed refs already locked (and sorted)\n+ */\n static int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n {\n \tstruct ref_dir *packed;\n@@ -2486,19 +2488,10 @@ static int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \t\tif (get_packed_ref(refnames[i]))\n \t\t\tbreak;\n \n-\t/* Avoid locking if we have nothing to do */\n+\t/* Avoid processing if we have nothing to do */\n \tif (i == n)\n \t\treturn 0; /* no refname exists in packed refs */\n \n-\tif (lock_packed_refs(0)) {\n-\t\tif (err) {\n-\t\t\tunable_to_lock_message(git_path(\"packed-refs\"), errno,\n-\t\t\t\t\t       err);\n-\t\t\treturn -1;\n-\t\t}\n-\t\tunable_to_lock_error(git_path(\"packed-refs\"), errno);\n-\t\treturn error(\"cannot delete '%s' from packed refs\", refnames[i]);\n-\t}\n \tpacked = get_packed_refs(&ref_cache);\n \n \t/* Remove refnames from the cache */\n@@ -3632,10 +3625,12 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,\n int transaction_commit(struct ref_transaction *transaction,\n \t\t\t   struct strbuf *err)\n {\n-\tint ret = 0, delnum = 0, i, df_conflict = 0;\n+\tint ret = 0, delnum = 0, i, df_conflict = 0, need_repack = 0;\n \tconst char **delnames;\n \tint n = transaction->nr;\n+\tstruct packed_ref_cache *packed_ref_cache;\n \tstruct ref_update **updates = transaction->updates;\n+\tstruct ref_dir *packed;\n \n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tdie(\"BUG: commit called for transaction that is not open\");\n@@ -3655,12 +3650,100 @@ int transaction_commit(struct ref_transaction *transaction,\n \t\tgoto cleanup;\n \t}\n \n-\t/* Acquire all ref locks while verifying old values */\n+\t/* Lock packed refs during commit */\n+\tif (lock_packed_refs(0)) {\n+\t\tif (err)\n+\t\t\tunable_to_lock_message(git_path(\"packed-refs\"),\n+\t\t\t\t\t       errno, err);\n+\t\tret = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/* any loose refs are to be deleted are first copied to packed refs */\n+\tfor (i = 0; i < n; i++) {\n+\t\tstruct ref_update *update = updates[i];\n+\t\tunsigned char sha1[20];\n+\n+\t\tif (update->update_type != UPDATE_SHA1)\n+\t\t\tcontinue;\n+\t\tif (!is_null_sha1(update->new_sha1))\n+\t\t\tcontinue;\n+\t\tif (get_packed_ref(update->refname))\n+\t\t\tcontinue;\n+\t\tif (!resolve_ref_unsafe(update->refname, sha1, 1, NULL))\n+\t\t\tcontinue;\n+\n+\t\tadd_packed_ref(update->refname, sha1);\n+\t\tneed_repack = 1;\n+\t}\n+\tif (need_repack) {\n+\t\tpacked = get_packed_refs(&ref_cache);;\n+\t\tsort_ref_dir(packed);\n+\t\tif (commit_packed_refs()){\n+\t\t\tstrbuf_addf(err, \"unable to overwrite old ref-pack \"\n+\t\t\t\t    \"file\");\n+\t\t\tret = -1;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\t\t/* lock the packed refs again so no one can change it */\n+\t\tif (lock_packed_refs(0)) {\n+\t\t\tif (err)\n+\t\t\t\tunable_to_lock_message(git_path(\"packed-refs\"),\n+\t\t\t\t\t\t       errno, err);\n+\t\t\tret = -1;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * At this stage any refs that are to be deleted have been moved to the\n+\t * packed refs file anf the packed refs file is deleted. We can now\n+\t * safely delete these loose refs.\n+\t */\n+\n+\t/* Unlink any loose refs scheduled for deletion */\n+\tfor (i = 0; i < n; i++) {\n+\t\tstruct ref_update *update = updates[i];\n+\n+\t\tif (update->update_type != UPDATE_SHA1)\n+\t\t\tcontinue;\n+\t\tif (!is_null_sha1(update->new_sha1))\n+\t\t\tcontinue;\n+\t\tupdate->lock = lock_ref_sha1_basic(update->refname,\n+\t\t\t\t\t\t   (update->have_old ?\n+\t\t\t\t\t\t    update->old_sha1 :\n+\t\t\t\t\t\t    NULL),\n+\t\t\t\t\t\t   update->flags,\n+\t\t\t\t\t\t   &update->type,\n+\t\t\t\t\t\t   delnames, delnum);\n+\t\tif (!update->lock) {\n+\t\t\tif (errno == ENOTDIR)\n+\t\t\t\tdf_conflict = 1;\n+\t\t\tif (err)\n+\t\t\t\tstrbuf_addf(err, \"Cannot lock the ref '%s'.\",\n+\t\t\t\t\t    update->refname);\n+\t\t\tret = -1;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\t\tif (delete_ref_loose(update->lock, update->type, err)) {\n+\t\t\tret = -1;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\t\ttry_remove_empty_parents((char *)update->refname);\n+\t\tif (!(update->flags & REF_ISPRUNING))\n+\t\t\t  delnames[delnum++] = xstrdup(update->lock->ref_name);\n+\t\tunlock_ref(update->lock);\n+\t\tupdate->lock = NULL;\n+\t}\n+\n+\t/* Acquire all ref locks for updates while verifying old values */\n \tfor (i = 0; i < n; i++) {\n \t\tstruct ref_update *update = updates[i];\n \n \t\tif (update->update_type != UPDATE_SHA1)\n \t\t\tcontinue;\n+\t\tif (is_null_sha1(update->new_sha1))\n+\t\t\tcontinue;\n \t\tupdate->lock = lock_ref_sha1_basic(update->refname,\n \t\t\t\t\t\t   (update->have_old ?\n \t\t\t\t\t\t    update->old_sha1 :\n@@ -3679,9 +3762,14 @@ int transaction_commit(struct ref_transaction *transaction,\n \t\t}\n \t}\n \n+\t/* delete reflog for all deleted refs */\n+\tfor (i = 0; i < delnum; i++)\n+\t\tunlink_or_warn(git_path(\"logs/%s\", delnames[i]));\n+\n \t/* Lock all reflog files */\n \tfor (i = 0; i < n; i++) {\n \t\tstruct ref_update *update = updates[i];\n+\t\tchar log_file[PATH_MAX];\n \n \t\tif (update->update_type != UPDATE_LOG)\n \t\t\tcontinue;\n@@ -3690,6 +3778,14 @@ int transaction_commit(struct ref_transaction *transaction,\n \t\t\tupdate->reflog_lock = update->orig_update->reflog_lock;\n \t\t\tcontinue;\n \t\t}\n+\t\tif (log_ref_setup(update->refname, log_file,\n+\t\t\t\t  sizeof(log_file))) {\n+\t\t\tret = -1;\n+\t\t\tif (err)\n+\t\t\t\tstrbuf_addf(err, \"Failed to setup reflog for \"\n+\t\t\t\t\t    \"%s\", update->refname);\n+\t\t\tgoto cleanup;\n+\t\t}\n \t\tupdate->reflog_fd = hold_lock_file_for_append(\n \t\t\t\t\tupdate->reflog_lock,\n \t\t\t\t\tgit_path(\"logs/%s\", update->refname),\n@@ -3705,7 +3801,7 @@ int transaction_commit(struct ref_transaction *transaction,\n \t\t}\n \t}\n \n-\t/* Perform ref updates first so live commits remain referenced */\n+\t/* Perform ref updates */\n \tfor (i = 0; i < n; i++) {\n \t\tstruct ref_update *update = updates[i];\n \n@@ -3726,21 +3822,6 @@ int transaction_commit(struct ref_transaction *transaction,\n \t\t}\n \t}\n \n-\t/* Perform deletes now that updates are safely completed */\n-\tfor (i = 0; i < n; i++) {\n-\t\tstruct ref_update *update = updates[i];\n-\n-\t\tif (update->update_type != UPDATE_SHA1)\n-\t\t\tcontinue;\n-\t\tif (update->lock) {\n-\t\t\tif (delete_ref_loose(update->lock, update->type, err))\n-\t\t\t\tret = -1;\n-\n-\t\t\tif (!(update->flags & REF_ISPRUNING))\n-\t\t\t\tdelnames[delnum++] = update->lock->ref_name;\n-\t\t}\n-\t}\n-\n \t/*\n \t * Update all reflog files\n \t * We have already committed all ref updates and deletes.\n@@ -3793,11 +3874,12 @@ int transaction_commit(struct ref_transaction *transaction,\n \n \tif (repack_without_refs(delnames, delnum, err))\n \t\tret = -1;\n-\tfor (i = 0; i < delnum; i++)\n-\t\tunlink_or_warn(git_path(\"logs/%s\", delnames[i]));\n \tclear_loose_ref_cache(&ref_cache);\n \n cleanup:\n+\tpacked_ref_cache = get_packed_ref_cache(&ref_cache);\n+\tif (packed_ref_cache->lock)\n+\t\trollback_packed_refs();\n \ttransaction->state = ret ? REF_TRANSACTION_ERROR\n \t\t: REF_TRANSACTION_CLOSED;\n \n-- \n2.0.0.583.g402232d\n"},{"id":"243432","messageId":"1402010234-7628-5-git-send-email-sahlberg@google.com","threadId":"36849","inReplyTo":"1402010234-7628-1-git-send-email-sahlberg@google.com","subject":"[PATCH 4/4] refs.c: update rename_ref to use a transaction","fromName":"Ronnie Sahlberg","fromEmail":"sahlberg@google.com","sentAt":"2014-06-05T23:17:14Z","receivedAt":"2014-06-05T23:17:14Z","isPatch":true,"sender":{"key":"sahlberg@google.com","avatar":"https://avatars.githubusercontent.com/u/7320636?v=4"},"body":"Change refs.c to use a single transaction to copy/rename both the refs and\nits reflog. Since we are no longer using rename() to move the reflog file\nwe no longer need to disallow rename_ref for refs with a symlink for its reflog\nso we can remove that test from the testsuite.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\n---\n refs.c            | 140 +++++++++++++++++++++++++-----------------------------\n t/t3200-branch.sh |   7 ---\n 2 files changed, 64 insertions(+), 83 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 3692a4d..4281011 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2632,22 +2632,33 @@ static int rename_tmp_log(const char *newrefname)\n \treturn 0;\n }\n \n-static int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1,\n-\t\t\t  const char *logmsg);\n+struct rename_reflog_cb {\n+\tstruct ref_transaction *transaction;\n+\tconst char *refname;\n+\tstruct strbuf *err;\n+};\n+\n+static int rename_reflog_ent(unsigned char *osha1, unsigned char *nsha1,\n+\t\t\t     const char *email, unsigned long timestamp, int tz,\n+\t\t\t     const char *message, void *cb_data)\n+{\n+\tstruct rename_reflog_cb *cb = cb_data;\n+\n+\treturn transaction_update_reflog(cb->transaction, cb->refname,\n+\t\t\t\t\t nsha1, osha1, email, timestamp, tz,\n+\t\t\t\t\t message, 0, cb->err);\n+}\n \n int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)\n {\n-\tunsigned char sha1[20], orig_sha1[20];\n-\tint flag = 0, logmoved = 0;\n-\tstruct ref_lock *lock;\n-\tstruct stat loginfo;\n-\tint log = !lstat(git_path(\"logs/%s\", oldrefname), &loginfo);\n+\tunsigned char sha1[20];\n+\tint flag = 0, log;\n+\tstruct ref_transaction *transaction = NULL;\n+\tstruct strbuf err = STRBUF_INIT;\n \tconst char *symref = NULL;\n+\tstruct rename_reflog_cb cb;\n \n-\tif (log && S_ISLNK(loginfo.st_mode))\n-\t\treturn error(\"reflog for %s is a symlink\", oldrefname);\n-\n-\tsymref = resolve_ref_unsafe(oldrefname, orig_sha1, 1, &flag);\n+\tsymref = resolve_ref_unsafe(oldrefname, sha1, 1, &flag);\n \tif (flag & REF_ISSYMREF)\n \t\treturn error(\"refname %s is a symbolic ref, renaming it is not supported\",\n \t\t\toldrefname);\n@@ -2656,77 +2667,54 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \n \tif (!is_refname_available(newrefname, get_packed_refs(&ref_cache),\n \t\t\t\t  &oldrefname, 1))\n-\t\treturn 1;\n+\t\treturn -1;\n \n \tif (!is_refname_available(newrefname, get_loose_refs(&ref_cache),\n \t\t\t\t  &oldrefname, 1))\n-\t\treturn 1;\n-\n-\tif (log && rename(git_path(\"logs/%s\", oldrefname), git_path(TMP_RENAMED_LOG)))\n-\t\treturn error(\"unable to move logfile logs/%s to \"TMP_RENAMED_LOG\": %s\",\n-\t\t\toldrefname, strerror(errno));\n-\n-\tif (delete_ref(oldrefname, orig_sha1, REF_NODEREF)) {\n-\t\terror(\"unable to delete old %s\", oldrefname);\n-\t\tgoto rollback;\n-\t}\n-\n-\tif (!read_ref_full(newrefname, sha1, 1, NULL) &&\n-\t    delete_ref(newrefname, sha1, REF_NODEREF)) {\n-\t\tif (errno==EISDIR) {\n-\t\t\tif (remove_empty_directories(git_path(\"%s\", newrefname))) {\n-\t\t\t\terror(\"Directory not empty: %s\", newrefname);\n-\t\t\t\tgoto rollback;\n-\t\t\t}\n-\t\t} else {\n-\t\t\terror(\"unable to delete existing %s\", newrefname);\n-\t\t\tgoto rollback;\n-\t\t}\n-\t}\n-\n-\tif (log && rename_tmp_log(newrefname))\n-\t\tgoto rollback;\n-\n-\tlogmoved = log;\n-\n-\tlock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL, NULL, 0);\n-\tif (!lock) {\n-\t\terror(\"unable to lock %s for update\", newrefname);\n-\t\tgoto rollback;\n-\t}\n-\tlock->force_write = 1;\n-\thashcpy(lock->old_sha1, orig_sha1);\n-\tif (write_ref_sha1(lock, orig_sha1, logmsg)) {\n-\t\terror(\"unable to write current sha1 into %s\", newrefname);\n-\t\tgoto rollback;\n-\t}\n+\t\treturn -1;\n \n+\tlog = reflog_exists(oldrefname);\n+\ttransaction = transaction_begin(&err);\n+\tif (!transaction)\n+\t\tgoto fail;\n+\n+\tif (strcmp(oldrefname, newrefname)) {\n+\t\tif (log && transaction_update_reflog(transaction, newrefname,\n+\t\t\t\t\t\t     sha1, sha1,\n+\t\t\t\t\t\t     git_committer_info(0),\n+\t\t\t\t\t\t     0, 0, NULL,\n+\t\t\t\t\t\t     REFLOG_TRUNCATE, &err))\n+\t\t\tgoto fail;\n+\t\tcb.transaction = transaction;\n+\t\tcb.refname = newrefname;\n+\t\tcb.err = &err;\n+\t\tif (log && for_each_reflog_ent(oldrefname, rename_reflog_ent,\n+\t\t\t\t\t       &cb))\n+\t\t\tgoto fail;\n+\n+\t\tif (transaction_delete_sha1(transaction, oldrefname, sha1,\n+\t\t\t\t\t    REF_NODEREF,\n+\t\t\t\t\t    1, NULL, &err))\n+\t\t\tgoto fail;\n+\t}\n+\tif (transaction_update_sha1(transaction, newrefname, sha1,\n+\t\t\t\t    NULL, 0, 0, NULL, &err))\n+\t\tgoto fail;\n+\tif (log && transaction_update_reflog(transaction, newrefname, sha1,\n+\t\t\t\t\t     sha1, git_committer_info(0),\n+\t\t\t\t\t     0, 0, logmsg,\n+\t\t\t\t\t     REFLOG_EMAIL_IS_COMMITTER, &err))\n+\t\tgoto fail;\n+\tif (transaction_commit(transaction, &err))\n+\t\tgoto fail;\n+\ttransaction_free(transaction);\n \treturn 0;\n \n- rollback:\n-\tlock = lock_ref_sha1_basic(oldrefname, NULL, 0, NULL, NULL, 0);\n-\tif (!lock) {\n-\t\terror(\"unable to lock %s for rollback\", oldrefname);\n-\t\tgoto rollbacklog;\n-\t}\n-\n-\tlock->force_write = 1;\n-\tflag = log_all_ref_updates;\n-\tlog_all_ref_updates = 0;\n-\tif (write_ref_sha1(lock, orig_sha1, NULL))\n-\t\terror(\"unable to write current sha1 into %s\", oldrefname);\n-\tlog_all_ref_updates = flag;\n-\n- rollbacklog:\n-\tif (logmoved && rename(git_path(\"logs/%s\", newrefname), git_path(\"logs/%s\", oldrefname)))\n-\t\terror(\"unable to restore logfile %s from %s: %s\",\n-\t\t\toldrefname, newrefname, strerror(errno));\n-\tif (!logmoved && log &&\n-\t    rename(git_path(TMP_RENAMED_LOG), git_path(\"logs/%s\", oldrefname)))\n-\t\terror(\"unable to restore logfile %s from \"TMP_RENAMED_LOG\": %s\",\n-\t\t\toldrefname, strerror(errno));\n-\n-\treturn 1;\n+ fail:\n+\terror(\"rename_ref failed: %s\", err.buf);\n+\tstrbuf_release(&err);\n+\ttransaction_free(transaction);\n+\treturn -1;\n }\n \n int close_ref(struct ref_lock *lock)\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex ac31b71..64f5bf2 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -293,13 +293,6 @@ test_expect_success 'renaming a symref is not allowed' '\n \ttest_path_is_missing .git/refs/heads/master3\n '\n \n-test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog for u is a symlink' '\n-\tgit branch -l u &&\n-\tmv .git/logs/refs/heads/u real-u &&\n-\tln -s real-u .git/logs/refs/heads/u &&\n-\ttest_must_fail git branch -m u v\n-'\n-\n test_expect_success 'test tracking setup via --track' '\n \tgit config remote.local.url . &&\n \tgit config remote.local.fetch refs/heads/*:refs/remotes/local/* &&\n-- \n2.0.0.583.g402232d\n"}]}