From patchwork Tue Jun 14 19:27:29 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Derrick Stolee X-Patchwork-Id: 12881491 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0F689C43334 for ; Tue, 14 Jun 2022 19:27:44 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237431AbiFNT1m (ORCPT ); Tue, 14 Jun 2022 15:27:42 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:38986 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233205AbiFNT1j (ORCPT ); Tue, 14 Jun 2022 15:27:39 -0400 Received: from mail-wr1-x432.google.com (mail-wr1-x432.google.com [IPv6:2a00:1450:4864:20::432]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1300C10548 for ; Tue, 14 Jun 2022 12:27:38 -0700 (PDT) Received: by mail-wr1-x432.google.com with SMTP id a15so12561472wrh.2 for ; Tue, 14 Jun 2022 12:27:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=n/FybgHynVZBfzJ0ZJ1MF3pZGUQ1LYk/5QHlUrASDcs=; b=N2M6iAqKmV37AyaoW5UL7xeAcoeOx58Tqz6voM01r3GJQd5yr7p0i/bBBMTVJpYTuR +7CDuZGGuhX0Ipfg+GlvtJAS/9WbhrNNcaZcTyry8kCv+d2frzFGOHdujkM7/ze0q44W lZqzvcImP7WzHa1Tt+ljlMmX71+NBxlCU1uLV5z14w+qhNm/fU8tbZQHZYiGLYEJoumS L4DBDdqvdMYPMEfxXmkFiPbW1f8QVCc1hdaAVs3W9Lfq2Yqk0dyJvkpNfrUhzamqBOdu HorQ3QQLcCZeiWXMLkR5je4w83Wy8uyhvp0d/HF60DTiYUFoAvbRQF2UfNbio30yb+bw 0QMA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=n/FybgHynVZBfzJ0ZJ1MF3pZGUQ1LYk/5QHlUrASDcs=; b=6XbkGopgR7X4AVVlsZv6/NYfcozAjmgtmSG2VkAluy32akcsbU9XgrTwhjSCvCldH3 mOXNeFObvDi66IBUCwzyP1/UdJMdu69OlV9XoDpUDptH7YkaerqNQg1qA/FCV0A0IsQr u0SncRmWa1j5FqIMXedP8pd+zfcThjbNzX6w7keDKnLUb31Y1m8FZ3VpRLTYANnTOL6X gPisFcuu+uyHA5g+deYl7pEdjacf4n3LutxNDlv1OeOWrHjD/cre1waKvF0X/9bny3xz KGmMUz5xNFAG71mgks70Kn/1MfC1lLWIbg7PvqFDUb6yinIZ0J4rOWER9/xtDtoHWP8D ks6A== X-Gm-Message-State: AJIora8tPGLO/bJcVZWx3vrKW8UC6GoWC0bqdGsKABEzN3p9xWi25hHS eDxl/xGwAMkjXG14SLDsOygopQTvkHkuPw== X-Google-Smtp-Source: AGRyM1vXtOe/YGYwBpP2w2Az9F7LaxqPMSHGHgQJ2vslq8CqZef+Q/mEq7xaSIzU0meWjICqBTI+Dw== X-Received: by 2002:a05:6000:1446:b0:218:5a5d:6cb5 with SMTP id v6-20020a056000144600b002185a5d6cb5mr6170426wrx.629.1655234856110; Tue, 14 Jun 2022 12:27:36 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id v28-20020adfa1dc000000b0021576694d9dsm12896260wrv.97.2022.06.14.12.27.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Jun 2022 12:27:35 -0700 (PDT) Message-Id: In-Reply-To: References: Date: Tue, 14 Jun 2022 19:27:29 +0000 Subject: [PATCH v2 1/5] branch: add branch_checked_out() helper Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: gitster@pobox.com, johannes.schindelin@gmx.de, me@ttaylorr.com, Jeff Hostetler , Phillip Wood , Elijah Newren , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsA==?= Bjarmason , Derrick Stolee , Derrick Stolee Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Derrick Stolee From: Derrick Stolee The validate_new_branchname() method contains a check to see if a branch is checked out in any non-bare worktree. This is intended to prevent a force push that will mess up an existing checkout. This helper is not suitable to performing just that check, because the method will die() when the branch is checked out instead of returning an error code. Create a new branch_checked_out() helper that performs the most basic form of this check. To ensure we can call branch_checked_out() in a loop with good performance, do a single preparation step that iterates over all worktrees and stores their current HEAD branches in a strmap. The branch_checked_out() helper can then discover these branches using a hash lookup. This helper is currently missing some key functionality. Namely: it doesn't look for active rebases or bisects which mean that the branch is "checked out" even though HEAD doesn't point to that ref. This functionality will be added in a coming change. We could use branch_checked_out() in validate_new_branchname(), but this missing functionality would be a regression. However, we have no tests that cover this case! Add a new test script that will be expanded with these cross-worktree ref updates. The current tests would still pass if we refactored validate_new_branchname() to use this version of branch_checked_out(). The next change will fix that functionality and add the proper test coverage. Signed-off-by: Derrick Stolee --- branch.c | 36 ++++++++++++++++++++++++++++++++++++ branch.h | 7 +++++++ t/t2407-worktree-heads.sh | 29 +++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100755 t/t2407-worktree-heads.sh diff --git a/branch.c b/branch.c index 2d6569b0c62..42ba60b7cb5 100644 --- a/branch.c +++ b/branch.c @@ -10,6 +10,7 @@ #include "worktree.h" #include "submodule-config.h" #include "run-command.h" +#include "strmap.h" struct tracking { struct refspec_item spec; @@ -369,6 +370,41 @@ int validate_branchname(const char *name, struct strbuf *ref) return ref_exists(ref->buf); } +static int initialized_checked_out_branches; +static struct strmap current_checked_out_branches = STRMAP_INIT; + +static void prepare_checked_out_branches(void) +{ + int i = 0; + struct worktree **worktrees; + + if (initialized_checked_out_branches) + return; + initialized_checked_out_branches = 1; + + worktrees = get_worktrees(); + + while (worktrees[i]) { + struct worktree *wt = worktrees[i++]; + + if (wt->is_bare) + continue; + + if (wt->head_ref) + strmap_put(¤t_checked_out_branches, + wt->head_ref, + xstrdup(wt->path)); + } + + free_worktrees(worktrees); +} + +const char *branch_checked_out(const char *refname) +{ + prepare_checked_out_branches(); + return strmap_get(¤t_checked_out_branches, refname); +} + /* * Check if a branch 'name' can be created as a new branch; die otherwise. * 'force' can be used when it is OK for the named branch already exists. diff --git a/branch.h b/branch.h index 560b6b96a8f..ef56103c050 100644 --- a/branch.h +++ b/branch.h @@ -101,6 +101,13 @@ void create_branches_recursively(struct repository *r, const char *name, const char *tracking_name, int force, int reflog, int quiet, enum branch_track track, int dry_run); + +/* + * If the branch at 'refname' is currently checked out in a worktree, + * then return the path to that worktree. + */ +const char *branch_checked_out(const char *refname); + /* * Check if 'name' can be a valid name for a branch; die otherwise. * Return 1 if the named branch already exists; return 0 otherwise. diff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh new file mode 100755 index 00000000000..305ab46e38e --- /dev/null +++ b/t/t2407-worktree-heads.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +test_description='test operations trying to overwrite refs at worktree HEAD' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit init && + git branch -f fake-1 && + git branch -f fake-2 && + + for i in 1 2 3 4 + do + test_commit $i && + git branch wt-$i && + git worktree add wt-$i wt-$i || return 1 + done +' + +test_expect_success 'refuse to overwrite: checked out in worktree' ' + for i in 1 2 3 4 + do + test_must_fail git branch -f wt-$i HEAD 2>err + grep "cannot force update the branch" err || return 1 + done +' + +test_done From patchwork Tue Jun 14 19:27:30 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Derrick Stolee X-Patchwork-Id: 12881492 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id B4DDFCCA47A for ; Tue, 14 Jun 2022 19:27:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S238912AbiFNT1o (ORCPT ); Tue, 14 Jun 2022 15:27:44 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:39016 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235235AbiFNT1k (ORCPT ); Tue, 14 Jun 2022 15:27:40 -0400 Received: from mail-wm1-x331.google.com (mail-wm1-x331.google.com [IPv6:2a00:1450:4864:20::331]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 6B8EBDFF3 for ; Tue, 14 Jun 2022 12:27:39 -0700 (PDT) Received: by mail-wm1-x331.google.com with SMTP id z17so5207862wmi.1 for ; Tue, 14 Jun 2022 12:27:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=4k6d7Lt2WBi3GOPApoM/jn2bT82xIJh1tXyG8Vad2OE=; b=gWHHGR1njZjaoI01g3Tdpf6YQpyjObvy0+7til/+Z6k1F46/v1VD53ljkgmz1MjawK K7ZMy16qw74iSOPwjYa9NkcYhTtHPZn3yI6H64RlK5x64KugwGuDGRU7r0kJo7b5n4Tz ovdOSaKZbOrhIPvgpEOUISOW5vQOER7/1wtk4r4ATbJCj4PhLdKgTIJ7Y6dkEDN1qMaP kZj4wVikIBUe4LhkbT1GAKRIVe6Ba4aYkCFgqNeIpkvlT8TFOXRClJdP3RymprMfq23h AOD8krjjPeUZQq6gtEQCvhC9hLtwly69YGCBsehIN1vDno1458WNo+EZaJV5FC+lWJ+U gbZA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=4k6d7Lt2WBi3GOPApoM/jn2bT82xIJh1tXyG8Vad2OE=; b=DRPrczN2bSA6ErINSJbG75pJ6ij7PAEIOHFLRUFaGyajymcvkQHhKhrFbW+3WiwOYi jp31KwTQRDaslDLbCovXz969u78PtT9rZnUW6veCkFAw2jzdurDe2z1PubG/adfvkibS fNXVYTLUbMBbrdcm554FQGOgdUvSmJZcSPldpnBUajss7U2HQH2omK4qmbKJ0DJkybup 9lmipU2Nh0dnEKQOYi0mZGqf1rO2XvL898WiGx1XAKQoVtVwHHj/RZSwKGJmW0wk3egY /oTbP9eWwUeuOp8+mBK5FEv80ALW697xQHbQEP2sNHC2/l/FSef3720mXePmPtIm2Hp7 e5bQ== X-Gm-Message-State: AOAM533NmQl5T8m4r1Rli4ECCs+PRd7tTEv/kTUW+i/UicHxzqDuiXp6 s8OUbl26HDDquHPyLgO6FNyAhTxOy3/IZQ== X-Google-Smtp-Source: ABdhPJxHDc2yyEHFbnr92le3emB1BBXehAsAw6U8PlsroGhWGfrUiALr8KIgMWrC3Vo3zvaMYYzbaA== X-Received: by 2002:a7b:c4d8:0:b0:39c:97ed:baa3 with SMTP id g24-20020a7bc4d8000000b0039c97edbaa3mr5825163wmk.58.1655234857456; Tue, 14 Jun 2022 12:27:37 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id m127-20020a1ca385000000b0039c95b31e66sm7274233wme.31.2022.06.14.12.27.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Jun 2022 12:27:36 -0700 (PDT) Message-Id: <9347303db8911187f1d257e0336dc9ca523d713c.1655234853.git.gitgitgadget@gmail.com> In-Reply-To: References: Date: Tue, 14 Jun 2022 19:27:30 +0000 Subject: [PATCH v2 2/5] branch: check for bisects and rebases Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: gitster@pobox.com, johannes.schindelin@gmx.de, me@ttaylorr.com, Jeff Hostetler , Phillip Wood , Elijah Newren , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsA==?= Bjarmason , Derrick Stolee , Derrick Stolee Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Derrick Stolee From: Derrick Stolee The branch_checked_out() helper was added by the previous change, but it used an over-simplified view to check if a branch is checked out. It only focused on the HEAD symref, but ignored whether a bisect or rebase was happening. Teach branch_checked_out() to check for these things, and also add tests to ensure that we do not lose this functionality in the future. Now that this test coverage exists, we can safely refactor validate_new_branchname() to use branch_checked_out(). Note that we need to prepend "refs/heads/" to the 'state.branch' after calling wt_status_check_*(). We also need to duplicate wt->path so the value is not freed at the end of the call. Signed-off-by: Derrick Stolee --- branch.c | 35 +++++++++++++++++++++++++++-------- t/t2407-worktree-heads.sh | 22 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/branch.c b/branch.c index 42ba60b7cb5..d4ddec6f4e5 100644 --- a/branch.c +++ b/branch.c @@ -385,6 +385,7 @@ static void prepare_checked_out_branches(void) worktrees = get_worktrees(); while (worktrees[i]) { + struct wt_status_state state = { 0 }; struct worktree *wt = worktrees[i++]; if (wt->is_bare) @@ -394,6 +395,29 @@ static void prepare_checked_out_branches(void) strmap_put(¤t_checked_out_branches, wt->head_ref, xstrdup(wt->path)); + + if (wt_status_check_rebase(wt, &state) && + (state.rebase_in_progress || state.rebase_interactive_in_progress) && + state.branch) { + struct strbuf ref = STRBUF_INIT; + strbuf_addf(&ref, "refs/heads/%s", state.branch); + strmap_put(¤t_checked_out_branches, + ref.buf, + xstrdup(wt->path)); + strbuf_release(&ref); + } + wt_status_state_free_buffers(&state); + + if (wt_status_check_bisect(wt, &state) && + state.branch) { + struct strbuf ref = STRBUF_INIT; + strbuf_addf(&ref, "refs/heads/%s", state.branch); + strmap_put(¤t_checked_out_branches, + ref.buf, + xstrdup(wt->path)); + strbuf_release(&ref); + } + wt_status_state_free_buffers(&state); } free_worktrees(worktrees); @@ -413,9 +437,7 @@ const char *branch_checked_out(const char *refname) */ int validate_new_branchname(const char *name, struct strbuf *ref, int force) { - struct worktree **worktrees; - const struct worktree *wt; - + const char *path; if (!validate_branchname(name, ref)) return 0; @@ -423,13 +445,10 @@ int validate_new_branchname(const char *name, struct strbuf *ref, int force) die(_("a branch named '%s' already exists"), ref->buf + strlen("refs/heads/")); - worktrees = get_worktrees(); - wt = find_shared_symref(worktrees, "HEAD", ref->buf); - if (wt && !wt->is_bare) + if ((path = branch_checked_out(ref->buf))) die(_("cannot force update the branch '%s' " "checked out at '%s'"), - ref->buf + strlen("refs/heads/"), wt->path); - free_worktrees(worktrees); + ref->buf + strlen("refs/heads/"), path); return 1; } diff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh index 305ab46e38e..a838f2be474 100755 --- a/t/t2407-worktree-heads.sh +++ b/t/t2407-worktree-heads.sh @@ -26,4 +26,26 @@ test_expect_success 'refuse to overwrite: checked out in worktree' ' done ' +test_expect_success 'refuse to overwrite: worktree in bisect' ' + test_when_finished rm -rf .git/worktrees/wt-*/BISECT_* && + + touch .git/worktrees/wt-4/BISECT_LOG && + echo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START && + + test_must_fail git branch -f fake-2 HEAD 2>err && + grep "cannot force update the branch '\''fake-2'\'' checked out at.*wt-4" err +' + +test_expect_success 'refuse to overwrite: worktree in rebase' ' + test_when_finished rm -rf .git/worktrees/wt-*/rebase-merge && + + mkdir -p .git/worktrees/wt-3/rebase-merge && + touch .git/worktrees/wt-3/rebase-merge/interactive && + echo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-merge/head-name && + echo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-merge/onto && + + test_must_fail git branch -f fake-1 HEAD 2>err && + grep "cannot force update the branch '\''fake-1'\'' checked out at.*wt-3" err +' + test_done From patchwork Tue Jun 14 19:27:31 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Derrick Stolee X-Patchwork-Id: 12881493 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id E678CC433EF for ; Tue, 14 Jun 2022 19:27:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S243571AbiFNT1u (ORCPT ); Tue, 14 Jun 2022 15:27:50 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:39068 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236660AbiFNT1l (ORCPT ); Tue, 14 Jun 2022 15:27:41 -0400 Received: from mail-wr1-x436.google.com (mail-wr1-x436.google.com [IPv6:2a00:1450:4864:20::436]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id ABB72DFF3 for ; Tue, 14 Jun 2022 12:27:40 -0700 (PDT) Received: by mail-wr1-x436.google.com with SMTP id q15so12513206wrc.11 for ; Tue, 14 Jun 2022 12:27:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=TQOfzG3gc2ljmIL19/ejawb0LXd+xsTXQkMKPYfW7LU=; b=ELf0mHCYUBpykTxXcrH2nqbBd0C1t8yG6sFOJVAP9LVGOrUrLfYw7S59aGHsd2cj8M qR6DGJPwswaX+xLQUC0yzJjs6kzApcZzJGaiEs5EwaTgt3Zn/xTPhweGF6QFNL10ceji dVpL4i1baDZyIF/PpEqFD9DwWFnVH8dXpj4Jdll51QyklJvRtNQLww1QMOSHTYdwP5dz 103LkwTjS9/vAyaQhuf+OenJsVS5e6GlKpgea1txMLdVaBRlA1cYduupo8gmB4D+Wg4I 3M99pfP71WtpeXk/EyS9jKItgUOkdjKw+1qEKvuFJrp2VDDTQIYFd5SjX64PDebzEOCL /ZfA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=TQOfzG3gc2ljmIL19/ejawb0LXd+xsTXQkMKPYfW7LU=; b=DwEo69Z8hEoh2exiMNfAbkbz3dOkhKWdzd28FvMexZngj1UeEamNnOlubUYnoPOM0Z +e+1CYRfkhBlojswHpqi75GWuWZR00HKQd58RrQmsNXHehJjMbX49+b0azrP5stuEO63 Z0HQRRlKelRzvF6GjpXxZPmSK1qNtPo19PuqzvA/l93l5MqeLHYs7aGXBLe/DO6JtMLt OaCjBMWJ3zuGTFYpt/MtlLRfrvUYklYOn75B7Tz+gR2O68qiQ6QdU3rweqbTAXJPyZ6e IxJqzTm1xKgY6qtA/FcLxyo0heL9gRcEktzum7BM04f8vk+wZyG7lAFJdhcSpPuL3975 EE3Q== X-Gm-Message-State: AJIora8lIGBYeniQSArH55+iFhnquim8CRXxCyFqu8sFGTNZzLWoRmjv kkgIoGBHiufKkjhjiCUC6pui0BZ8xUyt1g== X-Google-Smtp-Source: AGRyM1uWRI8xKYnWO3gNwRgqxWeRR4RuEY6S8APvdtC4blcT8yInSfXSrFtQN2EfT6FxLN1mz1KOiw== X-Received: by 2002:a05:6000:1866:b0:218:40cc:a26e with SMTP id d6-20020a056000186600b0021840cca26emr6418863wri.678.1655234858711; Tue, 14 Jun 2022 12:27:38 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id q7-20020a056000136700b0020c6b78eb5asm12494664wrz.68.2022.06.14.12.27.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Jun 2022 12:27:38 -0700 (PDT) Message-Id: <1c764bfcfe4d2a3233055c8f5175ebbf0076c6dd.1655234853.git.gitgitgadget@gmail.com> In-Reply-To: References: Date: Tue, 14 Jun 2022 19:27:31 +0000 Subject: [PATCH v2 3/5] fetch: use new branch_checked_out() and add tests Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: gitster@pobox.com, johannes.schindelin@gmx.de, me@ttaylorr.com, Jeff Hostetler , Phillip Wood , Elijah Newren , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsA==?= Bjarmason , Derrick Stolee , Derrick Stolee Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Derrick Stolee From: Derrick Stolee When fetching refs from a remote, it is possible that the refspec will cause use to overwrite a ref that is checked out in a worktree. The existing logic in builtin/fetch.c uses a possibly-slow mechanism. Update those sections to use the new, more efficient branch_checked_out() helper. These uses were not previously tested, so add a test case that can be used for these kinds of collisions. There is only one test now, but more tests will be added as other consumers of branch_checked_out() are added. Note that there are two uses in builtin/fetch.c, but only one of the messages is tested. This is because the tested check is run before completing the fetch, and the untested check is not reachable without concurrent updates to the filesystem. Thus, it is beneficial to keep that extra check for the sake of defense-in-depth. However, we should not attempt to test the check, as the effort required is too complicated to be worth the effort. This use in update_local_ref() also requires a change in the error message because we no longer have access to the worktree struct, only the path of the worktree. This error is so rare that making a distinction between the two is not critical. Signed-off-by: Derrick Stolee --- builtin/fetch.c | 22 +++++++----------- t/t2407-worktree-heads.sh | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/builtin/fetch.c b/builtin/fetch.c index ac29c2b1ae3..7fdbfee5c93 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -885,7 +885,6 @@ static int update_local_ref(struct ref *ref, struct worktree **worktrees) { struct commit *current = NULL, *updated; - const struct worktree *wt; const char *pretty_ref = prettify_refname(ref->name); int fast_forward = 0; @@ -900,16 +899,14 @@ static int update_local_ref(struct ref *ref, } if (!update_head_ok && - (wt = find_shared_symref(worktrees, "HEAD", ref->name)) && - !wt->is_bare && !is_null_oid(&ref->old_oid)) { + !is_null_oid(&ref->old_oid) && + branch_checked_out(ref->name)) { /* * If this is the head, and it's not okay to update * the head, and the old value of the head isn't empty... */ format_display(display, '!', _("[rejected]"), - wt->is_current ? - _("can't fetch in current branch") : - _("checked out in another worktree"), + _("can't fetch into checked-out branch"), remote, pretty_ref, summary_width); return 1; } @@ -1434,19 +1431,16 @@ cleanup: return result; } -static void check_not_current_branch(struct ref *ref_map, - struct worktree **worktrees) +static void check_not_current_branch(struct ref *ref_map) { - const struct worktree *wt; + const char *path; for (; ref_map; ref_map = ref_map->next) if (ref_map->peer_ref && starts_with(ref_map->peer_ref->name, "refs/heads/") && - (wt = find_shared_symref(worktrees, "HEAD", - ref_map->peer_ref->name)) && - !wt->is_bare) + (path = branch_checked_out(ref_map->peer_ref->name))) die(_("refusing to fetch into branch '%s' " "checked out at '%s'"), - ref_map->peer_ref->name, wt->path); + ref_map->peer_ref->name, path); } static int truncate_fetch_head(void) @@ -1650,7 +1644,7 @@ static int do_fetch(struct transport *transport, ref_map = get_ref_map(transport->remote, remote_refs, rs, tags, &autotags); if (!update_head_ok) - check_not_current_branch(ref_map, worktrees); + check_not_current_branch(ref_map); retcode = open_fetch_head(&fetch_head); if (retcode) diff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh index a838f2be474..1fbde05fe2b 100755 --- a/t/t2407-worktree-heads.sh +++ b/t/t2407-worktree-heads.sh @@ -15,6 +15,21 @@ test_expect_success 'setup' ' test_commit $i && git branch wt-$i && git worktree add wt-$i wt-$i || return 1 + done && + + # Create a server that updates each branch by one commit + git init server && + test_commit -C server initial && + git remote add server ./server && + for i in 1 2 3 4 + do + git -C server checkout -b wt-$i && + test_commit -C server A-$i || return 1 + done && + for i in 1 2 + do + git -C server checkout -b fake-$i && + test_commit -C server f-$i || return 1 done ' @@ -48,4 +63,36 @@ test_expect_success 'refuse to overwrite: worktree in rebase' ' grep "cannot force update the branch '\''fake-1'\'' checked out at.*wt-3" err ' +test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: checked out' ' + test_must_fail git fetch server +refs/heads/wt-3:refs/heads/wt-3 2>err && + grep "refusing to fetch into branch '\''refs/heads/wt-3'\''" err && + + # General fetch into refs/heads/ will fail on first ref, + # so use a generic error message check. + test_must_fail git fetch server +refs/heads/*:refs/heads/* 2>err && + grep "refusing to fetch into branch" err +' + +test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: worktree in bisect' ' + test_when_finished rm -rf .git/worktrees/wt-*/BISECT_* && + + touch .git/worktrees/wt-4/BISECT_LOG && + echo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START && + + test_must_fail git fetch server +refs/heads/fake-2:refs/heads/fake-2 2>err && + grep "refusing to fetch into branch" err +' + +test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: worktree in rebase' ' + test_when_finished rm -rf .git/worktrees/wt-*/rebase-merge && + + mkdir -p .git/worktrees/wt-4/rebase-merge && + touch .git/worktrees/wt-4/rebase-merge/interactive && + echo refs/heads/fake-1 >.git/worktrees/wt-4/rebase-merge/head-name && + echo refs/heads/fake-2 >.git/worktrees/wt-4/rebase-merge/onto && + + test_must_fail git fetch server +refs/heads/fake-1:refs/heads/fake-1 2>err && + grep "refusing to fetch into branch" err +' + test_done From patchwork Tue Jun 14 19:27:32 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Derrick Stolee X-Patchwork-Id: 12881494 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 7CB08C43334 for ; Tue, 14 Jun 2022 19:27:56 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S245620AbiFNT1w (ORCPT ); Tue, 14 Jun 2022 15:27:52 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:39134 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S238360AbiFNT1n (ORCPT ); Tue, 14 Jun 2022 15:27:43 -0400 Received: from mail-wr1-x429.google.com (mail-wr1-x429.google.com [IPv6:2a00:1450:4864:20::429]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id D834811A0A for ; Tue, 14 Jun 2022 12:27:41 -0700 (PDT) Received: by mail-wr1-x429.google.com with SMTP id h5so12609556wrb.0 for ; Tue, 14 Jun 2022 12:27:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=ovwh48JS+zXXejB+qWQus7s/m8HrzCWv9GDjfY+y+H8=; b=YGqtZvTWKgzgIaqWytH8V/3qEFPsoczXOmwszbAU9P6zBc4Xpw5ZIwN3b5I/kv72nV /e5lP2le9lZecpxyLg7yIfgyBw+M6ZmAABjNW9CjNbxXJs6MK1lTa53aDKi7kMv/t5dV EDfdVQrBOUTE4CSpC2rvHB+gMMqN0wiyfS33PTuuMgwohJ9ffDhteZATYsinW/hgqePl hzSsI/Kf4uxgLiDiAJvtEdBjywJ8wOq8VmmXH1M5zYBgPR53A4lKkL3+swF/jLBLxvim zKql026q5TFDTuR0Rfa7l5R5TXkEo4A8hr0Ydld7Qf2ueA8EqkUqS1VGU0tyAH1Tiz7y /V6w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=ovwh48JS+zXXejB+qWQus7s/m8HrzCWv9GDjfY+y+H8=; b=TTUxCUNomAeEBJO3eOT7RPtagcTczl4+//EOsi35y/ipx39OLR7C5VWGOoYHOhKxrx RufCx/yXkuPE6jched4wib6xyV2yktpuMMrU0RC24ExJLdJOb1yyfvkBidGMuaaHZK+h U0MDRJOUY4kgCZJOef6KI3eNETAay79zaIfKjyr5PCr8m3KRYZP80Od7niEbzfB+EBM+ EsQVLcc7jV7Z2+3GFbrSv1JhlJvxVWjw3KngYNW2beeu/RSaa9dM+DzMuhxuayIr6Htz V0FqzYSnJtPblf82mou2sxXuJCikMEX3AHOntLKBhy7/8pVge+Cp+7PBY8vqdK3UHBFz senQ== X-Gm-Message-State: AJIora8XCHpPJA++QW/fxQGK4rpsvwQKEC+aH9z7ikOsgQPAAbOsae4c p9tgE4Ki+/xYc2Sf8jnuer4/um12Tzia2A== X-Google-Smtp-Source: AGRyM1vRL+kYyPaXKqvZMtRaj9ys0fYg4Wd4mhLfORhPFnPUKkPRmHAif5PW+nIPYJiFBW5xgQYP4w== X-Received: by 2002:a5d:47a7:0:b0:218:5a5d:6c55 with SMTP id 7-20020a5d47a7000000b002185a5d6c55mr6230582wrb.192.1655234859907; Tue, 14 Jun 2022 12:27:39 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id az21-20020a05600c601500b0039c871d3191sm13296019wmb.3.2022.06.14.12.27.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Jun 2022 12:27:39 -0700 (PDT) Message-Id: In-Reply-To: References: Date: Tue, 14 Jun 2022 19:27:32 +0000 Subject: [PATCH v2 4/5] branch: use branch_checked_out() when deleting refs Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: gitster@pobox.com, johannes.schindelin@gmx.de, me@ttaylorr.com, Jeff Hostetler , Phillip Wood , Elijah Newren , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsA==?= Bjarmason , Derrick Stolee , Derrick Stolee Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Derrick Stolee From: Derrick Stolee This is the last current use of find_shared_symref() that can easily be replaced by branch_checked_out(). The benefit of this switch is that the code is a bit simpler, but also it is faster on repeated calls. The remaining uses of find_shared_symref() are non-trivial to remove, so we probably should not continue in that direction: * builtin/notes.c uses find_shared_symref() with "NOTES_MERGE_REF" instead of "HEAD", so it doesn't have an immediate analogue with branch_checked_out(). Perhaps we should consider extending it to include that symref in addition to HEAD, BISECT_HEAD, and REBASE_HEAD. * receive-pack.c checks to see if a worktree has a checkout for the ref that is being updated. The tricky part is that it can actually decide to update the worktree directly instead of just skipping the update. This all depends on the receive.denyCurrentBranch config option. The implementation currenty cares about receiving the worktree in the result, so the current branch_checked_out() prototype is insufficient currently. This is something to investigate later, though, since a large number of refs could be updated at the same time and using the strmap implementation of branch_checked_out() could be beneficial. Signed-off-by: Derrick Stolee --- builtin/branch.c | 7 +++---- t/t2407-worktree-heads.sh | 5 ++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index 5d00d0b8d32..f875952e7b5 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -253,12 +253,11 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, name = mkpathdup(fmt, bname.buf); if (kinds == FILTER_REFS_BRANCHES) { - const struct worktree *wt = - find_shared_symref(worktrees, "HEAD", name); - if (wt) { + const char *path; + if ((path = branch_checked_out(name))) { error(_("Cannot delete branch '%s' " "checked out at '%s'"), - bname.buf, wt->path); + bname.buf, path); ret = 1; continue; } diff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh index 1fbde05fe2b..a5aec1486c5 100755 --- a/t/t2407-worktree-heads.sh +++ b/t/t2407-worktree-heads.sh @@ -37,7 +37,10 @@ test_expect_success 'refuse to overwrite: checked out in worktree' ' for i in 1 2 3 4 do test_must_fail git branch -f wt-$i HEAD 2>err - grep "cannot force update the branch" err || return 1 + grep "cannot force update the branch" err && + + test_must_fail git branch -D wt-$i 2>err + grep "Cannot delete branch" err || return 1 done ' From patchwork Tue Jun 14 19:27:33 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Derrick Stolee X-Patchwork-Id: 12881495 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 78C61C433EF for ; Tue, 14 Jun 2022 19:28:06 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S239258AbiFNT2B (ORCPT ); Tue, 14 Jun 2022 15:28:01 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:39182 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231730AbiFNT1o (ORCPT ); Tue, 14 Jun 2022 15:27:44 -0400 Received: from mail-wm1-x32f.google.com (mail-wm1-x32f.google.com [IPv6:2a00:1450:4864:20::32f]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 517C313EBA for ; Tue, 14 Jun 2022 12:27:43 -0700 (PDT) Received: by mail-wm1-x32f.google.com with SMTP id n185so5189226wmn.4 for ; Tue, 14 Jun 2022 12:27:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=xivVobC5MZizYnrUWGjHH9ct5f/yTO+O0jOCbEvOrPM=; b=IF7pjFBdTqGZX22tjUK5UMU6pA01xMxAztnG9k7QN0yOYE3IIvVpcUlHsDfO5UOnRA J75A+szXwOyTBDLOqkFBo+AH6cFnDgHTX63yiVAlXdIt3CebYX4RO3SqAeOsRcf6X5n1 WJ4IKl7vZ4NqqlyvltJtMS2Rik9Lo7KzVoEmOpxPT4YfhciqzTC4yDRT0+V35uvRLC8P pYlTY5b2j1w2sezvpvHH+vJZeYUS3y5n64HZio7KwhZ7P2GjxKQTWp6tAwHCj8ZcOXqu 9wJ57Ntz5/7WyuIjhPMGmE0bbgv6vah70JPRPYk8U3RdU8Cmp0m85xjpQxfj0NpxZEQD 46yw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=xivVobC5MZizYnrUWGjHH9ct5f/yTO+O0jOCbEvOrPM=; b=ann2zBgrrZV9DQMVRkx2NfuJzOHy0750lk354WZAg6FGaPcljJK3zSsoeVS2s7aAqd 03y3Ctut8UxNoGYo8RifMMjc/mF5zYEM+2rthYNEAR3hpiCNkITr+p3MKGA1kK/iSnd0 xDWjmi9hE2BupJEoooTQ546BK85Zge35k7U+P7MhmwJIH0vKvwTroRcwhJGpBD0/8PDr WLJGfuUaqZPJ3Lni5QkVzFu2xYswdaTfjHJOqqgsh/MPh/wO80mzeawHECSjNMUuh5tS GNmtgFBLmx6zmXeJ0+5OJwcvxIl7xobajTcZSv85WYdvsVof3Vn+vE+nIxUkYllgXspZ JnRA== X-Gm-Message-State: AOAM532w3wm3fMOzBr3W8qS0jRv0pO1U7TazGhvPubyUFQcVmx/pIHRH S1d7H/Nx1+g0BFFRejHk36Attz6TIvW3ZA== X-Google-Smtp-Source: ABdhPJz17mHLtBK3Z6Mlhz62ZaXgaTDje7dhNWQkKohQqGjZ7njSLXZbden343UjUChxh29tqmCRzg== X-Received: by 2002:a1c:7703:0:b0:39c:521d:e9af with SMTP id t3-20020a1c7703000000b0039c521de9afmr5596024wmi.170.1655234861291; Tue, 14 Jun 2022 12:27:41 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id c7-20020a05600c0ac700b0039c4b518df4sm16772144wmr.5.2022.06.14.12.27.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Jun 2022 12:27:40 -0700 (PDT) Message-Id: <0aa9478bc3815726a47ea99cac0fd333e95c1584.1655234853.git.gitgitgadget@gmail.com> In-Reply-To: References: Date: Tue, 14 Jun 2022 19:27:33 +0000 Subject: [PATCH v2 5/5] branch: fix branch_checked_out() leaks Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: gitster@pobox.com, johannes.schindelin@gmx.de, me@ttaylorr.com, Jeff Hostetler , Phillip Wood , Elijah Newren , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsA==?= Bjarmason , Derrick Stolee , Derrick Stolee Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Derrick Stolee From: Derrick Stolee The branch_checked_out() method populates a strmap linking a refname to a worktree that has that branch checked out. While unlikely, it is possible that a bug or filesystem manipulation could create a scenario where the same ref is checked out in multiple places. Further, there are some states in an interactive rebase where HEAD and REBASE_HEAD point to the same ref, leading to multiple insertions into the strmap. In either case, the strmap_put() method returns the old value which is leaked. Update branch_checked_out() to consume that pointer and free it. Add a test in t2407 that checks this erroneous case. The test "checks itself" by first confirming that the filesystem manipulations it makes trigger the branch_checked_out() logic, and then sets up similar manipulations to make it look like there are multiple worktrees pointing to the same ref. While TEST_PASSES_SANITIZE_LEAK would be helpful to demonstrate the leakage and prevent it in the future, t2407 uses helpers such as 'git clone' that cause the test to fail under that mode. Signed-off-by: Derrick Stolee --- branch.c | 25 +++++++++++++++---------- t/t2407-worktree-heads.sh | 28 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/branch.c b/branch.c index d4ddec6f4e5..6007ee7baeb 100644 --- a/branch.c +++ b/branch.c @@ -385,25 +385,29 @@ static void prepare_checked_out_branches(void) worktrees = get_worktrees(); while (worktrees[i]) { + char *old; struct wt_status_state state = { 0 }; struct worktree *wt = worktrees[i++]; if (wt->is_bare) continue; - if (wt->head_ref) - strmap_put(¤t_checked_out_branches, - wt->head_ref, - xstrdup(wt->path)); + if (wt->head_ref) { + old = strmap_put(¤t_checked_out_branches, + wt->head_ref, + xstrdup(wt->path)); + free(old); + } if (wt_status_check_rebase(wt, &state) && (state.rebase_in_progress || state.rebase_interactive_in_progress) && state.branch) { struct strbuf ref = STRBUF_INIT; strbuf_addf(&ref, "refs/heads/%s", state.branch); - strmap_put(¤t_checked_out_branches, - ref.buf, - xstrdup(wt->path)); + old = strmap_put(¤t_checked_out_branches, + ref.buf, + xstrdup(wt->path)); + free(old); strbuf_release(&ref); } wt_status_state_free_buffers(&state); @@ -412,9 +416,10 @@ static void prepare_checked_out_branches(void) state.branch) { struct strbuf ref = STRBUF_INIT; strbuf_addf(&ref, "refs/heads/%s", state.branch); - strmap_put(¤t_checked_out_branches, - ref.buf, - xstrdup(wt->path)); + old = strmap_put(¤t_checked_out_branches, + ref.buf, + xstrdup(wt->path)); + free(old); strbuf_release(&ref); } wt_status_state_free_buffers(&state); diff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh index a5aec1486c5..b6be42f74a2 100755 --- a/t/t2407-worktree-heads.sh +++ b/t/t2407-worktree-heads.sh @@ -98,4 +98,32 @@ test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: worktree in rebase grep "refusing to fetch into branch" err ' +test_expect_success 'refuse to overwrite when in error states' ' + test_when_finished rm -rf .git/worktrees/wt-*/rebase-merge && + test_when_finished rm -rf .git/worktrees/wt-*/BISECT_* && + + # Both branches are currently under rebase. + mkdir -p .git/worktrees/wt-3/rebase-merge && + touch .git/worktrees/wt-3/rebase-merge/interactive && + echo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-merge/head-name && + echo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-merge/onto && + mkdir -p .git/worktrees/wt-4/rebase-merge && + touch .git/worktrees/wt-4/rebase-merge/interactive && + echo refs/heads/fake-2 >.git/worktrees/wt-4/rebase-merge/head-name && + echo refs/heads/fake-1 >.git/worktrees/wt-4/rebase-merge/onto && + + # Both branches are currently under bisect. + touch .git/worktrees/wt-4/BISECT_LOG && + echo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START && + touch .git/worktrees/wt-1/BISECT_LOG && + echo refs/heads/fake-1 >.git/worktrees/wt-1/BISECT_START && + + for i in 1 2 + do + test_must_fail git branch -f fake-$i HEAD 2>err && + grep "cannot force update the branch '\''fake-$i'\'' checked out at" err || + return 1 + done +' + test_done