Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
92390db
fix: Existing Folder workflow reuses local repo instead of bare clone
PureWeen Apr 4, 2026
5bc7555
Address PR review: safety guards, scoped reuse, orphan cleanup
PureWeen Apr 6, 2026
324d08f
fix: update PR #533 tests for skip-bare-clone approach
PureWeen Apr 7, 2026
8cc6adb
fix: repo picker shows full repo name instead of last dash-segment (f…
PureWeen Apr 15, 2026
8bb14c4
fix: migrate existing repo and group names to URL-derived format (iss…
PureWeen Apr 15, 2026
5f731c6
fix: 'Existing Folder' no longer overwrites URL-based repo's bare clone
PureWeen Apr 15, 2026
549bbc5
fix: validate bare clone before reuse to handle corrupt/partial direc…
PureWeen Apr 15, 2026
1bbe2d0
fix: 'Existing Folder' creates separate repo when URL-based repo exists
PureWeen Apr 15, 2026
0c58613
test: add coverage for local repo ID format, validation errors, and .…
PureWeen Apr 15, 2026
702fbb9
fix: enable Add button immediately on keystroke (use oninput binding)
PureWeen Apr 16, 2026
6ba25ad
fix: worktree creation fails with too-long-path on Windows for maui r…
PureWeen Apr 16, 2026
671f1ad
fix: preserve user-renamed repo and group names across app restart
PureWeen Apr 16, 2026
e962882
fix: replace non-deterministic GetHashCode with SHA256 for persistent…
PureWeen Apr 16, 2026
3ab93e9
fix: serialize concurrent worktree creation and validate reused workt…
PureWeen Apr 16, 2026
18f7dfc
fix: address v7 review findings — PathsEqual null safety, hash collis…
PureWeen Apr 17, 2026
a3e1b4c
fix: address v7/v8 review findings — structural tests, oninput, fallb…
PureWeen Apr 17, 2026
32f2545
fix: convert remaining structural tests to behavioral tests
PureWeen Apr 17, 2026
aec49e7
fix: address v10 review — atomic Save, lock safety, platform-aware paths
PureWeen Apr 17, 2026
9f2882e
test: add behavioral worktree reuse and stale eviction tests
PureWeen Apr 17, 2026
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
348 changes: 325 additions & 23 deletions PolyPilot.Tests/AddExistingRepoTests.cs

Large diffs are not rendered by default.

706 changes: 666 additions & 40 deletions PolyPilot.Tests/RepoManagerTests.cs

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions PolyPilot.Tests/WorktreeStrategyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public FakeRepoManager(List<RepositoryInfo> repos)
}

public override Task<WorktreeInfo> CreateWorktreeAsync(string repoId, string branchName,
string? baseBranch = null, bool skipFetch = false, string? localPath = null, CancellationToken ct = default)
string? baseBranch = null, bool skipFetch = false, CancellationToken ct = default)
{
CreateCalls.Add((repoId, branchName, skipFetch));
var id = $"wt-{Interlocked.Increment(ref _worktreeCounter)}";
Expand Down Expand Up @@ -560,7 +560,7 @@ public FailingRepoManager(List<RepositoryInfo> repos)
}

public override Task<WorktreeInfo> CreateWorktreeAsync(string repoId, string branchName,
string? baseBranch = null, bool skipFetch = false, string? localPath = null, CancellationToken ct = default)
string? baseBranch = null, bool skipFetch = false, CancellationToken ct = default)
{
throw new InvalidOperationException("Simulated git failure");
}
Expand Down
22 changes: 8 additions & 14 deletions PolyPilot/Components/Layout/SessionSidebar.razor
Original file line number Diff line number Diff line change
Expand Up @@ -1004,13 +1004,12 @@ else
</button>
@if (!string.IsNullOrEmpty(group.RepoId))
{
@* 📁 group backed by a bare clone — offer full branch/worktree features *@
@* 📁 group backed by a repo — offer full branch/worktree features *@
var lfRepoId = group.RepoId!;
var lfLocalPath = group.LocalPath!;
<button class="group-menu-item" @onclick="() => { openGroupMenuId = null; QuickCreateSessionForRepo(lfRepoId, gId, lfLocalPath); }">
<button class="group-menu-item" @onclick="() => { openGroupMenuId = null; QuickCreateSessionForRepo(lfRepoId, gId); }">
⚡ Quick Branch + Session
</button>
<button class="group-menu-item" @onclick="() => { openGroupMenuId = null; StartQuickBranch(lfRepoId, gId, lfLocalPath); }">
<button class="group-menu-item" @onclick="() => { openGroupMenuId = null; StartQuickBranch(lfRepoId, gId); }">
⑂ Named Branch + Session
</button>
}
Expand Down Expand Up @@ -1957,7 +1956,6 @@ else
// Quick-create inline branch input
private string? quickBranchRepoId = null;
private string? quickBranchGroupId = null;
private string? quickBranchLocalPath = null;
private string quickBranchInput = "";
private bool quickBranchIsCreating = false;
private string? quickBranchError = null;
Expand Down Expand Up @@ -2568,7 +2566,7 @@ else
}
}

