commit 93d213ec350c9e74725a35816e23de4da87ea8f7 from: Stefan Sperling date: Tue Jan 21 22:18:07 2025 UTC do not delete objects reached via nested tags during 'gotadmin cleanup' Make gotadmin cleanup resolve multiple levels of nested tags, in order to ensure that objects referenced via nested tags will not get deleted. Add test coverage for this edge case. commit - bf60f5a292208fc82ac27b5992aecaf567a00b10 commit + 93d213ec350c9e74725a35816e23de4da87ea8f7 blob - 6a69dc4a2cda758167861d0358d22d0a9dd1b468 blob + 6bb4d83c2ec663c885893601aafb2d48f211c031 --- lib/repository_admin.c +++ lib/repository_admin.c @@ -1011,33 +1011,55 @@ load_commit_or_tag(int *ncommits, struct got_object_id } if (tag) { - /* Find a tree object to scan. */ + struct got_object_id *id; + obj_type = got_object_tag_get_object_type(tag); + while (obj_type == GOT_OBJ_TYPE_TAG) { + struct got_tag_object *next_tag; + + id = got_object_tag_get_object_id(tag); + if (!got_object_idset_contains(traversed_ids, + id)) { + err = got_object_idset_add( + traversed_ids, id, NULL); + if (err) + goto done; + } + + err = got_object_open_as_tag(&next_tag, repo, + id); + if (err) + goto done; + + got_object_tag_close(tag); + tag = next_tag; + obj_type = got_object_tag_get_object_type(tag); + } + id = got_object_tag_get_object_id(tag); switch (obj_type) { case GOT_OBJ_TYPE_COMMIT: err = got_object_open_as_commit(&commit, repo, - got_object_tag_get_object_id(tag)); + id); if (err) goto done; tree_id = got_object_commit_get_tree_id(commit); break; case GOT_OBJ_TYPE_TREE: - tree_id = got_object_tag_get_object_id(tag); + tree_id = id; break; - default: - /* - * Tag points at something other than a - * commit or tree. Leave this weird tag object - * and the object it points to. - */ + case GOT_OBJ_TYPE_BLOB: if (got_object_idset_contains(traversed_ids, - got_object_tag_get_object_id(tag))) + id)) break; - err = got_object_idset_add(traversed_ids, - got_object_tag_get_object_id(tag), NULL); + err = got_object_idset_add(traversed_ids, id, + NULL); if (err) goto done; break; + default: + /* should not happen */ + err = got_error(GOT_ERR_OBJ_TYPE); + goto done; } } else if (tree_id == NULL) { /* Blob which has already been marked as traversed. */ blob - 8b953f1ee28370617e8dbb0865f36ea6604711e0 blob + f55eceed41973eb52bc69e9fb467054747cde376 --- regress/cmdline/cleanup.sh +++ regress/cmdline/cleanup.sh @@ -499,13 +499,105 @@ test_cleanup_non_commit_ref() { # create a reference which points at the tree got ref -r $testroot/repo -c "$tree_id" treeref + + inner_tag_date=$(date +%s) + + # Create a nested tag of another tree. + # Ensure that gotadmin cleanup follows chains of tags and + # all objects referenced via this chain. + + echo bar > $testroot/t/bar + bar_id=$(git -C $testroot/repo hash-object -t blob -w $testroot/t/bar) + + printf "10644 blob $foo_id\tfoo\n" > $testroot/tree-desc2 + printf "10644 blob $bar_id\tbar\n" >> $testroot/tree-desc2 + tree_id2=$(git -C $testroot/repo mktree < $testroot/tree-desc2) + + # verify that the second tree object can be read + got cat -r $testroot/repo "$tree_id2" > $testroot/stdout + printf "$bar_id 0010644 bar\n" > $testroot/stdout.expected + printf "$foo_id 0010644 foo\n" >> $testroot/stdout.expected + 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 + + cat > $testroot/inner-tag-desc < $testroot/stdout + + cat > $testroot/stdout.expected < $testroot/tag-desc < $testroot/stdout + + cat > $testroot/stdout.expected < $testroot/stdout cat > $testroot/stdout.expected < $testroot/stdout + printf "$bar_id 0010644 bar\n" > $testroot/stdout.expected + printf "$foo_id 0010644 foo\n" >> $testroot/stdout.expected + 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 + + # verify that the inner tag object can be read + got cat -r $testroot/repo "$inner_tag_id" > $testroot/stdout + + cat > $testroot/stdout.expected < $testroot/stdout + + cat > $testroot/stdout.expected <