Skip to content

Keeping local PR worktrees current with master

scripts/dev/sync-worktrees-with-master.ps1 keeps every local PR worktree merged up with origin/master so in-flight branches don't drift and master/branch conflicts surface early instead of at merge time.

What it does

On each run it:

  1. git fetch origin master.
  2. Resolves the set of open-PR head branches via gh pr list.
  3. Walks git worktree list; for every worktree whose branch is one of those open-PR branches, merges origin/master in.

Safety rules (a sweep must never damage in-progress work):

Situation Behaviour
Branch already contains master skipped as current
Worktree has uncommitted changes skipped as skipped-dirty (untouched)
Merge hits conflicts git merge --abort, reported as conflict — left exactly as found
Clean merge merged (local only)
Open PR with no local worktree ignored (nothing to merge into)

It is a merge, never a rebase — no history rewrite, no force-push, safe for shared/pushed PR branches.

Usage

# preview only
./scripts/dev/sync-worktrees-with-master.ps1 -DryRun

# local sweep (default): merge master into clean PR worktrees
./scripts/dev/sync-worktrees-with-master.ps1

# also push clean merges back to each PR branch (triggers pr-ci per branch)
./scripts/dev/sync-worktrees-with-master.ps1 -Push

-Push is opt-in on purpose: pushing N branches kicks off N pr-ci runs on the self-hosted runners. The default keeps the work local; push the branches you care about by hand, or schedule a -Push run at a quiet hour.

Exit code is non-zero when any branch ends in conflict, so a scheduler can alert.

Automating it (Windows Scheduled Task — polling)

There is no local push webhook from GitHub, so we poll: a Scheduled Task runs the sweep every ~10 min. When origin/master hasn't moved every branch reports current and nothing is written, so the poll is cheap.

Register it (runs as the logged-on user so it inherits your git + gh creds; hidden window; logs to %TEMP%\sync-worktrees.log):

$repo = 'C:\repos\rakletv3'
$script = "$repo\scripts\dev\sync-worktrees-with-master.ps1"
$log = "$env:TEMP\sync-worktrees.log"
# Windows PowerShell 5.1 -- always at a fixed path Task Scheduler resolves. The
# store/MSIX pwsh.exe alias does NOT activate under Task Scheduler, so use 5.1
# (the script is 5.1-compatible) and let it pass -LogPath for an inspectable run.
$exe = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"

$action  = New-ScheduledTaskAction -Execute $exe -WorkingDirectory $repo `
  -Argument "-NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$script`" -LogPath `"$log`""
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) `
  -RepetitionInterval (New-TimeSpan -Minutes 10)
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable `
  -MultipleInstances IgnoreNew -ExecutionTimeLimit (New-TimeSpan -Minutes 15)

Register-ScheduledTask -TaskName 'RakletSyncWorktreesWithMaster' `
  -Action $action -Trigger $trigger -Settings $settings `
  -RunLevel Limited -Description 'Merge origin/master into open-PR worktrees every 10 min'

Inspect / remove:

Get-ScheduledTaskInfo RakletSyncWorktreesWithMaster      # last/next run + result
Get-Content $env:TEMP\sync-worktrees.log -Tail 40        # what it did
Unregister-ScheduledTask RakletSyncWorktreesWithMaster -Confirm:$false

The task runs only while the user is logged on (it needs your git/gh credentials). On the dev VM the default terminal is pinned to conhost, so -WindowStyle Hidden suppresses the window cleanly (unlike Windows Terminal, which ignores it).