@@ -4,6 +4,7 @@ package integration
44
55import (
66 "context"
7+ "os"
78 "os/exec"
89 "path/filepath"
910 "strings"
@@ -170,11 +171,154 @@ func TestReftableRepository_LinkedWorktree(t *testing.T) {
170171 }
171172}
172173
174+ // TestReftableRepository_GitRefsBackend exercises the full capture flow against a
175+ // reftable repository using the shipped default checkpoint backend (git-refs),
176+ // where each checkpoint is condensed to its own ref under refs/entire/checkpoints
177+ // rather than to the entire/checkpoints/v1 branch. Every ref write and read goes
178+ // through gitrepo.reftableStorer, so this proves the reftable backend works with
179+ // the default per-checkpoint ref layout, not just the git-branch flow. It runs
180+ // WITHOUT --checkpoint-backend and without an ENTIRE_CHECKPOINTS_PRIMARY override
181+ // so it pins the actual shipped default.
182+ func TestReftableRepository_GitRefsBackend (t * testing.T ) {
183+ t .Parallel ()
184+ requireGitReftableSupport (t )
185+
186+ env := NewTestEnv (t )
187+ bootstrapReftableRepo (t , env )
188+
189+ // No --checkpoint-backend flag: exercise the shipped first-run default, which
190+ // must write the git-refs primary into settings.json.
191+ env .RunCLI ("enable" , "--no-github" , "--agent" , "claude-code" , "--telemetry=false" )
192+ if s := env .ReadFile (".entire/settings.json" ); ! strings .Contains (s , `"git-refs"` ) {
193+ t .Fatalf ("first-run enable on a reftable repo should default to the git-refs backend, settings.json:\n %s" , s )
194+ }
195+
196+ initialHead := gitOutput (t , env .RepoDir , "rev-parse" , "HEAD" )
197+
198+ sess := env .NewSession ()
199+ prompt := "Create a file in the reftable repo"
200+ if err := env .SimulateUserPromptSubmitWithPromptAndTranscriptPath (sess .ID , prompt , sess .TranscriptPath ); err != nil {
201+ t .Fatalf ("user-prompt-submit failed: %v" , err )
202+ }
203+
204+ const mainContent = "package main\n \n func main() {}\n "
205+ env .WriteFile ("main.go" , mainContent )
206+ sess .CreateTranscript (prompt , []FileChange {{Path : "main.go" , Content : mainContent }})
207+ if err := env .SimulateStop (sess .ID , sess .TranscriptPath ); err != nil {
208+ t .Fatalf ("stop hook failed creating first checkpoint: %v" , err )
209+ }
210+
211+ state , err := env .GetSessionState (sess .ID )
212+ if err != nil {
213+ t .Fatalf ("GetSessionState failed: %v" , err )
214+ }
215+ if state == nil || state .StepCount != 1 {
216+ t .Fatalf ("session StepCount after first checkpoint = %#v, want 1" , state )
217+ }
218+
219+ // The shadow branch is created and advanced via reftable ref writes.
220+ shadowBranch := env .GetShadowBranchNameForCommit (initialHead )
221+ if got := gitOutput (t , env .RepoDir , "rev-parse" , shadowBranch ); got == "" {
222+ t .Fatalf ("expected shadow branch %s to resolve" , shadowBranch )
223+ }
224+
225+ env .GitCommitWithShadowHooks ("Add reftable main" , "main.go" )
226+ if userHead := gitOutput (t , env .RepoDir , "rev-parse" , "HEAD" ); userHead == initialHead {
227+ t .Fatal ("expected user commit to advance HEAD" )
228+ }
229+
230+ // git-refs artifact: the condensed checkpoint lands on a per-checkpoint ref
231+ // under refs/entire/checkpoints (written through the reftable storer), and the
232+ // v1 branch is never created under this backend.
233+ if refs := gitOutput (t , env .RepoDir , "for-each-ref" , checkpointRefPrefix ); refs == "" {
234+ t .Fatalf ("expected a per-checkpoint ref under %s for the git-refs default" , checkpointRefPrefix )
235+ }
236+ if env .BranchExists (paths .MetadataBranchName ) {
237+ t .Fatalf ("git-refs default must not create the %s branch" , paths .MetadataBranchName )
238+ }
239+
240+ // The checkpoint's exact ref resolves through the reftable read path, and its
241+ // ID is recoverable from the code commit's Entire-Checkpoint trailer.
242+ checkpointID := env .GetLatestCheckpointIDFromHistory ()
243+ if ! refExists (t , env .RepoDir , checkpointRefName (checkpointID )) {
244+ t .Fatalf ("expected checkpoint ref %s to resolve" , checkpointRefName (checkpointID ))
245+ }
246+
247+ // checkpoint list must work against the reftable repo (read path).
248+ if listOut := env .RunCLI ("checkpoint" , "list" ); ! strings .Contains (listOut , checkpointID ) {
249+ t .Fatalf ("checkpoint list missing checkpoint %s:\n %s" , checkpointID , listOut )
250+ }
251+ }
252+
253+ // TestReftableRepository_GitRefsBackend_LinkedWorktree verifies that first-run
254+ // enable with the default git-refs backend succeeds inside a linked worktree of a
255+ // reftable repository (shared reftable stack under the common git dir) and that
256+ // the reftable read paths work from the worktree. The git-branch variant is
257+ // covered by TestReftableRepository_LinkedWorktree.
258+ func TestReftableRepository_GitRefsBackend_LinkedWorktree (t * testing.T ) {
259+ t .Parallel ()
260+ requireGitReftableSupport (t )
261+
262+ env := NewTestEnv (t )
263+ bootstrapReftableRepo (t , env )
264+
265+ worktreePath := filepath .Join (t .TempDir (), "wt" )
266+ gitOutput (t , env .RepoDir , "worktree" , "add" , "-b" , "feature/wt" , worktreePath )
267+
268+ // Default backend (git-refs): no --checkpoint-backend flag.
269+ runCLIIn (t , env , worktreePath , "enable" , "--no-github" , "--agent" , "claude-code" , "--telemetry=false" )
270+
271+ if s := readWorktreeFile (t , worktreePath , ".entire/settings.json" ); ! strings .Contains (s , `"git-refs"` ) {
272+ t .Fatalf ("enable in a reftable worktree should default to git-refs, settings.json:\n %s" , s )
273+ }
274+ if got := gitOutput (t , worktreePath , "rev-parse" , "--show-ref-format" ); got != refFormatReftable {
275+ t .Fatalf ("worktree ref format = %q, want reftable" , got )
276+ }
277+
278+ // A ref read against the worktree (HEAD resolution through the reftable storer,
279+ // whose HEAD stub go-git cannot read) must return the real branch.
280+ if branch := gitOutput (t , worktreePath , "rev-parse" , "--abbrev-ref" , "HEAD" ); branch != "feature/wt" {
281+ t .Fatalf ("worktree branch = %q, want feature/wt" , branch )
282+ }
283+
284+ // The git-refs default must not bootstrap the v1 branch.
285+ if got := gitOutput (t , worktreePath , "for-each-ref" , checkpointRefPrefix ); got != "" {
286+ t .Fatalf ("no checkpoint should exist yet, got refs:\n %s" , got )
287+ }
288+ }
289+
290+ // bootstrapReftableRepo initializes env.RepoDir as a reftable repository with an
291+ // initial commit via the git CLI. Integration tests deliberately avoid the enable
292+ // bootstrap path and drive hooks through getTestBinary(), so the repo is created
293+ // directly here.
294+ func bootstrapReftableRepo (t * testing.T , env * TestEnv ) {
295+ t .Helper ()
296+ gitOutput (t , "" , "init" , "--ref-format=reftable" , env .RepoDir )
297+ gitOutput (t , env .RepoDir , "config" , "user.name" , "Test User" )
298+ gitOutput (t , env .RepoDir , "config" , "user.email" , "test@example.com" )
299+ gitOutput (t , env .RepoDir , "config" , "commit.gpgsign" , "false" )
300+ env .WriteFile ("README.md" , "# reftable repo\n " )
301+ gitOutput (t , env .RepoDir , "add" , "README.md" )
302+ gitOutput (t , env .RepoDir , "commit" , "-m" , "Initial reftable commit" )
303+ }
304+
305+ // readWorktreeFile reads a file relative to a linked worktree root. env.ReadFile
306+ // is scoped to env.RepoDir, so worktree-local files (e.g. a per-worktree
307+ // .entire/settings.json) need a direct read.
308+ func readWorktreeFile (t * testing.T , worktreePath , rel string ) string {
309+ t .Helper ()
310+ data , err := os .ReadFile (filepath .Join (worktreePath , rel ))
311+ if err != nil {
312+ t .Fatalf ("read %s in worktree: %v" , rel , err )
313+ }
314+ return string (data )
315+ }
316+
173317// runCLIIn runs the built entire binary in an arbitrary directory (e.g. a linked
174318// worktree) with the same isolated environment RunCLI uses, detached from any
175319// controlling TTY (matching TestEnv.RunCLIWithError) so an interactive prompt
176320// path can't hang the test.
177- func runCLIIn (t * testing.T , env * TestEnv , dir string , args ... string ) string {
321+ func runCLIIn (t * testing.T , env * TestEnv , dir string , args ... string ) {
178322 t .Helper ()
179323 cmd := execx .NonInteractive (context .Background (), getTestBinary (), args ... )
180324 cmd .Dir = dir
@@ -183,7 +327,6 @@ func runCLIIn(t *testing.T, env *TestEnv, dir string, args ...string) string {
183327 if err != nil {
184328 t .Fatalf ("entire %s (in %s) failed: %v\n %s" , strings .Join (args , " " ), dir , err , out )
185329 }
186- return string (out )
187330}
188331
189332func requireGitReftableSupport (t * testing.T ) {
0 commit comments