From 1918b329a488e5796d91dcd98c947898d792c844 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 19 Jun 2026 12:56:00 +0100 Subject: [PATCH 1/2] test: harden durable snapshot history --- internal/share/share.go | 5 +- internal/share/share_test.go | 108 +++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/internal/share/share.go b/internal/share/share.go index 71ae39d..2df4ddb 100644 --- a/internal/share/share.go +++ b/internal/share/share.go @@ -883,14 +883,15 @@ func tableSnapshotFiles(table TableManifest) []string { func materializeRefFile(ctx context.Context, opts mirror.Options, ref, filePath, targetRoot string) error { clean := path.Clean(filepath.ToSlash(strings.TrimSpace(filePath))) - if clean == "." || clean == ".." || path.IsAbs(clean) || strings.HasPrefix(clean, "../") || strings.ContainsRune(clean, '\x00') { + native := filepath.FromSlash(clean) + if clean == "." || clean == ".." || path.IsAbs(clean) || filepath.IsAbs(native) || filepath.VolumeName(native) != "" || strings.HasPrefix(clean, "../") || strings.ContainsRune(clean, '\x00') { return fmt.Errorf("invalid historical share path %q", filePath) } body, _, err := mirror.ReadFileAt(ctx, opts, ref, clean) if err != nil { return err } - target := filepath.Join(targetRoot, filepath.FromSlash(clean)) + target := filepath.Join(targetRoot, native) if err := os.MkdirAll(filepath.Dir(target), 0o750); err != nil { return fmt.Errorf("create historical share directory: %w", err) } diff --git a/internal/share/share_test.go b/internal/share/share_test.go index 9508c6a..2df32ea 100644 --- a/internal/share/share_test.go +++ b/internal/share/share_test.go @@ -1809,6 +1809,114 @@ func TestImportAtRestoresTaggedSnapshotWithoutMovingCheckout(t *testing.T) { require.Equal(t, headBefore, strings.TrimSpace(testGitOutput(t, ctx, opts.RepoPath, "rev-parse", "HEAD"))) } +func TestTaggedSnapshotHistorySurvivesConcurrentRemotePush(t *testing.T) { + ctx := context.Background() + dir := t.TempDir() + src := seedStore(t, filepath.Join(dir, "src.db")) + defer func() { _ = src.Close() }() + seedDirectMessageData(t, ctx, src) + + remote := filepath.Join(dir, "remote.git") + // #nosec G204 -- fixed git argv in test setup. + require.NoError(t, exec.CommandContext(t.Context(), "git", "-C", dir, "init", "--bare", remote).Run()) + publisher := filepath.Join(dir, "publisher") + opts := Options{RepoPath: publisher, Remote: remote, Branch: "main", Tag: "snapshot-old"} + oldManifest, err := Export(ctx, src, opts) + require.NoError(t, err) + require.Equal(t, 1, tableEntry(t, oldManifest, "messages").Rows) + configureGitUser(t, publisher) + committed, err := Commit(ctx, opts, "test: old snapshot") + require.NoError(t, err) + require.True(t, committed) + _, err = CreateImmutableTag(ctx, opts) + require.NoError(t, err) + require.NoError(t, Push(ctx, opts)) + oldRemoteTag := strings.TrimSpace(testGitOutput(t, ctx, dir, "--git-dir", remote, "rev-parse", "refs/tags/snapshot-old")) + + reporter := filepath.Join(dir, "reporter") + testGitRun(t, ctx, dir, "clone", "--branch", "main", remote, reporter) + configureGitUser(t, reporter) + require.NoError(t, os.WriteFile(filepath.Join(reporter, "README.md"), []byte("concurrent field notes\n"), 0o600)) + testGitRun(t, ctx, reporter, "add", "README.md") + testGitRun(t, ctx, reporter, "commit", "-m", "docs: add concurrent field notes") + reporterHead := strings.TrimSpace(testGitOutput(t, ctx, reporter, "rev-parse", "HEAD")) + + now := time.Now().UTC().Format(time.RFC3339Nano) + require.NoError(t, src.UpsertMessages(ctx, []store.MessageMutation{{ + Record: store.MessageRecord{ + ID: "m1", + GuildID: "g1", + ChannelID: "c1", + ChannelName: "general", + AuthorID: "u1", + AuthorName: "Peter", + CreatedAt: now, + Content: "durable snapshot current", + NormalizedContent: "durable snapshot current", + RawJSON: `{}`, + }, + EventType: "upsert", + PayloadJSON: `{"id":"m1"}`, + }})) + opts.Tag = "snapshot-new" + newManifest, err := Export(ctx, src, opts) + require.NoError(t, err) + require.Equal(t, 1, tableEntry(t, newManifest, "messages").Rows) + committed, err = Commit(ctx, opts, "test: new snapshot") + require.NoError(t, err) + require.True(t, committed) + _, err = CreateImmutableTag(ctx, opts) + require.NoError(t, err) + testGitRun(t, ctx, publisher, "branch", "preserved-local-branch") + preservedBranch := strings.TrimSpace(testGitOutput(t, ctx, publisher, "rev-parse", "preserved-local-branch")) + + // Move the remote after the snapshot tag was created. Push must rebase and + // retarget only the unpublished tag before atomically publishing both refs. + testGitRun(t, ctx, reporter, "push", "origin", "main") + require.NoError(t, Push(ctx, opts)) + require.Equal(t, "main", strings.TrimSpace(testGitOutput(t, ctx, publisher, "branch", "--show-current"))) + require.Equal(t, preservedBranch, strings.TrimSpace(testGitOutput(t, ctx, publisher, "rev-parse", "preserved-local-branch"))) + require.Equal(t, oldRemoteTag, strings.TrimSpace(testGitOutput(t, ctx, dir, "--git-dir", remote, "rev-parse", "refs/tags/snapshot-old"))) + newRemoteTag := strings.TrimSpace(testGitOutput(t, ctx, dir, "--git-dir", remote, "rev-parse", "refs/tags/snapshot-new")) + require.NotEqual(t, oldRemoteTag, newRemoteTag) + require.Equal(t, newRemoteTag, strings.TrimSpace(testGitOutput(t, ctx, dir, "--git-dir", remote, "rev-parse", "refs/heads/main"))) + testGitRun(t, ctx, dir, "--git-dir", remote, "merge-base", "--is-ancestor", reporterHead, "refs/heads/main") + + opts.Tag = "snapshot-old" + _, err = CreateImmutableTag(ctx, opts) + require.Error(t, err) + require.Equal(t, oldRemoteTag, strings.TrimSpace(testGitOutput(t, ctx, dir, "--git-dir", remote, "rev-parse", "refs/tags/snapshot-old"))) + + subscriber := filepath.Join(dir, "subscriber") + subOpts := Options{RepoPath: subscriber, Remote: remote, Branch: "main"} + require.NoError(t, Pull(ctx, subOpts)) + subscriberBranch := strings.TrimSpace(testGitOutput(t, ctx, subscriber, "branch", "--show-current")) + subscriberHead := strings.TrimSpace(testGitOutput(t, ctx, subscriber, "rev-parse", "HEAD")) + dst, err := store.Open(ctx, filepath.Join(dir, "dst.db")) + require.NoError(t, err) + defer func() { _ = dst.Close() }() + seedDirectMessageData(t, ctx, dst) + + imported, err := ImportAt(ctx, dst, subOpts, "snapshot-old") + require.NoError(t, err) + require.Equal(t, 1, tableEntry(t, imported, "messages").Rows) + results, err := dst.SearchMessages(ctx, store.SearchOptions{Query: "launch checklist", Limit: 10}) + require.NoError(t, err) + require.Len(t, results, 1) + dmResults, err := dst.SearchMessages(ctx, store.SearchOptions{Query: "private dm content", Limit: 10}) + require.NoError(t, err) + require.Len(t, dmResults, 1) + + imported, err = ImportAt(ctx, dst, subOpts, "snapshot-new") + require.NoError(t, err) + require.Equal(t, 1, tableEntry(t, imported, "messages").Rows) + results, err = dst.SearchMessages(ctx, store.SearchOptions{Query: "durable snapshot current", Limit: 10}) + require.NoError(t, err) + require.Len(t, results, 1) + require.Equal(t, subscriberBranch, strings.TrimSpace(testGitOutput(t, ctx, subscriber, "branch", "--show-current"))) + require.Equal(t, subscriberHead, strings.TrimSpace(testGitOutput(t, ctx, subscriber, "rev-parse", "HEAD"))) +} + func TestPushRebasesRemoteReadmeUpdates(t *testing.T) { ctx := context.Background() src := seedStore(t, filepath.Join(t.TempDir(), "src.db")) From a850960ca12fa4d558d93ad3a7a3c45812214602 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 19 Jun 2026 13:05:06 +0100 Subject: [PATCH 2/2] fix: preserve historical snapshot fingerprints --- internal/share/share.go | 6 ++++++ internal/share/share_test.go | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/internal/share/share.go b/internal/share/share.go index 2df4ddb..08b9a0a 100644 --- a/internal/share/share.go +++ b/internal/share/share.go @@ -832,6 +832,12 @@ func ImportAt(ctx context.Context, s *store.Store, opts Options, ref string) (Ma if err != nil { return Manifest{}, err } + manifest = enrichManifestFromGit(ctx, opts.RepoPath, commit, manifest) + manifestBody, err = json.MarshalIndent(manifest, "", " ") + if err != nil { + return Manifest{}, fmt.Errorf("marshal historical manifest: %w", err) + } + manifestBody = append(manifestBody, '\n') tempDir, err := os.MkdirTemp("", "discrawl-share-ref-*") if err != nil { return Manifest{}, fmt.Errorf("create historical share directory: %w", err) diff --git a/internal/share/share_test.go b/internal/share/share_test.go index 2df32ea..39bfdef 100644 --- a/internal/share/share_test.go +++ b/internal/share/share_test.go @@ -1754,8 +1754,10 @@ func TestImportAtRestoresTaggedSnapshotWithoutMovingCheckout(t *testing.T) { EmbeddingModel: "text-embedding-3-small", EmbeddingInputVersion: store.EmbeddingInputVersion, } - _, err = Export(ctx, src, opts) + oldManifest, err := Export(ctx, src, opts) require.NoError(t, err) + writeShareManifest(t, opts.RepoPath, stripFileManifests(oldManifest)) + configureGitUser(t, opts.RepoPath) committed, err := Commit(ctx, opts, "old snapshot") require.NoError(t, err) require.True(t, committed) @@ -1796,6 +1798,10 @@ func TestImportAtRestoresTaggedSnapshotWithoutMovingCheckout(t *testing.T) { manifest, err := ImportAt(ctx, dst, restoreOpts, "snapshot-old") require.NoError(t, err) require.False(t, manifest.GeneratedAt.IsZero()) + require.NotEmpty(t, tableEntry(t, manifest, "messages").FileManifests) + storedManifest, ok := PreviousImportedManifest(ctx, dst, opts) + require.True(t, ok) + require.NotEmpty(t, tableEntry(t, storedManifest, "messages").FileManifests) results, err := dst.SearchMessages(ctx, store.SearchOptions{Query: "launch", Limit: 10}) require.NoError(t, err) require.Len(t, results, 1)