git_operations: stop shallow-fetching the metadata tip · Entire

git_operations: stop shallow-fetching the metadata tip

ebe73db→main·

Soph·1mo ago·2 files·+117 added/-7 removed

FetchMetadataTreeOnly resolves the latest checkpoint on resume / explain / attach. It fetched with --depth=1, which adds the fetched tip to .git/shallow. Once the entire/checkpoints/v1 tip is a shallow boundary, a later git merge-base against refs/remotes/origin/entire/checkpoints/v1 can no longer reach the real common ancestor (it lives below the boundary), so the disconnection check falsely reports "no common ancestor" — aborting git push and looping entire doctor. This is the upstream source of the shallow-metadata false-disconnect: it is self-inflicted by the CLI's own tip-read on essentially every resume when checkpoints live on origin (no checkpoint_remote short-circuit).

The shallow boundary is intrinsic to --depth=1 — the fetched commit is truncated regardless of which ref it lands on, and git opportunistically points refs/remotes/origin/ at it anyway. So the fix is to not shallow at all: fetch the metadata commit+tree graph at full depth and rely on --filter=blob:none (when filtered fetches are enabled) to skip blob content. git fetches incrementally, so after the first fetch only new commits/trees travel. The remote-tracking ref stays connected and merge-base works.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Sessions

b85c03197b07View transcript

