Skip to content
Open
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
30 changes: 27 additions & 3 deletions cmd/entire/cli/dispatch/mode_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,15 +376,39 @@ func reachableCheckpointIDsInRange(ctx context.Context, repoRoot, revRange strin
// and leave the dispatch effectively empty. In that case we summarize
// everything reachable from HEAD in the window, matching the server-side
// dispatch. The base..HEAD exclusion only applies to feature branches.
//
// The same "nothing to exclude" reasoning holds for any branch whose base..HEAD
// range is empty — a feature-branch worktree freshly created off the up-to-date
// default branch (the standard git-worktree workflow), or a branch whose work
// has already merged into the base. There the exclusion would again drop all
// reachable in-window activity and report "no activity" despite HEAD clearly
// carrying merged work (ENT-1201). Detecting the empty range directly, rather
// than matching the default branch by name, covers both cases.
func branchLocalRevRange(ctx context.Context, repoRoot, currentBranch string) string {
const headRev = "HEAD"
base := defaultBranchRef(ctx, repoRoot)
if base == "" {
return "HEAD"
return headRev
}
if currentBranch != "" && strings.TrimPrefix(base, "origin/") == currentBranch {
return "HEAD"
return headRev
}
if revRangeIsEmpty(ctx, repoRoot, base+".."+headRev) {
return headRev
}
return base + ".." + headRev
}

// revRangeIsEmpty reports whether revRange contains no commits (i.e. HEAD has no
// commits the base lacks). A git failure is treated as non-empty so the caller
// keeps the base..HEAD exclusion — the conservative choice that never widens
// scope on error.
func revRangeIsEmpty(ctx context.Context, repoRoot, revRange string) bool {
out, ok := runGitOutput(ctx, repoRoot, "rev-list", "--count", revRange)
if !ok {
return false
}
return base + "..HEAD"
return strings.TrimSpace(out) == "0"
Comment on lines +406 to +411
}

// defaultBranchRef resolves the repository's default branch, preferring
Expand Down
62 changes: 62 additions & 0 deletions cmd/entire/cli/dispatch/mode_local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,68 @@ func TestLocalMode_ImplicitCurrentBranchOnDefaultBranchIncludesMergedWork(t *tes
}
}

// TestLocalMode_ImplicitCurrentBranchFeatureBranchNoOwnCommitsIncludesMergedWork
// is the regression test for ENT-1201: dispatching from a feature-branch
// worktree that has no commits of its own (a fresh worktree branched off the
// up-to-date default branch, or a branch whose work has already merged into it)
// must still surface the default branch's in-window merged work reachable from
// HEAD. The bug: branchLocalRevRange returned <default>..HEAD for any
// non-default branch, which is EMPTY here, so the reachable-trailer set was
// empty and every merged checkpoint was dropped — the dispatch came back empty
// even though HEAD clearly carried the work. ENT-1188 fixed this only for the
// literal default branch (matched by name); this covers the general
// empty-range case, which the common worktree workflow hits constantly.
func TestLocalMode_ImplicitCurrentBranchFeatureBranchNoOwnCommitsIncludesMergedWork(t *testing.T) {
dir := t.TempDir()
stubGeneratedLocalDispatch(t)
testutil.InitRepo(t, dir)
testutil.WriteFile(t, dir, "seed.txt", "seed")
testutil.GitAdd(t, dir, "seed.txt")
testutil.GitCommit(t, dir, "initial")
addOriginRemote(t, dir)

// Merged feature-branch work now sitting ON the default branch, carrying a
// checkpoint trailer and reachable from HEAD. The checkpoint's own branch is
// the (merged) feature branch, not the default branch.
testutil.WriteFile(t, dir, "feature.md", "ship it")
testutil.GitAdd(t, dir, "feature.md")
commitWithMessage(t, dir, trailers.FormatCheckpoint("feature work", mustCheckpointID(t, testCheckpointID)))

createdAt := time.Now().UTC()
seedCommittedCheckpoint(t, dir, seededCheckpoint{
id: testCheckpointID,
branch: "already-merged-feature",
createdAt: createdAt,
filesTouched: []string{"feature.md"},
outcome: testLocalFallbackText,
})

// A fresh worktree branch off the default branch's tip: it shares HEAD with
// the default branch and has no commits of its own, so <default>..HEAD is
// empty. This is the standard `marvin wt new` / git-worktree setup.
testutil.GitCheckoutNewBranch(t, dir, "my-worktree-branch")

oldNow := nowUTC
nowUTC = func() time.Time { return createdAt.Add(time.Hour) }
t.Cleanup(func() { nowUTC = oldNow })

t.Chdir(dir)

got, err := Run(context.Background(), Options{
Mode: ModeLocal,
Since: "7d",
Branches: []string{"my-worktree-branch"},
ImplicitCurrentBranch: true,
})
if err != nil {
t.Fatal(err)
}
if len(got.Repos) != 1 || len(got.Repos[0].Sections) == 0 || len(got.Repos[0].Sections[0].Bullets) == 0 ||
got.Repos[0].Sections[0].Bullets[0].Text != testLocalFallbackText {
t.Fatalf("merged work missing from feature-branch-with-no-own-commits dispatch: %+v", got)
}
}

// TestLocalMode_IncludesReachableCheckpointMissingFromLocalStore is the other
// half of the ENT-1188 fix: dispatch --local must summarize work reachable from
// HEAD by commit trailer even when the checkpoint itself is absent from the
Expand Down
Loading