Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions cmd/entire/cli/integration_test/http_remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,3 +446,138 @@ func TestHTTPS_PushFailsWithoutToken(t *testing.T) {
}
assertRemoteHasCheckpointCommit(t, bareDir, checkpointID)
}

// TestHTTPS_EnableBootstrapsMetadataFromCheckpointRemote reproduces
// https://github.com/entireio/cli/issues/1374: when a second device clones a
// repo whose checkpoint data lives on a configured checkpoint_remote (a
// separate repo from origin), `entire enable` must populate the local
// entire/checkpoints/v1 branch from that remote instead of minting an
// unrelated empty orphan. The orphan made `entire checkpoint list` return
// nothing and caused non-fast-forward rejections on later fetches.
func TestHTTPS_EnableBootstrapsMetadataFromCheckpointRemote(t *testing.T) {
t.Parallel()

srv := startGitHTTPSServer(t, "testorg/main-repo", "testorg/checkpoints")
env := NewFeatureBranchEnv(t)

mainBare := srv.BareDirs["testorg/main-repo"]
checkpointBare := srv.BareDirs["testorg/checkpoints"]
httpsURL := srv.URL + "/testorg/main-repo.git"
seedBareRepo(t, env, mainBare, httpsURL)

checkpointRemoteSettings := map[string]any{
"strategy_options": map[string]any{
"checkpoint_remote": map[string]any{
"provider": "github",
"repo": "testorg/checkpoints",
},
},
}

// Device A: create a checkpoint and push — metadata routes to the
// checkpoint remote, never to origin.
cloneA := cloneFromBareWithHTTPS(t, env, mainBare, httpsURL)
cloneA.ExtraEnv = srv.tokenEnv("clone-a-token")
cloneA.GitCheckoutNewBranch("feature/clone-a")
cloneA.PatchSettings(checkpointRemoteSettings)
checkpointA := createCheckpointedCommit(t, cloneA, "Work in clone A", "a.go", "package a", "Work from A")
cloneA.RunPrePush("origin")

if !cloneA.BranchExistsOnRemote(checkpointBare, paths.MetadataBranchName) {
t.Fatal("precondition: checkpoint branch should be on checkpoint remote after push")
}
if cloneA.BranchExistsOnRemote(mainBare, paths.MetadataBranchName) {
t.Fatal("precondition: checkpoint branch should NOT be on origin")
}

// Device B: fresh clone of origin (which has no metadata branch) with the
// same checkpoint_remote configured. No token — fetch (upload-pack) is
// unauthenticated; only push requires one.
cloneB := cloneFromBareWithHTTPS(t, env, mainBare, httpsURL)
cloneB.ExtraEnv = srv.sslEnv()
cloneB.PatchSettings(checkpointRemoteSettings)

cloneB.RunCLI("enable", "--agent", "claude-code", "--telemetry=false")

// The local metadata branch must match the checkpoint remote tip — not be
// a fresh empty orphan with unrelated history.
if !cloneB.BranchExists(paths.MetadataBranchName) {
t.Fatal("local metadata branch should exist after enable")
}
remoteTip := revParse(t, checkpointBare, "refs/heads/"+paths.MetadataBranchName)
localTip := revParse(t, cloneB.RepoDir, "refs/heads/"+paths.MetadataBranchName)
if localTip != remoteTip {
t.Errorf("local metadata branch tip = %s, want checkpoint remote tip %s (empty-orphan bug: enable did not fetch from checkpoint_remote)", localTip, remoteTip)
}

// The fetched branch must contain device A's checkpoint metadata.
summaryA := CheckpointSummaryPath(checkpointA)
if _, found := cloneB.ReadFileFromBranch(paths.MetadataBranchName, summaryA); !found {
t.Errorf("local metadata branch should contain checkpoint A summary at %s", summaryA)
}
}