[?
so in ../entiredb "filtered_fetches" is enabled, now today one of my coworkers had this issue:Claude Code·1 step](/content/gh/entireio/cli/session/e1c2cd64-bc7d-4315-9abd-f106c4eaa991#timeline-b85c03197b07/index.html)

Changes

2

412 unmodified lines

413
414
415
416
417
418
419
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
421
431
432
433
434
435
426
436
437
438
19 unmodified lines

458
459
460
452
461
462
463

412 unmodified lines

return fetchMetadataFromOrigin(ctx, fetchMetadataOpts{NoFilter: true})

// FetchMetadataTreeOnly fetches just the tip of the entire/checkpoints/v1
// branch (--depth=1). Used by resume/explain to resolve the latest checkpoint
// cheaply without pulling the entire history. May leave .git/shallow set;
// FetchMetadataBranch will undo that when full ancestry is later needed.
// FetchMetadataTreeOnly fetches the entire/checkpoints/v1 commit+tree graph
// from origin to resolve the latest checkpoint, relying on --filter=blob:none
// (when filtered fetches are enabled) to skip blob content rather than on a
// shallow --depth=1 fetch.
//
// It deliberately does NOT use --depth=1. A depth-1 fetch adds the fetched tip
// to .git/shallow, and any ref pointing at a shallow commit (the durable
// refs/remotes/origin/<branch> that git updates opportunistically, or the local
// primary) can no longer be walked past that boundary. A later `git merge-base`
// against it then falsely reports "no common ancestor", which makes push and
// `entire doctor` treat an ordinary diverged-but-behind branch as disconnected
// (see strategy.IsMetadataDisconnected). Fetching at full depth keeps the
// remote-tracking ref connected; git fetches incrementally, so after the first
// fetch only new commits/trees travel.
func FetchMetadataTreeOnly(ctx context.Context) error {
    return fetchMetadataFromOrigin(ctx, fetchMetadataOpts{Shallow: true})
    return fetchMetadataFromOrigin(ctx, fetchMetadataOpts{})
}

type fetchMetadataOpts struct {
    NoFilter  bool
    Shallow   bool
    Unshallow bool
}

19 unmodified lines

RefSpecs:  []string{refSpec},
    NoTags:    true,
    NoFilter:  fopts.NoFilter,
    Shallow:   fopts.Shallow,
    Unshallow: fopts.Unshallow,
})
    if fetchErr != nil {

Mcmd/entire/cli/git_operations.go+15/-7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

package cli

import (
    "context"
    "os/exec"
    "path/filepath"
    "strings"
    "testing"

"github.com/entireio/cli/cmd/entire/cli/paths"
    "github.com/entireio/cli/cmd/entire/cli/testutil"
)

// TestFetchMetadataTreeOnly_DoesNotShallowRepo is a regression test for the
// shallow-metadata false-disconnect.
//
// FetchMetadataTreeOnly resolves the latest checkpoint on resume/explain/attach.
// It used to fetch with --depth=1, which adds the fetched tip to .git/shallow.
// Once the metadata tip is a shallow boundary, a later `git merge-base` against
// refs/remotes/origin/entire/checkpoints/v1 can't reach the real common
// ancestor (it's below the boundary) and the disconnection check falsely
// reports "no common ancestor" — aborting push and looping doctor.
//
// The fix drops --depth=1 and relies on blob filtering for cheapness, so the
// fetch never creates a shallow boundary.
func TestFetchMetadataTreeOnly_DoesNotShallowRepo(t *testing.T) {
    // Uses t.Chdir() — cannot run in parallel.

tmpDir := t.TempDir()
    bareDir := filepath.Join(tmpDir, "bare.git")
    localDir := filepath.Join(tmpDir, "local")

runGit(t, tmpDir, "init", "--bare", bareDir)

// Seed: a main commit plus an orphan metadata branch with two checkpoint
    // commits, so a --depth=1 fetch would visibly truncate to one.
    testutil.InitRepo(t, localDir)
    testutil.WriteFile(t, localDir, "README.md", "hello")
    testutil.GitAdd(t, localDir, "README.md")
    testutil.GitCommit(t, localDir, "init")
    runGit(t, localDir, "branch", "-M", "main")
    runGit(t, localDir, "remote", "add", "origin", bareDir)
    runGit(t, localDir, "checkout", "--orphan", paths.MetadataBranchName)
    runGit(t, localDir, "rm", "-rf", ".")
    testutil.WriteFile(t, localDir, "a/metadata.json", `{\"checkpoint_id\":\"deadbeef0001\"}`)
    testutil.GitAdd(t, localDir, "a/metadata.json")
    testutil.GitCommit(t, localDir, "Checkpoint: deadbeef0001")
    testutil.WriteFile(t, localDir, "b/metadata.json", `{\"checkpoint_id\":\"deadbeef0002\"}`)
    testutil.GitAdd(t, localDir, "b/metadata.json")
    testutil.GitCommit(t, localDir, "Checkpoint: deadbeef0002")
    runGit(t, localDir, "checkout", "main")
    runGit(t, localDir, "push", "origin", "HEAD:refs/heads/main", paths.MetadataBranchName)
    runGit(t, bareDir, "symbolic-ref", "HEAD", "refs/heads/main")

originTip := gitRevParse(t, bareDir, "refs/heads/"+paths.MetadataBranchName)

clonedDir := filepath.Join(tmpDir, "cloned")
    runGit(t, tmpDir, "clone", bareDir, clonedDir)
    runGit(t, clonedDir, "config", "user.email", "test@example.com")
    runGit(t, clonedDir, "config", "user.name", "Test")

t.Chdir(clonedDir)

if err := FetchMetadataTreeOnly(t.Context()); err != nil {
         t.Fatalf("FetchMetadataTreeOnly: %v", err)
    }

// The fix: the tip-read must not leave the repo shallow. Under the old
    // --depth=1 behavior this would be "true".
    if shallow := gitOut(t, clonedDir, "rev-parse", "--is-shallow-repository"); shallow != "false" {
         t.Errorf("repo is shallow after tree-only fetch (--is-shallow-repository=%q); the tip-read must not create a shallow boundary", shallow)
    }

// The full metadata history is present (two commits), not truncated to one.
    originRef := "refs/remotes/origin/" + paths.MetadataBranchName
    if n := gitOut(t, clonedDir, "rev-list", "--count", originRef); n != "2" {
         t.Errorf("origin metadata history has %s commit(s), want 2 (full depth)", n)
    }

// The local primary ref is advanced to the tip so reads work.
    localRef := "refs/heads/" + paths.MetadataBranchName
    if got := gitRevParse(t, clonedDir, localRef); got != originTip {
         t.Errorf("local primary ref %s = %q, want origin tip %q", localRef, got, originTip)
    }
}

func gitRevParse(t *testing.T, dir, rev string) string {
     t.Helper()
    return gitOut(t, dir, "rev-parse", rev)
}

func gitOut(t *testing.T, dir string, args ...string) string {
     t.Helper()
    cmd := exec.CommandContext(context.Background(), "git", args...)
    cmd.Dir = dir
    cmd.Env = testutil.GitIsolatedEnv()
    out, err := cmd.Output()
    if err != nil {
         t.Fatalf("git %s in %s: %v", strings.Join(args, " "), dir, err)
    }
    return strings.TrimSpace(string(out))
}