Skip to content

Commit 3f70ea5

Browse files
committed
Don't allow forceable removal
1 parent 97ede05 commit 3f70ea5

4 files changed

Lines changed: 126 additions & 63 deletions

File tree

scripts/git-utilities.ps1

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,3 @@ function Test-GitBranchExists {
172172
return ($output.Count -gt 0)
173173
}
174174

175-
function Reset-GitBranchToRef {
176-
[CmdletBinding()]
177-
param(
178-
[Parameter(Mandatory=$true)][string]$Branch,
179-
[Parameter(Mandatory=$true)][string]$Ref
180-
)
181-
182-
# Ensures the agent branch is a mirror of the desired base ref.
183-
Invoke-GitSafe @('branch','-f',$Branch,$Ref) -Quiet
184-
}

scripts/multi-agent-containers-workflow.md

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,16 @@ The script will:
6262
- `-OpenVSCode` - launches a VS Code window per agent with `FW_AGENT_CONTAINER` set
6363

6464
The script will:
65-
- Create worktrees at `...\worktrees\agent-1..N` with branches `agents/agent-1..N`
65+
- Create NEW worktrees at `...\worktrees\agent-1..N` with branches `agents/agent-1..N` from BaseRef
66+
- **SKIP existing worktrees** (preserves your work - never resets or modifies existing worktrees)
6667
- Build or reuse the `fw-build:ltsc2022` Windows image
67-
- Start containers `fw-agent-1..N` with per-agent NuGet caches
68-
- Generate `.vscode/tasks.json` wired to the matching container
68+
- Start containers `fw-agent-1..N` with per-agent NuGet caches (skipped for existing worktrees)
69+
- Generate `.vscode/tasks.json` wired to the matching container (only for new worktrees)
6970
- Generate `.vscode/settings.json` with unique colors to keep agent windows visually distinct
7071
- Optionally open each worktree in a new VS Code window
7172

73+
**IMPORTANT**: This script **NEVER modifies existing worktrees** to prevent data loss. If you want to reset a worktree to a different branch, use `tear-down-agents.ps1 -RemoveWorktrees` first, then re-run spin-up.
74+
7275
3. Work per agent
7376

7477
- Open the worktree folder in VS Code (one window per agent)
@@ -84,6 +87,16 @@ All registry and COM operations occur inside the container, isolated from your h
8487
- `gh pr create -H agents/agent-1 -B release/9.3 -t "..." -b "..."`
8588
- Avoid running Git inside the container for that worktree to prevent file locking.
8689

90+
**Branch reuse behavior**: If `agents/agent-N` branches already exist, spin-up will:
91+
- Create worktrees attached to the existing branches at their current commits
92+
- **NOT** reset branches to BaseRef automatically
93+
- Let you manually sync branches if needed:
94+
```powershell
95+
cd worktrees\agent-1
96+
git fetch origin
97+
git merge origin/release/9.3 # or git reset --hard origin/release/9.3
98+
```
99+
87100
## Performance and limits
88101

89102
- Avoid building all 5 agents simultaneously on a 32 GB machine; stagger builds or reduce msbuild parallelism
@@ -105,6 +118,26 @@ Remove containers, worktrees, agent branches, and per-agent NuGet caches:
105118
-RemoveWorktrees
106119
```
107120

121+
**IMPORTANT**: Tear-down will **ERROR and refuse to remove worktrees** that have uncommitted changes to prevent data loss. You'll see:
122+
```
123+
Worktree agent-1 has uncommitted changes at: C:\...\worktrees\agent-1
124+
125+
To protect your work, tear-down will NOT remove this worktree.
126+
```
127+
128+
To proceed, commit or stash your changes first:
129+
```powershell
130+
cd worktrees\agent-1
131+
git add .
132+
git commit -m "Work in progress"
133+
# Then re-run tear-down
134+
```
135+
136+
Only use `-ForceRemoveDirty` if you're **certain** you want to discard uncommitted work:
137+
```powershell
138+
.\scripts\tear-down-agents.ps1 -RepoRoot "C:\dev\FieldWorks" -RemoveWorktrees -ForceRemoveDirty
139+
```
140+
108141
You can pass `-WorktreesRoot` if you placed worktrees outside the default path; otherwise the script respects `FW_WORKTREES_ROOT` when present.
109142

110143
## Notes
@@ -152,11 +185,25 @@ docker logs fw-agent-1 # Check container logs
152185
```
153186

