ignore stale directories when checking reflog existence
authorJeff King <peff@peff.net>
Tue, 4 Nov 2014 13:24:53 +0000 (08:24 -0500)
committerJunio C Hamano <gitster@pobox.com>
Tue, 4 Nov 2014 20:18:44 +0000 (12:18 -0800)
When we update a ref, we have two rules for whether or not
we actually update the reflog:

  1. If the reflog already exists, we will always append to
     it.

  2. If log_all_ref_updates is set, we will create a new
     reflog file if necessary.

We do the existence check by trying to open the reflog file,
either with or without O_CREAT (depending on log_all_ref_updates).
If it fails, then we check errno to see what happened.

If we were not using O_CREAT and we got ENOENT, the file
doesn't exist, and we return success (there isn't a reflog
already, and we were not told to make a new one).

If we get EISDIR, then there is likely a stale directory
that needs to be removed (e.g., there used to be "foo/bar",
it was deleted, and the directory "foo" was left. Now we
want to create the ref "foo"). If O_CREAT is set, then we
catch this case, try to remove the directory, and retry our
open. So far so good.

But if we get EISDIR and O_CREAT is not set, then we treat
this as any other error, which is not right. Like ENOENT,
EISDIR is an indication that we do not have a reflog, and we
should silently return success (we were not told to create
it). Instead, the current code reports this as an error, and
we fail to update the ref at all.

Note that this is relatively unlikely to happen, as you
would have to have had reflogs turned on, and then later
turned them off (it could also happen due to a bug in fetch,
but that was fixed in the previous commit). However, it's
quite easy to fix: we just need to treat EISDIR like ENOENT
for the non-O_CREAT case, and silently return (note that
this early return means we can also simplify the O_CREAT
case).

Our new tests cover both cases (O_CREAT and non-O_CREAT).
The first one already worked, of course.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
refs.c
t/t1410-reflog.sh

diff --git a/refs.c b/refs.c
index f0bd7ac..e9eda88 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -2751,10 +2751,10 @@ int log_ref_setup(const char *refname, char *logfile, int bufsize)
 
        logfd = open(logfile, oflags, 0666);
        if (logfd < 0) {
-               if (!(oflags & O_CREAT) && errno == ENOENT)
+               if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR))
                        return 0;
 
-               if ((oflags & O_CREAT) && errno == EISDIR) {
+               if (errno == EISDIR) {
                        if (remove_empty_directories(logfile)) {
                                return error("There are still logs under '%s'",
                                             logfile);
index 236b13a..e4409e3 100755 (executable)
@@ -245,4 +245,38 @@ test_expect_success 'gc.reflogexpire=false' '
 
 '
 
+test_expect_success 'stale dirs do not cause d/f conflicts (reflogs on)' '
+       test_when_finished "git branch -d a || git branch -d a/b" &&
+
+       git branch a/b master &&
+       echo "a/b@{0} branch: Created from master" >expect &&
+       git log -g --format="%gd %gs" a/b >actual &&
+       test_cmp expect actual &&
+       git branch -d a/b &&
+
+       # now logs/refs/heads/a is a stale directory, but
+       # we should move it out of the way to create "a" reflog
+       git branch a master &&
+       echo "a@{0} branch: Created from master" >expect &&
+       git log -g --format="%gd %gs" a >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'stale dirs do not cause d/f conflicts (reflogs off)' '
+       test_when_finished "git branch -d a || git branch -d a/b" &&
+
+       git branch a/b master &&
+       echo "a/b@{0} branch: Created from master" >expect &&
+       git log -g --format="%gd %gs" a/b >actual &&
+       test_cmp expect actual &&
+       git branch -d a/b &&
+
+       # same as before, but we only create a reflog for "a" if
+       # it already exists, which it does not
+       git -c core.logallrefupdates=false branch a master &&
+       : >expect &&
+       git log -g --format="%gd %gs" a >actual &&
+       test_cmp expect actual
+'
+
 test_done