Skip to content
Merged
28 changes: 23 additions & 5 deletions cmd/entire/cli/strategy/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,19 +285,37 @@ func resolveAgentType(ctxAgentType agent.AgentType, state *SessionState) agent.A
return DefaultAgentType
}

// ensureMetadataBranch creates the orphan entire/checkpoints/v1 branch if it doesn't exist.
// This branch has no parent and starts with an empty tree.
// EnsureMetadataBranch creates the local entire/checkpoints/v1 branch if it doesn't exist.
// If the remote-tracking branch (origin/entire/checkpoints/v1) exists, creates the local
// branch from it to preserve existing checkpoint data. Otherwise creates an empty orphan.
func EnsureMetadataBranch(repo *git.Repository) error {
refName := plumbing.NewBranchReferenceName(paths.MetadataBranchName)

// Check if branch already exists
// Check if local branch already exists
_, err := repo.Reference(refName, true)
if err == nil {
// Branch already exists
return nil
}
if !errors.Is(err, plumbing.ErrReferenceNotFound) {
return fmt.Errorf("failed to check metadata branch: %w", err)
}

// Local branch doesn't exist — create from remote if available
remoteRefName := plumbing.NewRemoteReferenceName("origin", paths.MetadataBranchName)
remoteRef, remoteErr := repo.Reference(remoteRefName, true)
if remoteErr != nil && !errors.Is(remoteErr, plumbing.ErrReferenceNotFound) {
return fmt.Errorf("failed to check remote metadata branch: %w", remoteErr)
}
if remoteErr == nil {
ref := plumbing.NewHashReference(refName, remoteRef.Hash())
if err := repo.Storer.SetReference(ref); err != nil {
return fmt.Errorf("failed to create metadata branch from remote: %w", err)
}
fmt.Fprintf(os.Stderr, "✓ Created local branch '%s' from origin\n", paths.MetadataBranchName)
return nil
}
Comment thread
dvydra marked this conversation as resolved.

// Create empty tree (no files)
// No local or remote branch — create empty orphan
emptyTree := &object.Tree{Entries: []object.TreeEntry{}}
obj := repo.Storer.NewEncodedObject()
if err := emptyTree.Encode(obj); err != nil {
Expand Down
108 changes: 108 additions & 0 deletions cmd/entire/cli/strategy/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,114 @@ func TestIsProtectedPath(t *testing.T) {
}
}

// initBareWithMetadataBranch creates a bare repo with a main branch and an
// entire/checkpoints/v1 branch containing checkpoint data via git CLI.
func initBareWithMetadataBranch(t *testing.T) string {
t.Helper()
bareDir := t.TempDir()

// Init bare, create main branch with a commit
workDir := t.TempDir()
run := func(dir string, args ...string) {
cmd := exec.CommandContext(context.Background(), "git", args...)
cmd.Dir = dir
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git %v failed: %v\n%s", args, err, out)
}
}
run(bareDir, "init", "--bare", "-b", "main")
run(workDir, "clone", bareDir, ".")
run(workDir, "config", "user.email", "test@test.com")
run(workDir, "config", "user.name", "Test User")
if err := os.WriteFile(filepath.Join(workDir, "README.md"), []byte("# Test"), 0o644); err != nil {
t.Fatalf("failed to write file: %v", err)
}
run(workDir, "add", ".")
run(workDir, "commit", "-m", "init")
run(workDir, "push", "origin", "main")
Comment thread
dvydra marked this conversation as resolved.

// Create orphan entire/checkpoints/v1 with data
run(workDir, "checkout", "--orphan", paths.MetadataBranchName)
run(workDir, "rm", "-rf", ".")
if err := os.WriteFile(filepath.Join(workDir, "metadata.json"), []byte(`{"checkpoint_id":"test123"}`), 0o644); err != nil {
t.Fatalf("failed to write file: %v", err)
}
run(workDir, "add", ".")
run(workDir, "commit", "-m", "Checkpoint: test123")
run(workDir, "push", "origin", paths.MetadataBranchName)

return bareDir
}

func TestEnsureMetadataBranch(t *testing.T) {
t.Parallel()

t.Run("creates from remote on fresh clone", func(t *testing.T) {
bareDir := initBareWithMetadataBranch(t)
cloneDir := filepath.Join(t.TempDir(), "clone")
cmd := exec.CommandContext(context.Background(), "git", "clone", bareDir, cloneDir)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("clone failed: %v\n%s", err, out)
}

repo, err := git.PlainOpenWithOptions(cloneDir, &git.PlainOpenOptions{EnableDotGitCommonDir: true})
if err != nil {
t.Fatalf("failed to open repo: %v", err)
}

if err := EnsureMetadataBranch(repo); err != nil {
t.Fatalf("EnsureMetadataBranch() failed: %v", err)
}

// Local branch should exist with data (not empty)
ref, err := repo.Reference(plumbing.NewBranchReferenceName(paths.MetadataBranchName), true)
if err != nil {
t.Fatalf("local branch not found: %v", err)
}
commit, err := repo.CommitObject(ref.Hash())
if err != nil {
t.Fatalf("failed to get commit: %v", err)
}
tree, err := commit.Tree()
if err != nil {
t.Fatalf("failed to get tree: %v", err)
}
if len(tree.Entries) == 0 {
t.Error("local branch has empty tree — remote data was not preserved")
}
})

t.Run("creates empty orphan when no remote", func(t *testing.T) {
t.Parallel()
dir := t.TempDir()
initTestRepo(t, dir)
repo, err := git.PlainOpen(dir)
if err != nil {
t.Fatalf("failed to open repo: %v", err)
}

if err := EnsureMetadataBranch(repo); err != nil {
t.Fatalf("EnsureMetadataBranch() failed: %v", err)
}

ref, err := repo.Reference(plumbing.NewBranchReferenceName(paths.MetadataBranchName), true)
if err != nil {
t.Fatalf("branch not found: %v", err)
}
commit, err := repo.CommitObject(ref.Hash())
if err != nil {
t.Fatalf("failed to get commit: %v", err)
}
tree, err := commit.Tree()
if err != nil {
t.Fatalf("failed to get tree: %v", err)
}
if len(tree.Entries) != 0 {
t.Errorf("expected empty tree, got %d entries", len(tree.Entries))
}
})
}

func TestIsEmptyRepository(t *testing.T) {
t.Parallel()
t.Run("empty repo returns true", func(t *testing.T) {
Expand Down
Loading