diff mbox series

[v2] branch: allow deleting dangling branches with --force

Message ID 325d64e9-8a31-6ba0-73f2-5e9d67b8682f@web.de (mailing list archive)
State Superseded
Headers show
Series [v2] branch: allow deleting dangling branches with --force | expand

Commit Message

René Scharfe Aug. 26, 2021, 6:19 p.m. UTC
git branch only allows deleting branches that point to valid commits.
Skip that check if --force is given, as the caller is indicating with
it that they know what they are doing and accept the consequences.
This allows deleting dangling branches, which previously had to be
reset to a valid start-point using --force first.

Reported-by: Ulrich Windl <Ulrich.Windl@rz.uni-regensburg.de>
Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: René Scharfe <l.s.r@web.de>
---
Changes since v1:
- Added Reported-by and Helped-by.
- Made test independent of ref store.

 Documentation/git-branch.txt |  3 ++-
 builtin/branch.c             |  2 +-
 t/t3200-branch.sh            | 12 ++++++++++++
 3 files changed, 15 insertions(+), 2 deletions(-)

--
2.33.0

Comments

Junio C Hamano Aug. 26, 2021, 7:05 p.m. UTC | #1
René Scharfe <l.s.r@web.de> writes:

> +	hash=$(git rev-parse HEAD) &&
> +	objpath=$(echo $hash | sed -e "s|^..|.git/objects/&/|") &&
> +	git branch --no-track dangling &&
> +	test_when_finished "test -f $objpath.x && mv $objpath.x $objpath" &&

Do we need test -f here?

> +	mv $objpath $objpath.x &&
> +	git branch --delete --force dangling &&

> +	test -z "$(git for-each-ref refs/heads/dangling)"

It is not wrong per-se, but maybe

	git show-ref --quiet refs/heads/dangling

is more straight-forward.

Thanks.
Ævar Arnfjörð Bjarmason Aug. 26, 2021, 7:12 p.m. UTC | #2
On Thu, Aug 26 2021, René Scharfe wrote:

> - Added Reported-by and Helped-by.

Thanks, this whole thing looks good to me.

> - Made test independent of ref store.

Also thanks. Just my 0.02: I think even with v1 this patch is fine to go
in (but thanks for the re-roll!). I.e. under a full run of the testsuite
with reftable a bunch of things are broken currently.

It's not really that much more effort to just fix up code like in the v1
of this patch when we get to fixing those with the reftable integration,
and putting the onus on patch authors on testing that topic in "seen"
with their tests is probably not a good time investment overall
v.s. just fixing them in bulk later.

Particularly since in this case we can make it refstore independent,
since it's about a disappearing loose object, but in some other cases
it's either the whole test that needs to be skipped, or we'd be better
off with some helpers to produce the corruption in one way under
REFFILES, and in another way under !REFFILES....
René Scharfe Aug. 26, 2021, 9:01 p.m. UTC | #3
Am 26.08.21 um 21:05 schrieb Junio C Hamano:
> René Scharfe <l.s.r@web.de> writes:
>
>> +	hash=$(git rev-parse HEAD) &&
>> +	objpath=$(echo $hash | sed -e "s|^..|.git/objects/&/|") &&
>> +	git branch --no-track dangling &&
>> +	test_when_finished "test -f $objpath.x && mv $objpath.x $objpath" &&
>
> Do we need test -f here?

If the mv in the next line fails, then test in the cleanup prevents it
from adding another confusing error.  So it's not really needed, but
kinda nice to have.

>> +	mv $objpath $objpath.x &&
>> +	git branch --delete --force dangling &&
>
>> +	test -z "$(git for-each-ref refs/heads/dangling)"
>
> It is not wrong per-se, but maybe
>
> 	git show-ref --quiet refs/heads/dangling
>
> is more straight-forward.

Actually it *is* wrong, because that check passes even if the dangling
ref still exists due to for-each-ref checking if the ref target exists
and just erroring out if it doesn't.  I somehow assumed it wouldn't do
this needless verification.  So we'd need to check its return value:

	git for-each-ref refs/heads/dangling >actual &&
	test_must_be_empty actual

git show-ref fails both if the ref is missing and if it's dangling, so
we'd need to check its stderr to distinguish between those cases:

	test_must_fail git show-ref --quiet refs/heads/dangling 2>err &&
	test_must_be_empty err

To avoid these complications we could ask git branch itself:

	test -z $(git branch --list dangling)

René
diff mbox series

Patch

diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 94dc9a54f2..5449767121 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -118,7 +118,8 @@  OPTIONS
 	Reset <branchname> to <startpoint>, even if <branchname> exists
 	already. Without `-f`, 'git branch' refuses to change an existing branch.
 	In combination with `-d` (or `--delete`), allow deleting the
-	branch irrespective of its merged status. In combination with
+	branch irrespective of its merged status, or whether it even
+	points to a valid commit. In combination with
 	`-m` (or `--move`), allow renaming the branch even if the new
 	branch name already exists, the same applies for `-c` (or `--copy`).

diff --git a/builtin/branch.c b/builtin/branch.c
index b23b1d1752..03c7b7253a 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -168,7 +168,7 @@  static int check_branch_commit(const char *branchname, const char *refname,
 			       int kinds, int force)
 {
 	struct commit *rev = lookup_commit_reference(the_repository, oid);
-	if (!rev) {
+	if (!force && !rev) {
 		error(_("Couldn't look up commit object for '%s'"), refname);
 		return -1;
 	}
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index cc4b10236e..d0d28c8ea7 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1272,6 +1272,18 @@  test_expect_success 'attempt to delete a branch merged to its base' '
 	test_must_fail git branch -d my10
 '

+test_expect_success 'branch --delete --force removes dangling branch' '
+	git checkout main &&
+	test_commit unstable &&
+	hash=$(git rev-parse HEAD) &&
+	objpath=$(echo $hash | sed -e "s|^..|.git/objects/&/|") &&
+	git branch --no-track dangling &&
+	test_when_finished "test -f $objpath.x && mv $objpath.x $objpath" &&
+	mv $objpath $objpath.x &&
+	git branch --delete --force dangling &&
+	test -z "$(git for-each-ref refs/heads/dangling)"
+'
+
 test_expect_success 'use --edit-description' '
 	write_script editor <<-\EOF &&
 		echo "New contents" >"$1"