// TestHTTPS_ReEnableRecoversFromEmptyMetadataOrphan covers the recovery path
// after a bootstrap that found nothing on the checkpoint remote: device B
// enables before any checkpoint exists (the bootstrap fetch legitimately
// finds no branch, so EnsureSetup mints the empty orphan), device A then
// pushes checkpoint data, and a second `entire enable` on device B must
// replace the empty orphan with the fetched branch — not skip the fetch
// because a local branch already exists.
func TestHTTPS_ReEnableRecoversFromEmptyMetadataOrphan(t *testing.T) {
t.Parallel()

srv := startGitHTTPSServer(t, "testorg/main-repo", "testorg/checkpoints")
env := NewFeatureBranchEnv(t)

mainBare := srv.BareDirs["testorg/main-repo"]
checkpointBare := srv.BareDirs["testorg/checkpoints"]
httpsURL := srv.URL + "/testorg/main-repo.git"
seedBareRepo(t, env, mainBare, httpsURL)

checkpointRemoteSettings := map[string]any{
"strategy_options": map[string]any{
"checkpoint_remote": map[string]any{
"provider": "github",
"repo": "testorg/checkpoints",
},
},
}

// Device B: enable while the checkpoint remote has no metadata branch yet.
// The bootstrap fetch finds nothing and the empty orphan is minted — the
// expected fallback for a brand-new checkpoint remote.
cloneB := cloneFromBareWithHTTPS(t, env, mainBare, httpsURL)
cloneB.ExtraEnv = srv.sslEnv()
cloneB.PatchSettings(checkpointRemoteSettings)
cloneB.RunCLI("enable", "--agent", "claude-code", "--telemetry=false")
if !cloneB.BranchExists(paths.MetadataBranchName) {
t.Fatal("precondition: enable should mint a local metadata branch")
}

// Device A: create a checkpoint and push it to the checkpoint remote.
cloneA := cloneFromBareWithHTTPS(t, env, mainBare, httpsURL)
cloneA.ExtraEnv = srv.tokenEnv("clone-a-token")
cloneA.GitCheckoutNewBranch("feature/clone-a")
cloneA.PatchSettings(checkpointRemoteSettings)
checkpointA := createCheckpointedCommit(t, cloneA, "Work in clone A", "a.go", "package a", "Work from A")
cloneA.RunPrePush("origin")
if !cloneA.BranchExistsOnRemote(checkpointBare, paths.MetadataBranchName) {
t.Fatal("precondition: checkpoint branch should be on checkpoint remote after push")
}

// Device B: re-running enable must recover — the empty orphan must not
// permanently block the checkpoint-remote bootstrap.
cloneB.RunCLI("enable", "--agent", "claude-code", "--telemetry=false")

remoteTip := revParse(t, checkpointBare, "refs/heads/"+paths.MetadataBranchName)
localTip := revParse(t, cloneB.RepoDir, "refs/heads/"+paths.MetadataBranchName)
if localTip != remoteTip {
t.Errorf("local metadata branch tip = %s, want checkpoint remote tip %s (empty orphan blocked re-bootstrap)", localTip, remoteTip)
}

summaryA := CheckpointSummaryPath(checkpointA)
if _, found := cloneB.ReadFileFromBranch(paths.MetadataBranchName, summaryA); !found {
t.Errorf("local metadata branch should contain checkpoint A summary at %s", summaryA)
}
}
96 changes: 76 additions & 20 deletions cmd/entire/cli/strategy/checkpoint_remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"context"
"fmt"
"log/slog"
"os"
"strings"
"time"

"github.com/entireio/cli/cmd/entire/cli/checkpoint"
"github.com/entireio/cli/cmd/entire/cli/checkpoint/remote"
"github.com/entireio/cli/cmd/entire/cli/logging"
"github.com/entireio/cli/cmd/entire/cli/paths"
"github.com/entireio/cli/cmd/entire/cli/settings"

"github.com/go-git/go-git/v6/plumbing"
Expand Down Expand Up @@ -82,11 +84,12 @@ func resolvePushSettings(ctx context.Context, pushRemoteName string) pushSetting

ps.checkpointURL = checkpointURL

