From patchwork Wed Jun 8 20:08:59 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Derrick Stolee X-Patchwork-Id: 12874628 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 84D24C43334 for ; Wed, 8 Jun 2022 20:09:17 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233038AbiFHUJO (ORCPT ); Wed, 8 Jun 2022 16:09:14 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48292 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230388AbiFHUJI (ORCPT ); Wed, 8 Jun 2022 16:09:08 -0400 Received: from mail-wr1-x430.google.com (mail-wr1-x430.google.com [IPv6:2a00:1450:4864:20::430]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 284F248321 for ; Wed, 8 Jun 2022 13:09:07 -0700 (PDT) Received: by mail-wr1-x430.google.com with SMTP id s1so6622742wra.9 for ; Wed, 08 Jun 2022 13:09:07 -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=R3V855V+ISCUa1svxDwCezUG4lO07M4yYlLGx6Lk2ow=; b=E9aD1TUJUuMPpDTs+ygdaA1VVf5oyJSnQM5r3v70d4udF5aMwsk1yMGWdz4bXakgPM aUNjFyhOOqn4vgyLZgZaB7vWIf2UXAtSDxHlC3yWkU9qa+iRHYz05ugsAKhQQbouOWmt WmDngVipB1yZHZQdOAVhGG5bbh3PVILoTwsaRugfvVuMR69SjFN1SY5yQqKuitxO4cQt xGyPZcv1d7RMjYlfHwUwAUO1nT6tsd78jBDoiZMsD2b09pqnBYSAOOISwtE0IzBOKGhX n5A32L5KKvGwI4HsWzxFLj0DY//uy9v6n7sWNemji34x5Xg9+rX/CGiTVLnyndQqZy70 1J+g== 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=R3V855V+ISCUa1svxDwCezUG4lO07M4yYlLGx6Lk2ow=; b=1Rz3VDUXwPzgiLdXEZZlYUg2ujzbRT7R0+CHIf/u0+E+AhEnWHBPJZQfmU/sNLOJcJ qUw7tdhXM9cTEwx8+vev/dDFxMoCj1wpmESYrZ9eP1sK+ZK/i7+FbHdZ1GsaVNyCh0YA 8b50ikBbywC+spBD7GnPmjsFxmPXguOZp+QVJVOd3Cz2Z9iGG5ZUxTiwx6WVHiMYMkDz 4qK8h0brWAst9Ytj27LU6C/999v3WxnlNI1/rv2glDDsYu20A7hA0cgPKekWFkmBPGRb Za2rNsoL1k96Tzn0Jp8BtThQCJiKl32nE5cYgO0QCMH8BdVXasH9Hnd2VJqG9XwJXgeQ Q1Ew== X-Gm-Message-State: AOAM530c6O4SiRHqVx3uYaWrTn0UDyxJ1OITWu8B0zGTUS8PpnQrRymc ArmmmST2DPGnmKwfEwslpPo6JbRHXramR89k X-Google-Smtp-Source: ABdhPJyPiZbBEvOtPkBbno19GWzSDSLyMN0ca91gezHOi8Yh5t+3xN1HZ7BOxOaJ1+ZDVTtFlT5pJQ== X-Received: by 2002:adf:b358:0:b0:216:508c:e0bf with SMTP id k24-20020adfb358000000b00216508ce0bfmr23516102wrd.204.1654718945386; Wed, 08 Jun 2022 13:09:05 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id z7-20020a5d6547000000b0021020517639sm22880091wrv.102.2022.06.08.13.09.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 08 Jun 2022 13:09:04 -0700 (PDT) Message-Id: In-Reply-To: References: Date: Wed, 08 Jun 2022 20:08:59 +0000 Subject: [PATCH 1/4] 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 , 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 | 42 +++++++++++++++++++++++++++++++++++++++ branch.h | 8 ++++++++ t/t2407-worktree-heads.sh | 24 ++++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100755 t/t2407-worktree-heads.sh diff --git a/branch.c b/branch.c index 2d6569b0c62..061a11f3415 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,47 @@ 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); +} + +int branch_checked_out(const char *refname, char **path) +{ + const char *path_in_set; + prepare_checked_out_branches(); + + path_in_set = strmap_get(¤t_checked_out_branches, refname); + if (path_in_set && path) + *path = xstrdup(path_in_set); + + return !!path_in_set; +} + /* * 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..5ea93d217b1 100644 --- a/branch.h +++ b/branch.h @@ -101,6 +101,14 @@ 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); + +/* + * Returns true if the branch at 'refname' is checked out at any + * non-bare worktree. The path of the worktree is stored in the + * given 'path', if provided. + */ +int branch_checked_out(const char *refname, char **path); + /* * 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..dd905dc1a5c --- /dev/null +++ b/t/t2407-worktree-heads.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +test_description='test operations trying to overwrite refs at worktree HEAD' + +. ./test-lib.sh + +test_expect_success 'setup' ' + 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 Wed Jun 8 20:09:00 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Derrick Stolee X-Patchwork-Id: 12874629 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 C2BC4C433EF for ; Wed, 8 Jun 2022 20:09:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233505AbiFHUJR (ORCPT ); Wed, 8 Jun 2022 16:09:17 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48406 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231452AbiFHUJK (ORCPT ); Wed, 8 Jun 2022 16:09:10 -0400 Received: from mail-wr1-x430.google.com (mail-wr1-x430.google.com [IPv6:2a00:1450:4864:20::430]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A37E1AF1F5 for ; Wed, 8 Jun 2022 13:09:08 -0700 (PDT) Received: by mail-wr1-x430.google.com with SMTP id a15so21214147wrh.2 for ; Wed, 08 Jun 2022 13:09:08 -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=iFH2Di6aV0LKXtrBPtO+xoD4fNPtx8EAFI7FuSQQ1NI=; b=XrnwIB9d/t+stgFGU02X2sfN3sTxmRyIveLcmuUP9EaPUKkI/qe9ElexBQBrEHM0Wh 2oBprLxLFwZEkRhBY2m939AS1P8WEhPvnD9d/Cmw50N7aELoMJzSbt+2CPmiCbRmL5gM fx0EsHRhuDBlZ3+785MEtxN9V40/3DvL8J4aTJbjt48Y2KDX2VEyNLmcstv13obog/X3 31AjkSFeEOHNvKs6UftcOZhU0o3/35CAdRscLWe9Zh8kh3ROdlxVXMxsHL2q46gjbL7o qVNG1+sUfLfMwocB0AQ5onvs3V+qlmo3HgbM6gWxPit8rcxVYfwR5Ir/SdW+tNAtwj3q zUcw== 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=iFH2Di6aV0LKXtrBPtO+xoD4fNPtx8EAFI7FuSQQ1NI=; b=q9mzBSyZnhFkAbsuZFj16Q/eo7Iu8brkAbi6//kmPgJCQyl4TruCDw0EXBjN8irigk v8vO/Y+GZUczx9fCmriiaZtiRSttyeawElTgaWNVl6rdqGSfwWDMGOdoREVty55Ii1XJ /kX8aLaWixIfccVh0tAB2+qopi+j5PHJd1tGkLGZOkQameX92svFJvAj+OgpxObeSwpS kpi9UqtUidSDGwNPK4fhT5fhnTFKku3MoWS2nY5WmqFR6X256IUuUQwL9odbQ2nPJjUm 9vut7wOwngpOax1QKOaSHCJdvumHBKY/KtXrujcX1H0qBGRkLOGnFfO6sDR9yB/OCamy 0R+g== X-Gm-Message-State: AOAM533+HHVuqtWrMkWmy3wxUwZ+SMyjBGA5/6aBgbS5BRb0E8BInKsl 1XOztKu04LyLwn0mZaYZl2bbJxjIHi7FFcz1 X-Google-Smtp-Source: ABdhPJz7+qBbT11w7m6bFlESWAux1ctm7cAE/+gPQXjvRVQgd4wqEXmDYast+q8u26guloqhj0I+LQ== X-Received: by 2002:a5d:4ec4:0:b0:210:21a5:2007 with SMTP id s4-20020a5d4ec4000000b0021021a52007mr34461369wrv.348.1654718946845; Wed, 08 Jun 2022 13:09:06 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id m18-20020a05600c3b1200b00397122e63b6sm25540884wms.29.2022.06.08.13.09.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 08 Jun 2022 13:09:05 -0700 (PDT) Message-Id: <18bad9b0c496fc0ceab1e567aee83f2160ae5d75.1654718942.git.gitgitgadget@gmail.com> In-Reply-To: References: Date: Wed, 08 Jun 2022 20:09:00 +0000 Subject: [PATCH 2/4] 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 , 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 | 29 +++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/branch.c b/branch.c index 061a11f3415..c0fe6ea0b65 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); @@ -419,9 +443,7 @@ int branch_checked_out(const char *refname, char **path) */ int validate_new_branchname(const char *name, struct strbuf *ref, int force) { - struct worktree **worktrees; - const struct worktree *wt; - + char *path; if (!validate_branchname(name, ref)) return 0; @@ -429,13 +451,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 (branch_checked_out(ref->buf, &path)) 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 dd905dc1a5c..12faca7f655 100755 --- a/t/t2407-worktree-heads.sh +++ b/t/t2407-worktree-heads.sh @@ -21,4 +21,33 @@ test_expect_success 'refuse to overwrite: checked out in worktree' ' done ' +test_expect_success 'refuse to overwrite: worktree in bisect' ' + test_when_finished test_might_fail git -C wt-4 bisect reset && + + ( + git -C wt-4 bisect start && + git -C wt-4 bisect bad HEAD && + git -C wt-4 bisect good HEAD~3 + ) && + + test_must_fail git branch -f wt-4 HEAD 2>err && + grep "cannot force update the branch '\''wt-4'\'' checked out at" err +' + +. "$TEST_DIRECTORY"/lib-rebase.sh + +test_expect_success 'refuse to overwrite: worktree in rebase' ' + test_when_finished test_might_fail git -C wt-4 rebase --abort && + + ( + set_fake_editor && + FAKE_LINES="edit 1 2 3" \ + git -C wt-4 rebase -i HEAD~3 >rebase && + git -C wt-4 status + ) && + + test_must_fail git branch -f wt-4 HEAD 2>err && + grep "cannot force update the branch '\''wt-4'\'' checked out at" err +' + test_done From patchwork Wed Jun 8 20:09:01 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Derrick Stolee X-Patchwork-Id: 12874630 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 DB4D9C433EF for ; Wed, 8 Jun 2022 20:09:20 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S234229AbiFHUJU (ORCPT ); Wed, 8 Jun 2022 16:09:20 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48500 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231775AbiFHUJL (ORCPT ); Wed, 8 Jun 2022 16:09:11 -0400 Received: from mail-wr1-x42b.google.com (mail-wr1-x42b.google.com [IPv6:2a00:1450:4864:20::42b]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E9555AFB34 for ; Wed, 8 Jun 2022 13:09:09 -0700 (PDT) Received: by mail-wr1-x42b.google.com with SMTP id q26so19356476wra.1 for ; Wed, 08 Jun 2022 13:09:09 -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=ooa+r+KA0ROv78hgZ3byUALMb8dAeoa5zSIOtZeuH74=; b=B61092oDahMktdge4VLs7oFOKnM/1bZl4RGYGTjYXiOge8GcOFZA5y+O2OY+LLbgvk 35e0ASutPQFWpY9F1icHb33C48g9aw6IFDv9S7af/nlNfLmCj0mUWgciSq6XUI1LqiqI YPthGNU6DWfSH8K75r5RO+GK8y6Ku06uWBbwbeFYYR58SZDI+417FZJTGMCIf0z5Mdgd oKF8/ZNWxu+j5btSAnjnWEqiG7a0Ndaup5KlDZbQ2Gp2MuCh2fN8b7b1gZJ9qJxTTTYU mh9isSFJwD78hiFBQflFcrhnPEL4N4Eqzo7fPWLMO9LO+792XVyuHSarD1rtBIZJDGh+ zaJw== 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=ooa+r+KA0ROv78hgZ3byUALMb8dAeoa5zSIOtZeuH74=; b=Zk2Xf+IGdnMp3MUEiOoXB7pOvx1T4QYJHxhqmwz9W/eWBZ8XujXjnc42VVmoWqT7HO PiRXruvp1bStTH50jPnpuLzxw9RXIhLwz8Rb7MElefhOwm6QNznwfw2rdDIqv7nkRH/B hkxAzIhDYM4wFAhHIskfOcAGdnRG0nH+mj6fXKi0mBH7ZhINWykWCNFDZ0hqgCY76a5J PPKdqrQc1eLVcMXZH7gSOBATZO1tK7PcUvcBnMceuDaoemuEzPz9MjjZC7PMh19Og3VP F9QBGMMUSYmqQw8SdZ4e3o5a7T/zamcQBVgAKrSxje9RqDcCE1FjkrnFaFTtyWWEhD2M sVFg== X-Gm-Message-State: AOAM532/a4F9dDc+08GH3vUsvZmUF1ZfCJmQbBOfk86oJWpJPWfgwOQL F+yan7J0MhqBEPTLGxJBeFYIm8MwIJuqwdAg X-Google-Smtp-Source: ABdhPJy5UIvd7qZI1NUUwhIAHazVKi7wRbwN2BXSFyhaqBbSGovMgd3ZbJl5/HZ3NtPiut0a5N1i6A== X-Received: by 2002:adf:ded2:0:b0:218:4336:55b1 with SMTP id i18-20020adfded2000000b00218433655b1mr15390822wrn.24.1654718948151; Wed, 08 Jun 2022 13:09:08 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id n27-20020a05600c3b9b00b0039c64d0c4e8sm3004515wms.45.2022.06.08.13.09.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 08 Jun 2022 13:09:07 -0700 (PDT) Message-Id: <4540dbeed385341f8c5b45134e1a65dc48c75b0c.1654718942.git.gitgitgadget@gmail.com> In-Reply-To: References: Date: Wed, 08 Jun 2022 20:09:01 +0000 Subject: [PATCH 3/4] 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 , 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. Signed-off-by: Derrick Stolee --- builtin/fetch.c | 25 +++++++++++-------------- t/t2407-worktree-heads.sh | 29 +++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/builtin/fetch.c b/builtin/fetch.c index ac29c2b1ae3..1ba56240312 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -885,7 +885,7 @@ static int update_local_ref(struct ref *ref, struct worktree **worktrees) { struct commit *current = NULL, *updated; - const struct worktree *wt; + char *path = NULL; const char *pretty_ref = prettify_refname(ref->name); int fast_forward = 0; @@ -900,17 +900,17 @@ 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, &path)) { /* * 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"), + path ? _("can't fetch in current branch") : + _("checked out in another worktree"), remote, pretty_ref, summary_width); + free(path); return 1; } @@ -1434,19 +1434,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; + 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) + branch_checked_out(ref_map->peer_ref->name, &path)) 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 +1647,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 12faca7f655..f3f8b0b2b79 100755 --- a/t/t2407-worktree-heads.sh +++ b/t/t2407-worktree-heads.sh @@ -10,6 +10,15 @@ 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 clone . server && + git remote add server ./server && + for i in 1 2 3 4 + do + git -C server checkout wt-$i && + test_commit -C server A-$i || return 1 done ' @@ -21,6 +30,16 @@ test_expect_success 'refuse to overwrite: checked out in worktree' ' done ' +test_expect_success 'refuse to overwrite during fetch' ' + 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 'refuse to overwrite: worktree in bisect' ' test_when_finished test_might_fail git -C wt-4 bisect reset && @@ -31,7 +50,10 @@ test_expect_success 'refuse to overwrite: worktree in bisect' ' ) && test_must_fail git branch -f wt-4 HEAD 2>err && - grep "cannot force update the branch '\''wt-4'\'' checked out at" err + grep "cannot force update the branch '\''wt-4'\'' checked out at" err && + + test_must_fail git fetch server +refs/heads/wt-4:refs/heads/wt-4 2>err && + grep "refusing to fetch into branch '\''refs/heads/wt-4'\''" err ' . "$TEST_DIRECTORY"/lib-rebase.sh @@ -47,7 +69,10 @@ test_expect_success 'refuse to overwrite: worktree in rebase' ' ) && test_must_fail git branch -f wt-4 HEAD 2>err && - grep "cannot force update the branch '\''wt-4'\'' checked out at" err + grep "cannot force update the branch '\''wt-4'\'' checked out at" err && + + test_must_fail git fetch server +refs/heads/wt-4:refs/heads/wt-4 2>err && + grep "refusing to fetch into branch '\''refs/heads/wt-4'\''" err ' test_done From patchwork Wed Jun 8 20:09:02 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Derrick Stolee X-Patchwork-Id: 12874631 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 8AFE5C43334 for ; Wed, 8 Jun 2022 20:09:22 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232839AbiFHUJW (ORCPT ); Wed, 8 Jun 2022 16:09:22 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48624 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232166AbiFHUJM (ORCPT ); Wed, 8 Jun 2022 16:09:12 -0400 Received: from mail-wr1-x430.google.com (mail-wr1-x430.google.com [IPv6:2a00:1450:4864:20::430]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 288ADDE8 for ; Wed, 8 Jun 2022 13:09:11 -0700 (PDT) Received: by mail-wr1-x430.google.com with SMTP id p10so29721000wrg.12 for ; Wed, 08 Jun 2022 13:09:11 -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=p7+AIWYTu3atPQftovOCI7nQNMiowZwBtLWmOsZeL0o=; b=Ly1q1K8mEBiEDCP5WgRVkOVWiDWDYuk5YqNaT0Kyiiy3qPa1AjQIgpURHx5w2FN5o4 N0yY7TU4nOw5xflni/5lW6LNCujvR4+H9ayywWXPZBWMiM8uUl33uAzXlI2Caai5i1vT nNkFM2JD9KaVtNfqI+SkCN0B0qcccLbBCuM/jj0D/NM2AwFJ+UtRlj1j7q+CRWcg1ZHf xYhEL3R0Htm+2O1u1YOiibRuk0jOPsbto/qZ9ZsrAW6kaYvwXx3HfrnxeNH18Ld9dm2k QccKLVh5wnuG80/03ZtE+PQMBKvaVWeHSXLW9klbRhJAa2lkYGkToocqI1pUgpOKj3at /EzA== 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=p7+AIWYTu3atPQftovOCI7nQNMiowZwBtLWmOsZeL0o=; b=pP4+Z1zqAgJl2CPRF0MDv8mCSTorqZANaomFjmvH2xQsLgCDuJ2XcFHKw2aZNWCR1A +moz+JNQyj20yjOyrXGq8cDs8Jlx+XWBpHyI6wHHkprcsTUFvroo8wzOC/8z8ZOVbArI N7+iruzQ54vRNVWzgIZbDNHGHhFyXIxu4vYZQUkH8Khl9hDaCFNVqi4FpZjKXpwzmdkQ VD5K2tHZn7Ki2pLcXQIjNw5q5lp7gKmdFuLoCRQZ6DqAYKrfHa1+QkEsMz8nhTzrc6Wt P2IxA44GsVtvlIZuTO2axLRJnHPaqyyEuiICRtMAQld4oCz7Y3RdUXfqAOoDE0DnCMRn NPIA== X-Gm-Message-State: AOAM533rl000N6XAJle0SQlFtd4U1vXMqLycB/IHdU9eUuc+N+mzNp+S cYOzjAsM+vAzFj6e+0CYZPzw3k3TksFaRSSk X-Google-Smtp-Source: ABdhPJxUL/0JqY2hyHbIJDq7wc0pzqziTvO5aDtazeDM2eRFJ/tcPkTea4zbhkdXKAzZ/Q3fZFVbzw== X-Received: by 2002:adf:d0c3:0:b0:218:4f53:5823 with SMTP id z3-20020adfd0c3000000b002184f535823mr10804488wrh.132.1654718949320; Wed, 08 Jun 2022 13:09:09 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id bg30-20020a05600c3c9e00b0039c5497deccsm371408wmb.1.2022.06.08.13.09.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 08 Jun 2022 13:09:08 -0700 (PDT) Message-Id: In-Reply-To: References: Date: Wed, 08 Jun 2022 20:09:02 +0000 Subject: [PATCH 4/4] 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 , 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 | 8 ++++---- t/t2407-worktree-heads.sh | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index 5d00d0b8d32..8e11e433840 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -253,12 +253,12 @@ 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) { + char *path; + if (branch_checked_out(name, &path)) { error(_("Cannot delete branch '%s' " "checked out at '%s'"), - bname.buf, wt->path); + bname.buf, path); + free(path); ret = 1; continue; } diff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh index f3f8b0b2b79..6dcc0d39a2d 100755 --- a/t/t2407-worktree-heads.sh +++ b/t/t2407-worktree-heads.sh @@ -26,7 +26,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 Mon Jun 13 14:59:58 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Derrick Stolee X-Patchwork-Id: 12879918 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 1DC1FC433EF for ; Mon, 13 Jun 2022 18:40:20 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1343611AbiFMSkS (ORCPT ); Mon, 13 Jun 2022 14:40:18 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44196 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1343595AbiFMSjz (ORCPT ); Mon, 13 Jun 2022 14:39:55 -0400 Received: from mail-qt1-x834.google.com (mail-qt1-x834.google.com [IPv6:2607:f8b0:4864:20::834]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1250B4EF78 for ; Mon, 13 Jun 2022 08:00:00 -0700 (PDT) Received: by mail-qt1-x834.google.com with SMTP id fu17so4061769qtb.2 for ; Mon, 13 Jun 2022 08:00:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=github.com; s=google; h=message-id:date:mime-version:user-agent:subject:content-language:to :cc:references:from:in-reply-to:content-transfer-encoding; bh=KrMpKvDQtuaeEWDoBtYEom4UAj5jNioDlT49pbVzUFI=; b=fvJrwU4fOh20SwZBt277/ZQlglYQkfaCwFI6tFCBIxq2iW0LVGXMnXvY9H96bC6Fj6 TctW6c92UaFTJ/BriUFx+0S3mBJJGMPv0OdZcSt3PgiXThzgvLjnmTDRd3D1SvPTL/cx x8XztrYwZ1X4ZXVumm1qvx4FlsxWO69yZ7V7/pD3ypeZaNysXBJr92sIHCGz7jXL66Tt 0+okCPPJk4AUhDn2Lz6lb1ab+J+2pH6zZj55tezK3w39+lR8rWfC7aXhXBqqCqal3SU1 0vCeeeWVWZI7tisDSHZ290A0dpfUBu81w/x+JXWnAILhCPtHUiUJPYVKkcrVZQusTvow t3Ag== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:message-id:date:mime-version:user-agent:subject :content-language:to:cc:references:from:in-reply-to :content-transfer-encoding; bh=KrMpKvDQtuaeEWDoBtYEom4UAj5jNioDlT49pbVzUFI=; b=U9d/4V+lMWwy0G0ByKrqCl2vWqadkJQWB3RC9JTPr+gidpmdYixmsB8IjXt8/qztjs jYx6/cYzmfPOVfpYqVAhyBVbe0XY3ff6CpmZXUsR4oK3Qo81Tmxil66xqK92OKOwdHyb z1/krrjeBE3lWzBJa6MEUtNcWK9C/i09p8RFolzvvdf4isWQ8TfXu6Uw7Pr1CQsVJQL6 XF/0dF2rD/KZwShmfM5+/SleEnCcZ53aWSVZJUQiGwevERVY6YRbjNSRc7++UkTKUOCn RUZt1Kjg2DAEKyHvV+BuTPY0Oj9/psAwwdLb5LROUxgW/2DwbNcc+saOGrD8xiriZc61 YBBA== X-Gm-Message-State: AOAM533f0r3TWK3kj9X5dsSb5bnPJSGGbXR/lTgzRokwORlhTEKikdxd IIYnVATDDSerLbL2/l7eX9ot X-Google-Smtp-Source: ABdhPJyLtZk/twNdUaxrZRJFomMTBxgiIh4U/Nexo8Ht4UW1dDPHbkm9lmIMZGh1scWdDF0I2k6nYg== X-Received: by 2002:a05:622a:1794:b0:304:cb20:5195 with SMTP id s20-20020a05622a179400b00304cb205195mr97753qtk.266.1655132399169; Mon, 13 Jun 2022 07:59:59 -0700 (PDT) Received: from ?IPV6:2600:1700:e72:80a0:170:45d1:1083:f688? ([2600:1700:e72:80a0:170:45d1:1083:f688]) by smtp.gmail.com with ESMTPSA id y18-20020ac85f52000000b003050bd1f7c9sm5272754qta.76.2022.06.13.07.59.58 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Mon, 13 Jun 2022 07:59:58 -0700 (PDT) Message-ID: <6cd7db33-6ab5-9843-4483-4cce9835b177@github.com> Date: Mon, 13 Jun 2022 10:59:58 -0400 MIME-Version: 1.0 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Thunderbird/91.10.0 Subject: [PATCH 5/5] branch: fix branch_checked_out() leaks Content-Language: en-US To: Derrick Stolee via GitGitGadget , git@vger.kernel.org Cc: gitster@pobox.com, johannes.schindelin@gmx.de, me@ttaylorr.com, Jeff Hostetler , Phillip Wood , Elijah Newren References: From: Derrick Stolee In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org On 6/8/2022 4:08 PM, Derrick Stolee via GitGitGadget wrote: > This is a replacement for some patches from v2 of my 'git rebase > --update-refs' topic [1]. After some feedback from Philip, I've decided to > pull that topic while I rework how I track the refs to rewrite [2]. This > series moves forward with the branch_checked_out() helper that was a bit > more complicated than expected at first glance. This series is a culmination > of the discussion started by Junio at [3]. > Junio pointed out that patch 1 introduced a memory leak when a ref is checked out in multiple places. Here is a patch to fix that scenario. It applies cleanly on top of patch 4, so I include it as a new "patch 5". I will include it in any v2 of the full series, if needed. Thanks, -Stolee ---- >8 ---- From c3842b36ebb4053ac49b0306154b840431f9bf6f Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 13 Jun 2022 10:33:20 -0400 Subject: [PATCH 5/5] branch: fix branch_checked_out() leaks 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 | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/branch.c b/branch.c index c0fe6ea0b65..390c092a18f 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 6dcc0d39a2d..0760595337b 100755 --- a/t/t2407-worktree-heads.sh +++ b/t/t2407-worktree-heads.sh @@ -78,4 +78,43 @@ test_expect_success 'refuse to overwrite: worktree in rebase' ' grep "refusing to fetch into branch '\''refs/heads/wt-4'\''" 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_* && + + git branch -f fake1 && + mkdir -p .git/worktrees/wt-3/rebase-merge && + touch .git/worktrees/wt-3/rebase-merge/interactive && + echo refs/heads/fake1 >.git/worktrees/wt-3/rebase-merge/head-name && + echo refs/heads/fake2 >.git/worktrees/wt-3/rebase-merge/onto && + + git branch -f fake2 && + touch .git/worktrees/wt-4/BISECT_LOG && + echo refs/heads/fake2 >.git/worktrees/wt-4/BISECT_START && + + # First, ensure we prevent writing when only one reason to fail. + 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 && + + # Second, set up duplicate values. + mkdir -p .git/worktrees/wt-4/rebase-merge && + touch .git/worktrees/wt-4/rebase-merge/interactive && + echo refs/heads/fake2 >.git/worktrees/wt-4/rebase-merge/head-name && + echo refs/heads/fake1 >.git/worktrees/wt-4/rebase-merge/onto && + + touch .git/worktrees/wt-1/BISECT_LOG && + echo refs/heads/fake1 >.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