gout

A static git page generator
git clone https://git.bracken.jp/gout.git
Log | Files | Refs | README | LICENSE

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:
Msrc/writer/cache/cache.c | 2+-
Msrc/writer/cache/cache_tests.c | 31+++++++++++++++++++++++++++++++
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); +}