Skip to content

Instantly share code, notes, and snippets.

@electricessence
Last active January 16, 2025 16:39
Show Gist options
  • Save electricessence/0a48e283f585837b0c39ff6762b9c804 to your computer and use it in GitHub Desktop.
Save electricessence/0a48e283f585837b0c39ff6762b9c804 to your computer and use it in GitHub Desktop.
Git-Worktree Function for Powershell (add to $PROFILE)
function Git-Worktree-List {
git worktree list
}
function Copy-UntrackedFiles {
param (
[string]$SourcePath,
[string]$TargetPath
)
Write-Host ''
Write-Host $SourcePath
# Check for changed, untracked and ignored files
$changes = git -C $SourcePath status --porcelain --ignored | ForEach-Object { $_.Substring(3) }
if (-not $changes) {
Write-Host "No uncommitted or untracked files." -ForegroundColor Green
return
}
# Get the count of untracked and modified files
$count = $changes.Count
Write-Host "Copying [$count] uncommitted or untracked folders/files." -ForegroundColor Yellow
# Copy untracked and modified files
$changes | ForEach-Object -Parallel {
$sourceFile = Join-Path $using:SourcePath $_
$targetFile = Join-Path $using:TargetPath $_
$targetDir = Split-Path -Parent $targetFile
if (-not (Test-Path -Path $targetDir)) {
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
}
Copy-Item -Path $sourceFile -Destination $targetFile -Force
Write-Host $_ # Comment this out if you don't want to see the copied folders/files.
}
Write-Host "Done." -ForegroundColor Green
# Recurse through each submodule
git -C $SourcePath submodule --quiet foreach 'echo $sm_path' | ForEach-Object {
$sp = Join-Path $SourcePath $_
$tp = Join-Path $TargetPath $_
Copy-UntrackedFiles $sp $tp
}
}
function Git-Worktree {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]$WorktreeName,
[Parameter(Mandatory=$false)]
[switch]$Mirror,
[Parameter(Mandatory=$false)]
[switch]$Delete,
[Parameter(Mandatory=$false)]
[switch]$Force
)
# Find the root of the git repository
$dotGitpath = git rev-parse --path-format=absolute --git-common-dir #includes '.git' at the end
$gitRoot = Split-Path -Path $dotGitpath -Parent
if ($LASTEXITCODE -ne 0 -or -not $gitRoot) {
Write-Error "Not inside a git repository or unable to find the git repository root."
return
}
# Get the branch name that the root is on.
$currentRootBranch = git -C $gitRoot rev-parse --abbrev-ref HEAD
if($WorktreeName -eq $currentRootBranch) {
Write-Host "The root is already on branch '$WorktreeName'."
# Check if we are on the root. If not, switch to the root.
if ($PWD.Path -ne $gitRoot) {
Set-Location -Path $gitRoot
Write-Host '===> Switched to the root.' -ForegroundColor Yellow
}
return
}
# Before potentially switching from one worktree to another, get the current worktree path
$currentWorktreePath = git rev-parse --show-toplevel
$currentWorktreePath = $currentWorktreePath -replace '\\', '/'
# Create the worktree path
$worktreePath = Join-Path $gitRoot '.git' '.wt' $WorktreeName
# Normalize the path to compare with existing worktree paths
$normalizedWorktreePath = $worktreePath -replace '\\', '/'
if ($Delete) {
if ($currentWorktreePath -like $normalizedWorktreePath) {
Set-Location -Path $gitRoot
Write-Host '===> Switched to root' -ForegroundColor Yellow
}
try {
if (Test-Path -Path $worktreePath) {
if($Force) {
git worktree remove $worktreePath --force
}
else {
$changes = git -C $worktreePath status --porcelain
if ($changes) {
Write-Host 'Uncommitted changes found.' -ForegroundColor Red
Write-Host 'Use -Force if you are sure you want to delete.' -ForegroundColor Red
return
}
git worktree remove $worktreePath
}
}
else {
if($Force) {
git worktree remove $WorktreeName --force
}
else {
$changes = git -C $worktreePath status --porcelain
if ($changes) {
Write-Host 'Uncommitted changes found.' -ForegroundColor Red
Write-Host 'Use -Force if you are sure you want to delete.' -ForegroundColor Red
return
}
git worktree remove $WorktreeName
}
}
if ($LASTEXITCODE -ne 0) {
Write-Host 'Could not remove.' -ForegroundColor Red
return
}
Write-Host "Worktree '$WorktreeName' has been removed." -ForegroundColor Yellow
} catch {
Write-Error "Error: Failed to remove worktree for branch '$WorktreeName' at '$worktreePath'."
}
return
}
# Check to see if the current worktree is already on the branch
$activeBranch = git rev-parse --abbrev-ref HEAD
if ($activeBranch -like $WorktreeName) {
Write-Host "Already on branch: $activeBranch"
return
}
# Get the list of existing worktrees
$existingWorktreePaths = git worktree list --porcelain | Where-Object { $_ -match '^worktree (.+)' } | ForEach-Object {
$Matches[1] -replace '\\', '/'
}
$switched = $false
foreach ($existingWorktreePath in $existingWorktreePaths) {
# Matching worktree?
if ($existingWorktreePath -like $normalizedWorktreePath) {
Set-Location -Path $existingWorktreePath
Write-Host "===> Switched to worktree: $WorktreeName" -ForegroundColor Yellow
$worktreePath = $existingWorktreePath
$switched = $true
break
}
$wtBranch = git -C $existingWorktreePath rev-parse --abbrev-ref HEAD
if ($LASTEXITCODE -ne 0) {
continue
}
# Matching branch?
if($wtBranch -like $WorktreeName) {
Write-Host "Found branch '$WorktreeName' in worktree:"
Set-Location -Path $existingWorktreePath
Write-Host "===> Switched to: $existingWorktreePath" -ForegroundColor Yellow
$worktreePath = $existingWorktreePath
return
}
}
if (-not $switched) {
try {
git worktree add $WorktreePath
if ($LASTEXITCODE -ne 0) {
throw "Could not add new worktree for branch '$WorktreeName' at '$WorktreePath'."
}
Write-Host "Worktree for branch '$WorktreeName' successfully created:" -ForegroundColor Yellow
Write-Host $worktreePath -ForegroundColor Yellow
Set-Location -Path $worktreePath
}
catch {
Write-Host "Error: Failed to create worktree for branch '$WorktreeName' and path '$worktreePath'." -ForegroundColor Red
return
}
}
if ($Mirror) {
Copy-UntrackedFiles $currentWorktreePath $worktreePath -Verbose
}
}
Set-Alias worktree Git-Worktree
Set-Alias worktree-list Git-Worktree-List
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment