test: add git-branch divergence matrix + replay fidelity guards (D1, D4, D5) · Entire
test: add git-branch divergence matrix + replay fidelity guards (D1, D4, D5)
ea4cc21·
Soph·1w ago·2 files·+505 added/-0 removed
Systematize the local-v1 divergence & recovery matrix (the most re-broken area, #953/#1251/#1252/#1260) plus the specifically-untested replay edges.
D1 (integration, git-branch)
drive the local-v1 state {ahead, behind, diverged, disconnected} through the real triggers end-to-end:
- pre-push (real hook): local-only survives; ahead fast-forwards; diverged replays both sides on a linear tip; disconnected cherry-picks; a repeated push is idempotent (double-replay guard, #1260).
- explain fetch-on-miss for {behind, diverged}, extending the existing {missing, ahead} coverage: a remote-only checkpoint is fetched/reconciled and local-only checkpoints survive.
- doctor --force reconciles a disconnected v1, preserving both sides.
D4 (strategy unit)
replay fidelity edges. A no-op commit must not clobber the remote tip's accumulated tree (regression 743c43f4c); a root-commit chain replays cleanly. Plus a KNOWN BUG pin: >1000-commit replay is still capped (collectCommitsSince) because the cap-removal (4cf01edb3) lives on the unmerged branch origin/no-limit; flip the assertion when it lands.
D5 (strategy)
TOCTOU guard for 1e8628ade -- fetchAndRebaseRefCommon replays onto the freshly fetched remote tip, not a stale earlier hash. The literal ls-remote seam the fix patched no longer exists (fix is an ancestor), so this pins the behavioural invariant.
Sessions
b19f4c2ff3bdView transcript
Changes
2
cmd/entire/cli
integration_test
Adivergence_matrix_test.go+254strategy
Areplay_fidelity_test.go+251
//go:build integration
package integration
import (
"os/exec"
"strings"
"testing"
"github.com/entireio/cli/cmd/entire/cli/paths"
"github.com/entireio/cli/cmd/entire/cli/testutil"
"github.com/stretchr/testify/require"
)
// =============================================================================
// D1 -- git-branch divergence & recovery matrix
// Systematizes the local-v1 state {ahead, behind, diverged, disconnected}
// missing} x trigger {pre-push, explain fetch-on-miss, doctor} matrix that the
// #953/#1251/#1252/#1260 fixes patched piecemeal.
// The reconcile engine itself
// (SafelyAdvanceLocalRef, ReconcileDisconnectedMetadataRef, cherryPickOnto) is
// unit-tested exhaustively in the strategy package (safely_advance_local_ref_test.go,
// metadata_reconcile_test.go);
func advanceRemoteV1(t *testing.T, srcEnv *TestEnv, bareDir, prompt string) string {
t.Helper()
other := srcEnv.CloneFrom(bareDir)
other.GitCheckoutNewBranch("feature/remote-advance")
cp := createCheckpointedCommit(t, other, prompt, "remote_adv.go", "package remoteadv", prompt)
require.NotEmpty(t, cp, "remote-advance clone should produce a checkpoint")
other.RunPrePush("origin")
require.True(t, srcEnv.CheckpointExistsOnRemote(bareDir, cp),
"remote v1 should carry the advanced checkpoint after the clone pushed")
return cp
}
func TestDivergedV1_PrePushMatrix(t *testing.T) {
t.Parallel()
for _, state := range []string{"ahead", "behind", "diverged", "disconnected"} {
t.Run(state, func(t *testing.T) {
t.Parallel()
env := NewFeatureBranchEnv(t)
bareDir := env.SetupBareRemote()
baseCP := createCheckpointedCommit(t, env, "base work", "base.go", "package base", "base work")
require.NotEmpty(t, baseCP)
env.GitPush("origin", "HEAD")
env.RunPrePush("origin")
var localCP, remoteCP string
switch state {
case "ahead":
localCP = createCheckpointedCommit(t, env, "local ahead", "ahead.go", "package ahead", "local ahead")
case "behind":
remoteCP = advanceRemoteV1(t, env, bareDir, "remote behind work")
case "diverged":
localCP = createCheckpointedCommit(t, env, "local diverge", "diverge.go", "package diverge", "local diverge")
remoteCP = advanceRemoteV1(t, env, bareDir, "remote diverge work")
case "disconnected":
localCP = "abcdef012345"
forceLocalV1Orphan(t, env, localCP)
}
trigger := "push-trigger-" + state + ".txt"
env.WriteFile(trigger, "x")
env.GitAdd(trigger)
env.GitCommit("push trigger (" + state + ")")
env.GitPushWithHooks("origin", "HEAD")
require.True(t, env.CheckpointExistsOnRemote(bareDir, baseCP),
"[%s] base checkpoint must always survive on the remote", state)
switch state {
case "ahead":
require.True(t, env.CheckpointExistsOnRemote(bareDir, localCP),
"ahead: local-only checkpoint should fast-forward onto the remote")
case "behind":
require.True(t, env.CheckpointExistsOnRemote(bareDir, remoteCP),
"behind: the remote's checkpoint must remain")
case "diverged":
require.True(t, env.CheckpointExistsOnRemote(bareDir, localCP),
"diverged: local checkpoint must be replayed onto the remote")
require.True(t, env.CheckpointExistsOnRemote(bareDir, remoteCP),
"diverged: remote checkpoint must be preserved (not overwritten)")
require.Equal(t, 1, env.GetBranchTipParentCount(paths.MetadataBranchName),
"diverged: local v1 tip must be linear after replay (double-replay guard, #1260)")
case "disconnected":
require.True(t, env.CheckpointExistsOnRemote(bareDir, localCP),
"disconnected: orphaned local checkpoint must be cherry-picked onto the remote")
}
before := env.RemoteCheckpointState(bareDir)
env.RunPrePush("origin")
after := env.RemoteCheckpointState(bareDir)
require.Equal(t, before, after,
"[%s] a repeated pre-push must not re-replay or otherwise mutate the remote", state)
})
}
}
func TestDivergedV1_DoctorReconcilesDisconnected(t *testing.T) {
t.Parallel()
env := NewFeatureBranchEnv(t)
bareDir := env.SetupBareRemote()
baseCP := createCheckpointedCommit(t, env, "base work", "base.go", "package base", "base work")
require.NotEmpty(t, baseCP)
env.GitPush("origin", "HEAD")
env.RunPrePush("origin")
require.True(t, env.CheckpointExistsOnRemote(bareDir, baseCP))
env.FetchMetadataBranch(bareDir)
orphanCP := "abcdef012345"
forceLocalV1Orphan(t, env, orphanCP)
out := env.RunCLI("doctor", "--force")
require.Contains(t, out, "reconciled", "doctor should report it reconciled the disconnected branches")
require.True(t, env.FileExistsInBranch(paths.MetadataBranchName, CheckpointSummaryPath(baseCP)),
"doctor: remote base checkpoint must be present locally after reconcile")
require.True(t, env.FileExistsInBranch(paths.MetadataBranchName, CheckpointSummaryPath(orphanCP)),
"doctor: orphaned local checkpoint must survive the reconcile")
}