Interactive Git commands for Far Manager + FarNet.PowerShellFar
<# | |
Interactive Git commands for Far Manager + FarNet.PowerShellFar | |
<https://gist.github.com/nightroman/1d4806e4bcd2fae1b852> | |
HOW TO USE | |
Create a directory FarGit in any of $env:PSModulePath. As far as it is for | |
PowerShellFar only consider %FARHOME%\FarNet\Modules\PowerShellFar\Modules | |
Download FarGit.psm1 to the created directory FarGit. | |
Import the module and call its commands in PowerShellFar. | |
Or add these commands to your Far Manager user menu: | |
h: Show help (user menu needs Far 3.0.4330+) | |
ps: Import-Module FarGit; Show-FarGitHelp # | |
s: Show status | |
ps: Import-Module FarGit; Show-FarGitStatus # | |
r: Go to root | |
ps: Import-Module FarGit; Set-FarGitRoot # | |
e: Edit config | |
ps: Import-Module FarGit; Edit-FarGitConfig # | |
: Branch | |
{ | |
b: Switch branch | |
ps: Import-Module FarGit; Invoke-FarGitCheckoutBranch # | |
c: Create branch | |
ps: Import-Module FarGit; Invoke-FarGitBranchCreate # | |
m: Merge branch | |
ps: Import-Module FarGit; Invoke-FarGitMergeBranch # | |
d: Delete branch (safe) | |
ps: Import-Module FarGit; Invoke-FarGitBranchDelete # | |
k: Delete branch (force) | |
ps: Import-Module FarGit; Invoke-FarGitBranchDelete -Force # | |
r: Rename current branch | |
ps: Import-Module FarGit; Invoke-FarGitBranchRename # | |
} | |
: Stash | |
{ | |
a: Apply stash | |
ps: Import-Module FarGit; Invoke-FarGitStashApply # | |
p: Pop stash | |
ps: Import-Module FarGit; Invoke-FarGitStashPop # | |
d: Drop stash | |
ps: Import-Module FarGit; Invoke-FarGitStashDrop # | |
s: Show stash | |
ps: Import-Module FarGit; Invoke-FarGitStashShow # | |
} | |
#> | |
$ErrorActionPreference = 'Stop' | |
### Exported commands. | |
<# | |
.Synopsis | |
Shows git help for the git command. | |
.Description | |
The command shows HTML help the git command found in the editor, dialog, or | |
command line. If 'git command' is not found then the main page is shown. | |
#> | |
function Show-FarGitHelp { | |
if ($Far.Line.Text -match '\bgit\s+(\S+)') { | |
$null = Invoke-Git {git ($matches[1]) --help} | |
} | |
else { | |
Invoke-Item -LiteralPath "$(Invoke-Git {git --html-path})\index.html" | |
} | |
} | |
<# | |
.Synopsis | |
Opens the current config file in the editor. | |
#> | |
function Edit-FarGitConfig { | |
try { | |
Assert-Git | |
$root = Invoke-Git {git rev-parse --git-dir} | |
Open-FarEditor $root\config | |
} | |
catch { | |
Show-Error | |
} | |
} | |
<# | |
.Synopsis | |
Sets the repository root current in the panel. | |
#> | |
function Set-FarGitRoot { | |
try { | |
Assert-Git | |
$root = Invoke-Git {git rev-parse --show-toplevel} | |
$Far.Panel.CurrentDirectory = $root | |
} | |
catch { | |
Show-Error | |
} | |
} | |
<# | |
.Synopsis | |
Shows changed files and navigates to a selected. | |
.Description | |
The command shows the list of changed files. | |
Select a file to navigate to in the active panel. | |
#> | |
function Show-FarGitStatus { | |
try { | |
Assert-Git | |
$info = Invoke-Git {git status --porcelain} | ConvertFrom-Octet | .{process{ | |
($_ -replace '^ ', '-') -replace '^(.) ', '$1-' | |
}} | Out-FarList -Title "On branch $(Get-BranchCurrent)" | |
if (!$info) {return} | |
if ($info -notmatch '^(..)\s+"?(.+?)"?(?:\s+->\s+"?(.+?)"?)?$') {throw "Unexpected status format: $info"} | |
$path = if ($matches[3]) {$matches[3]} else {$matches[2]} | |
$Far.Panel.GoToPath("$(Invoke-Git {git rev-parse --show-toplevel})\$path") | |
} | |
catch { | |
Show-Error | |
} | |
} | |
<# | |
.Synopsis | |
Switches to another branch. | |
.Description | |
The command shows the branch list. | |
Select a branch to be set the current. | |
If there are no branches the command prompts to create a new branch. | |
#> | |
function Invoke-FarGitCheckoutBranch { | |
try { | |
Assert-Git | |
if (!($branch = Get-BranchOther)) { | |
if (0 -eq (Show-FarMessage 'No other branches. Create?' -Buttons YesNo)) { | |
Invoke-FarGitBranchCreate | |
} | |
return | |
} | |
$branch = $branch | Out-FarList -Title "Switch from $(Get-BranchCurrent)" | |
if ($branch) { | |
Invoke-Git {git checkout $branch} | |
} | |
} | |
catch { | |
Show-Error | |
} | |
} | |
<# | |
.Synopsis | |
Merges another branch into the current. | |
.Description | |
The command shows the branch list. | |
Select a branch to merge into the current. | |
#> | |
function Invoke-FarGitMergeBranch { | |
try { | |
Assert-Git | |
if (!($branch = Get-BranchOther)) { | |
Show-FarMessage 'No other branches.' | |
return | |
} | |
$branch = $branch | Out-FarList -Title "Merge to $(Get-BranchCurrent)" | |
if ($branch) { | |
Invoke-Git {git merge $branch} | |
} | |
} | |
catch { | |
Show-Error | |
} | |
} | |
<# | |
.Synopsis | |
Deletes another branch. | |
.Parameter Force | |
Tells to delete a branch irrespective of its merged status. | |
.Description | |
The command shows the branch list. | |
Select a branch to be deleted. | |
By default the branch must be fully merged in its upstream branch. Use the | |
switch Force in order to delete a branch irrespective of its merged status. | |
#> | |
function Invoke-FarGitBranchDelete { | |
[CmdletBinding()] | |
param( | |
[switch]$Force | |
) | |
try { | |
Assert-Git | |
if (!($branch = Get-BranchOther)) { | |
Show-FarMessage 'No other branches.' | |
return | |
} | |
$branch = $branch | Out-FarList -Title 'Delete branch' | |
if (!$branch) {return} | |
#?? | |
if ($branch -ceq 'master') { | |
Show-FarMessage 'Cannot delete master.' | |
return | |
} | |
$message = if ($Force) {"Delete branch $branch (force)"} else {"Delete branch $branch"} | |
if (0 -ne (Show-FarMessage $message -Buttons YesNo -IsWarning:$Force)) {return} | |
if ($Force) { | |
Invoke-Git {git branch -D $branch} | |
} | |
else { | |
Invoke-Git {git branch -d $branch} | |
} | |
} | |
catch { | |
Show-Error | |
} | |
} | |
<# | |
.Synopsis | |
Creates a new branch and switches to it. | |
.Description | |
The command shows the input box. | |
Enter the new branch name and press enter. | |
#> | |
function Invoke-FarGitBranchCreate { | |
try { | |
Assert-Git | |
$currentBranch = Get-BranchCurrent | |
$newBranch = if ($currentBranch -eq 'master') {''} else {$currentBranch} | |
$newBranch = $Far.Input('Branch name', 'GitBranch', "New branch from $currentBranch", $newBranch) | |
if ($newBranch) { | |
Invoke-Git {git checkout -b $newBranch} | |
} | |
} | |
catch { | |
Show-Error | |
} | |
} | |
<# | |
.Synopsis | |
Renames the current branch. | |
.Description | |
The command shows the input box with the current branch name. | |
Enter the new branch name and press enter. | |
#> | |
function Invoke-FarGitBranchRename { | |
param ( | |
[switch]$DatePrefix | |
) | |
try { | |
Assert-Git | |
$currentBranch = Get-BranchCurrent | |
$newBranch = if ($DatePrefix -and $currentBranch -notmatch '^\d\d\d\d\d\d-') { | |
'{0:yyMMdd}-{1}' -f (Get-Date), $currentBranch | |
} | |
else { | |
$currentBranch | |
} | |
$newBranch = $Far.Input('Branch name', 'GitBranch', "Rename branch $currentBranch", $newBranch) | |
if ($newBranch) { | |
Invoke-Git {git branch -m $newBranch} | |
} | |
} | |
catch { | |
Show-Error | |
} | |
} | |
<# | |
.Synopsis | |
Invokes git stash apply. | |
.Description | |
The command shows the stash list. | |
Select a stash to be applied. | |
#> | |
function Invoke-FarGitStashApply { | |
try { | |
Assert-Git | |
if ($stash = Select-StashName 'Apply stash') { | |
Invoke-Git {git stash apply $stash} | |
} | |
} | |
catch { | |
Show-Error | |
} | |
} | |
<# | |
.Synopsis | |
Invokes git stash drop. | |
.Description | |
The command shows the stash list. | |
Select a stash to be dropped. | |
#> | |
function Invoke-FarGitStashDrop { | |
try { | |
Assert-Git | |
if ($stash = Select-StashName 'Drop stash') { | |
Invoke-Git {git stash drop $stash} | |
} | |
} | |
catch { | |
Show-Error | |
} | |
} | |
<# | |
.Synopsis | |
Invokes git stash pop. | |
.Description | |
The command shows the stash list. | |
Select a stash to be popped. | |
#> | |
function Invoke-FarGitStashPop { | |
try { | |
Assert-Git | |
if ($stash = Select-StashName 'Pop stash') { | |
Invoke-Git {git stash pop $stash} | |
} | |
} | |
catch { | |
Show-Error | |
} | |
} | |
<# | |
.Synopsis | |
Invokes git stash show. | |
.Description | |
The command shows the stash list. | |
Select a stash to show its patch details. | |
#> | |
function Invoke-FarGitStashShow { | |
try { | |
Assert-Git | |
if ($stash = Select-StashName 'Show stash') { | |
Invoke-Git {git stash show -p $stash} | |
} | |
} | |
catch { | |
Show-Error | |
} | |
} | |
### Internal commands. | |
# Decodes git strings with octets. | |
filter ConvertFrom-Octet { | |
if (!$_.Contains('\')) { | |
return $_ | |
} | |
$bytes = [System.Collections.ArrayList]@() | |
foreach($m in ([regex]'\\\d\d\d|.').Matches($_)) { | |
$t = $m.ToString() | |
if (($b = $t[0]) -eq '\') { | |
$b = [Convert]::ToInt32($t.Substring(1), 8) | |
} | |
$null = $bytes.Add($b) | |
} | |
[System.Text.Encoding]::UTF8.GetString($bytes) | |
} | |
# Gets the current branch name. | |
function Get-BranchCurrent { | |
foreach($branch in Invoke-Git {git branch --list --quiet}) { | |
if ($branch -notmatch '^(\*)?\s*(\S+)') {throw "Unexpected branch format: '$branch'."} | |
if ($matches[1]) { | |
return $matches[2] | |
} | |
} | |
} | |
# Gets branch names except the current. | |
function Get-BranchOther { | |
foreach($branch in Invoke-Git {git branch --list --quiet}) { | |
if ($branch -notmatch '^(\*)?\s*(\S+)') {throw "Unexpected branch format: '$branch'."} | |
if (!$matches[1]) { | |
$matches[2] | |
} | |
} | |
} | |
# Gets stash strings. | |
function Get-StashText { | |
Invoke-Git {git stash list} | |
} | |
# Gets the stash name from a string. | |
function Get-StashName { | |
[CmdletBinding()] | |
param( | |
$Stash | |
) | |
if ($Stash -notmatch '^(stash@{\d+})') {throw "Unexpected stash format: $Stash."} | |
$matches[1] | |
} | |
# Shows the stash list and gets the selected stash name. | |
function Select-StashName { | |
[CmdletBinding()] | |
param( | |
$Title | |
) | |
if (!($stash = Get-StashText)) { | |
Show-FarMessage 'There are no stashes.' | |
return | |
} | |
$stash = $stash | Out-FarList -Title $Title | |
if (!$stash) {return} | |
Get-StashName $stash | |
} | |
# Invokes a native command and checks for $LASTEXITCODE. | |
function Invoke-Git($Command) { | |
${*OutputEncoding} = [Console]::OutputEncoding | |
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 | |
try { | |
. $Command | |
if ($LASTEXITCODE) {Write-Error "Exit code: $LASTEXITCODE. Command: $Command" -ErrorAction 1} | |
} | |
finally { | |
[Console]::OutputEncoding = ${*OutputEncoding} | |
} | |
} | |
# Shows the error $_. | |
function Show-Error { | |
Write-Host $_ -ForegroundColor Red | |
} | |
# Throw if not a repo. | |
function Assert-Git { | |
[CmdletBinding()]param() | |
$path = $PSCmdlet.GetUnresolvedProviderPathFromPSPath('.') | |
do { | |
if (Test-Path "$path\.git") {return} | |
} while($Path = Split-Path $Path) | |
throw 'Not a git repository.' | |
} | |
Export-ModuleMember -Function *-FarGit* |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment