All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Thomas Bachem via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Patrick Steinhardt <ps@pks.im>,
	Phillip Wood <phillip.wood@dunelm.org.uk>,
	Junio C Hamano <gitster@pobox.com>,
	Phillip Wood <phillip.wood123@gmail.com>,
	Thomas Bachem <mail@thomasbachem.com>,
	Thomas Bachem <mail@thomasbachem.com>
Subject: [PATCH v8 1/2] rerere: wait for MERGE_RR.lock before giving up
Date: Tue, 13 Oct 2026 12:07:52 +0000	[thread overview]
Message-ID: <06f6c774391bf8d6d40a30d0b7fef6c80596afca.1791893274.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2214.v8.git.1791893274.gitgitgadget@gmail.com>

From: Thomas Bachem <mail@thomasbachem.com>

setup_rerere() takes MERGE_RR.lock with LOCK_DIE_ON_ERROR, so when two
processes want it at the same time, the second one dies. That
used to be rare. Since 452b12c2e0 (builtin/maintenance: use
"geometric" strategy by default, 2026-02-24) the auto maintenance
after a commit runs "git rerere gc" whenever rr-cache has enough
stale entries, and the gc holds the lock while it prunes.

If a rebase's next pick conflicts while the gc holds the lock, the
rebase dies inside repo_rerere(), before the sequencer has written what
"git rebase --continue" needs. Every later "git rebase --continue"
then fails with "you have staged changes in your working tree".

Wait for the lock for up to rerere.lockTimeout milliseconds, 1000 by
default, and only then fail as before. Pruning a few thousand entries
takes well under a second, so the default covers a rebase that runs
into the gc.

Assisted-by: Claude Fable 5.1
Signed-off-by: Thomas Bachem <mail@thomasbachem.com>
---
 Documentation/config/rerere.adoc |  7 +++++
 rerere.c                         | 22 ++++++++++---
 t/t4200-rerere.sh                | 53 ++++++++++++++++++++++++++++++++
 3 files changed, 77 insertions(+), 5 deletions(-)

diff --git a/Documentation/config/rerere.adoc b/Documentation/config/rerere.adoc
index 3a78b5ebb1..ab035e2d7d 100644
--- a/Documentation/config/rerere.adoc
+++ b/Documentation/config/rerere.adoc
@@ -10,3 +10,10 @@ rerere.enabled::
 	enabled if there is an `rr-cache` directory under the
 	`$GIT_DIR`, e.g. if "rerere" was previously used in the
 	repository.
+
+rerere.lockTimeout::
+	The length of time, in milliseconds, to wait for the rerere
+	lock when another process holds it, typically a background
+	`git rerere gc`.  Value 0 means not to wait at all; -1 means
+	to wait indefinitely.  Default is 1000 (i.e., wait for 1
+	second).
diff --git a/rerere.c b/rerere.c
index 1c3745d9e3..64fac07c71 100644
--- a/rerere.c
+++ b/rerere.c
@@ -33,6 +33,9 @@ static int rerere_enabled = -1;
 /* automatically update cleanly resolved paths to the index */
 static int rerere_autoupdate;
 
+/* how long to wait for MERGE_RR.lock, in milliseconds */
+static int rerere_lock_timeout_ms = 1000;
+
 #define RR_HAS_POSTIMAGE 1
 #define RR_HAS_PREIMAGE 2
 struct rerere_dir {
@@ -850,6 +853,8 @@ static void git_rerere_config(void)
 {
 	repo_config_get_bool(the_repository, "rerere.enabled", &rerere_enabled);
 	repo_config_get_bool(the_repository, "rerere.autoupdate", &rerere_autoupdate);
+	repo_config_get_int(the_repository, "rerere.locktimeout",
+			    &rerere_lock_timeout_ms);
 	repo_config(the_repository, git_default_config, NULL);
 }
 
@@ -882,12 +887,19 @@ int setup_rerere(struct repository *r, struct string_list *merge_rr, int flags)
 
 	if (flags & (RERERE_AUTOUPDATE|RERERE_NOAUTOUPDATE))
 		rerere_autoupdate = !!(flags & RERERE_AUTOUPDATE);
-	if (flags & RERERE_READONLY)
+	if (flags & RERERE_READONLY) {
 		fd = 0;
-	else
-		fd = repo_hold_lock_file_for_update(r, &write_lock,
-						    git_path_merge_rr(r),
-						    LOCK_DIE_ON_ERROR);
+	} else {
+		/*
+		 * Another process may hold the lock for a while, e.g.
+		 * "git rerere gc" while it prunes rr-cache, so wait for
+		 * it instead of dying right away.
+		 */
+		fd = repo_hold_lock_file_for_update_timeout(r, &write_lock,
+							    git_path_merge_rr(r),
+							    LOCK_DIE_ON_ERROR,
+							    rerere_lock_timeout_ms);
+	}
 	read_rr(r, merge_rr);
 	return fd;
 }
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
index 7bb601e117..94165c8417 100755
--- a/t/t4200-rerere.sh
+++ b/t/t4200-rerere.sh
@@ -242,6 +242,59 @@ test_expect_success 'old records rest in peace' '
 	test_path_is_missing $rr2/preimage
 '
 
