fix(desktop): stop rendering a repo's main checkout as a duplicate sidebar lane

The main-checkout test compared the two probe roots with raw string
equality. When they differed only in separator spelling, the repo's own
checkout was misclassified as a linked worktree: it fell through to the
worktree branch and was labeled by directory basename. The sidebar then
showed one checkout twice — a dir-labeled lane plus the branch-labeled
`main` lane built from the same sessions.

Compare with `_path_key` so platform path identity decides, matching how
every other path comparison in this module is already keyed.

Tests cover the single-checkout case and the main + linked-worktree case;
both fail before this change (the lane comes back labeled `repo`, not
`main`).
This commit is contained in:
bb 2026-08-07 12:46:35 -05:00
parent aaa4299a28
commit 4cefba3ec9
2 changed files with 47 additions and 1 deletions

View File

@ -189,6 +189,52 @@ def test_unrecorded_and_recorded_main_share_one_lane():
assert len(main_lanes[0]["sessions"]) == 2
def test_main_checkout_detected_when_roots_differ_only_in_path_spelling():
# The two roots come from DIFFERENT git probes: `rev-parse --show-toplevel`
# emits forward slashes, while the `--git-common-dir` path goes through
# os.path.dirname and keeps Windows backslashes. The main checkout must be
# recognized by path IDENTITY, not by raw string equality — otherwise the
# repo's own checkout is misread as a linked worktree and the sidebar shows
# both a dir-labeled lane and a branch-labeled "main" lane for one checkout.
resolve = _resolver(
{
"C:/repo": ("C:\\repo", "C:/repo"),
}
)
sessions = [_session("C:/repo", branch="main")]
tree = pt.build_tree([], sessions, [], resolve, hydrate=True)
project = next(p for p in tree["projects"] if pt._path_key(p["id"]) == pt._path_key("C:/repo"))
lanes = [g for repo in project["repos"] for g in repo["groups"]]
assert len(lanes) == 1
assert lanes[0]["isMain"] is True
# Labeled by branch (a main checkout), never by the directory basename.
assert lanes[0]["label"] == "main"
def test_main_and_linked_worktree_do_not_duplicate_one_checkout():
# End-to-end shape of the reported bug: the repo's own checkout plus a real
# linked worktree. Mixed separators across probes must still yield exactly
# one lane per checkout — a branch lane for main, a dir lane for the linked
# worktree — not three lanes for two checkouts.
resolve = _resolver(
{
"C:/repo": ("C:\\repo", "C:/repo"),
"C:/repo-wt": ("C:\\repo", "C:/repo-wt"),
}
)
sessions = [_session("C:/repo", branch="main"), _session("C:/repo-wt", branch="feature")]
tree = pt.build_tree([], sessions, [], resolve, hydrate=True)
project = next(p for p in tree["projects"] if pt._path_key(p["id"]) == pt._path_key("C:/repo"))
lanes = [g for repo in project["repos"] for g in repo["groups"]]
assert len(lanes) == 2
assert [g["label"] for g in lanes] == ["main", "repo-wt"]
assert [g["isMain"] for g in lanes] == [True, False]
def test_persisted_repo_root_used_when_no_live_probe():
# No resolver (remote backend): fall back to the persisted git_repo_root and
# split the main checkout by the session's recorded branch.

View File

@ -230,7 +230,7 @@ def _place(cwd: str, branch: str, resolve: Optional[Resolve], persisted_root: st
if info and info.get("repo_root") and info.get("worktree_root"):
repo_root = info["repo_root"]
worktree_root = info["worktree_root"]
is_main = worktree_root == repo_root or bool(info.get("is_main"))
is_main = _path_key(worktree_root) == _path_key(repo_root) or bool(info.get("is_main"))
if is_main:
# Unrecorded branch folds into the one trunk lane, so a repo never