Commands/Get-GitPrompt.ps1
|
function Get-GitPrompt { <# .SYNOPSIS Git Prompt .DESCRIPTION Git friendly PowerShell prompt. Shows current branch, modified, untracked, added, and removed files. #> [Alias('git.prompt')] param( # The icon used for git branches. # By default `⑆` (OCR Branch Bank Identification [0x2446]) [string] $BranchIcon = '⑆', # The prompt icon after a successful command. # By default, `≻` (Succeeds [0x227b]) [string] $SuccessIcon = '≻', #'⊃', # '>', "➫", "⮚" # By default, `≻` (Does not Succeed [0x2281]) [string] $ErrorIcon = '⊁', # The home icon. # This is displayed when the path is # beneath the user home directory. # By default: `⌂` (House [0x2302]) [string] $HomeIcon = '⌂', [string] $BranchOpenIcon = '⟦', [string] $BranchCloseIcon = '⟧', [string] $JobIcon = '⏲', # The icon used for slash. # This will separate directories. # By default ` ⹊ ` (Dotted Solidus [0x2e4a]) [string] $Slash = " ⹊ ", # '⹊' #, '/' # The style to use when things succeed [string] $SuccessStyle = $PSStyle.Foreground.Green, # The style to use when things fail [string] $ErrorStyle = $PSStyle.Foreground.Red, # The style to use when warning [string] $WarningStyle = $PSStyle.Foreground.Yellow, # The style to use when cyan [string] $InfoStyle = $PSStyle.Foreground.Cyan, [string] $UnknownStyle = $PSStyle.Foreground.Magenta, # The style to use when bolding text. [string] $BoldStyle = $psStyle.Bold, [string] $BoldStyleOff = $psStyle.Bold.Off, # The string used to reset styles. [string] $ResetStyle = $PSStyle.Reset, # If set, will display the history ID [switch] $ShowHistoryId ) # Capture if the last statement worked # (if we do not do this first, it will not be accurate) $worked = $? # If we have a nested prompt level greater than one # we are debugging or in another nested context. # Performance will be more important in this case if ($NestedPromptLevel -gt 1) { # so simply multiply our prompt icon by the nested prompt level return ($SuccessIcon * $NestedPromptLevel) } # We may want to pad the lines $LeftPadding = # If we are not showing the history ID if (-not $ShowHistoryId) { ' ' # we can padd with a single space } else { # Otherwise, pad with the history id length. ' ' * "$($MyInvocation.HistoryId)".Length } # Determine if nothing happened # (someone just pressing `ENTER` will not increment the history ID) $isNoOp = $script:LastHistoryId -eq $MyInvocation.HistoryId # Determine our display path. $displayPath = # If we're somewhere beneath `$home` if ("$pwd".StartsWith("$home")) { # Use the home icon $HomeIcon, # and the path beneath home "$pwd".Substring( "$home".Length ) -replace # Fix slashes to unix style '[\\/]+','/' -replace # then replace any duplicate slashes '/+','/' -join '' } else { # Otherwise, just fix slashes to unix style. "$pwd" -replace '[\\/]+', '/' } # Replace the last slash, and then replace slashes with our slash icon $displayPath = $displayPath -replace '/{0,}$' -replace '/+',$Slash # If nothing happened if ($isNoOp) { # Output our prompt return "$( # If the last command worked, use the info style; if ($worked) { $InfoStyle } # otherwise, use the error style. else { $ErrorStyle } )$( # If the last command worked, use the success icon; if ($worked) {$SuccessIcon} # otherwise, use the error icon. else { $ErrorIcon } )$($LeftPadding)$( $ResetStyle )" } else { # If something happened, update our history ID. $script:LastHistoryId = $MyInvocation.HistoryId } # Next we want to check for git remotes. # Cache any remote paths we find if (-not $script:GitRemotePath) { $script:GitRemotePath = [Ordered]@{} } # If we do not know the remote for the current directory if (-not $script:GitRemotePath.Contains("$pwd")) { # get the remote $script:GitRemotePath["$pwd"] = git remote -ErrorAction Ignore | git remote show } # There might be more than one remote. We want the first. $gitRemoteUrl = @($script:GitRemotePath["$pwd"].RemoteUrls.Fetch)[0] $repoUrl = $gitRemoteUrl -replace '^ssh://', 'https://' -replace '\.git$' -replace '\.wiki$', '/wiki' -as [uri] # Now git our status $gitStatus = if ([IO.Directory]::Exists($pwd)) { git status . -WarningAction Ignore } else { $null } # If somehow that failed if (-not $gitStatus) { # If there was no status url, we're not in a repository. # Output our prompt return "$( # Show the history ID (or not) if ($ShowHistoryId) { "$($myInvocation.HistoryId) " } else { '' } )$( # Followed by the display path $displayPath ) $( # Followed by the appropriate style if ($worked) { $InfoStyle } else { $ErrorStyle } )$( # and icon (based off of the success/failure of the last command) if ($worked) {$SuccessIcon} else {$ErrorIcon } )$( $ResetStyle )$(' ' * 1)" } # Collect the files that have been staged for addition $stagedModified = @($gitStatus.Added) # and deletion. $stagedDeletes = @($gitStatus.Removed) # Collect the files that have been locally modified $modified = @($gitStatus.Modified) # and deleted. $deleted = @($gitStatus.Deleted) # Last but not least, collect untracked files. $untracked = @($gitStatus.Untracked) # Create a link to our repo $LinkToRepo = $PSStyle.FormatHyperlink( # This will make our file tree link to the repo tree $displayPath, "$repoUrl" ) # Get our git relative path. $gitRelativePath = "$pwd".Substring($gitStatus.GitRoot.Length) -replace '[\\/]', '/' # Prepare the branch info portion of our prompt $gitBranchInfo = @( # If the branch is in `main`, `master`, or `latest`, # you might want to check out. if ($GitStatus.BranchName -in 'main', 'master', 'latest') { # So we will display that as a warning $WarningStyle } else { # If we are behind the upstream if ($gitCommitsBehind -or # or have diverged, we're in trouble. $gitStatus.Status -match 'diverged') { $ErrorStyle # and should show the repo using the error style. } elseif ($gitStatus.Staged) { # If we've got commits staged for addition $SuccessStyle # we're green / good to go. } else { # Otherwise, we're just informing you about the branch name. $InfoStyle } } # The rest of our git branch info is: $BoldStyle # * Bold $BranchIcon # * Branch Icon $LeftPadding # * LeftPadding $BranchOpenIcon # * BranchOpenIcon $gitStatus.BranchName # * `$gitStatus.BranchName` $BranchCloseIcon # * Close our branch. $BoldStyleOff # * Bold Off $ResetStyle # * Reset Style ) # Now we just have to hyperlink things $gitInfo = @( # If we have a repo url if ($repoUrl) { # make a hyperlink to it and display the branch info $PSStyle.FormatHyperlink( "$($gitBranchInfo -join '')", "$repoUrl/tree/$($gitStatus.BranchName)$GitRelativePath" ) } else { $gitBranchInfo -join '' } # If we are in a git subdirectory if ($gitRelativePath) { # hyperlink to this exact spot in the tree. $PSStyle.FormatHyperlink( ($gitRelativePath -replace '[\\/]', $Slash), "$repoUrl/tree/$($gitStatus.BranchName)$GitRelativePath" ) } # If the branches have diverged if ($gitStatus.Status -match 'diverged') { # Make sure we notice. "$($ErrorStyle) diverged!$($($ResetStyle))" } # If we are behind if ($gitStatus.Behind -gt 0) { # make sure we notice. "$($ErrorStyle)(-$($gitStatus.Behind))$($ResetStyle)" } # If we are ahead if ($gitStatus.Ahead -gt 0) { # make sure we notice and feel successful. "$($SuccessStyle)(+$($gitStatus.Ahead))$($ResetStyle)" } # If we have any staged files, if ($stagedModified) { # make sure we know how many have been added. $SuccessStyle, " $($stagedModified.Length) added", $ResetStyle } # If we have any deleted files, if ($stagedDeletes) { # make sure we know how many have been removed. $ErrorStyle, " $($stagedDeletes.Length) removed", $ResetStyle } # If we have any modified files if ($modified) { # Display the info. $InfoStyle, " $($modified.Length) modified", $ResetStyle } # If we have any deleted files if ($deleted) { # Make sure we know. $ErrorStyle, " $($deleted.Length) deleted", $ResetStyle } # If we have any untracked files if ($untracked) { $PSStyle.Foreground.Magenta, " $($untracked.Count) untracked", $ResetStyle } $ResetStyle ) -join '' # ⮚ ⟣ ⤳ ⟫ ⟩ "$( if (-not $ShowHistoryId) { '' } else { "$($myInvocation.HistoryId) " } )$LinkToRepo", "$gitInfo", "$( if ($worked) { $InfoStyle } else { $ErrorStyle } )$( if ($worked) {$SuccessIcon} else {$ErrorIcon } )$LeftPadding$( $ResetStyle )" -join [Environment]::NewLine } |