From patchwork Mon Apr 17 09:33:35 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacob Abel X-Patchwork-Id: 13213555 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 24323C77B72 for ; Mon, 17 Apr 2023 09:35:38 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229911AbjDQJfh (ORCPT ); Mon, 17 Apr 2023 05:35:37 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36292 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229634AbjDQJfd (ORCPT ); Mon, 17 Apr 2023 05:35:33 -0400 Received: from mail-4022.proton.ch (mail-4022.proton.ch [185.70.40.22]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A1B993AA1 for ; Mon, 17 Apr 2023 02:34:50 -0700 (PDT) Date: Mon, 17 Apr 2023 09:33:35 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nullpo.dev; s=protonmail3; t=1681724030; x=1681983230; bh=CvZmQDe/1jgZsnUPyv9kSZCw8AoISXvuQ7Mfsh4OMa0=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=QQbFzfPH/Oi1We/bRud0eYk6wPkK+QUUqdD22gusNWBNeVKr6r7ZJCDs1DjwpcO0f xo6sztuwnyOVp7NoT1CtOV4RuMdM+JAnQYlvtDb2ekligqjcSAAqftK1BpgSVHw/Mc ky7wbgMtILgEyr82Tb4GxLv4BV3pik4vZ2pk4Rwscob7pMfTHUEr8H0uf1shGZpVPL 0NaVmNPmTwEXvUcRbD/2xiwrktYo+NT3YIbHjeFFRuW6a1Dx7vZxSvjIZ37FUnLSTu oIgICLRfwHozz2EtRMjm1WZfylKdaYBJC8o77rgdo3+OvOsGVJrxCs7cqW4m+ae6Gi BvqmsBEiSEVPQ== To: git@vger.kernel.org From: Jacob Abel Cc: Jacob Abel , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsCBC?= =?utf-8?b?amFybWFzb24=?= , Eric Sunshine , Junio C Hamano , Phillip Wood , =?utf-8?q?Rub=C3=A9n_Justo?= , Taylor Blau , rsbecker@nexbridge.com Subject: [PATCH v9 1/8] worktree add: include -B in usage docs Message-ID: <20230417093255.31079-2-jacobabel@nullpo.dev> In-Reply-To: <20230417093255.31079-1-jacobabel@nullpo.dev> References: <20230417093255.31079-1-jacobabel@nullpo.dev> Feedback-ID: 21506737:user:proton MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org Document `-B` next to where `-b` is already documented to bring the usage docs in line with other commands such as git checkout. Signed-off-by: Jacob Abel --- Documentation/git-worktree.txt | 2 +- builtin/worktree.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) -- 2.39.2 diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 063d6eeb99..b9c12779f1 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason ]] - [-b ] [] + [(-b | -B) ] [] 'git worktree list' [-v | --porcelain [-z]] 'git worktree lock' [--reason ] 'git worktree move' diff --git a/builtin/worktree.c b/builtin/worktree.c index 39e9e5c9ce..d1b4b53f2c 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -22,7 +22,7 @@ #define BUILTIN_WORKTREE_ADD_USAGE \ N_("git worktree add [-f] [--detach] [--checkout] [--lock [--reason ]]\n" \ - " [-b ] []") + " [(-b | -B) ] []") #define BUILTIN_WORKTREE_LIST_USAGE \ N_("git worktree list [-v | --porcelain [-z]]") #define BUILTIN_WORKTREE_LOCK_USAGE \ From patchwork Mon Apr 17 09:33:44 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacob Abel X-Patchwork-Id: 13213554 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 62639C77B7C for ; Mon, 17 Apr 2023 09:35:39 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230178AbjDQJfi (ORCPT ); Mon, 17 Apr 2023 05:35:38 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36160 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229654AbjDQJfd (ORCPT ); Mon, 17 Apr 2023 05:35:33 -0400 Received: from mail-4317.proton.ch (mail-4317.proton.ch [185.70.43.17]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id BCFCE4205 for ; Mon, 17 Apr 2023 02:34:50 -0700 (PDT) Date: Mon, 17 Apr 2023 09:33:44 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nullpo.dev; s=protonmail3; t=1681724030; x=1681983230; bh=c6CW5zlosP9UlTEEz1g9iqvpzkXVpHPsYU4nYoTaiZM=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=aLIWi5uxXcS3Ooq5Tgs1ElWAEBXVqK+v7DlYe2Fl7eFgb08I2FECsillqA1sQ7kQy zp09UWEK+0KswXoNZafMNEqig+/4EFqKtEXGwAjEbvEfn5K6qVftU1WCvbzpkIBOFu QG8FfEDh84K7i6L4bNoywBaZq84Ioa/0JSHY9DtP0wAt1yqp4FayKQaE+GxQ5c4OQB b5lcApqOnHFrXDoIcHo8AeaoqDI7z/DpS0X98Y9oJqljpiJTWvvJc5+8vIuTJ/Ea2I qKh9nDHashe64UocKXhY0srtJRoXq4pGrUQrh6RJ4OGXisp7v9wZzBdrHyjx27vSyT zy+H9XBU19FJQ== To: git@vger.kernel.org From: Jacob Abel Cc: Jacob Abel , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsCBC?= =?utf-8?b?amFybWFzb24=?= , Eric Sunshine , Junio C Hamano , Phillip Wood , =?utf-8?q?Rub=C3=A9n_Justo?= , Taylor Blau , rsbecker@nexbridge.com Subject: [PATCH v9 2/8] t2400: print captured git output when finished Message-ID: <20230417093255.31079-3-jacobabel@nullpo.dev> In-Reply-To: <20230417093255.31079-1-jacobabel@nullpo.dev> References: <20230417093255.31079-1-jacobabel@nullpo.dev> Feedback-ID: 21506737:user:proton MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org Update tests that capture stderr so that at the end of the test they print the captured text back out to stderr. This simplifies debugging when inspecting test logs after executing with `-x`. Signed-off-by: Jacob Abel --- t/t2400-worktree-add.sh | 2 ++ 1 file changed, 2 insertions(+) -- 2.39.2 diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index d587e0b20d..9bc3db20e4 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -326,6 +326,8 @@ test_expect_success 'add -B' ' ' test_expect_success 'add --quiet' ' + test_when_finished "git worktree remove -f -f another-worktree" && + test_when_finished cat actual >&2 && git worktree add --quiet another-worktree main 2>actual && test_must_be_empty actual ' From patchwork Mon Apr 17 09:33:52 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacob Abel X-Patchwork-Id: 13213560 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 A5BD4C77B72 for ; Mon, 17 Apr 2023 09:36:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231129AbjDQJgx (ORCPT ); Mon, 17 Apr 2023 05:36:53 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36648 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230426AbjDQJgd (ORCPT ); Mon, 17 Apr 2023 05:36:33 -0400 Received: from mail-0301.mail-europe.com (mail-0301.mail-europe.com [188.165.51.139]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7501A5270 for ; Mon, 17 Apr 2023 02:35:50 -0700 (PDT) Date: Mon, 17 Apr 2023 09:33:52 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nullpo.dev; s=protonmail3; t=1681724042; x=1681983242; bh=P1xg6yj8okoWGv7OpoeTO1kuEztET7PfgyrR1Rgs4c0=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=BY+p422M3Rcoyzo0TE0LCq7sEQOaLEYrP7bftROnVvU5EBIJseqpFR0eNh9gDwNOj PtQmWjdQF+1eizmel29zsN9IJGiZ77eDpwrRXVkbrKbaYXm65LTYt/tTuV1fT9d7+O qpCPHACdD0e+MDIbAez5ejYAqAAwf/bRGQmus5LIGXFilNrBbCDZAXM3uvsdu/Z8dx 4iimPRxKmBXzAnzBQjOG1RGwHd4jQzBQnKSq/I9sZ4B+1KqAHu+BCwDHmDQ6BuhEiA hy7KtM7ZQWFRAyt7pNuIXRnKslUSf/p/Esd8HNqCB/HhXBfhXNG6HqJgx0i5gZhU+4 HW+PT0Ra95mFg== To: git@vger.kernel.org From: Jacob Abel Cc: Jacob Abel , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsCBC?= =?utf-8?b?amFybWFzb24=?= , Eric Sunshine , Junio C Hamano , Phillip Wood , =?utf-8?q?Rub=C3=A9n_Justo?= , Taylor Blau , rsbecker@nexbridge.com Subject: [PATCH v9 3/8] t2400: refactor "worktree add" opt exclusion tests Message-ID: <20230417093255.31079-4-jacobabel@nullpo.dev> In-Reply-To: <20230417093255.31079-1-jacobabel@nullpo.dev> References: <20230417093255.31079-1-jacobabel@nullpo.dev> Feedback-ID: 21506737:user:proton MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org Pull duplicate test code into a function so that additional opt combinations can be tested succinctly. Signed-off-by: Jacob Abel --- t/t2400-worktree-add.sh | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) -- 2.39.2 diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index 9bc3db20e4..6822be4666 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -298,17 +298,21 @@ test_expect_success '"add" no auto-vivify with --detach and omitted' ' test_must_fail git -C mish/mash symbolic-ref HEAD ' -test_expect_success '"add" -b/-B mutually exclusive' ' - test_must_fail git worktree add -b poodle -B poodle bamboo main -' - -test_expect_success '"add" -b/--detach mutually exclusive' ' - test_must_fail git worktree add -b poodle --detach bamboo main -' +# Helper function to test mutually exclusive options. +# +# Note: Quoted arguments containing spaces are not supported. +test_wt_add_excl () { + local opts="$*" && + test_expect_success "'worktree add' with '$opts' has mutually exclusive options" ' + test_when_finished cat actual >&2 && + test_must_fail git worktree add $opts 2>actual && + grep -E "fatal:( options)? .* cannot be used together" actual + ' +} -test_expect_success '"add" -B/--detach mutually exclusive' ' - test_must_fail git worktree add -B poodle --detach bamboo main -' +test_wt_add_excl -b poodle -B poodle bamboo main +test_wt_add_excl -b poodle --detach bamboo main +test_wt_add_excl -B poodle --detach bamboo main test_expect_success '"add -B" fails if the branch is checked out' ' git rev-parse newmain >before && From patchwork Mon Apr 17 09:34:01 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacob Abel X-Patchwork-Id: 13213556 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 598FFC77B76 for ; Mon, 17 Apr 2023 09:36:02 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230225AbjDQJgB (ORCPT ); Mon, 17 Apr 2023 05:36:01 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36416 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230232AbjDQJfx (ORCPT ); Mon, 17 Apr 2023 05:35:53 -0400 Received: from mail-4022.proton.ch (mail-4022.proton.ch [185.70.40.22]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id CDE5035A4 for ; Mon, 17 Apr 2023 02:35:14 -0700 (PDT) Date: Mon, 17 Apr 2023 09:34:01 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nullpo.dev; s=protonmail3; t=1681724054; x=1681983254; bh=aBiVHdDsHi7BmU3F2GCbI/dj6+93w9h45avpchtYU7U=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=gfPf/2UZG/5ztCCfEDpyia4HBquwrihJULLtJA52E1ZcCE54AMAr18cjMqdWYxwrf HxKgU7hdY7wIYtOLl/io9a1QuRkpJE69k24U7ekHAPuUJ0plF040yvIZfGCv4mEcAc /Yc4bRcacyCqTzacltn0cEGljU09D4qoav0ijejVhALWUyQTbE7NFdZ2zeJ+fo9Cuw C9mwlQ4CmS4e3/oQEVpNASK1MnmtsxFB8HOmM4AQ5ZhGxEMsMdloSFW2PpQkMQZtsw 6jT5aUQ+xGfWiLylyhZeGfSA9Z4W0wfKFIrikVKKnzW69NjqHwg/jh4u/7nLYIYxnM t8EBui43dlT/g== To: git@vger.kernel.org From: Jacob Abel Cc: Jacob Abel , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsCBC?= =?utf-8?b?amFybWFzb24=?= , Eric Sunshine , Junio C Hamano , Phillip Wood , =?utf-8?q?Rub=C3=A9n_Justo?= , Taylor Blau , rsbecker@nexbridge.com Subject: [PATCH v9 4/8] t2400: add tests to verify --quiet Message-ID: <20230417093255.31079-5-jacobabel@nullpo.dev> In-Reply-To: <20230417093255.31079-1-jacobabel@nullpo.dev> References: <20230417093255.31079-1-jacobabel@nullpo.dev> Feedback-ID: 21506737:user:proton MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org Add tests to verify that the command performs operations the same with `--quiet` as without it. Additionally verifies that all non-fatal output is suppressed. Signed-off-by: Jacob Abel --- t/t2400-worktree-add.sh | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) -- 2.39.2 diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index 6822be4666..18831c4d93 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -336,6 +336,14 @@ test_expect_success 'add --quiet' ' test_must_be_empty actual ' +test_expect_success 'add --quiet -b' ' + test_when_finished "git branch -D quietnewbranch" && + test_when_finished "git worktree remove -f -f another-worktree" && + test_when_finished cat actual >&2 && + git worktree add --quiet -b quietnewbranch another-worktree 2>actual && + test_must_be_empty actual +' + test_expect_success 'local clone from linked checkout' ' git clone --local here here-clone && ( cd here-clone && git fsck ) @@ -534,6 +542,36 @@ test_expect_success 'git worktree add --guess-remote sets up tracking' ' test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo ) ' +test_expect_success 'git worktree add --guess-remote sets up tracking (quiet)' ' + test_when_finished rm -rf repo_a repo_b foo && + test_when_finished cat repo_b/actual >&2 && + setup_remote_repo repo_a repo_b && + ( + cd repo_b && + git worktree add --quiet --guess-remote ../foo 2>actual && + test_must_be_empty actual + ) && + ( + cd foo && + test_branch_upstream foo repo_a foo && + test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo + ) +' + +test_expect_success 'git worktree --no-guess-remote (quiet)' ' + test_when_finished rm -rf repo_a repo_b foo && + setup_remote_repo repo_a repo_b && + ( + cd repo_b && + git worktree add --quiet --no-guess-remote ../foo + ) && + ( + cd foo && + test_must_fail git config "branch.foo.remote" && + test_must_fail git config "branch.foo.merge" && + test_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo + ) +' test_expect_success 'git worktree add with worktree.guessRemote sets up tracking' ' test_when_finished rm -rf repo_a repo_b foo && From patchwork Mon Apr 17 09:34:10 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Jacob Abel X-Patchwork-Id: 13213557 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 85869C77B70 for ; Mon, 17 Apr 2023 09:36:12 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230324AbjDQJgJ (ORCPT ); Mon, 17 Apr 2023 05:36:09 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36538 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230135AbjDQJf7 (ORCPT ); Mon, 17 Apr 2023 05:35:59 -0400 Received: from mail-4317.proton.ch (mail-4317.proton.ch [185.70.43.17]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 0B9A56192 for ; Mon, 17 Apr 2023 02:35:21 -0700 (PDT) Date: Mon, 17 Apr 2023 09:34:10 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nullpo.dev; s=protonmail3; t=1681724064; x=1681983264; bh=kUNuR7Ky1YOtdKTE2BNyvfMKVcILyKgWzDpanSaQovw=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=IQKmzapLRRVVz9/DqGkBNEb0fW9ucjHFDJpvvwntM2uA/aLOlDE+2QZNsiq3P9Hbr 749qMpgFde8vFwZZWc5eBObsQgPsni40AUD97TJvab1rI/Rh9Qzk3ar+CRKNx9A2Ig quVFWQ2p1EB6GZAFmMeC8jFBdZF5YeknC0zatC2FMzuT8W4jgIBJGWcMXqPQlbgCJV WDhF6S5K41DC9Ot7IPqUbAMpVHxMXhxvW4ABXPr1tV+UIfZmHGWFxHVJqLX9fFiPLu 1sePk0JQ7pynaBF7BHnPCJsCnmxM9ebQV4xhBAs/pIndzg25UXAMMLTRnduhStAiFQ I6SdQr25Mt5oA== To: git@vger.kernel.org From: Jacob Abel Cc: Jacob Abel , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsCBC?= =?utf-8?b?amFybWFzb24=?= , Eric Sunshine , Junio C Hamano , Phillip Wood , =?utf-8?q?Rub=C3=A9n_Justo?= , Taylor Blau , rsbecker@nexbridge.com Subject: [PATCH v9 5/8] worktree add: add --orphan flag Message-ID: <20230417093255.31079-6-jacobabel@nullpo.dev> In-Reply-To: <20230417093255.31079-1-jacobabel@nullpo.dev> References: <20230417093255.31079-1-jacobabel@nullpo.dev> Feedback-ID: 21506737:user:proton MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org Add support for creating an orphan branch when adding a new worktree. The functionality of this flag is equivalent to git switch's --orphan option. Current Behavior: % git -C foo.git --no-pager branch -l + main % git -C foo.git worktree add main/ Preparing worktree (new branch 'main') HEAD is now at 6c93a75 a commit % % git init bar.git Initialized empty Git repository in /path/to/bar.git/ % git -C bar.git --no-pager branch -l % git -C bar.git worktree add main/ Preparing worktree (new branch 'main') fatal: not a valid object name: 'HEAD' % New Behavior: % git -C foo.git --no-pager branch -l + main % git -C foo.git worktree add main/ Preparing worktree (new branch 'main') HEAD is now at 6c93a75 a commit % % git init --bare bar.git Initialized empty Git repository in /path/to/bar.git/ % git -C bar.git --no-pager branch -l % git -C bar.git worktree add main/ Preparing worktree (new branch 'main') fatal: invalid reference: HEAD % git -C bar.git worktree add --orphan -b main/ Preparing worktree (new branch 'main') % git -C bar.git worktree add --orphan -b newbranch worktreedir/ Preparing worktree (new branch 'newbranch') % Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Jacob Abel --- Documentation/git-worktree.txt | 6 ++- builtin/worktree.c | 67 +++++++++++++++++++++++++++------ t/t2400-worktree-add.sh | 69 ++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 12 deletions(-) -- 2.39.2 diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index b9c12779f1..485d865eb2 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason ]] - [(-b | -B) ] [] + [--orphan] [(-b | -B) ] [] 'git worktree list' [-v | --porcelain [-z]] 'git worktree lock' [--reason ] 'git worktree move' @@ -222,6 +222,10 @@ This can also be set up as the default behaviour by using the With `prune`, do not remove anything; just report what it would remove. +--orphan:: + With `add`, make the new worktree and index empty, associating + the worktree with a new orphan/unborn branch named ``. + --porcelain:: With `list`, output in an easy-to-parse format for scripts. This format will remain stable across Git versions and regardless of user diff --git a/builtin/worktree.c b/builtin/worktree.c index d1b4b53f2c..48de7fc3b0 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -22,7 +22,8 @@ #define BUILTIN_WORKTREE_ADD_USAGE \ N_("git worktree add [-f] [--detach] [--checkout] [--lock [--reason ]]\n" \ - " [(-b | -B) ] []") + " [--orphan] [(-b | -B) ] []") + #define BUILTIN_WORKTREE_LIST_USAGE \ N_("git worktree list [-v | --porcelain [-z]]") #define BUILTIN_WORKTREE_LOCK_USAGE \ @@ -95,6 +96,7 @@ struct add_opts { int detach; int quiet; int checkout; + int orphan; const char *keep_locked; }; @@ -368,6 +370,22 @@ static int checkout_worktree(const struct add_opts *opts, return run_command(&cp); } +static int make_worktree_orphan(const char * ref, const struct add_opts *opts, + struct strvec *child_env) +{ + struct strbuf symref = STRBUF_INIT; + struct child_process cp = CHILD_PROCESS_INIT; + + validate_new_branchname(ref, &symref, 0); + strvec_pushl(&cp.args, "symbolic-ref", "HEAD", symref.buf, NULL); + if (opts->quiet) + strvec_push(&cp.args, "--quiet"); + strvec_pushv(&cp.env, child_env->v); + strbuf_release(&symref); + cp.git_cmd = 1; + return run_command(&cp); +} + static int add_worktree(const char *path, const char *refname, const struct add_opts *opts) { @@ -397,7 +415,7 @@ static int add_worktree(const char *path, const char *refname, die_if_checked_out(symref.buf, 0); } commit = lookup_commit_reference_by_name(refname); - if (!commit) + if (!commit && !opts->orphan) die(_("invalid reference: %s"), refname); name = worktree_basename(path, &len); @@ -486,10 +504,10 @@ static int add_worktree(const char *path, const char *refname, strvec_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path); cp.git_cmd = 1; - if (!is_branch) + if (!is_branch && commit) { strvec_pushl(&cp.args, "update-ref", "HEAD", oid_to_hex(&commit->object.oid), NULL); - else { + } else { strvec_pushl(&cp.args, "symbolic-ref", "HEAD", symref.buf, NULL); if (opts->quiet) @@ -501,6 +519,10 @@ static int add_worktree(const char *path, const char *refname, if (ret) goto done; + if (opts->orphan && + (ret = make_worktree_orphan(refname, opts, &child_env))) + goto done; + if (opts->checkout && (ret = checkout_worktree(opts, &child_env))) goto done; @@ -520,7 +542,7 @@ static int add_worktree(const char *path, const char *refname, * Hook failure does not warrant worktree deletion, so run hook after * is_junk is cleared, but do return appropriate code when hook fails. */ - if (!ret && opts->checkout) { + if (!ret && opts->checkout && !opts->orphan) { struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL); @@ -568,7 +590,7 @@ static void print_preparing_worktree_line(int detach, else { struct commit *commit = lookup_commit_reference_by_name(branch); if (!commit) - die(_("invalid reference: %s"), branch); + BUG(_("unreachable: invalid reference: %s"), branch); fprintf_ln(stderr, _("Preparing worktree (detached HEAD %s)"), repo_find_unique_abbrev(the_repository, &commit->object.oid, DEFAULT_ABBREV)); } @@ -620,6 +642,7 @@ static int add(int ac, const char **av, const char *prefix) N_("create a new branch")), OPT_STRING('B', NULL, &new_branch_force, N_("branch"), N_("create or reset a branch")), + OPT_BOOL(0, "orphan", &opts.orphan, N_("create unborn/orphaned branch")), OPT_BOOL('d', "detach", &opts.detach, N_("detach HEAD at named commit")), OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")), OPT_BOOL(0, "lock", &keep_locked, N_("keep the new working tree locked")), @@ -640,6 +663,17 @@ static int add(int ac, const char **av, const char *prefix) ac = parse_options(ac, av, prefix, options, git_worktree_add_usage, 0); if (!!opts.detach + !!new_branch + !!new_branch_force > 1) die(_("options '%s', '%s', and '%s' cannot be used together"), "-b", "-B", "--detach"); + if (opts.detach && opts.orphan) + die(_("options '%s', and '%s' cannot be used together"), + "--orphan", "--detach"); + if (opts.orphan && opt_track) + die(_("'%s' and '%s' cannot be used together"), "--orphan", "--track"); + if (opts.orphan && !opts.checkout) + die(_("'%s' and '%s' cannot be used together"), "--orphan", + "--no-checkout"); + if (opts.orphan && ac == 2) + die(_("'%s' and '%s' cannot be used together"), "--orphan", + _("")); if (lock_reason && !keep_locked) die(_("the option '%s' requires '%s'"), "--reason", "--lock"); if (lock_reason) @@ -668,13 +702,17 @@ static int add(int ac, const char **av, const char *prefix) strbuf_release(&symref); } - if (ac < 2 && !new_branch && !opts.detach) { + if (opts.orphan && !new_branch) { + int n; + const char *s = worktree_basename(path, &n); + new_branch = xstrndup(s, n); + } else if (new_branch || opts.detach || opts.orphan) { + // No-op + } else if (ac < 2) { const char *s = dwim_branch(path, &new_branch); if (s) branch = s; - } - - if (ac == 2 && !new_branch && !opts.detach) { + } else if (ac == 2) { struct object_id oid; struct commit *commit; const char *remote; @@ -688,10 +726,17 @@ static int add(int ac, const char **av, const char *prefix) } } } + + if (!opts.orphan && !lookup_commit_reference_by_name(branch)) { + die(_("invalid reference: %s"), branch); + } + if (!opts.quiet) print_preparing_worktree_line(opts.detach, branch, new_branch, !!new_branch_force); - if (new_branch) { + if (opts.orphan) { + branch = new_branch; + } else if (new_branch) { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; strvec_push(&cp.args, "branch"); diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index 18831c4d93..2ea4342867 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -313,6 +313,10 @@ test_wt_add_excl () { test_wt_add_excl -b poodle -B poodle bamboo main test_wt_add_excl -b poodle --detach bamboo main test_wt_add_excl -B poodle --detach bamboo main +test_wt_add_excl --orphan --detach bamboo +test_wt_add_excl --orphan --no-checkout bamboo +test_wt_add_excl --orphan bamboo main +test_wt_add_excl --orphan -b bamboo wtdir/ main test_expect_success '"add -B" fails if the branch is checked out' ' git rev-parse newmain >before && @@ -344,6 +348,63 @@ test_expect_success 'add --quiet -b' ' test_must_be_empty actual ' +test_expect_success '"add --orphan"' ' + test_when_finished "git worktree remove -f -f orphandir" && + git worktree add --orphan -b neworphan orphandir && + echo refs/heads/neworphan >expected && + git -C orphandir symbolic-ref HEAD >actual && + test_cmp expected actual +' + +test_expect_success '"add --orphan (no -b)"' ' + test_when_finished "git worktree remove -f -f neworphan" && + git worktree add --orphan neworphan && + echo refs/heads/neworphan >expected && + git -C neworphan symbolic-ref HEAD >actual && + test_cmp expected actual +' + +test_expect_success '"add --orphan --quiet"' ' + test_when_finished "git worktree remove -f -f orphandir" && + test_when_finished cat log.actual >&2 && + git worktree add --quiet --orphan -b neworphan orphandir 2>log.actual && + test_must_be_empty log.actual && + echo refs/heads/neworphan >expected && + git -C orphandir symbolic-ref HEAD >actual && + test_cmp expected actual +' + +test_expect_success '"add --orphan" fails if the branch already exists' ' + test_when_finished "git branch -D existingbranch" && + git worktree add -b existingbranch orphandir main && + git worktree remove orphandir && + test_must_fail git worktree add --orphan -b existingbranch orphandir +' + +test_expect_success '"add --orphan" with empty repository' ' + test_when_finished "rm -rf empty_repo" && + echo refs/heads/newbranch >expected && + GIT_DIR="empty_repo" git init --bare && + git -C empty_repo worktree add --orphan -b newbranch worktreedir && + git -C empty_repo/worktreedir symbolic-ref HEAD >actual && + test_cmp expected actual +' + +test_expect_success '"add" worktree with orphan branch and lock' ' + git worktree add --lock --orphan -b orphanbr orphan-with-lock && + test_when_finished "git worktree unlock orphan-with-lock || :" && + test -f .git/worktrees/orphan-with-lock/locked +' + +test_expect_success '"add" worktree with orphan branch, lock, and reason' ' + lock_reason="why not" && + git worktree add --detach --lock --reason "$lock_reason" orphan-with-lock-reason main && + test_when_finished "git worktree unlock orphan-with-lock-reason || :" && + test -f .git/worktrees/orphan-with-lock-reason/locked && + echo "$lock_reason" >expect && + test_cmp expect .git/worktrees/orphan-with-lock-reason/locked +' + test_expect_success 'local clone from linked checkout' ' git clone --local here here-clone && ( cd here-clone && git fsck ) @@ -460,6 +521,14 @@ setup_remote_repo () { ) } +test_expect_success '"add" w/ no HEAD' ' + test_when_finished rm -rf repo_upstream repo_local foo && + setup_remote_repo repo_upstream repo_local && + git -C repo_local config --bool core.bare true && + git -C repo_local branch -D main && + git -C repo_local worktree add ./foo repo_upstream/foo +' + test_expect_success '--no-track avoids setting up tracking' ' test_when_finished rm -rf repo_upstream repo_local foo && setup_remote_repo repo_upstream repo_local && From patchwork Mon Apr 17 09:34:19 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacob Abel X-Patchwork-Id: 13213561 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 E87ECC77B70 for ; Mon, 17 Apr 2023 09:37:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230112AbjDQJhW (ORCPT ); Mon, 17 Apr 2023 05:37:22 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36386 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229854AbjDQJhB (ORCPT ); Mon, 17 Apr 2023 05:37:01 -0400 Received: from mail-0301.mail-europe.com (mail-0301.mail-europe.com [188.165.51.139]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id C03CE6197 for ; Mon, 17 Apr 2023 02:36:28 -0700 (PDT) Date: Mon, 17 Apr 2023 09:34:19 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nullpo.dev; s=protonmail3; t=1681724065; x=1681983265; bh=/dmtjt1dS8kS7iYS5LED8Lh++1sokTbSGBHnK1iOUPg=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=cAdF0XsTmpdD21IvNqzrBs5qVnZxKE54NhdtiXKnopHChU3fm5zmfooEEv59uDGo2 3wpbYpW5CX+IGUC5fbIBLw5EeiADofp6l32+/W/B1d5JQxm751RcIOXOdFa3lU7+zi F7YdgpaPNYrib4j+hLUzdW7H+2gFFmrBlNlND/v3Efmi4Zg/DkNR+y6GjMdAOr0bf5 YqHMS0MN73F4ju37/oPCS1/FaDTpBnHWUobdhJuTSlNnIvuugvEc1+LqtwAMk0DGuq ZDQoanlh2sM5kDUYOxxnqVvbO0dP5xdYTKrrqsZpkeqyMDHih6/sw8VwfTgjajDBQ1 19Od3mMSUha7w== To: git@vger.kernel.org From: Jacob Abel Cc: Jacob Abel , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsCBC?= =?utf-8?b?amFybWFzb24=?= , Eric Sunshine , Junio C Hamano , Phillip Wood , =?utf-8?q?Rub=C3=A9n_Justo?= , Taylor Blau , rsbecker@nexbridge.com Subject: [PATCH v9 6/8] worktree add: introduce "try --orphan" hint Message-ID: <20230417093255.31079-7-jacobabel@nullpo.dev> In-Reply-To: <20230417093255.31079-1-jacobabel@nullpo.dev> References: <20230417093255.31079-1-jacobabel@nullpo.dev> Feedback-ID: 21506737:user:proton MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org Add a new advice/hint in `git worktree add` for when the user tries to create a new worktree from a reference that doesn't exist. Current Behavior: % git init foo Initialized empty Git repository in /path/to/foo/ % touch file % git -C foo commit -q -a -m "test commit" % git -C foo switch --orphan norefbranch % git -C foo worktree add newbranch/ Preparing worktree (new branch 'newbranch') fatal: invalid reference: HEAD % New Behavior: % git init --bare foo Initialized empty Git repository in /path/to/foo/ % touch file % git -C foo commit -q -a -m "test commit" % git -C foo switch --orphan norefbranch % git -C foo worktree add newbranch/ Preparing worktree (new branch 'newbranch') hint: If you meant to create a worktree containing a new orphan branch hint: (branch with no commits) for this repository, you can do so hint: using the --orphan option: hint: hint: git worktree add --orphan newbranch/ hint: hint: Disable this message with "git config advice.worktreeAddOrphan false" fatal: invalid reference: HEAD % git -C foo worktree add -b newbranch2 new_wt/ Preparing worktree (new branch 'newbranch') hint: If you meant to create a worktree containing a new orphan branch hint: (branch with no commits) for this repository, you can do so hint: using the --orphan option: hint: hint: git worktree add --orphan -b newbranch2 new_wt/ hint: hint: Disable this message with "git config advice.worktreeAddOrphan false" fatal: invalid reference: HEAD % Signed-off-by: Jacob Abel --- Documentation/config/advice.txt | 4 ++++ advice.c | 1 + advice.h | 1 + builtin/worktree.c | 25 +++++++++++++++++++++ t/t2400-worktree-add.sh | 39 +++++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+) -- 2.39.2 diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt index c96b5b2e5d..c548a91e67 100644 --- a/Documentation/config/advice.txt +++ b/Documentation/config/advice.txt @@ -138,4 +138,8 @@ advice.*:: checkout. diverging:: Advice shown when a fast-forward is not possible. + worktreeAddOrphan:: + Advice shown when a user tries to create a worktree from an + invalid reference, to instruct how to create a new orphan + branch instead. -- diff --git a/advice.c b/advice.c index d6232439c3..e5a9bb9b44 100644 --- a/advice.c +++ b/advice.c @@ -78,6 +78,7 @@ static struct { [ADVICE_SUBMODULES_NOT_UPDATED] = { "submodulesNotUpdated", 1 }, [ADVICE_UPDATE_SPARSE_PATH] = { "updateSparsePath", 1 }, [ADVICE_WAITING_FOR_EDITOR] = { "waitingForEditor", 1 }, + [ADVICE_WORKTREE_ADD_ORPHAN] = { "worktreeAddOrphan", 1 }, }; static const char turn_off_instructions[] = diff --git a/advice.h b/advice.h index 0f584163f5..2affbe1426 100644 --- a/advice.h +++ b/advice.h @@ -49,6 +49,7 @@ struct string_list; ADVICE_UPDATE_SPARSE_PATH, ADVICE_WAITING_FOR_EDITOR, ADVICE_SKIPPED_CHERRY_PICKS, + ADVICE_WORKTREE_ADD_ORPHAN, }; int git_default_advice_config(const char *var, const char *value); diff --git a/builtin/worktree.c b/builtin/worktree.c index 48de7fc3b0..12348d3d16 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -39,6 +39,20 @@ #define BUILTIN_WORKTREE_UNLOCK_USAGE \ N_("git worktree unlock ") +#define WORKTREE_ADD_ORPHAN_WITH_DASH_B_HINT_TEXT \ + _("If you meant to create a worktree containing a new orphan branch\n" \ + "(branch with no commits) for this repository, you can do so\n" \ + "using the --orphan flag:\n" \ + "\n" \ + " git worktree add --orphan -b %s %s\n") + +#define WORKTREE_ADD_ORPHAN_NO_DASH_B_HINT_TEXT \ + _("If you meant to create a worktree containing a new orphan branch\n" \ + "(branch with no commits) for this repository, you can do so\n" \ + "using the --orphan flag:\n" \ + "\n" \ + " git worktree add --orphan %s\n") + static const char * const git_worktree_usage[] = { BUILTIN_WORKTREE_ADD_USAGE, BUILTIN_WORKTREE_LIST_USAGE, @@ -634,6 +648,7 @@ static int add(int ac, const char **av, const char *prefix) const char *opt_track = NULL; const char *lock_reason = NULL; int keep_locked = 0; + int used_new_branch_options; struct option options[] = { OPT__FORCE(&opts.force, N_("checkout even if already checked out in other worktree"), @@ -686,6 +701,7 @@ static int add(int ac, const char **av, const char *prefix) path = prefix_filename(prefix, av[0]); branch = ac < 2 ? "HEAD" : av[1]; + used_new_branch_options = new_branch || new_branch_force; if (!strcmp(branch, "-")) branch = "@{-1}"; @@ -728,6 +744,15 @@ static int add(int ac, const char **av, const char *prefix) } if (!opts.orphan && !lookup_commit_reference_by_name(branch)) { + int attempt_hint = !opts.quiet && (ac < 2); + if (attempt_hint && used_new_branch_options) { + advise_if_enabled(ADVICE_WORKTREE_ADD_ORPHAN, + WORKTREE_ADD_ORPHAN_WITH_DASH_B_HINT_TEXT, + new_branch, path); + } else if (attempt_hint) { + advise_if_enabled(ADVICE_WORKTREE_ADD_ORPHAN, + WORKTREE_ADD_ORPHAN_NO_DASH_B_HINT_TEXT, path); + } die(_("invalid reference: %s"), branch); } diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index 2ea4342867..7ea56ef7c1 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -405,6 +405,45 @@ test_expect_success '"add" worktree with orphan branch, lock, and reason' ' test_cmp expect .git/worktrees/orphan-with-lock-reason/locked ' +# Note: Quoted arguments containing spaces are not supported. +test_wt_add_orphan_hint () { + local context="$1" && + local use_branch=$2 && + shift 2 && + local opts="$*" && + test_expect_success "'worktree add' show orphan hint in bad/orphan HEAD w/ $context" ' + test_when_finished "rm -rf repo" && + git init repo && + (cd repo && test_commit commit) && + git -C repo switch --orphan noref && + test_when_finished cat actual >&2 && + test_must_fail git -C repo worktree add $opts foobar/ 2>actual && + ! grep "error: unknown switch" actual && + grep "hint: If you meant to create a worktree containing a new orphan branch" actual && + if [ $use_branch -eq 1 ] + then + grep -E "^hint:\s+git worktree add --orphan -b \S+ \S+\s*$" actual + else + grep -E "^hint:\s+git worktree add --orphan \S+\s*$" actual + fi + + ' +} + +test_wt_add_orphan_hint 'no opts' 0 +test_wt_add_orphan_hint '-b' 1 -b foobar_branch +test_wt_add_orphan_hint '-B' 1 -B foobar_branch + +test_expect_success "'worktree add' doesn't show orphan hint in bad/orphan HEAD w/ --quiet" ' + test_when_finished "rm -rf repo" && + git init repo && + (cd repo && test_commit commit) && + test_when_finished cat actual >&2 && + test_must_fail git -C repo worktree add --quiet foobar_branch foobar/ 2>actual && + ! grep "error: unknown switch" actual && + ! grep "hint: If you meant to create a worktree containing a new orphan branch" actual +' + test_expect_success 'local clone from linked checkout' ' git clone --local here here-clone && ( cd here-clone && git fsck ) From patchwork Mon Apr 17 09:34:27 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacob Abel X-Patchwork-Id: 13213558 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 17385C77B76 for ; Mon, 17 Apr 2023 09:36:23 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230272AbjDQJgV (ORCPT ); Mon, 17 Apr 2023 05:36:21 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36750 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230173AbjDQJgK (ORCPT ); Mon, 17 Apr 2023 05:36:10 -0400 Received: from mail-4018.proton.ch (mail-4018.proton.ch [185.70.40.18]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B1ED92718 for ; Mon, 17 Apr 2023 02:35:30 -0700 (PDT) Date: Mon, 17 Apr 2023 09:34:27 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nullpo.dev; s=protonmail3; t=1681724080; x=1681983280; bh=JbBUitJyPqj5GuFTQaLxU6Twsk7ImRm/tFpKoiZaAtU=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=wVj/W/mFE+FHTuzLGDuhAfJ4fzsrGFqLIBc69z256Fj/AwcqSmoZOvEuwKvlZEgst nuNHOyj7QaQQjJD0XAqidm2HsvPdLGU5JP3ddD67kY5a4aRdEFeG3xSqXmor8trgE5 8X/mrQUPRbQ2nN7Jesx8oK4KWR9rx2IO/elDQSJjccg6doHjq/UH5fbE/HrKVU9Nk5 NcmoPRUzeLEFYPv5vsq8qYT5KMhMIecQrBJLFEXhxj2qZpsdeXPfPuzSlrdfwZ42lE fejVqXLyzeFteIYHIf18NDj/h93NssYwdL+IR3BYNOdY9Y08cUcYEBLdyjYH2cay/H FgjNDrfqOqGHA== To: git@vger.kernel.org From: Jacob Abel Cc: Jacob Abel , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsCBC?= =?utf-8?b?amFybWFzb24=?= , Eric Sunshine , Junio C Hamano , Phillip Wood , =?utf-8?q?Rub=C3=A9n_Justo?= , Taylor Blau , rsbecker@nexbridge.com Subject: [PATCH v9 7/8] worktree add: extend DWIM to infer --orphan Message-ID: <20230417093255.31079-8-jacobabel@nullpo.dev> In-Reply-To: <20230417093255.31079-1-jacobabel@nullpo.dev> References: <20230417093255.31079-1-jacobabel@nullpo.dev> Feedback-ID: 21506737:user:proton MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org Extend DWIM to try to infer `--orphan` when in an empty repository. i.e. a repository with an invalid/unborn HEAD, no local branches, and if `--guess-remote` is used then no remote branches. This behavior is equivalent to `git switch -c` or `git checkout -b` in an empty repository. Also warn the user (overriden with `-f`/`--force`) when they likely intend to checkout a remote branch to the worktree but have not yet fetched from the remote. i.e. when using `--guess-remote` and there is a remote but no local or remote refs. Current Behavior: % git --no-pager branch --list --remotes % git remote origin % git workree add ../main hint: If you meant to create a worktree containing a new orphan branch [...] hint: Disable this message with "git config advice.worktreeAddOrphan false" fatal: invalid reference: HEAD % git workree add --guess-remote ../main hint: If you meant to create a worktree containing a new orphan branch [...] hint: Disable this message with "git config advice.worktreeAddOrphan false" fatal: invalid reference: HEAD % git fetch --quiet % git --no-pager branch --list --remotes origin/HEAD -> origin/main origin/main % git workree add --guess-remote ../main Preparing worktree (new branch 'main') branch 'main' set up to track 'origin/main'. HEAD is now at dadc8e6dac commit message % New Behavior: % git --no-pager branch --list --remotes % git remote origin % git workree add ../main No possible source branch, inferring '--orphan' Preparing worktree (new branch 'main') % git worktree remove ../main % git workree add --guess-remote ../main fatal: No local or remote refs exist despite at least one remote present, stopping; use 'add -f' to overide or fetch a remote first % git workree add --guess-remote -f ../main No possible source branch, inferring '--orphan' Preparing worktree (new branch 'main') % git worktree remove ../main % git fetch --quiet % git --no-pager branch --list --remotes origin/HEAD -> origin/main origin/main % git workree add --guess-remote ../main Preparing worktree (new branch 'main') branch 'main' set up to track 'origin/main'. HEAD is now at dadc8e6dac commit message % Signed-off-by: Jacob Abel --- Documentation/git-worktree.txt | 10 + builtin/worktree.c | 114 ++++++++++- t/t2400-worktree-add.sh | 332 +++++++++++++++++++++++++++++++++ 3 files changed, 455 insertions(+), 1 deletion(-) -- 2.39.2 diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 485d865eb2..a4fbf5e838 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -95,6 +95,16 @@ exist, a new branch based on `HEAD` is automatically created as if `-b ` was given. If `` does exist, it will be checked out in the new worktree, if it's not checked out anywhere else, otherwise the command will refuse to create the worktree (unless `--force` is used). ++ +If `` is omitted, neither `--detach`, or `--orphan` is +used, and there are no valid local branches (or remote branches if +`--guess-remote` is specified) then, as a convenience, the new worktree is +associated with a new orphan branch named `` (after +`$(basename )` if neither `-b` or `-B` is used) as if `--orphan` was +passed to the command. In the event the repository has a remote and +`--guess-remote` is used, but no remote or local branches exist, then the +command fails with a warning reminding the user to fetch from their remote +first (or override by using `-f/--force`). list:: diff --git a/builtin/worktree.c b/builtin/worktree.c index 12348d3d16..95b5bbb1d2 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -11,6 +11,7 @@ #include "strvec.h" #include "branch.h" #include "refs.h" +#include "remote.h" #include "run-command.h" #include "hook.h" #include "sigchain.h" @@ -39,6 +40,9 @@ #define BUILTIN_WORKTREE_UNLOCK_USAGE \ N_("git worktree unlock ") +#define WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT \ + _("No possible source branch, inferring '--orphan'") + #define WORKTREE_ADD_ORPHAN_WITH_DASH_B_HINT_TEXT \ _("If you meant to create a worktree containing a new orphan branch\n" \ "(branch with no commits) for this repository, you can do so\n" \ @@ -612,6 +616,107 @@ static void print_preparing_worktree_line(int detach, } } +/** + * Callback to short circuit iteration over refs on the first reference + * corresponding to a valid oid. + * + * Returns 0 on failure and non-zero on success. + */ +static int first_valid_ref(const char *refname, + const struct object_id *oid, + int flags, + void *cb_data) +{ + return 1; +} + +/** + * Verifies HEAD and determines whether there exist any valid local references. + * + * - Checks whether HEAD points to a valid reference. + * + * - Checks whether any valid local branches exist. + * + * Returns 1 if any of the previous checks are true, otherwise returns 0. + */ +static int can_use_local_refs(const struct add_opts *opts) +{ + if (head_ref(first_valid_ref, NULL)) { + return 1; + } else if (for_each_branch_ref(first_valid_ref, NULL)) { + return 1; + } + return 0; +} + +/** + * Reports whether the necessary flags were set and whether the repository has + * remote references to attempt DWIM tracking of upstream branches. + * + * 1. Checks that `--guess-remote` was used or `worktree.guessRemote = true`. + * + * 2. Checks whether any valid remote branches exist. + * + * 3. Checks that there exists at least one remote and emits a warning/error + * if both checks 1. and 2. are false (can be bypassed with `--force`). + * + * Returns 1 if checks 1. and 2. are true, otherwise 0. + */ +static int can_use_remote_refs(const struct add_opts *opts) +{ + if (!guess_remote) { + if (!opts->quiet) + fprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT); + return 0; + } else if (for_each_remote_ref(first_valid_ref, NULL)) { + return 1; + } else if (!opts->force && remote_get(NULL)) { + die(_("No local or remote refs exist despite at least one remote\n" + "present, stopping; use 'add -f' to overide or fetch a remote first")); + } else if (!opts->quiet) { + fprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT); + } + return 0; +} + +/** + * Determines whether `--orphan` should be inferred in the evaluation of + * `worktree add path/` or `worktree add -b branch path/` and emits an error + * if the supplied arguments would produce an illegal combination when the + * `--orphan` flag is included. + * + * `opts` and `opt_track` contain the other options & flags supplied to the + * command. + * + * remote determines whether to check `can_use_remote_refs()` or not. This + * is primarily to differentiate between the basic `add` DWIM and `add -b`. + * + * Returns 1 when inferring `--orphan`, 0 otherwise, and emits an error when + * `--orphan` is inferred but doing so produces an illegal combination of + * options and flags. Additionally produces an error when remote refs are + * checked and the repo is in a state that looks like the user added a remote + * but forgot to fetch (and did not override the warning with -f). + */ +static int dwim_orphan(const struct add_opts *opts, int opt_track, int remote) +{ + if (can_use_local_refs(opts)) { + return 0; + } else if (remote && can_use_remote_refs(opts)) { + return 0; + } else if (!opts->quiet) { + fprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT); + } + + if (opt_track) { + die(_("'%s' and '%s' cannot be used together"), "--orphan", + "--track"); + } else if (!opts->checkout) { + die(_("'%s' and '%s' cannot be used together"), "--orphan", + "--no-checkout"); + } + return 1; +} + static const char *dwim_branch(const char *path, const char **new_branch) { int n; @@ -722,12 +827,19 @@ static int add(int ac, const char **av, const char *prefix) int n; const char *s = worktree_basename(path, &n); new_branch = xstrndup(s, n); - } else if (new_branch || opts.detach || opts.orphan) { + } else if (opts.orphan || opts.detach) { // No-op + } else if (ac < 2 && new_branch) { + // DWIM: Infer --orphan when repo has no refs. + opts.orphan = dwim_orphan(&opts, !!opt_track, 0); } else if (ac < 2) { + // DWIM: Guess branch name from path. const char *s = dwim_branch(path, &new_branch); if (s) branch = s; + + // DWIM: Infer --orphan when repo has no refs. + opts.orphan = (!s) && dwim_orphan(&opts, !!opt_track, 1); } else if (ac == 2) { struct object_id oid; struct commit *commit; diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index 7ea56ef7c1..e5cca1d11b 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -712,6 +712,338 @@ test_expect_success 'git worktree --no-guess-remote option overrides config' ' ) ' +test_dwim_orphan () { + local info_text="No possible source branch, inferring '--orphan'" && + local fetch_error_text="fatal: No local or remote refs exist despite at least one remote" && + local orphan_hint="hint: If you meant to create a worktree containing a new orphan branch" && + local invalid_ref_regex="^fatal: invalid reference:\s\+.*" && + local bad_combo_regex="^fatal: '[a-z-]\+' and '[a-z-]\+' cannot be used together" && + + local git_ns="repo" && + local dashc_args="-C $git_ns" && + local use_cd=0 && + + local bad_head=0 && + local empty_repo=1 && + local local_ref=0 && + local use_quiet=0 && + local remote=0 && + local remote_ref=0 && + local use_new_branch=0 && + + local outcome="$1" && + local outcome_text && + local success && + shift && + local args="" && + local context="" && + case "$outcome" in + "infer") + success=1 && + outcome_text='"add" DWIM infer --orphan' + ;; + "no_infer") + success=1 && + outcome_text='"add" DWIM doesnt infer --orphan' + ;; + "fetch_error") + success=0 && + outcome_text='"add" error need fetch' + ;; + "fatal_orphan_bad_combo") + success=0 && + outcome_text='"add" error inferred "--orphan" gives illegal opts combo' + ;; + *) + echo "test_dwim_orphan(): invalid outcome: '$outcome'" >&2 && + return 1 + ;; + esac && + while [ $# -gt 0 ] + do + case "$1" in + # How and from where to create the worktree + "-C_repo") + use_cd=0 && + git_ns="repo" && + dashc_args="-C $git_ns" && + context="$context, 'git -C repo'" + ;; + "-C_wt") + use_cd=0 && + git_ns="wt" && + dashc_args="-C $git_ns" && + context="$context, 'git -C wt'" + ;; + "cd_repo") + use_cd=1 && + git_ns="repo" && + dashc_args="" && + context="$context, 'cd repo && git'" + ;; + "cd_wt") + use_cd=1 && + git_ns="wt" && + dashc_args="" && + context="$context, 'cd wt && git'" + ;; + + # Bypass the "pull first" warning + "force") + args="$args --force" && + context="$context, --force" + ;; + + # Try to use remote refs when DWIM + "guess_remote") + args="$args --guess-remote" && + context="$context, --guess-remote" + ;; + "no_guess_remote") + args="$args --no-guess-remote" && + context="$context, --no-guess-remote" + ;; + + # Whether there is at least one local branch present + "local_ref") + empty_repo=0 && + local_ref=1 && + context="$context, >=1 local branches" + ;; + "no_local_ref") + empty_repo=0 && + context="$context, 0 local branches" + ;; + + # Whether the HEAD points at a valid ref (skip this opt when no refs) + "good_head") + # requires: local_ref + context="$context, valid HEAD" + ;; + "bad_head") + bad_head=1 && + context="$context, invalid (or orphan) HEAD" + ;; + + # Whether the code path is tested with the base add command or -b + "no_-b") + use_new_branch=0 && + context="$context, no --branch" + ;; + "-b") + use_new_branch=1 && + context="$context, --branch" + ;; + + # Whether to check that all output is suppressed (except errors) + # or that the output is as expected + "quiet") + use_quiet=1 && + args="$args --quiet" && + context="$context, --quiet" + ;; + "no_quiet") + use_quiet=0 && + context="$context, no --quiet (expect output)" + ;; + + # Whether there is at least one remote attached to the repo + "remote") + empty_repo=0 && + remote=1 && + context="$context, >=1 remotes" + ;; + "no_remote") + empty_repo=0 && + remote=0 && + context="$context, 0 remotes" + ;; + + # Whether there is at least one valid remote ref + "remote_ref") + # requires: remote + empty_repo=0 && + remote_ref=1 && + context="$context, >=1 fetched remote branches" + ;; + "no_remote_ref") + empty_repo=0 && + remote_ref=0 && + context="$context, 0 fetched remote branches" + ;; + + # Options or flags that become illegal when --orphan is inferred + "no_checkout") + args="$args --no-checkout" && + context="$context, --no-checkout" + ;; + "track") + args="$args --track" && + context="$context, --track" + ;; + + # All other options are illegal + *) + echo "test_dwim_orphan(): invalid arg: '$1'" >&2 && + return 1 + ;; + esac && + shift + done && + context="${context#', '}" && + if [ $use_new_branch -eq 1 ] + then + args="$args -b foo" + else + context="DWIM (no --branch), $context" + fi && + if [ $empty_repo -eq 1 ] + then + context="empty repo, $context" + fi && + args="$args ../foo" && + context="${context%', '}" && + test_expect_success "$outcome_text w/ $context" ' + test_when_finished "rm -rf repo" && + git init repo && + if [ $local_ref -eq 1 ] && [ "$git_ns" = "repo" ] + then + (cd repo && test_commit commit) && + if [ $bad_head -eq 1 ] + then + git -C repo symbolic-ref HEAD refs/heads/badbranch + fi + elif [ $local_ref -eq 1 ] && [ "$git_ns" = "wt" ] + then + test_when_finished "git -C repo worktree remove -f ../wt" && + git -C repo worktree add --orphan -b main ../wt && + (cd wt && test_commit commit) && + if [ $bad_head -eq 1 ] + then + git -C wt symbolic-ref HEAD refs/heads/badbranch + fi + elif [ $local_ref -eq 0 ] && [ "$git_ns" = "wt" ] + then + test_when_finished "git -C repo worktree remove -f ../wt" && + git -C repo worktree add --orphan -b orphanbranch ../wt + fi && + + if [ $remote -eq 1 ] + then + test_when_finished "rm -rf upstream" && + git init upstream && + (cd upstream && test_commit commit) && + git -C upstream switch -c foo && + git -C repo remote add upstream ../upstream + fi && + + if [ $remote_ref -eq 1 ] + then + git -C repo fetch + fi && + if [ $success -eq 1 ] + then + test_when_finished git -C repo worktree remove ../foo + fi && + if [ $use_cd -eq 1 ] + then + test_when_finished cat "$git_ns/actual" >&2 + else + test_when_finished cat actual >&2 + fi && + ( + if [ $use_cd -eq 1 ] + then + cd $git_ns + fi && + if [ "$outcome" = "infer" ] + then + git $dashc_args worktree add $args 2>actual && + if [ $use_quiet -eq 1 ] + then + test_must_be_empty actual + else + grep "$info_text" actual + fi + elif [ "$outcome" = "no_infer" ] + then + git $dashc_args worktree add $args 2>actual && + if [ $use_quiet -eq 1 ] + then + test_must_be_empty actual + else + ! grep "$info_text" actual + fi + elif [ "$outcome" = "fetch_error" ] + then + test_must_fail git $dashc_args worktree add $args 2>actual && + grep "$fetch_error_text" actual + elif [ "$outcome" = "fatal_orphan_bad_combo" ] + then + test_must_fail git $dashc_args worktree add $args 2>actual && + if [ $use_quiet -eq 1 ] + then + ! grep "$info_text" actual + else + grep "$info_text" actual + fi && + grep "$bad_combo_regex" actual + elif [ "$outcome" = "warn_bad_head" ] + then + test_must_fail git $dashc_args worktree add $args 2>actual && + if [ $use_quiet -eq 1 ] + then + grep "$invalid_ref_regex" actual && + ! grep "$orphan_hint" actual + else + headpath=$(git $dashc_args rev-parse --sq --path-format=absolute --git-path HEAD) && + headcontents=$(cat "$headpath") && + grep "HEAD points to an invalid (or orphaned) reference" actual && + grep "HEAD path:\s*.$headpath." actual && + grep "HEAD contents:\s*.$headcontents." actual && + grep "$orphan_hint" actual && + ! grep "$info_text" actual + fi && + grep "$invalid_ref_regex" actual + else + # Unreachable + false + fi + ) && + if [ $success -ne 1 ] + then + test_path_is_missing foo + fi + ' +} + +for quiet_mode in "no_quiet" "quiet" +do + for changedir_type in "cd_repo" "cd_wt" "-C_repo" "-C_wt" + do + dwim_test_args="$quiet_mode $changedir_type" + test_dwim_orphan 'infer' $dwim_test_args no_-b + test_dwim_orphan 'no_infer' $dwim_test_args no_-b local_ref good_head + test_dwim_orphan 'infer' $dwim_test_args no_-b no_local_ref no_remote no_remote_ref no_guess_remote + test_dwim_orphan 'infer' $dwim_test_args no_-b no_local_ref remote no_remote_ref no_guess_remote + test_dwim_orphan 'fetch_error' $dwim_test_args no_-b no_local_ref remote no_remote_ref guess_remote + test_dwim_orphan 'infer' $dwim_test_args no_-b no_local_ref remote no_remote_ref guess_remote force + test_dwim_orphan 'no_infer' $dwim_test_args no_-b no_local_ref remote remote_ref guess_remote + + test_dwim_orphan 'infer' $dwim_test_args -b + test_dwim_orphan 'no_infer' $dwim_test_args -b local_ref good_head + test_dwim_orphan 'infer' $dwim_test_args -b no_local_ref no_remote no_remote_ref no_guess_remote + test_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref no_guess_remote + test_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref guess_remote + test_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote remote_ref guess_remote + done + + test_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode no_-b no_checkout + test_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode no_-b track + test_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode -b no_checkout + test_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode -b track +done + post_checkout_hook () { test_when_finished "rm -rf .git/hooks" && mkdir .git/hooks && From patchwork Mon Apr 17 09:34:34 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacob Abel X-Patchwork-Id: 13213562 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 4FD61C77B77 for ; Mon, 17 Apr 2023 09:37:28 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231133AbjDQJhZ (ORCPT ); Mon, 17 Apr 2023 05:37:25 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37230 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230120AbjDQJhC (ORCPT ); Mon, 17 Apr 2023 05:37:02 -0400 Received: from mail-0301.mail-europe.com (mail-0301.mail-europe.com [188.165.51.139]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 863116194 for ; Mon, 17 Apr 2023 02:36:28 -0700 (PDT) Date: Mon, 17 Apr 2023 09:34:34 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nullpo.dev; s=protonmail3; t=1681724080; x=1681983280; bh=HazpFulvYkemTU2flJd+J/W2Z21YBUgTlpnvL6voaIw=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=hVa6HOz6osPQ9Ytv8reVchMrOrPHnvkNTJ2uZZSJTwmybml+w1l+kaYccdcEY7Gta Xp5e0qidNGpByPgyIsQURjWfiGrMXkcKlgae0xYUvscv5NIXdppmYnnbTXasdj/rDY SmfbsQy38AYiH6a/HJ14+UnWK9BIOnAYSkEDGY2IDGdikAXEvo4HTemdP797OzhZ0E w4YseNnfqJzsOwiqDD0tJ+Hogxu1Is/tMoAtGDgCBYOFqlUPPZ+AZXCkWVse29FRpw LV9gTayAUGKrii1ZPoDQWXSvbKjXxO30v5KQLlOVkqnOzu5h3VnASz424yxJEssg+T hAvma6+XFgMBg== To: git@vger.kernel.org From: Jacob Abel Cc: Jacob Abel , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsCBC?= =?utf-8?b?amFybWFzb24=?= , Eric Sunshine , Junio C Hamano , Phillip Wood , =?utf-8?q?Rub=C3=A9n_Justo?= , Taylor Blau , rsbecker@nexbridge.com Subject: [PATCH v9 8/8] worktree add: emit warn when there is a bad HEAD Message-ID: <20230417093255.31079-9-jacobabel@nullpo.dev> In-Reply-To: <20230417093255.31079-1-jacobabel@nullpo.dev> References: <20230417093255.31079-1-jacobabel@nullpo.dev> Feedback-ID: 21506737:user:proton MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org Add a warning to `worktree add` when the command tries to reference HEAD, there exist valid local branches, and the HEAD points to a non-existent reference. Current Behavior: % git -C foo worktree list /path/to/repo/foo dadc8e6dac [main] /path/to/repo/foo_wt 0000000000 [badref] % git -C foo worktree add ../wt1 Preparing worktree (new branch 'wt1') HEAD is now at dadc8e6dac dummy commit % git -C foo_wt worktree add ../wt2 hint: If you meant to create a worktree containing a new orphan branch [...] hint: Disable this message with "git config advice.worktreeAddOrphan false" fatal: invalid reference: HEAD % New Behavior: % git -C foo worktree list /path/to/repo/foo dadc8e6dac [main] /path/to/repo/foo_wt 0000000000 [badref] % git -C foo worktree add ../wt1 Preparing worktree (new branch 'wt1') HEAD is now at dadc8e6dac dummy commit % git -C foo_wt worktree add ../wt2 warning: HEAD points to an invalid (or orphaned) reference. HEAD path: '/path/to/repo/foo/.git/worktrees/foo_wt/HEAD' HEAD contents: 'ref: refs/heads/badref' hint: If you meant to create a worktree containing a new orphan branch [...] hint: Disable this message with "git config advice.worktreeAddOrphan false" fatal: invalid reference: HEAD % Signed-off-by: Jacob Abel --- builtin/worktree.c | 34 +++++++++++++++++++++++++++++----- t/t2400-worktree-add.sh | 18 +++++++++++++++++- 2 files changed, 46 insertions(+), 6 deletions(-) -- 2.39.2 diff --git a/builtin/worktree.c b/builtin/worktree.c index 95b5bbb1d2..0fba4cfdf8 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -637,6 +637,9 @@ static int first_valid_ref(const char *refname, * * - Checks whether any valid local branches exist. * + * - Emits a warning if there exist any valid branches but HEAD does not point + * to a valid reference. + * * Returns 1 if any of the previous checks are true, otherwise returns 0. */ static int can_use_local_refs(const struct add_opts *opts) @@ -644,6 +647,23 @@ static int can_use_local_refs(const struct add_opts *opts) if (head_ref(first_valid_ref, NULL)) { return 1; } else if (for_each_branch_ref(first_valid_ref, NULL)) { + if (!opts->quiet) { + struct strbuf path = STRBUF_INIT; + struct strbuf contents = STRBUF_INIT; + + strbuf_add_real_path(&path, get_worktree_git_dir(NULL)); + strbuf_addstr(&path, "/HEAD"); + strbuf_read_file(&contents, path.buf, 64); + strbuf_stripspace(&contents, 0); + strbuf_strip_suffix(&contents, "\n"); + + warning(_("HEAD points to an invalid (or orphaned) reference.\n" + "HEAD path: '%s'\n" + "HEAD contents: '%s'"), + path.buf, contents.buf); + strbuf_release(&path); + strbuf_release(&contents); + } return 1; } return 0; @@ -665,16 +685,12 @@ static int can_use_local_refs(const struct add_opts *opts) static int can_use_remote_refs(const struct add_opts *opts) { if (!guess_remote) { - if (!opts->quiet) - fprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT); return 0; } else if (for_each_remote_ref(first_valid_ref, NULL)) { return 1; } else if (!opts->force && remote_get(NULL)) { die(_("No local or remote refs exist despite at least one remote\n" "present, stopping; use 'add -f' to overide or fetch a remote first")); - } else if (!opts->quiet) { - fprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT); } return 0; } @@ -827,8 +843,12 @@ static int add(int ac, const char **av, const char *prefix) int n; const char *s = worktree_basename(path, &n); new_branch = xstrndup(s, n); - } else if (opts.orphan || opts.detach) { + } else if (opts.orphan) { // No-op + } else if (opts.detach) { + // Check HEAD + if (!strcmp(branch, "HEAD")) + can_use_local_refs(&opts); } else if (ac < 2 && new_branch) { // DWIM: Infer --orphan when repo has no refs. opts.orphan = dwim_orphan(&opts, !!opt_track, 0); @@ -853,6 +873,10 @@ static int add(int ac, const char **av, const char *prefix) branch = remote; } } + + if (!strcmp(branch, "HEAD")) + can_use_local_refs(&opts); + } if (!opts.orphan && !lookup_commit_reference_by_name(branch)) { diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index e5cca1d11b..09bf506155 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -729,6 +729,7 @@ test_dwim_orphan () { local use_quiet=0 && local remote=0 && local remote_ref=0 && + local use_detach=0 && local use_new_branch=0 && local outcome="$1" && @@ -754,6 +755,10 @@ test_dwim_orphan () { success=0 && outcome_text='"add" error inferred "--orphan" gives illegal opts combo' ;; + "warn_bad_head") + success=0 && + outcome_text='"add" error, warn on bad HEAD, hint use orphan' + ;; *) echo "test_dwim_orphan(): invalid outcome: '$outcome'" >&2 && return 1 @@ -825,7 +830,7 @@ test_dwim_orphan () { context="$context, invalid (or orphan) HEAD" ;; - # Whether the code path is tested with the base add command or -b + # Whether the code path is tested with the base add command, -b, or --detach "no_-b") use_new_branch=0 && context="$context, no --branch" @@ -834,6 +839,10 @@ test_dwim_orphan () { use_new_branch=1 && context="$context, --branch" ;; + "detach") + use_detach=1 && + context="$context, --detach" + ;; # Whether to check that all output is suppressed (except errors) # or that the output is as expected @@ -894,6 +903,9 @@ test_dwim_orphan () { if [ $use_new_branch -eq 1 ] then args="$args -b foo" + elif [ $use_detach -eq 1 ] + then + args="$args --detach" else context="DWIM (no --branch), $context" fi && @@ -1036,6 +1048,10 @@ do test_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref no_guess_remote test_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref guess_remote test_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote remote_ref guess_remote + + test_dwim_orphan 'warn_bad_head' $dwim_test_args no_-b local_ref bad_head + test_dwim_orphan 'warn_bad_head' $dwim_test_args -b local_ref bad_head + test_dwim_orphan 'warn_bad_head' $dwim_test_args detach local_ref bad_head done test_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode no_-b no_checkout