Change refs.c to use a single transaction to perform the rename.
Change the function to return 1 on failure instead of either -1 or 1.

These changes make the rename_ref operation atomic.

Signed-off-by: Ronnie Sahlberg <sahlb...@google.com>
---
 refs.c            | 168 ++++++++++++++----------------------------------------
 t/t3200-branch.sh |   7 ---
 2 files changed, 43 insertions(+), 132 deletions(-)

diff --git a/refs.c b/refs.c
index 8ca6add..9a3c7fe 100644
--- a/refs.c
+++ b/refs.c
@@ -2757,60 +2757,6 @@ int delete_ref(const char *refname, const unsigned char 
*sha1, int delopt)
        return 0;
 }
 
-/*
- * People using contrib's git-new-workdir have .git/logs/refs ->
- * /some/other/path/.git/logs/refs, and that may live on another device.
- *
- * IOW, to avoid cross device rename errors, the temporary renamed log must
- * live into logs/refs.
- */
-#define TMP_RENAMED_LOG  "logs/refs/.tmp-renamed-log"
-
-static int rename_tmp_log(const char *newrefname)
-{
-       int attempts_remaining = 4;
-
- retry:
-       switch (safe_create_leading_directories(git_path("logs/%s", 
newrefname))) {
-       case SCLD_OK:
-               break; /* success */
-       case SCLD_VANISHED:
-               if (--attempts_remaining > 0)
-                       goto retry;
-               /* fall through */
-       default:
-               error("unable to create directory for %s", newrefname);
-               return -1;
-       }
-
-       if (rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) 
{
-               if ((errno==EISDIR || errno==ENOTDIR) && --attempts_remaining > 
0) {
-                       /*
-                        * rename(a, b) when b is an existing
-                        * directory ought to result in ISDIR, but
-                        * Solaris 5.8 gives ENOTDIR.  Sheesh.
-                        */
-                       if (remove_empty_directories(git_path("logs/%s", 
newrefname))) {
-                               error("Directory not empty: logs/%s", 
newrefname);
-                               return -1;
-                       }
-                       goto retry;
-               } else if (errno == ENOENT && --attempts_remaining > 0) {
-                       /*
-                        * Maybe another process just deleted one of
-                        * the directories in the path to newrefname.
-                        * Try again from the beginning.
-                        */
-                       goto retry;
-               } else {
-                       error("unable to move logfile "TMP_RENAMED_LOG" to 
logs/%s: %s",
-                               newrefname, strerror(errno));
-                       return -1;
-               }
-       }
-       return 0;
-}
-
 static int rename_ref_available(const char *oldname, const char *newname)
 {
        struct string_list skip = STRING_LIST_INIT_NODUP;
@@ -2859,91 +2805,63 @@ static int copy_reflog_into_strbuf(const char *refname, 
struct strbuf *buf)
 
 int rename_ref(const char *oldrefname, const char *newrefname, const char 
*logmsg)
 {
-       unsigned char sha1[20], orig_sha1[20];
-       int flag = 0, logmoved = 0;
-       struct ref_lock *lock;
-       struct stat loginfo;
-       int log = !lstat(git_path("logs/%s", oldrefname), &loginfo);
+       unsigned char sha1[20];
+       int flag = 0;
+       int log;
+       struct transaction *transaction = NULL;
+       struct strbuf err = STRBUF_INIT;
        const char *symref = NULL;
+       struct reflog_committer_info ci;
 
-       if (log && S_ISLNK(loginfo.st_mode))
-               return error("reflog for %s is a symlink", oldrefname);
+       memset(&ci, 0, sizeof(ci));
+       ci.committer_info = git_committer_info(0);
 
        symref = resolve_ref_unsafe(oldrefname, RESOLVE_REF_READING,
-                                   orig_sha1, &flag);
-       if (flag & REF_ISSYMREF)
-               return error("refname %s is a symbolic ref, renaming it is not 
supported",
-                       oldrefname);
-       if (!symref)
-               return error("refname %s not found", oldrefname);
-
-       if (!rename_ref_available(oldrefname, newrefname))
+                                   sha1, &flag);
+       if (flag & REF_ISSYMREF) {
+               error("refname %s is a symbolic ref, renaming it is not "
+                     "supported", oldrefname);
                return 1;
-
-       if (log && rename(git_path("logs/%s", oldrefname), 
git_path(TMP_RENAMED_LOG)))
-               return error("unable to move logfile logs/%s to 
"TMP_RENAMED_LOG": %s",
-                       oldrefname, strerror(errno));
-
-       if (delete_ref(oldrefname, orig_sha1, REF_NODEREF)) {
-               error("unable to delete old %s", oldrefname);
-               goto rollback;
        }
-
-       if (!read_ref_full(newrefname, RESOLVE_REF_READING, sha1, NULL) &&
-           delete_ref(newrefname, sha1, REF_NODEREF)) {
-               if (errno==EISDIR) {
-                       if (remove_empty_directories(git_path("%s", 
newrefname))) {
-                               error("Directory not empty: %s", newrefname);
-                               goto rollback;
-                       }
-               } else {
-                       error("unable to delete existing %s", newrefname);
-                       goto rollback;
-               }
+       if (!symref) {
+               error("refname %s not found", oldrefname);
+               return 1;
        }
 
-       if (log && rename_tmp_log(newrefname))
-               goto rollback;
+       if (!rename_ref_available(oldrefname, newrefname))
+               return 1;
 
-       logmoved = log;
+       log = reflog_exists(oldrefname);
 
-       lock = lock_ref_sha1_basic(newrefname, NULL, NULL, 0, NULL);
-       if (!lock) {
-               error("unable to lock %s for update", newrefname);
-               goto rollback;
-       }
-       lock->force_write = 1;
-       hashcpy(lock->old_sha1, orig_sha1);
-       if (write_ref_sha1(lock, orig_sha1, logmsg)) {
-               error("unable to write current sha1 into %s", newrefname);
-               goto rollback;
+       transaction = transaction_begin(&err);
+       if (!transaction)
+               goto fail;
+
+       if (strcmp(oldrefname, newrefname)) {
+               if (transaction_delete_ref(transaction, oldrefname, sha1,
+                                          REF_NODEREF, 1, NULL, &err))
+                       goto fail;
+               if (log && transaction_rename_reflog(transaction, oldrefname,
+                                                    newrefname, &err))
+                       goto fail;
+               if (log && transaction_update_reflog(transaction, newrefname,
+                                    sha1, sha1, &ci, logmsg,
+                                    REFLOG_COMMITTER_INFO_IS_VALID, &err))
+                       goto fail;
        }
 
+       if (transaction_update_ref(transaction, newrefname, sha1,
+                                  NULL, 0, 0, NULL, &err))
+               goto fail;
+       if (transaction_commit(transaction, &err))
+               goto fail;
+       transaction_free(transaction);
        return 0;
 
- rollback:
-       lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, 0, NULL);
-       if (!lock) {
-               error("unable to lock %s for rollback", oldrefname);
-               goto rollbacklog;
-       }
-
-       lock->force_write = 1;
-       flag = log_all_ref_updates;
-       log_all_ref_updates = 0;
-       if (write_ref_sha1(lock, orig_sha1, NULL))
-               error("unable to write current sha1 into %s", oldrefname);
-       log_all_ref_updates = flag;
-
- rollbacklog:
-       if (logmoved && rename(git_path("logs/%s", newrefname), 
git_path("logs/%s", oldrefname)))
-               error("unable to restore logfile %s from %s: %s",
-                       oldrefname, newrefname, strerror(errno));
-       if (!logmoved && log &&
-           rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", oldrefname)))
-               error("unable to restore logfile %s from "TMP_RENAMED_LOG": %s",
-                       oldrefname, strerror(errno));
-
+ fail:
+       error("rename_ref failed: %s", err.buf);
+       strbuf_release(&err);
+       transaction_free(transaction);
        return 1;
 }
 
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 432921b..c6c53e4 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -302,13 +302,6 @@ test_expect_success 'renaming a symref is not allowed' '
        test_path_is_missing .git/refs/heads/master3
 '
 
-test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog 
for u is a symlink' '
-       git branch -l u &&
-       mv .git/logs/refs/heads/u real-u &&
-       ln -s real-u .git/logs/refs/heads/u &&
-       test_must_fail git branch -m u v
-'
-
 test_expect_success 'test tracking setup via --track' '
        git config remote.local.url . &&
        git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
-- 
2.1.0.rc2.206.gedb03e5

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to