fix(checkpoint): stamp phantom remotes left by failed filtered fetches · Entire

fix(checkpoint): stamp phantom remotes left by failed filtered fetches

4ccc0dc→main·

pjbgf·3d ago·2 files·+73 added/-10 removed

Git writes remote..promisor eagerly during connection setup, so a filtered fetch that later fails (e.g. a missing ref during resume) still leaves the URL-keyed phantom remote behind. The previous logic returned early on fetch error before stamping, and because it only stamped sections that did not exist beforehand, the entry then looked pre-existing on every later attempt and lingered unstamped forever.

Check section existence before and after the fetch and stamp when it newly appeared, regardless of fetch success. The post-fetch recheck also avoids inventing a section when the fetch died before git wrote anything.

Assisted-by: Claude Opus 4.8 noreply@anthropic.com Signed-off-by: Paulo Gomes paulo@entire.io

Sessions

01KXGBRSF02QB8KFPPVGDEEWCYView transcript

Changes

2

88 unmodified lines

89
90
91
92
93
94
92
93
94
95
96
96
97
98
99
100
101
102
103
104
104
105
106
107
108
2 unmodified lines

111
112
113
113
114
115
116
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
1 unmodified line

133
134
135
126
136
137
138
139
140

88 unmodified lines

// (remote.<url>.*) so it can lazy-fetch filtered-out objects later. That
    // section also turns the URL into a phantom remote that `git fetch --all`
    // and `git remote update` keep dialing. When this fetch is the one creating
    // the section (it did not exist beforehand), stamp skipFetchAll/
    // skipDefaultUpdate so bulk fetches skip our adhoc remote. Remotes that
    // already existed are left untouched so we never rewrite the user's config.
    // the section, stamp skipFetchAll/skipDefaultUpdate so bulk fetches skip our
    // adhoc remote. Remotes that already existed are left untouched so we never
    // rewrite the user's config.
    var stampURL string
    var stampNewRemote bool
    var stampCandidate, existedBefore bool
    if filtered && IsURL(opts.Remote) {
        stampCandidate = true
        stampURL = opts.Remote
        if token := strings.TrimSpace(os.Getenv(CheckpointTokenEnvVar)); token != "" && isValidToken(token) {
            // With a checkpoint token, newCommand rewrites SSH targets to HTTPS
            // and git records the section under the rewritten URL.
            stampURL, _ = resolveTargetForTokenAuth(ctx, stampURL)
            }
        stampNewRemote = !gitRemoteSectionExists(ctx, opts.Dir, stampURL)
        existedBefore = gitRemoteSectionExists(ctx, opts.Dir, stampURL)
    }

cmd := newCommand(ctx, args...)
2 unmodified lines

}
    disableTerminalPrompt(cmd)
    out, err := cmd.CombinedOutput()
    if err != nil {
        return out, fmt.Errorf("git fetch: %w", err)
    }
    if stampNewRemote {

// Stamp whenever this fetch newly created the section — even on a fetch
    // error. Git writes remote.<url>.promisor eagerly during connection setup,
    // so a failed filtered fetch still leaves the phantom remote behind; if we
    // only stamped on success it would linger unstamped forever (the section
    // then exists on the next attempt, so it never looks "new" again). Checking
    // existence after the fetch keeps us from inventing a section when the fetch
    // died before git wrote anything.
    if stampCandidate && !existedBefore && gitRemoteSectionExists(ctx, opts.Dir, stampURL) {
        markRemoteSkipped(ctx, opts.Dir, stampURL)
    }

if err != nil {
        return out, fmt.Errorf("git fetch: %w", err)
    }
    return out, nil
}

1 unmodified line

// section so `git fetch --all` and `git remote update` skip it. Called only for
// remotes this fetch just created, so an adhoc checkpoint URL never lingers as a
// phantom remote that bulk fetches keep dialing.
// Best-effort: the fetch already succeeded, so failures only log.
// Best-effort: the git config write is not worth failing the fetch over, so
// failures only log.
func markRemoteSkipped(ctx context.Context, dir, url string) {
    for _, key := range []string{"skipFetchAll", "skipDefaultUpdate"} {
        fullKey := "remote." + url + "." + key

Mcmd/entire/cli/checkpoint/remote/git.go+21/-10

873 unmodified lines

874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931

873 unmodified lines

runIsolatedGit(ctx, t, cloneDir, "fetch", "--all", "--no-auto-gc")
}

// TestFetch_FailedFilteredFetchStillStampsNewRemote guards the resume
// regression: git writes remote.<url>.promisor eagerly during connection
// setup, so a filtered fetch that then fails (e.g. a missing ref) still leaves
// the phantom remote behind. The stamp must land anyway — otherwise the section
// exists on the next attempt, never looks new again, and lingers unstamped.
func TestFetch_FailedFilteredFetchStillStampsNewRemote(t *testing.T) {
    ctx := context.Background()

tmpDir := t.TempDir()
    originBare := filepath.Join(tmpDir, "origin.git")
    checkpointBare := filepath.Join(tmpDir, "checkpoints.git")
    seedDir := filepath.Join(tmpDir, "seed")
    cloneDir := filepath.Join(tmpDir, "clone")

testutil.InitRepo(t, seedDir)
    testutil.WriteFile(t, seedDir, "f.txt", "init")
    testutil.GitAdd(t, seedDir, "f.txt")
    testutil.GitCommit(t, seedDir, "init")

runIsolatedGit(ctx, t, "", "init", "--bare", originBare)
    runIsolatedGit(ctx, t, "", "init", "--bare", checkpointBare)
    runIsolatedGit(ctx, t, checkpointBare, "config", "uploadpack.allowFilter", "true")
    runIsolatedGit(ctx, t, seedDir, "push", originBare, "HEAD:refs/heads/main")
    runIsolatedGit(ctx, t, "", "clone", "--branch", "main", "file://"+originBare, cloneDir)

testutil.WriteFile(
        t, 
        cloneDir,
        ".entire/settings.json",
        `{"enabled": true, "strategy_options": {"filtered_fetches": true}}`,
    )
    t.Chdir(cloneDir)

fetchURL := "file://" + checkpointBare
    // Fetch a ref that does not exist on the checkpoint remote: the command
    // fails, but git has already recorded the URL-keyed promisor section.
    _, err := Fetch(ctx, FetchOptions{
        Remote:   fetchURL,
        RefSpecs: []string{"+refs/heads/does-not-exist:refs/entire-fetch-tmp/x"},
        NoTags:   true,
        Dir:      cloneDir,
    })
    require.Error(t, err, "fetch of a missing ref should fail")

require.True(t, gitConfigBool(ctx, cloneDir, "remote."+fetchURL+".promisor"),
        "git records the promisor section even when the fetch fails")
    assert.True(t, gitConfigBool(ctx, cloneDir, "remote."+fetchURL+".skipFetchAll"),
        "a phantom remote left by a failed fetch must still be stamped")
    assert.True(t, gitConfigBool(ctx, cloneDir, "remote."+fetchURL+".skipDefaultUpdate"),
        "a phantom remote left by a failed fetch must still be stamped")
}

// TestFetch_UnfilteredFetchDoesNotCreateConfigSection verifies the stamp is
// gated on a filtered fetch: a plain (unfiltered) URL fetch records no
// URL-keyed section, so we must not invent a remote.<url> config section.