154187
### Worktree already exists
155-
The script safely reuses existing worktrees and containers. To start fresh:
188+
The script **preserves existing worktrees** and will skip them to prevent data loss. You'll see:
189+
```
190+
Worktree already exists: C:\...\worktrees\agent-1 (skipping - will not modify existing worktree)
191+
Branch: agents/agent-1 (current state preserved)
192+
```
193+
194+
To reset a worktree to a different branch or base ref:
156195
```powershell
196+
# Option 1: Remove all worktrees and recreate fresh
157197
.\scripts\tear-down-agents.ps1 -RepoRoot "C:\dev\FieldWorks" -Count 3 -RemoveWorktrees
198+
199+
# Option 2: Manually delete specific worktree, then re-run spin-up
200+
Remove-Item -Recurse -Force "C:\...\worktrees\agent-1"
201+
git worktree prune
202+
.\scripts\spin-up-agents.ps1 -RepoRoot "C:\dev\FieldWorks" -Count 3
158203
```
159204

205+
**Never** try to force-reset worktrees - this was removed to prevent accidental data loss.
206+
160207
### Build fails inside container
161208
Verify the solution path is correct and accessible:
162209
```powershell

scripts/spin-up-agents.ps1

Lines changed: 53 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,24 @@ Prereqs:
66
- One primary clone on the host (e.g., C:\dev\FieldWorks)
77
- PowerShell 5+ (Windows)
88
9+
IMPORTANT: This script NEVER modifies existing worktrees to prevent data loss.
10+
- Existing worktrees are preserved as-is (skipped)
11+
- New worktrees are created from the specified BaseRef
12+
- If you want to reset a worktree, manually delete it first or use tear-down-agents.ps1
13+
914
Typical use:
1015
$env:FW_WORKTREES_ROOT = "C:\dev\FieldWorks\worktrees"
1116
1217
# Create agents based on current branch (default FieldWorks.proj build):
1318
.\scripts\spin-up-agents.ps1 -RepoRoot "C:\dev\FieldWorks" -Count 3
1419
15-
# Or specify a different base branch:
20+
# Or specify a different base branch (only affects NEW worktrees):
1621
.\scripts\spin-up-agents.ps1 -RepoRoot "C:\dev\FieldWorks" -Count 3 -BaseRef origin/release/9.3
1722
1823
# To prevent VS Code from opening automatically:
1924
.\scripts\spin-up-agents.ps1 -RepoRoot "C:\dev\FieldWorks" -Count 3 -SkipOpenVSCode
2025
21-
# To forcibly clean orphaned worktree directories without prompting:
22-
.\scripts\spin-up-agents.ps1 -RepoRoot "C:\dev\FieldWorks" -Count 3 -ForceCleanup
26+
# NOTE: -ForceCleanup parameter removed - use tear-down-agents.ps1 instead
2327
#>
2428

2529
[CmdletBinding()]
@@ -34,7 +38,6 @@ param(
3438
[switch]$SkipVsCodeSetup,
3539
[switch]$ForceVsCodeSetup,
3640
[switch]$SkipOpenVSCode,
37-
[switch]$ForceCleanup,
3841
[string]$ContainerMemory = "4g"
3942
)
4043

@@ -232,22 +235,8 @@ function Ensure-Image {
232235
}
233236
}
234237

235-
function Reset-AgentWorktree {
236-
param(
237-
[Parameter(Mandatory=$true)][string]$WorktreePath,
238-
[Parameter(Mandatory=$true)][string]$ResetRef
239-
)
240-
241-
if (-not (Test-Path -LiteralPath $WorktreePath)) { return }
242-
243-
Push-Location $WorktreePath
244-
try {
245-
Invoke-GitSafe @('reset','--hard',$ResetRef) -Quiet
246-
Invoke-GitSafe @('clean','-xfd') -Quiet
247-
} finally {
248-
Pop-Location
249-
}
250-
}
238+
# Reset-AgentWorktree function removed - we never modify existing worktrees
239+
# Existing worktrees are left untouched to prevent data loss
251240

252241
function Clear-AgentDirectory {
253242
param(
@@ -298,30 +287,39 @@ function Ensure-Worktree {
298287
}
299288

300289
if ($isRegistered -and $dirExists -and -not $isEmpty) {
301-
# Worktree is registered and has content - reset to requested base
302-
Write-Host "Worktree exists and is registered: $target"
303-
Reset-AgentWorktree -WorktreePath $target -ResetRef $BaseRef
290+
# Worktree exists with content - NEVER modify it to prevent data loss
291+
Write-Host "Worktree already exists: $target (skipping - will not modify existing worktree)"
292+
Write-Host " Branch: $branch (current state preserved)"
293+
# Skip to end - use existing worktree as-is
294+
$resolvedTarget = (Resolve-Path -LiteralPath $target).Path
295+
return @{ Branch = $branch; Path = $resolvedTarget; Skipped = $true }
304296
} elseif ($isRegistered -and (-not $dirExists -or $isEmpty)) {
305297
# Worktree is registered but directory is missing or empty - repair it
306298
Write-Host "Repairing worktree $target (directory missing or empty)..."
307299
Remove-GitWorktreePath -WorktreePath $thisWorktree.RawPath
308300
Prune-GitWorktreesNow
309301
$isRegistered = $false
310302
$branchWorktree = $null
311-
} elseif (-not $isRegistered -and $dirExists) {
312-
if (-not $ForceCleanup) {
313-
throw "Directory $target exists but is not a registered worktree. Close any VS Code windows, run tear-down with -RemoveWorktrees, or rerun spin-up with -ForceCleanup to reset it."
314-
}
315-
316-
Write-Host "Directory $target exists but is not registered; reinitializing worktree in place (contents will be reset)."
317-
try {
318-
Detach-GitWorktreeMetadata -WorktreePath $target | Out-Null
319-
} catch {
320-
$err = $_
321-
Write-Warning "Failed to remove stale git metadata from ${target}: $err"
322-
}
323-
324-
Clear-AgentDirectory -AgentPath $target
303+
} elseif (-not $isRegistered -and $dirExists -and -not $isEmpty) {
304+
# Directory exists with content but not a worktree - ERROR out
305+
throw @"
306+
Directory $target exists but is not a registered git worktree.
307+
308+
This could indicate:
309+
1. Leftover files from a previous worktree that wasn't cleaned up properly
310+
2. Manual files placed in the worktrees directory
311+
3. A corrupted worktree registration
312+
313+
To fix this:
314+
- OPTION 1 (preserve content): Move the directory elsewhere, then rerun this script
315+
- OPTION 2 (discard content): Delete the directory manually, then rerun this script
316+
- OPTION 3 (force cleanup): Run: .\scripts\tear-down-agents.ps1 -RemoveWorktrees
317+
318+
NEVER use -ForceCleanup as it will destroy your work without warning.
319+
"@
320+
} elseif (-not $isRegistered -and $dirExists -and $isEmpty) {
321+
# Directory exists but empty and not registered - safe to recreate
322+
Write-Host "Directory $target exists but is empty; will create worktree here."
325323
}
326324

327325
# Create worktree if needed
@@ -336,9 +334,8 @@ function Ensure-Worktree {
336334
$addArgs += $target
337335

338336
if (Test-GitBranchExists -Branch $branch) {
339-
Write-Host "Resetting $branch to $BaseRef before reuse..."
340-
Reset-GitBranchToRef -Branch $branch -Ref $BaseRef
341-
Write-Host "Creating worktree $target with existing branch $branch..."
337+
Write-Host "Creating worktree $target with existing branch $branch (preserving current branch state)..."
338+
Write-Host " Note: Branch will remain at its current commit; use 'git merge' or 'git reset' in the worktree if you want to sync with $BaseRef"
342339
$addArgs += $branch
343340
} else {
344341
Write-Host "Creating worktree $target with new branch $branch from $BaseRef..."
@@ -353,10 +350,7 @@ function Ensure-Worktree {
353350
}
354351
Ensure-RelativeGitDir -WorktreePath $target -RepoRoot $RepoRoot -WorktreeName "agent-$Index"
355352
$resolvedTarget = (Resolve-Path -LiteralPath $target).Path
356-
357-
if (-not $isRegistered) {
358-
Reset-AgentWorktree -WorktreePath $resolvedTarget -ResetRef $BaseRef
359-
}
353+
# Never reset worktrees - new worktrees are created at correct ref, existing ones are preserved
360354
} finally {
361355
Pop-Location
362356
}
@@ -595,6 +589,20 @@ $repoVsCodeSettings = Get-RepoVsCodeSettings -RepoRoot $RepoRoot
595589
$agents = @()
596590
for ($i=1; $i -le $Count; $i++) {
597591
$wt = Ensure-Worktree -Index $i
592+
593+
if ($wt.Skipped) {
594+
Write-Host "Agent-$i: Using existing worktree (no changes made)"
595+
$agents += [pscustomobject]@{
596+
Index = $i
597+
Worktree = $wt.Path
598+
Branch = $wt.Branch
599+
Container = "(existing)"
600+
Theme = "(preserved)"
601+
Status = "Skipped - existing worktree preserved"
602+
}
603+
continue
604+
}
605+
598606
$ct = Ensure-Container -Index $i -AgentPath $wt.Path -RepoRoot $RepoRoot -WorktreesRoot $WorktreesRoot
599607
Write-AgentConfig -AgentPath $wt.Path -SolutionRelPath $SolutionRelPath -Container $ct -RepoRoot $RepoRoot
600608
$colors = Get-AgentColors -Index $i
@@ -617,6 +625,7 @@ for ($i=1; $i -le $Count; $i++) {
617625
Branch = $wt.Branch
618626
Container = $ct.Name
619627
Theme = $colors.Name
628+
Status = "Ready"
620629
}
621630
}
622631

scripts/tear-down-agents.ps1

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
<#
22
Stop and remove fw-agent-* containers and optionally clean up agents/* caches.
3-
Worktrees themselves are preserved for reuse (we still warn about uncommitted changes
4-
unless -ForceRemoveDirty is specified).
3+
Worktrees themselves are preserved for reuse.
4+
5+
SAFETY: This script will ERROR and refuse to remove worktrees that have uncommitted
6+
changes, preventing accidental data loss. Use -ForceRemoveDirty only if you're certain
7+
you want to discard uncommitted work.
58
69
# Examples
710
# .\scripts\tear-down-agents.ps1 -RepoRoot "C:\dev\FieldWorks"
811
# (Stops all fw-agent-* containers but leaves worktrees/branches.)
912
#
1013
# .\scripts\tear-down-agents.ps1 -RepoRoot "C:\dev\FieldWorks" -RemoveWorktrees -RemoveNuGetCaches
1114
# (Also removes agents/agent-* worktrees, git branches, and per-agent NuGet caches.)
15+
# (Will ERROR if any worktree has uncommitted changes.)
1216
#
1317
# .\scripts\tear-down-agents.ps1 -RepoRoot "C:\dev\FieldWorks" -RemoveWorktrees -ForceRemoveDirty
14-
# (Removes worktrees without prompting even if uncommitted changes exist.)
18+
# (⚠️ DANGEROUS: Removes worktrees even with uncommitted changes - DATA LOSS!)
1519
#>
1620

1721
[CmdletBinding()]
@@ -191,9 +195,22 @@ $removeCaches = $RemoveWorktrees -or $RemoveNuGetCaches
191195
if (Test-Path -LiteralPath $wtPath) {
192196
$hasChanges = Test-WorktreeHasUncommittedChanges -WorktreePath $wtPath
193197
if ($hasChanges -and -not $ForceRemoveDirty) {
194-
Write-Warning "Worktree agent-$i has uncommitted changes; content will remain but tracking will be detached (use -ForceRemoveDirty to skip this warning)."
198+
throw @"
199+
Worktree agent-$i has uncommitted changes at: $wtPath
200+
201+
To protect your work, tear-down will NOT remove this worktree.
202+
203+
Options:
204+
1. Commit or stash your changes in the worktree, then re-run tear-down
205+
2. Push your changes to a remote branch for safekeeping
206+
3. Use -ForceRemoveDirty to override (WARNING: This will DELETE uncommitted work)
207+
208+
To check what's uncommitted:
209+
cd '$wtPath'
210+
git status
211+
"@
195212
}
196-
Write-Host "Detaching worktree agent-$i while leaving $wtPath on disk."
213+
Write-Host "Detaching worktree agent-$i (no uncommitted changes detected)."
197214
} else {
198215
Write-Host "Worktree agent-$i not found on disk; only detaching branch metadata."
199216
}

0 commit comments

Comments
 (0)