checkpoint: split the temporary store out of GitStore · Entire
checkpoint: split the temporary store out of GitStore
496411d→main·
Extract the shadow-branch (temporary) surface into an independent ephemeralStore type, leaving GitStore as the committed (persistent) store. The two share only package-level helpers (CreateCommit); there is no shared receiver. This is the structural groundwork for the persistent/ephemeral rename and the generic Read/Write surfaces.
- temporary.go + shadow_ref.go methods now hang off *ephemeralStore.
- Open() constructs an ephemeralStore for Stores.temporary; GitStore serves the committed surface only.
- Add exported NewEphemeralStore(repo, refs) for benchmarks/tests that drive the shadow-branch surface directly (production reaches it via Open().Temporary()).
- benchutil gains an Ephemeral field; temporary-only tests construct the ephemeral store. No behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Sessions
Changes
9
cmd/entire/cli
benchutil
- Mbench_test.go+5/-5
Mbenchutil.go+11/-7
checkpoint
- Mcheckpoint_test.go+16/-16
- Mopen.go+1/-1
- Mshadow_ref.go+1/-1
- Mstore.go+27/-7
- Mtemporary.go+14/-15
- Mtree_surgery_equiv_test.go+3/-3
Mexplain_test.go+4/-4
62 unmodified lines
63
64
65
66
66
67
68
69
54 unmodified lines
124
125
126
127
127
128
129
130
42 unmodified lines
173
174
175
176
176
177
178
179
29 unmodified lines
209
210
211
212
212
213
214
215
40 unmodified lines
256
257
258
259
259
260
261
262
62 unmodified lines
// measure the first-checkpoint path (which runs collectChangedFiles).
// We use a unique session ID per iteration to get a fresh shadow branch.
sid := fmt.Sprintf("bench-first-%d", i)
_, writeErr := repo.Store.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
_, writeErr := repo.Ephemeral.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
SessionID: sid,
BaseCommit: repo.HeadHash,
WorktreeID: repo.WorktreeID,
54 unmodified lines
ctx := context.Background()
b.ResetTimer()
for range b.N {
_, writeErr := repo.Store.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
_, writeErr := repo.Ephemeral.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
SessionID: sessionID,
BaseCommit: repo.HeadHash,
WorktreeID: repo.WorktreeID,
42 unmodified lines
ctx := context.Background()
b.ResetTimer()
for range b.N {
_, writeErr := repo.Store.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
_, writeErr := repo.Ephemeral.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
SessionID: sessionID,
BaseCommit: repo.HeadHash,
WorktreeID: repo.WorktreeID,
29 unmodified lines
ctx := context.Background()
b.ResetTimer()
for range b.N {
result, writeErr := repo.Store.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
result, writeErr := repo.Ephemeral.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
SessionID: sessionID,
BaseCommit: repo.HeadHash,
WorktreeID: repo.WorktreeID,
40 unmodified lines
ctx := context.Background()
b.ResetTimer()
for range b.N {
_, writeErr := repo.Store.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
_, writeErr := repo.Ephemeral.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
SessionID: sessionID,
BaseCommit: repo.HeadHash,
WorktreeID: repo.WorktreeID,
Mcmd/entire/cli/benchutil/bench_test.go+5/-5
37 unmodified lines
38
39
40
41
41
42
43
44
45
46
47
48
49
122 unmodified lines
172
173
174
172
173
174
175
176
175
176
177
178
179
180
181
182
183
177 unmodified lines
361
362
363
360
364
365
366
367
37 unmodified lines
// Repo is the go-git repository handle.
Repo *git.Repository
// Store is the checkpoint GitStore for this repo.
// Store is the committed (persistent) checkpoint store for this repo.
Store *checkpoint.GitStore
// Ephemeral is the shadow-branch (temporary) checkpoint store for this repo.
Ephemeral checkpoint.TemporaryStore
// HeadHash is the current HEAD commit hash string.
HeadHash string
122 unmodified lines
}
br := &BenchRepo{
Dir: dir,
Repo: repo,
Store: checkpoint.NewGitStore(repo, checkpoint.DefaultV1Refs()),
HeadHash: headHash.String(),
Strategy: opts.Strategy,
Dir: dir,
Repo: repo,
Store: checkpoint.NewGitStore(repo, checkpoint.DefaultV1Refs()),
Ephemeral: checkpoint.NewEphemeralStore(repo, checkpoint.DefaultV1Refs()),
HeadHash: headHash.String(),
Strategy: opts.Strategy,
}
// Determine worktree ID
177 unmodified lines
b.Fatalf("write transcript: %v", err)
}
_, err := br.Store.WriteTemporary(context.Background(), checkpoint.WriteTemporaryOptions{
_, err := br.Ephemeral.WriteTemporary(context.Background(), checkpoint.WriteTemporaryTaskOptions{
SessionID: sessionID,
BaseCommit: br.HeadHash,
WorktreeID: br.WorktreeID,
Mcmd/entire/cli/benchutil/benchutil.go+11/-7
345 unmodified lines
346
347
348
349
349
350
351
352
1447 unmodified lines
1800
1801
1802
1803
1803
1804
1805
1806
123 unmodified lines
1930
1931
1932
1933
1933
1934
1935
1936
93 unmodified lines
2030
2031
2032
2033
2033
2034
2035
2036
111 unmodified lines
2148
2149
2150
2151
2151
2152
2153
2154
108 unmodified lines
2263
2264
2265
2266
2266
2267
2268
2269
93 unmodified lines
2363
2364
2365
2366
2366
2367
2368
2369
114 unmodified lines
2484
2485
2486
2487
2487
2488
2489
2490
104 unmodified lines
2595
2596
2597
2598
2598
2599
2600
2601
117 unmodified lines
2719
2720
2721
2722
2722
2723
2724
2725
108 unmodified lines
2834
2835
2836
2837
2837
2838
2839
2840
92 unmodified lines
2933
2934
2935
2936
2936
2937
2938
2939
90 unmodified lines
3030
3031
3032
3033
3033
3034
3035
3036
1100 unmodified lines
4137
4138
4139
4140
4140
4141
4142
4143
283 unmodified lines
4427
4428
4429
4430
4430
4431
4432
4433
97 unmodified lines
4531
4532
4533
4534
4534
4535
4536
4537
345 unmodified lines
}
// Create checkpoint store
store := NewGitStore(repo, DefaultV1Refs())
store := newEphemeralStore(repo, DefaultV1Refs())
// First checkpoint should be created
baseCommit := initialCommit.String()
1447 unmodified lines
// Create checkpoint store and write first checkpoint
// Note: ModifiedFiles is empty because agent hasn't touched anything yet
// The first checkpoint should still capture README.md because it's modified in working dir
store := NewGitStore(repo, DefaultV1Refs())
store := newEphemeralStore(repo, DefaultV1Refs())
baseCommit := initialCommit.String()
result, err := store.WriteTemporary(context.Background(), WriteTemporaryOptions{
123 unmodified lines
t.Fatalf("failed to write transcript: %v", err)
}
store := NewGitStore(repo, DefaultV1Refs())
store := newEphemeralStore(repo, DefaultV1Refs())
result, err := store.WriteTemporary(context.Background(), WriteTemporaryOptions{
230 unmodified lines
}
// Create checkpoint store and write first checkpoint
store := NewGitStore(repo, DefaultV1Refs())
store := newEphemeralStore(repo, DefaultV1Refs())
baseCommit := initialCommit.String()
result, err := store.WriteTemporary(context.Background(), WriteTemporaryOptions{
getDir: tempDir
}}
}
result, err := store.WriteTemporary(context.Background(), WriteTemporaryOptions{
Mcmd/entire/cli/checkpoint/checkpoint_test.go+16/-16
41 unmodified lines
42
43
44
45
45
46
47
48
41 unmodified lines
}
return &Stores{
Primary: store,
temporary: store,
temporary: newEphemeralStore(repo, refs),
refs: refs,
}, nil
}
Mcmd/entire/cli/checkpoint/open.go+1/-1
34 unmodified lines
35 36 37 38 38 39 40 41
34 unmodified lines
// repository. Callers use the worktree root as cmd.Dir for git invocations // and the common dir to locate filesystem paths (lock files, loose objects) // — both without depending on the process cwd. func (s *GitStore) repoDirs(ctx context.Context) (worktreeRoot, commonDir string, err error) { func (s *ephemeralStore) repoDirs(ctx context.Context) (worktreeRoot, commonDir string, err error) { wt, err := s.repo.Worktree() if err != nil { return "", "", fmt.Errorf("open worktree: %w", err) } }
Mcmd/entire/cli/checkpoint/shadow_ref.go+1/-1
8 unmodified lines
9
10
11
12
12
13
14
15
16
16
17
18
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
28 unmodified lines
79
80
81
59
60
61
8 unmodified lines
var (
_ CommittedStore = (*GitStore)(nil)
_ TemporaryStore = (*GitStore)(nil)
_ AuthorReader = (*GitStore)(nil)
_ Writer = (*GitStore)(nil)
_ TemporaryStore = (*ephemeralStore)(nil)
)
// GitStore provides operations for both temporary and committed checkpoint
// storage. Writes target refs.Primary; committed reads resolve against
// refs.Read.
// GitStore is the committed (persistent) checkpoint store. Writes target
// refs.Primary; committed reads resolve against refs.Read. The temporary
// shadow-branch surface lives in ephemeralStore.
type GitStore struct {
repo *git.Repository
refs CommittedRefs
blobFetcher BlobFetchFunc
}
// ephemeralStore is the git shadow-branch (temporary) checkpoint store. It is
// an independent type from GitStore; the two share only package-level helpers.
type ephemeralStore struct {
repo *git.Repository
refs CommittedRefs
}
// newEphemeralStore creates the shadow-branch store for the given repository
// and committed-metadata topology (it consults refs.Primary to recognize the
// committed branch when listing shadow branches).
func newEphemeralStore(repo *git.Repository, refs CommittedRefs) *ephemeralStore {
return &ephemeralStore{repo: repo, refs: refs}
}
// NewEphemeralStore constructs the git shadow-branch (temporary) checkpoint
// store. Most callers reach it via Open(...).Temporary(); this direct
// constructor exists for benchmarks and tests that exercise the shadow-branch
// surface without the full facade.
func NewEphemeralStore(repo *git.Repository, refs CommittedRefs) TemporaryStore { //nolint:ireturn // temporary store capability is the abstraction boundary
return newEphemeralStore(repo, refs)
}
// NewGitStore creates a checkpoint store backed by the given git repository
// and committed-metadata topology. Pass DefaultV1Refs() for the v1-only default
// or ResolveCommittedRefs(ctx) in code paths that honor settings.
28 unmodified lines
}
return nil
// Compile-time check: GitStore satisfies the unified write surface.
var _ Writer = (*GitStore)(nil)
``
Mcmd/entire/cli/checkpoint/store.go+27/-7
53 unmodified lines
54 55 56 57 57 58 59 60 122 unmodified lines
183 184 185 186 186 187 188 189 25 unmodified lines
215 216 217 218 218 219 220 221 51 unmodified lines
273 274 275 276 276 277 278 279 81 unmodified lines
361 362 363 364 364 365 366 367 121 unmodified lines
489 490 491 492 492 493 494 495 1 unmodified line
497 498 499 500 500 501 502 503 504 505 506 506 507 508 509 75 unmodified lines
585 586 587 588 588 589 590 591 45 unmodified lines
637 638 639 640 640 641 642 643 38 unmodified lines
682 683 684 685 685 686 687 688 4 unmodified lines
693 694 695 696 696 697 698 699 37 unmodified lines
737 738 739 740 740 741 742 743 26 unmodified lines
770 771 772 773 773 774 775 776 68 unmodified lines
845 846 847 848 848 849 850
53 unmodified lines
// Returns the result containing commit hash and whether it was skipped. // If the new tree hash matches the last checkpoint's tree hash, the checkpoint // is skipped to avoid duplicate commits (deduplication). func (s *GitStore) WriteTemporary(ctx context.Context, opts WriteTemporaryOptions) (WriteTemporaryResult, error) { func (s *ephemeralStore) WriteTemporary(ctx context.Context, opts WriteTemporaryOptions) (WriteTemporaryResult, error) { // Validate base commit - required for shadow branch naming if opts.BaseCommit == "" { return WriteTemporaryResult{}, errors.New("BaseCommit is required for temporary checkpoint") } 122 unmodified lines
// ReadTemporary reads the latest checkpoint from a shadow branch. // Returns nil if the shadow branch doesn't exist. // worktreeID should be empty for main worktree or the internal git worktree name for linked worktrees. func (s *GitStore) ReadTemporary(ctx context.Context, baseCommit, worktreeID string) (*ReadTemporaryResult, error) { func (s *ephemeralStore) ReadTemporary(ctx context.Context, baseCommit, worktreeID string) (*ReadTemporaryResult, error) { if err := ctx.Err(); err != nil { return nil, err //nolint:wrapcheck // Propagating context cancellation } 25 unmodified lines
// ListTemporary lists all shadow branches with their checkpoint info. func (s *GitStore) ListTemporary(ctx context.Context) ([]TemporaryInfo, error) { func (s *ephemeralStore) ListTemporary(ctx context.Context) ([]TemporaryInfo, error) { if err := ctx.Err(); err != nil { return nil, err //nolint:wrapcheck // Propagating context cancellation } 51 unmodified lines
// WriteTemporaryTask writes a task checkpoint to a shadow branch. // Task checkpoints include both code changes and task-specific metadata. // Returns the commit hash of the created checkpoint. func (s *GitStore) WriteTemporaryTask(ctx context.Context, opts WriteTemporaryTaskOptions) (plumbing.Hash, error) { func (s *ephemeralStore) WriteTemporaryTask(ctx context.Context, opts WriteTemporaryTaskOptions) (plumbing.Hash, error) { // Validate base commit - required for shadow branch naming if opts.BaseCommit == "" { return plumbing.ZeroHash, errors.New("BaseCommit is required for task checkpoint") } 81 unmodified lines