"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
fix gotadmin cleanup failure with bad HEAD
To:
gameoftrees@openbsd.org
Date:
Fri, 13 Feb 2026 12:58:26 +0100

Download raw body.

Thread
  • Stefan Sperling:

    fix gotadmin cleanup failure with bad HEAD

gotadmin cleanup currently errors out if the HEAD reference does not
point at an existing branch. The most usual case looks like this:

  $ got ref -l
  HEAD: refs/heads/main
  refs/heads/master: de66c9099a70943844a8350b63e14b42d7cc7a7d
  $ gotadmin cleanup
  gotadmin: reference refs/heads/main not found

This can happen when people neglect to set a remote repository's HEAD
and never send a branch called 'main' to the server.

I've seen this happen on gothub.org which defaults to 'main' when
repositories are initially created. If users never send a main branch
then the HEAD will be left dangling. Users don't necesarily realize that
there is an issue because 'git clone' and 'got clone' have workarounds
for this situation. Cloning the repository will succeed anyway.

However, cleanup triggered from cronjobs will always fail, which results
in performance degrading over time as more and more pack files accumulate.

This patch makes gotadmin cleanup succeed in this case by ignoring
symbolic references which cannot be resolved.

ok?
 
M  lib/reference.c             |   4+  1-
M  lib/repository_admin.c      |   7+  1-
M  regress/cmdline/cleanup.sh  |  36+  0-

3 files changed, 47 insertions(+), 2 deletions(-)

commit - c357a25b5b44cc7a70e26d7a0a3d3177d4919751
commit + 9e76ce4eb8b9924c5f809d70e2b73a35c36ffa62
blob - cd44d800f2110bb1a8fa6f3eb5b03265ed174be3
blob + 792d13a093587be73459dbc1856937bce61a1b6c
--- lib/reference.c
+++ lib/reference.c
@@ -678,8 +678,11 @@ get_committer_time(struct got_reference *ref, struct g
 	struct got_object_id *id = NULL;
 
 	err = got_ref_resolve(&id, repo, ref);
-	if (err)
+	if (err) {
+		if (err->code == GOT_ERR_NOT_REF && got_ref_is_symbolic(ref))
+			return NULL;
 		return err;
+	}
 
 	err = got_object_get_type(&obj_type, repo, id);
 	if (err)
blob - fe0433e0d9628d4eecbf4e2a7b0e0f1327ee3cb5
blob + 431fcc73acd1fbef092a6e8ade063a761cf08f63
--- lib/repository_admin.c
+++ lib/repository_admin.c
@@ -96,8 +96,14 @@ get_reflist_object_ids(struct got_object_id ***ids, in
 		}
 
 		err = got_ref_resolve(&id, repo, re->ref);
-		if (err)
+		if (err) {
+			if (err->code == GOT_ERR_NOT_REF &&
+			    got_ref_is_symbolic(re->ref)) {
+				err = NULL;
+				continue;
+			}
 			goto done;
+		}
 
 		if (wanted_obj_type_mask != GOT_OBJ_TYPE_ANY) {
 			int obj_type;
blob - f55eceed41973eb52bc69e9fb467054747cde376
blob + ccc45f26994ec87846efcba9b463bdb3b0968fc9
--- regress/cmdline/cleanup.sh
+++ regress/cmdline/cleanup.sh
@@ -694,6 +694,41 @@ EOF
 	test_done "$testroot" "$ret"
 }
 
+test_cleanup_no_head_branch() {
+	local testroot=`test_init cleanup_no_head_branch`
+	local commit_id=`git_show_head $testroot/repo`
+
+	# create a dangling HEAD reference
+	echo 'ref: refs/heads/main' > $testroot/repo/.git/HEAD
+
+	# list references
+	got ref -r $testroot/repo -l > $testroot/stdout
+	cat > $testroot/stdout.expected <<EOF
+HEAD: refs/heads/main
+refs/heads/master: $commit_id
+EOF
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# gotadmin cleanup should not fail if HEAD is pointing at
+	# a reference which cannot be resolved.
+	gotadmin cleanup -a -q -r $testroot/repo > $testroot/stdout \
+		2> $testroot/stderr
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "gotadmin cleanup failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	test_done "$testroot" "$ret"
+}
+
 test_parseargs "$@"
 run_test test_cleanup_unreferenced_loose_objects
 run_test test_cleanup_redundant_loose_objects
@@ -701,3 +736,4 @@ run_test test_cleanup_redundant_pack_files
 run_test test_cleanup_precious_objects
 run_test test_cleanup_missing_pack_file
 run_test test_cleanup_non_commit_ref
+run_test test_cleanup_no_head_branch