+test_expect_success 'a held lock is waited out within rerere.lockTimeout' '
+	git reset --hard &&
+	rm -rf $rr &&
+	test_when_finished "wait && rm -f .git/MERGE_RR.lock" &&
+	>.git/MERGE_RR.lock &&
+	{
+		( sleep 1 && rm -f .git/MERGE_RR.lock ) &
+	} &&
+	test_must_fail git -c rerere.lockTimeout=5000 merge first 2>err &&
+	test_grep ! "Unable to create.*MERGE_RR\.lock" err &&
+	test_grep "^=======\$" $rr/preimage
+'
+
+test_expect_success 'merge fails on a lock it cannot take' '
+	git reset --hard &&
+	rm -rf $rr &&
+	test_when_finished "rm -f .git/MERGE_RR.lock" &&
+	>.git/MERGE_RR.lock &&
+	test_must_fail git -c rerere.lockTimeout=0 merge first 2>err &&
+	test_grep "Unable to create.*MERGE_RR\.lock" err &&
+	test_grep "^=======\$" a1 &&
+	test_path_is_missing $rr/preimage
+'
+
+test_expect_success 'rerere, forget, clear and gc fail on a lock they cannot take' '
+	test_when_finished "rm -f .git/MERGE_RR.lock" &&
+	>.git/MERGE_RR.lock &&
+	test_must_fail git -c rerere.lockTimeout=0 rerere 2>err &&
+	test_grep "Unable to create.*MERGE_RR\.lock" err &&
+	test_must_fail git -c rerere.lockTimeout=0 rerere forget a1 2>err &&
+	test_grep "Unable to create.*MERGE_RR\.lock" err &&
+	test_must_fail git -c rerere.lockTimeout=0 rerere clear 2>err &&
+	test_grep "Unable to create.*MERGE_RR\.lock" err &&
+	test_must_fail git -c rerere.lockTimeout=0 rerere gc 2>err &&
+	test_grep "Unable to create.*MERGE_RR\.lock" err
+'
+
+test_expect_success 'rebase --abort fails on a lock it cannot take' '
+	git reset --hard &&
+	git checkout -b lock-held-abort third &&
+	test_when_finished "git checkout third && git branch -D lock-held-abort" &&
+	test_must_fail git rebase first &&
+	test_when_finished "test_might_fail git rebase --abort" &&
+	test_when_finished "rm -f .git/MERGE_RR.lock" &&
+	>.git/MERGE_RR.lock &&
+	test_must_fail git -c rerere.lockTimeout=0 rebase --abort 2>err &&
+	test_grep "Unable to create.*MERGE_RR\.lock" err &&
+	test_path_is_dir .git/rebase-merge &&
+	rm .git/MERGE_RR.lock &&
+	git rebase --abort &&
+	test_path_is_missing .git/rebase-merge
+'
+
 rerere_gc_custom_expiry_test () {
 	five_days="$1" right_now="$2"
 	test_expect_success "rerere gc with custom expiry ($five_days, $right_now)" '
-- 
gitgitgadget


  reply	other threads:[~2026-10-13 12:07 UTC|newest]

Thread overview: 53+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-02  8:31 [PATCH] rerere: keep a background gc from killing a rebase Thomas Bachem via GitGitGadget
2026-09-02 13:27 ` Phillip Wood
2026-09-02 15:07   ` Thomas Bachem
2026-09-03 13:50     ` Phillip Wood
2026-09-03  7:40 ` Patrick Steinhardt
2026-09-03  8:11   ` Thomas Bachem
2026-09-03  8:32     ` Patrick Steinhardt
2026-09-03 12:12       ` Thomas Bachem
2026-09-03 13:50       ` Phillip Wood
2026-09-04  7:44 ` [PATCH v2] " Thomas Bachem via GitGitGadget
2026-09-04 15:21   ` Phillip Wood
2026-09-04 15:55     ` Thomas Bachem
2026-09-07 10:07       ` Phillip Wood
2026-09-04 17:06     ` Junio C Hamano
2026-09-04 18:17       ` Thomas Bachem
2026-09-04 15:51 ` [PATCH v3] " Thomas Bachem via GitGitGadget
2026-09-04 19:08   ` Junio C Hamano
2026-09-05  5:41     ` Thomas Bachem
2026-09-05 16:10       ` Junio C Hamano
2026-09-06 10:29         ` Thomas Bachem
2026-09-07  7:41   ` Patrick Steinhardt
2026-09-14  8:04 ` [PATCH v4 0/2] rerere: wait for MERGE_RR.lock, and go on at a conflict Thomas Bachem via GitGitGadget
2026-09-14  8:04   ` [PATCH v4 1/2] rerere: wait for MERGE_RR.lock, and let the gc skip it Thomas Bachem via GitGitGadget
2026-09-28  8:18     ` Patrick Steinhardt
2026-09-14  8:04   ` [PATCH v4 2/2] rerere: go on at a conflict when the lock stays busy Thomas Bachem via GitGitGadget
2026-09-28  8:18     ` Patrick Steinhardt
2026-09-28 11:58 ` [PATCH v5 0/3] rerere: wait for MERGE_RR.lock, and go on at a conflict Thomas Bachem via GitGitGadget
2026-09-28 11:58   ` [PATCH v5 1/3] rerere: wait for MERGE_RR.lock before giving up Thomas Bachem via GitGitGadget
2026-09-28 11:58   ` [PATCH v5 2/3] rerere: add "gc --auto" that skips a held lock Thomas Bachem via GitGitGadget
2026-09-30 15:00     ` Patrick Steinhardt
2026-10-01  8:08       ` Thomas Bachem
2026-10-01 11:19         ` Patrick Steinhardt
2026-09-28 11:58   ` [PATCH v5 3/3] rerere: go on at a conflict when the lock stays busy Thomas Bachem via GitGitGadget
2026-10-02 11:11 ` [PATCH v6 0/3] rerere: wait for MERGE_RR.lock, and go on at a conflict Thomas Bachem via GitGitGadget
2026-10-02 11:11   ` [PATCH v6 1/3] rerere: wait for MERGE_RR.lock before giving up Thomas Bachem via GitGitGadget
2026-10-02 11:11   ` [PATCH v6 2/3] rerere: add "gc --skip-locked" for auto maintenance Thomas Bachem via GitGitGadget
2026-10-09  9:06     ` Patrick Steinhardt
2026-10-10  9:09       ` Thomas Bachem
2026-10-02 11:11   ` [PATCH v6 3/3] rerere: go on at a conflict when the lock stays busy Thomas Bachem via GitGitGadget
2026-10-09  9:06     ` Patrick Steinhardt
2026-10-10  9:09       ` Thomas Bachem
2026-10-10 10:13 ` [PATCH v7 0/2] rerere: wait for MERGE_RR.lock, but not in auto maintenance Thomas Bachem via GitGitGadget
2026-10-10 10:13   ` [PATCH v7 1/2] rerere: wait for MERGE_RR.lock before giving up Thomas Bachem via GitGitGadget
2026-10-11  0:39     ` Junio C Hamano
2026-10-11 14:18       ` Thomas Bachem
2026-10-12  0:32         ` Junio C Hamano
2026-10-10 10:13   ` [PATCH v7 2/2] rerere: add "gc --skip-locked" for auto maintenance Thomas Bachem via GitGitGadget
2026-10-13  7:31     ` Patrick Steinhardt
2026-10-13 12:07 ` [PATCH v8 0/2] rerere: wait for MERGE_RR.lock, but not in " Thomas Bachem via GitGitGadget
2026-10-13 12:07   ` Thomas Bachem via GitGitGadget [this message]
2026-10-13 12:07   ` [PATCH v8 2/2] rerere: add "gc --skip-locked" for " Thomas Bachem via GitGitGadget
2026-10-13 12:22   ` [PATCH v8 0/2] rerere: wait for MERGE_RR.lock, but not in " Patrick Steinhardt
2026-10-13 16:20     ` Junio C Hamano

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=06f6c774391bf8d6d40a30d0b7fef6c80596afca.1791893274.git.gitgitgadget@gmail.com \
    --to=gitgitgadget@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    --cc=mail@thomasbachem.com \
    --cc=phillip.wood123@gmail.com \
    --cc=phillip.wood@dunelm.org.uk \
    --cc=ps@pks.im \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.