private async Task QuickCreateSessionForRepo(string repoId, string? targetGroupId = null, string? localPath = null)
private async Task QuickCreateSessionForRepo(string repoId, string? targetGroupId = null)
{
if (isCreating) return;
isCreating = true;
Expand All @@ -2580,8 +2578,7 @@ else
var sessionInfo = await CopilotService.CreateSessionWithWorktreeAsync(
repoId: repoId,
model: selectedModel,
targetGroupId: targetGroupId,
localPath: localPath);
targetGroupId: targetGroupId);
CopilotService.SaveUiState(currentPage, selectedModel: selectedModel);
await OnSessionSelected.InvokeAsync();
}
Expand All @@ -2598,19 +2595,18 @@ else
}
}

private void StartQuickBranch(string repoId, string? targetGroupId = null, string? localPath = null)
private void StartQuickBranch(string repoId, string? targetGroupId = null)
{
quickBranchRepoId = repoId;
quickBranchGroupId = targetGroupId;
quickBranchLocalPath = localPath;
quickBranchInput = "";
quickBranchError = null;
}

private async Task HandleQuickBranchKeyDown(KeyboardEventArgs e, string repoId)
{
if (e.Key == "Enter") await CommitQuickBranch(repoId);
else if (e.Key == "Escape") { quickBranchRepoId = null; quickBranchLocalPath = null; }
else if (e.Key == "Escape") { quickBranchRepoId = null; }
}

private async Task CommitQuickBranch(string repoId)
Expand Down Expand Up @@ -2648,12 +2644,10 @@ else
branchName: branchName,
prNumber: prNumber,
model: selectedModel,
targetGroupId: quickBranchGroupId,
localPath: quickBranchLocalPath);
targetGroupId: quickBranchGroupId);

quickBranchRepoId = null;
quickBranchGroupId = null;
quickBranchLocalPath = null;
quickBranchInput = "";
CopilotService.SaveUiState(currentPage, selectedModel: selectedModel);
await OnSessionSelected.InvokeAsync();
Expand Down
25 changes: 25 additions & 0 deletions PolyPilot/Services/CopilotService.Organization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,31 @@ internal void ReconcileOrganization(bool allowPruning = true)
if (GetOrCreateRepoGroup(repo.Id, repo.Name) != null)
changed = true;
}

// Migration: update group names that were derived from id.Split('-').Last() (issue #570).
// E.g., groups named "maui" for repo "nicknisi-vscode-maui" should become "vscode-maui".
// Only migrate names that still match the old broken derivation — if the user
// renamed the group (e.g., "maui - PP"), preserve their customization.
foreach (var g in Organization.Groups.Where(g => g.RepoId == repo.Id && !g.IsMultiAgent && !g.IsLocalFolder))
{
var correctName = repo.Name;
if (string.IsNullOrEmpty(correctName) || g.Name == correctName)
continue;
if (Organization.Groups.Any(other => other != g && other.RepoId == repo.Id && other.Name == correctName && !other.IsMultiAgent && !other.IsLocalFolder))
continue;

// The old code derived names via id.Split('-').Last(). Only overwrite if
// the current group name matches that old pattern — otherwise user renamed it.
var oldDerivedName = repo.Id.Contains('-')
? repo.Id.Split('-').Last()
: repo.Id;
if (string.Equals(g.Name, oldDerivedName, StringComparison.Ordinal))
{
Debug($"ReconcileOrganization: migrating group name '{g.Name}' → '{correctName}' (repoId: {repo.Id})");
g.Name = correctName;
changed = true;
}
}
}

// Migration: back-fill LocalPath/RepoId on groups that were created by an older version
Expand Down
3 changes: 1 addition & 2 deletions PolyPilot/Services/CopilotService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3074,7 +3074,6 @@ public async Task<AgentSessionInfo> CreateSessionWithWorktreeAsync(
string? model = null,
string? initialPrompt = null,
string? targetGroupId = null,
string? localPath = null,
CancellationToken ct = default)
{
// Remote mode: send the entire operation to the server as a single atomic command.
Expand Down Expand Up @@ -3150,7 +3149,7 @@ await _bridgeClient.CreateSessionWithWorktreeAsync(new CreateSessionWithWorktree
else
{
var branch = branchName ?? $"session-{DateTime.Now:yyyyMMdd-HHmmss}";
wt = await _repoManager.CreateWorktreeAsync(repoId, branch, null, localPath: localPath, ct: ct);
wt = await _repoManager.CreateWorktreeAsync(repoId, branch, null, ct: ct);
}

// Derive a friendly display name: prefer explicit sessionName, then branch name,
Expand Down
Loading