Subject: [PATCH 08/10] packed-backend: add check for object consistency

From: shejialuo <[email-masked]>
Date: Sun, 5 Jan 2025 21:50:19 +0800

If there is nothing wrong when parsing the raw file "packed-refs", we
could then iterate the "entries" to check the object consistency. There
are two kinds of ref entry: one is the normal and another is peeled. For
both situations, we need to use "parse_object" function to parse the
object id to get the object. If the object does not exist, we will
report an error to the user.

Create a new function "packed_fsck_ref_oid" to do above then update the
unit test to exercise the code.

Mentored-by: Patrick Steinhardt <[email-masked]>
Mentored-by: Karthik Nayak <[email-masked]>
Signed-off-by: shejialuo <[email-masked]>
---
 refs/packed-backend.c    | 50 +++++++++++++++++++++++++++++++++++++++-
 t/t0602-reffiles-fsck.sh | 35 ++++++++++++++++++++++++++++
 2 files changed, 84 insertions(+), 1 deletion(-)

diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 7386e6bfce..d83ce2838f 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -13,6 +13,7 @@
 #include "../iterator.h"
 #include "../lockfile.h"
 #include "../chdir-notify.h"
+#include "../packfile.h"
 #include "../statinfo.h"
 #include "../worktree.h"
 #include "../wrapper.h"
@@ -1933,6 +1934,52 @@ static int packed_fsck_ref_main_line(struct fsck_options *o,
 	return ret;
 }
 
+static int packed_fsck_ref_oid(struct fsck_options *o, struct ref_store *ref_store,
+			       struct fsck_packed_ref_entry **entries, int nr)
+{
+	struct strbuf packed_entry = STRBUF_INIT;
+	struct fsck_ref_report report = { 0 };
+	struct object *obj;
+	int ret = 0;
+
+	for (int i = 0; i < nr; i++) {
+		struct fsck_packed_ref_entry *entry = entries[i];
+
+		strbuf_release(&packed_entry);
+		strbuf_addf(&packed_entry, "packed-refs line %d", entry->line_number);
+		report.path = packed_entry.buf;
+
+		if (is_promisor_object(ref_store->repo, &entry->oid))
+			continue;
+
+		obj = parse_object(ref_store->repo, &entry->oid);
+		if (!obj) {
+			ret |= fsck_report_ref(o, &report,
+					       FSCK_MSG_BAD_PACKED_REF_ENTRY,
+					       "'%s' is not a valid object",
+					       oid_to_hex(&entry->oid));
+		}
+		if (entry->has_peeled) {
+			strbuf_reset(&packed_entry);
+			strbuf_addf(&packed_entry, "packed-refs line %d",
+				    entry->line_number + 1);
+			report.path = packed_entry.buf;
+
+			obj = parse_object(ref_store->repo, &entry->peeled);
+			if (!obj) {
+				ret |= fsck_report_ref(o, &report,
+						       FSCK_MSG_BAD_PACKED_REF_ENTRY,
+						       "'%s' is not a valid object",
+						       oid_to_hex(&entry->peeled));
+			}
+		}
+
+	}
+
+	strbuf_release(&packed_entry);
+	return ret;
+}
+
 static int packed_fsck_ref_content(struct fsck_options *o,
 				   struct ref_store *ref_store,
 				   const char *start, const char *eof)
@@ -1986,7 +2033,8 @@ static int packed_fsck_ref_content(struct fsck_options *o,
 	 */
 	if (ret)
 		o->safe_object_check = 0;
-
+	else
+		ret |= packed_fsck_ref_oid(o, ref_store, entries, entry_nr);
 
 	free_fsck_packed_ref_entries(entries, entry_nr);
 	return ret;
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 7e8b329425..faa7c80356 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -730,4 +730,39 @@ test_expect_success 'packed-refs content should be checked' '
 	test_cmp expect err
 '
 
+test_expect_success 'packed-refs objects should be checked' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	cd repo &&
+	test_commit default &&
+	git tag -a annotated-tag-1 -m tag-1 &&
+
+	tag_1_oid=$(git rev-parse annotated-tag-1) &&
+
+	for non_existing_oid in "$(test_oid 001)" "$(test_oid 002)"
+	do
+		printf "# pack-refs with: peeled fully-peeled sorted \n"  >.git/packed-refs &&
+		printf "%s refs/heads/foo\n" "$non_existing_oid" >>.git/packed-refs &&
+		test_must_fail git refs verify 2>err &&
+		cat >expect <<-EOF &&
+		error: packed-refs line 2: badPackedRefEntry: '\''$non_existing_oid'\'' is not a valid object
+		EOF
+		rm .git/packed-refs &&
+		test_cmp expect err || return 1
+	done &&
+
+	for non_existing_oid in "$(test_oid 001)" "$(test_oid 002)"
+	do
+		printf "# pack-refs with: peeled fully-peeled sorted \n"  >.git/packed-refs &&
+		printf "%s refs/tags/foo\n" "$tag_1_oid" >>.git/packed-refs &&
+		printf "^$non_existing_oid\n" >>.git/packed-refs &&
+		test_must_fail git refs verify 2>err &&
+		cat >expect <<-EOF &&
+		error: packed-refs line 3: badPackedRefEntry: '\''$non_existing_oid'\'' is not a valid object
+		EOF
+		rm .git/packed-refs &&
+		test_cmp expect err || return 1
+	done
+'
+
 test_done
-- 
2.47.1