/simplify: reuse refBase, drop dead branch, rename push entry · Entire

/simplify: reuse refBase, drop dead branch, rename push entry

b9159ed→main·

pfleidi·1w ago·5 files·+49 added/-38 removed

Resolve the existing ref through the refs store's refBase instead of an inline Reference/CommitObject chain, keeping the same semantics: a ref that doesn't resolve to a readable commit is treated as absent and the checkpoint is re-migrated as an orphan (pinned by a new test).

Drop the unreachable malformed-id skip — WalkCheckpointShards only yields validated IDs, and refBase would surface the error anyway.

Rename PushMigratedCheckpointRefs to PushQueuedCheckpointRefs: it flushes the whole queue, not just migrated refs.

Sessions

57d756e084fdView transcript

Changes

5

4 unmodified lines

5
6
7
8
8
9
10
3 unmodified lines

14
15
16
18
17
18
19
44 unmodified lines

64
65
66
69
70
71
72
73
74
75
76
77
67
68
69
70
71
83
84
85
86
87
88
89
90
91
92
93
94
95
96
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

4 unmodified lines

"encoding/json"
    "errors"
    "fmt"
    "log/slog"
    "strings"

git "github.com/go-git/go-git/v6"
3 unmodified lines

"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
    "github.com/entireio/cli/cmd/entire/cli/jsonutil"
    "github.com/entireio/cli/cmd/entire/cli/logging"
    "github.com/entireio/cli/cmd/entire/cli/paths"
)

44 unmodified lines

}
        result.Total++

refName, err := RefName(cid)
        if err != nil {
            // A malformed id on the branch can't map to a ref; skip it rather
            // than aborting the whole migration.
            logging.Warn(ctx, "migrate: skipping checkpoint with unmappable id",
                slog.String("id", cid.String()), slog.String("error", err.Error()))
            return nil
        }

migratedTree, err := migratedCheckpointTree(ctx, repo, cid, cpTreeHash)
        if err != nil {
            return fmt.Errorf("normalize checkpoint %s: %w", cid, err)
        }

// Resolve the existing ref once: it drives both the idempotency check and
        // the parent of the new commit. Only a ref that resolves to a real commit
        // becomes the parent — a ref pointing at an unreadable/non-commit object
        // is treated as absent (orphan) rather than parenting the new commit on a
        // bad hash, which would corrupt the commit graph for fetch+replay.
        parent := plumbing.ZeroHash
        if existing, err := repo.Reference(refName, true); err == nil {
            if commit, cerr := repo.CommitObject(existing.Hash()); cerr == nil {
                if commit.TreeHash == migratedTree {
                    result.Skipped++
                    return nil
                }
                parent = existing.Hash()
            }
        }
        // The existing ref drives the idempotency check and the new commit's
        // parent. Only a ref that resolves to a real commit becomes the parent —
        // unreadable ref state is treated as absent (orphan) rather than
        // parenting the new commit on a bad hash, which would corrupt the commit
        // graph for fetch+replay.
        parent, existingTree, err := refsStore.refBase(cid)
        if err != nil {
            parent, existingTree = plumbing.ZeroHash, nil
        }
        if existingTree != nil && existingTree.Hash == migratedTree {
            result.Skipped++
            return nil
        }

if dryRun {

Mcmd/entire/cli/checkpoint/migrate.go+12/-25

241 unmodified lines

242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271

241 unmodified lines

assert.Empty(t, queued, "dry-run must not enqueue refs for push")
}

func TestMigrateBranchToRefs_UnreadableRefIsReplacedWithOrphan(t *testing.T) {
    t.Parallel()
    repo, _ := setupBranchTestRepo(t)
    branch := NewGitStore(repo, DefaultV1Refs())
    cid := id.MustCheckpointID("a1b2c3d4e5f6")
    seedBranchCheckpoint(t, branch, cid, "s1")

// A ref at a nonexistent commit: the lookup succeeds but the commit read
    // fails. The migration treats it as absent rather than parenting on the
    // bad hash.
    refName, err := RefName(cid)
    require.NoError(t, err)
    bogus := plumbing.NewHash("0123456789abcdef0123456789abcdef01234567")
    require.NoError(t, repo.Storer.SetReference(plumbing.NewHashReference(refName, bogus)))

result, err := MigrateBranchToRefs(context.Background(), repo, false)
    require.NoError(t, err)
    assert.Len(t, result.Migrated, 1)

commit, err := repo.CommitObject(refHash(t, repo, cid))
    require.NoError(t, err)
    assert.Empty(t, commit.ParentHashes, "unreadable ref must not become the parent")
}

