commit 573d74cb9c9b09003bb8fec0ab0a48ca1e0fe1ac
parent e45efefa2b482397162bb687ca49ab909259426c
Author: Chris Bracken <chris@bracken.jp>
Date: Sat, 6 Jun 2026 14:42:51 +0900
cache: fix cache corruption on history rewrites
Only append previous cache contents in cache_finish if the cached HEAD
OID was successfully matched during log traversal. If the branch history
was rewritten (e.g. rebase or force-push), the cached OID will not be
matched, and appending the old cache contents would corrupt the log with
duplicate history.
Diffstat:
2 files changed, 32 insertions(+), 1 deletion(-)
diff --git a/src/writer/cache/cache.c b/src/writer/cache/cache.c
@@ -141,7 +141,7 @@ void cache_add_commit_row(Cache* cache, const GitCommit* commit) {
void cache_finish(Cache* cache) {
assert(cache != NULL);
- if (cache->cache_in) {
+ if (cache->cache_in && !cache->can_add_commits) {
// If we didn't write any records, copy the previous cache lastoid.
if (!cache->wrote_lastoid_out) {
fprintf(cache->cache_out, "%s\n", cache->lastoid_in);
diff --git a/src/writer/cache/cache_tests.c b/src/writer/cache/cache_tests.c
@@ -109,3 +109,34 @@ UTEST(cache, copy_log) {
EXPECT_STREQ("line1\nline2\n", out_buf);
free(out_buf);
}
+
+UTEST(cache, history_rewritten) {
+ char cache_data[] = "sha2\nrow:sha2\nrow:old_sha3\n";
+ FILE* in = fmemopen(cache_data, sizeof(cache_data), "r");
+
+ char* out_buf = NULL;
+ size_t out_size = 0;
+ FILE* out = open_memstream(&out_buf, &out_size);
+
+ Cache* cache = cache_create(in, out, mock_write_commit);
+
+ GitCommit c1 = {.oid = "sha1"};
+ GitCommit c3 = {.oid = "sha3"};
+
+ EXPECT_TRUE(cache_can_add_commits(cache));
+ cache_add_commit_row(cache, &c1);
+ EXPECT_TRUE(cache_can_add_commits(cache));
+ cache_add_commit_row(cache, &c3);
+ EXPECT_TRUE(cache_can_add_commits(cache));
+
+ cache_finish(cache);
+ cache_free(cache);
+ fclose(in);
+ fflush(out);
+ fclose(out);
+
+ const char* expected = "sha1\nrow:sha1\nrow:sha3\n";
+ EXPECT_STREQ(expected, out_buf);
+
+ free(out_buf);
+}