rename_tmp_log(): handle a possible mkdir/rmdir race

If a directory vanishes while renaming the temporary reflog file, retry (up to 3 times). This could happen if another process deletes the directory created by safe_create_leading_directories() just before we rename the file into the directory. As far as I can tell, this race could not occur internal to git. The only time that a directory under $GIT_DIR/logs is deleted is if room has to be made for a log file for a reference with the same name; for example, in the following sequence: git branch foo/bar # Creates file .git/logs/refs/heads/foo/bar git branch -d foo/bar # Deletes file but leaves .git/logs/refs/heads/foo/ git branch foo # Deletes .git/logs/refs/heads/foo/ But the only reason the last command deletes the directory is because it wants to create a file with the same name. So if another process (e.g., git branch foo/baz ) wants to create that directory, one of the two is doomed to failure anyway because of a D/F conflict. Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Michael Haggerty committed Jan 18, 2014 at 23:48 UTC ae4a283e3b4cdc99a548d215739539c3a690f290
1 file changed +10 -1
refs.c
+10 -1
@@ -2530,12 +2530,14 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
2530
2531 static int rename_tmp_log(const char *newrefname)
2532 {
2533 + int attempts_remaining = 3;
2534 +
2535 + retry:
2536 if (safe_create_leading_directories(git_path("logs/%s", newrefname))) {
2537 error("unable to create directory for %s", newrefname);
2538 return -1;
2539 }
2540
2538 - retry:
2541 if (rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) {
2542 if (errno==EISDIR || errno==ENOTDIR) {
2543 /*
@@ -2548,6 +2550,13 @@ static int rename_tmp_log(const char *newrefname)
2550 return -1;
2551 }
2552 goto retry;
2553 + } else if (errno == ENOENT && --attempts_remaining > 0) {
2554 + /*
2555 + * Maybe another process just deleted one of
2556 + * the directories in the path to newrefname.
2557 + * Try again from the beginning.
2558 + */
2559 + goto retry;
2560 } else {
2561 error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
2562 newrefname, strerror(errno));