All of lore.kernel.org
 help / color / mirror / Atom feed
From: Phillip Wood <phillip.wood123@gmail.com>
To: Derrick Stolee via GitGitGadget <gitgitgadget@gmail.com>,
	git@vger.kernel.org
Cc: gitster@pobox.com, johannes.schindelin@gmx.de, me@ttaylorr.com,
	"Jeff Hostetler" <git@jeffhostetler.com>,
	"Elijah Newren" <newren@gmail.com>,
	"Derrick Stolee" <derrickstolee@github.com>,
	"Ævar Arnfjörð Bjarmason" <avarab@gmail.com>
Subject: Re: [PATCH 1/4] branch: add branch_checked_out() helper
Date: Tue, 14 Jun 2022 11:09:15 +0100	[thread overview]
Message-ID: <42ada1a3-adc2-2c43-9aa1-7854858865ae@gmail.com> (raw)
In-Reply-To: <dbb7eae390c90d4b710f48d8875bd7db0409aea3.1654718942.git.gitgitgadget@gmail.com>

Hi Stolee

On 08/06/2022 21:08, Derrick Stolee via GitGitGadget wrote:
> From: Derrick Stolee <derrickstolee@github.com>
> 
> 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 <derrickstolee@github.com>
> ---
>   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;

I looks like this map is never freed which I think makes sense but makes 
me wonder about the relevance of patch 5. I think it would probably be 
worth marking the map with UNLEAK() in prepare_checked_out_branches().

> +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(&current_checked_out_branches,
> +				   wt->head_ref,
> +				   xstrdup(wt->path));

STRMAP_INIT sets .strdup_strings = 1, so the xstrdup() is unnecessary.

> +	}
> +
> +	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(&current_checked_out_branches, refname);
> +	if (path_in_set && path)
> +		*path = xstrdup(path_in_set);
> +
> +	return !!path_in_set;
> +}

I like the idea of having a specific function to see if a branch is 
checkout out rather than Ævar's suggestion of forcing all callers to do
     strmap_get(get_worktree_refs_strmap(), ref)
which will quickly get tiresome. I do wonder though if we'd be better 
off with a thin wrapper around strmap_get() such as

const char* branch_checked_out(const char *refname)
{
	prepare_checked_out_branches();
	return strmap_get(&current_checked_out_branches, refname);
}

so that the user can choose whether to copy the path or not.

Best Wishes

Phillip

>   /*
>    * 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


  parent reply	other threads:[~2022-06-14 10:10 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-06-08 20:08 [PATCH 0/4] Create branch_checked_out() helper Derrick Stolee via GitGitGadget
2022-06-08 20:08 ` [PATCH 1/4] branch: add " Derrick Stolee via GitGitGadget
2022-06-09 23:47   ` Junio C Hamano
2022-06-13 23:59   ` Ævar Arnfjörð Bjarmason
2022-06-14 13:32     ` Derrick Stolee
2022-06-14 15:24       ` Ævar Arnfjörð Bjarmason
2022-06-14 10:09   ` Phillip Wood [this message]
2022-06-14 11:22     ` Phillip Wood
2022-06-14 13:48     ` Derrick Stolee
2022-06-08 20:09 ` [PATCH 2/4] branch: check for bisects and rebases Derrick Stolee via GitGitGadget
2022-06-08 22:03   ` Junio C Hamano
2022-06-08 20:09 ` [PATCH 3/4] fetch: use new branch_checked_out() and add tests Derrick Stolee via GitGitGadget
2022-06-14  0:05   ` Ævar Arnfjörð Bjarmason
2022-06-14 10:10   ` Phillip Wood
2022-06-14 14:06     ` Derrick Stolee
2022-06-08 20:09 ` [PATCH 4/4] branch: use branch_checked_out() when deleting refs Derrick Stolee via GitGitGadget
2022-06-13 14:59 ` [PATCH 5/5] branch: fix branch_checked_out() leaks Derrick Stolee
2022-06-13 23:03   ` Junio C Hamano
2022-06-14  0:33   ` Ævar Arnfjörð Bjarmason
2022-06-14 15:37     ` Derrick Stolee
2022-06-14 16:43       ` Ævar Arnfjörð Bjarmason
2022-06-14 19:27 ` [PATCH v2 0/5] Create branch_checked_out() helper Derrick Stolee via GitGitGadget
2022-06-14 19:27   ` [PATCH v2 1/5] branch: add " Derrick Stolee via GitGitGadget
2022-06-14 19:27   ` [PATCH v2 2/5] branch: check for bisects and rebases Derrick Stolee via GitGitGadget
2022-06-14 19:27   ` [PATCH v2 3/5] fetch: use new branch_checked_out() and add tests Derrick Stolee via GitGitGadget
2022-06-14 19:27   ` [PATCH v2 4/5] branch: use branch_checked_out() when deleting refs Derrick Stolee via GitGitGadget
2022-06-14 19:27   ` [PATCH v2 5/5] branch: fix branch_checked_out() leaks Derrick Stolee via GitGitGadget
2022-06-19 13:58   ` [PATCH v2 0/5] Create branch_checked_out() helper Phillip Wood

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=42ada1a3-adc2-2c43-9aa1-7854858865ae@gmail.com \
    --to=phillip.wood123@gmail.com \
    --cc=avarab@gmail.com \
    --cc=derrickstolee@github.com \
    --cc=git@jeffhostetler.com \
    --cc=git@vger.kernel.org \
    --cc=gitgitgadget@gmail.com \
    --cc=gitster@pobox.com \
    --cc=johannes.schindelin@gmx.de \
    --cc=me@ttaylorr.com \
    --cc=newren@gmail.com \
    --cc=phillip.wood@dunelm.org.uk \
    /path/to/YOUR_REPLY

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

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