diff options
author | Jeff King <peff@peff.net> | 2015-09-01 18:14:09 -0400 |
---|---|---|
committer | Junio C Hamano <gitster@pobox.com> | 2015-09-01 15:52:54 -0700 |
commit | 9dd330e6cad6ce11557acb18f35136c549d8ac1b (patch) | |
tree | bc6bfca6e0354345a35d19f05c7742a75f30ee4b /t/t4150-am.sh | |
parent | Eighth batch for 2.6 (diff) | |
download | tgif-9dd330e6cad6ce11557acb18f35136c549d8ac1b.tar.xz |
rerere: release lockfile in non-writing functions
There's a bug in builtin/am.c in which we take a lock on
MERGE_RR recursively. But rather than fix am.c, this patch
fixes the confusing interface from rerere.c that caused the
bug. Read on for the gory details.
The setup_rerere() function both reads the existing MERGE_RR
file, and takes MERGE_RR.lock. In the rerere() and
rerere_forget() functions, we end up in write_rr(), which
will then commit the lock file.
But for functions like rerere_clear() that do not write to
MERGE_RR, we expect the caller to have handled
setup_rerere(). That caller would then need to release the
lockfile, but it can't; the lock struct is local to
rerere.c.
For builtin/rerere.c, this is OK. We run a single rerere
operation and then exit immediately, which has the side
effect of rolling back the lockfile.
But in builtin/am.c, this is actively wrong. If we run "git
am -3 --skip", we call setup-rerere twice without releasing
the lock:
1. The "--skip" causes us to call am_rerere_clear(), which
calls setup_rerere(), but never drops the lock.
2. We then proceed to the next patch.
3. The "--3way" may cause us to call rerere() to handle
conflicts in that patch, but we are already holding the
lock. The lockfile code dies with:
BUG: prepare_tempfile_object called for active object
We could fix this by having rerere_clear() call
rollback_lock_file(). But it feels a bit odd for it to roll
back a lockfile that it did not itself take. So let's
simplify the interface further, and handle setup_rerere in
the function itself, taking away the question from the
caller over whether they need to do so.
We can give rerere_gc() the same treatment, as well (even
though it doesn't have any callers besides builtin/rerere.c
at this point). Note that these functions don't take flags
from their callers to pass along to setup_rerere; that's OK,
because the flags would not be meaningful for what they are
doing.
Both of those functions need to hold the lock because even
though they do not write to MERGE_RR, they are still writing
and should be protected from a simultaneous "rerere" run.
But rerere_remaining(), "rerere diff", and "rerere status"
are all read-only operations. They want to setup_rerere(),
but do not care about taking the lock in the first place.
Since our update of MERGE_RR is the usual atomic rename done
by commit_lock_file, they can just do a lockless read. For
that, we teach setup_rerere a READONLY flag to avoid the
lock.
As a bonus, this pushes builtin/rerere.c's setup_rerere call
closer to the functions that use it. Which means that "git
rerere totally-bogus-command" will no longer silently
exit(0) in a repository without rerere enabled.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 't/t4150-am.sh')
-rwxr-xr-x | t/t4150-am.sh | 36 |
1 files changed, 36 insertions, 0 deletions
diff --git a/t/t4150-am.sh b/t/t4150-am.sh index dd627c42d3..af6053a242 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -873,4 +873,40 @@ test_expect_success 'am --message-id -s signs off after the message id' ' test_cmp expected actual ' +test_expect_success 'am -3 works with rerere' ' + rm -fr .git/rebase-apply && + git reset --hard && + + # make patches one->two and two->three... + test_commit one file && + test_commit two file && + test_commit three file && + git format-patch -2 --stdout >seq.patch && + + # and create a situation that conflicts... + git reset --hard one && + test_commit other file && + + # enable rerere... + test_config rerere.enabled true && + test_when_finished "rm -rf .git/rr-cache" && + + # ...and apply. Our resolution is to skip the first + # patch, and the rerere the second one. + test_must_fail git am -3 seq.patch && + test_must_fail git am --skip && + echo resolved >file && + git add file && + git am --resolved && + + # now apply again, and confirm that rerere engaged (we still + # expect failure from am because rerere does not auto-commit + # for us). + git reset --hard other && + test_must_fail git am -3 seq.patch && + test_must_fail git am --skip && + echo resolved >expect && + test_cmp expect file +' + test_done |