// If the v1 checkpoint branch doesn't exist locally, try to fetch it from the URL.
// This is a one-time operation — once the branch exists locally, subsequent pushes
// skip the fetch entirely. Only fetch the metadata branch; trails are always pushed
// to the user's push remote, not the checkpoint remote.
if err := fetchMetadataBranchIfMissing(ctx, checkpointURL); err != nil {
// If the v1 checkpoint branch doesn't exist locally (or is only the empty
// bootstrap orphan), try to fetch it from the URL. Once the branch has
// checkpoint data, subsequent pushes skip the fetch entirely. Only fetch
// the metadata branch; trails are always pushed to the user's push
// remote, not the checkpoint remote.
if _, err := fetchMetadataBranchIfMissing(ctx, checkpointURL); err != nil {
logging.Warn(ctx, "checkpoint-remote: failed to fetch metadata branch",
slog.String("error", err.Error()),
)
Expand Down Expand Up @@ -162,29 +165,82 @@ func fetchURLIntoTmpRef(ctx context.Context, remoteURL, srcRef, tmpRef, label st
return fmt.Errorf("fetch %s from %s failed: %w", label, redactedURL, fetchErr)
}

// fetchMetadataBranchIfMissing fetches the primary metadata ref from a URL only if it doesn't exist locally.
// This avoids network calls on every push — once the branch exists locally, this is a no-op.
// Fetch failures are silently swallowed (returns nil): the push will handle creating the
// branch on the remote. Only fatal errors (opening repo, creating local branch) are returned.
func fetchMetadataBranchIfMissing(ctx context.Context, remoteURL string) error {
// bootstrapMetadataFromCheckpointRemote populates the local metadata branch
// from the configured checkpoint_remote when it doesn't exist locally yet,
// or exists only as the empty orphan minted by a previous failed bootstrap.
//
// On a fresh clone of a repo whose checkpoints live in a separate repo,
// neither the local branch nor origin's remote-tracking ref exists — without
// this fetch, EnsureMetadataBranch would mint an unrelated empty orphan that
// hides existing checkpoints and rejects later fetches as non-fast-forward
// (issue #1374). Best-effort: on failure the caller falls back to orphan
// creation (the remote branch legitimately doesn't exist before the first
// push from any device).
func bootstrapMetadataFromCheckpointRemote(ctx context.Context) {
if !remote.Configured(ctx) {
return
}
checkpointURL, err := remote.FetchURL(ctx)
if err != nil {
logging.Warn(ctx, "checkpoint-remote: could not resolve fetch URL for metadata branch bootstrap",
slog.String("error", err.Error()),
)
return
}
fetched, err := fetchMetadataBranchIfMissing(ctx, checkpointURL)
if err != nil {
logging.Warn(ctx, "checkpoint-remote: metadata branch bootstrap failed",
slog.String("error", err.Error()),
)
return
}
if fetched {
fmt.Fprintf(os.Stderr, "✓ Created local branch '%s' from checkpoint remote\n", paths.MetadataBranchName)
}
}

// fetchMetadataBranchIfMissing fetches the primary metadata ref from a URL
// only if it doesn't exist locally with real data. This avoids network calls
// on every push — once the branch has checkpoint data, this is a no-op.
//
// An empty bootstrap orphan does not count as existing: it means a previous
// bootstrap couldn't reach the checkpoint remote (no token, network down,
// branch not pushed yet) and EnsureSetup fell back to orphan creation —
// fetching again is the recovery path, and SafelyAdvanceLocalRef discards
// the no-op orphan commit during the promote.
//
// Returns true when the branch was actually fetched. Fetch failures are
// logged but swallowed (returns nil error): the remote branch legitimately
// doesn't exist before the first push from any device, and push will create
// it. Only failures to open the repository are returned.
func fetchMetadataBranchIfMissing(ctx context.Context, remoteURL string) (bool, error) {
repo, err := OpenRepository(ctx)
if err != nil {
return fmt.Errorf("failed to open repository: %w", err)
return false, fmt.Errorf("failed to open repository: %w", err)
}
defer repo.Close()

// Check if branch already exists locally - if so, nothing to do
// Skip the network call when the branch already has checkpoint data.
refs := checkpoint.ResolveCommittedRefs(ctx)
if _, err := repo.Reference(refs.Primary, true); err == nil {
return nil // Branch exists locally, skip fetch
}

// Branch doesn't exist locally - try to fetch it from the URL.
// Fetch failures are not fatal: push will create it on the remote when it succeeds.
if ref, refErr := repo.Reference(refs.Primary, true); refErr == nil {
empty, emptyErr := isEmptyMetadataBranch(repo, ref)
if emptyErr != nil || !empty {
return false, nil // Branch has data (or is unreadable) — skip fetch
}
// Empty bootstrap orphan — fall through and try the fetch again.
}

// Branch is missing (or an empty orphan) — try to fetch it from the URL.
// Not fatal on failure, but log it: a silent swallow here made auth and
// network problems invisible while EnsureSetup fell back to minting an
// empty orphan.
if err := FetchMetadataBranch(ctx, remoteURL); err != nil {
return nil
logging.Warn(ctx, "checkpoint-remote: metadata branch fetch failed, continuing without it",
slog.String("error", err.Error()),
)
return false, nil
}

logging.Info(ctx, "checkpoint-remote: fetched metadata branch from URL")
return nil
return true, nil
}
Loading
Loading