func TestMigrateBranchToRefs_NoBranchIsNoop(t *testing.T) {
    t.Parallel()
    repo, _ := setupBranchTestRepo(t) // initial commit only; no v1 checkpoint branch yet

Mcmd/entire/cli/checkpoint/migrate_test.go+24

82 unmodified lines

83
84
85
86
86
87
88
89

82 unmodified lines

return nil
    }

pushed, err := strategy.PushMigratedCheckpointRefs(ctx, repo, remote)
    pushed, err := strategy.PushQueuedCheckpointRefs(ctx, repo, remote)
    if err != nil {
        if errors.Is(err, context.Canceled) {
            return NewSilentError(err)
        }

Mcmd/entire/cli/doctor_migrate.go+1/-1

170 unmodified lines

171
172
173
174
175
176
177
178
174
175
176
177
178
180
179
180
181
182
183

170 unmodified lines

return nil
// PushMigratedCheckpointRefs pushes any queued checkpoint refs to the configured
// checkpoint remote, surfacing errors (unlike the fail-soft pre-push path). It is
// the opt-in "push now" invoked by the checkpoint migration command; the caller
// owns the repo. Returns the number of refs pushed — a no-op (0, nil) when
// pushing is disabled or the queue is empty. Like the pre-push paths, a
// PushQueuedCheckpointRefs pushes any queued checkpoint refs to the configured
// checkpoint remote, surfacing errors (unlike the fail-soft pre-push path); the
// caller owns the repo. Returns the number of refs pushed — a no-op (0, nil)
// when pushing is disabled or the queue is empty. Like the pre-push paths, a
// checkpoint policy that blocks pushing errors with the refs left queued.
func PushMigratedCheckpointRefs(ctx context.Context, repo *git.Repository, remote string) (int, error) {
// Currently used by the checkpoint migration command's opt-in "push now".
func PushQueuedCheckpointRefs(ctx context.Context, repo *git.Repository, remote string) (int, error) {
    ps := resolvePushSettings(ctx, remote)
    if ps.pushDisabled {
        return 0, nil
    }

Mcmd/entire/cli/strategy/manual_commit_push.go+6/-6

213 unmodified lines

214
215
216
217
217
218
219
220
2 unmodified lines

223
224
225
226
226
227
228
229
5 unmodified lines

235
236
237
238
238
239
240
241
3 unmodified lines

245
246
247
248
248
249
250
251
10 unmodified lines

262
263
264
265
265
266
267
268
3 unmodified lines

272
273
274
275
275
276
277
278

213 unmodified lines

return queue
}

func TestPushMigratedCheckpointRefs(t *testing.T) {
func TestPushQueuedCheckpointRefs(t *testing.T) {
    workDir, bareDir, refs := setupRepoWithCheckpointRefs(t)
    t.Chdir(workDir)
    paths.ClearWorktreeRootCache()
2 unmodified lines

require.NoError(t, err)
    queue := enqueueRefs(t, repo, refs)

pushed, err := PushMigratedCheckpointRefs(context.Background(), repo, bareDir)
    pushed, err := PushQueuedCheckpointRefs(context.Background(), repo, bareDir)
    require.NoError(t, err)
    assert.Equal(t, len(refs), pushed)

5 unmodified lines

assert.Empty(t, remaining, "pushed refs are removed from the queue")
}

func TestPushMigratedCheckpointRefs_PolicyBlocked(t *testing.T) {
func TestPushQueuedCheckpointRefs_PolicyBlocked(t *testing.T) {
    workDir, bareDir, refs := setupRepoWithCheckpointRefs(t)
    t.Chdir(workDir)
    paths.ClearWorktreeRootCache()
3 unmodified lines

writeUnsupportedCheckpointPolicy(t, repo)
    queue := enqueueRefs(t, repo, refs)

pushed, err := PushMigratedCheckpointRefs(context.Background(), repo, bareDir)
    pushed, err := PushQueuedCheckpointRefs(context.Background(), repo, bareDir)
    require.ErrorContains(t, err, "checkpoint policy")
    assert.Equal(t, 0, pushed)

10 unmodified lines

}
}

func TestPushMigratedCheckpointRefs_FailureLeavesRefsQueued(t *testing.T) {
func TestPushQueuedCheckpointRefs_FailureLeavesRefsQueued(t *testing.T) {
    workDir, _, refs := setupRepoWithCheckpointRefs(t)
    t.Chdir(workDir)
    paths.ClearWorktreeRootCache()
3 unmodified lines

queue := enqueueRefs(t, repo, refs)

badTarget := filepath.Join(t.TempDir(), "missing.git")
    pushed, err := PushMigratedCheckpointRefs(context.Background(), repo, badTarget)
    pushed, err := PushQueuedCheckpointRefs(context.Background(), repo, badTarget)
    require.ErrorContains(t, err, "failed to push")
    assert.Equal(t, 0, pushed)

Mcmd/entire/cli/strategy/refs_push_test.go+6/-6