Skip to content

Commit

Permalink
rename_tmp_log(): handle a possible mkdir/rmdir race
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Michael Haggerty authored and Junio C Hamano committed Jan 21, 2014
1 parent fa59ae7 commit ae4a283
Showing 1 changed file with 10 additions and 1 deletion.
11 changes: 10 additions & 1 deletion refs.c
Original file line number Diff line number Diff line change
Expand Up @@ -2530,12 +2530,14 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)

static int rename_tmp_log(const char *newrefname)
{
int attempts_remaining = 3;

retry:
if (safe_create_leading_directories(git_path("logs/%s", newrefname))) {
error("unable to create directory for %s", newrefname);
return -1;
}

retry:
if (rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) {
if (errno==EISDIR || errno==ENOTDIR) {
/*
Expand All @@ -2548,6 +2550,13 @@ static int rename_tmp_log(const char *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));
Expand Down

0 comments on commit ae4a283

Please sign in to comment.