Repository: dahlbyk/posh-git Branch: master Commit: bbc5ac380018 Files: 52 Total size: 357.0 KB Directory structure: gitextract_xoqawrl5/ ├── .editorconfig ├── .git-blame-ignore-revs ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ ├── powershell.yml │ └── pwsh.yml ├── .gitignore ├── .vscode/ │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── CHANGELOG.md ├── ISSUE_TEMPLATE.md ├── LICENSE.txt ├── PSScriptAnalyzerSettings.psd1 ├── README.md ├── chocolatey/ │ ├── packAndLocalInstall.ps1 │ ├── poshgit.nuspec │ ├── tests/ │ │ └── InstallChocolatey.Tests.ps1 │ └── tools/ │ ├── chocolateyInstall.ps1 │ └── chocolateyUninstall.ps1 ├── install.ps1 ├── profile.example.ps1 ├── src/ │ ├── AnsiUtils.ps1 │ ├── CheckRequirements.ps1 │ ├── ConsoleMode.ps1 │ ├── GitParamTabExpansion.ps1 │ ├── GitPrompt.ps1 │ ├── GitTabExpansion.ps1 │ ├── GitUtils.ps1 │ ├── PoshGitTypes.ps1 │ ├── TortoiseGit.ps1 │ ├── Utils.ps1 │ ├── WindowTitle.ps1 │ ├── en-US/ │ │ └── about_posh-git.help.txt │ ├── posh-git.psd1 │ └── posh-git.psm1 └── test/ ├── Ansi.Tests.ps1 ├── CheckRequirements.Tests.ps1 ├── DefaultPrompt.Tests.ps1 ├── Get-GitBranch.Tests.ps1 ├── Get-GitDirectory.Tests.ps1 ├── Get-GitStatus.Tests.ps1 ├── GitParamTabExpansion.Tests.ps1 ├── GitParamTabExpansionVsts.Tests.ps1 ├── GitPrompt.Tests.ps1 ├── GitProxyFunctionExpansion.Tests.ps1 ├── ModuleManifest.Tests.ps1 ├── Shared.ps1 ├── TabExpansion.Tests.ps1 ├── Utils.Tests.ps1 └── git-help.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # To use this file you must have EditorConfig installed for your editor. # For Visual Studio Code, install the extension: "EditorConfig for VS Code" # https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig root = true [*] indent_style = space indent_size = 4 insert_final_newline = true [*.{ps1,psd1,psm1}] trim_trailing_whitespace = true ================================================ FILE: .git-blame-ignore-revs ================================================ # Normalize line endings 4256c20d7fa3514651df10f4dbc38bcd8fa012e3 ================================================ FILE: .gitattributes ================================================ # Enable LF normalization for all files * text=auto ================================================ FILE: .github/FUNDING.yml ================================================ github: [dahlbyk, rkeithhill] ================================================ FILE: .github/workflows/powershell.yml ================================================ name: Windows PowerShell on: push: branches: [ main, master ] pull_request: branches: [ main, master ] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: strategy: fail-fast: false matrix: os: ['windows-latest'] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - name: install dependencies shell: powershell run: | "Git version: $(git --version)" "PSVersion: $($PSVersionTable.PSVersion)" "Host name: $($Host.Name)" Set-PSRepository -Name PSGallery -InstallationPolicy Trusted Install-Module Pester -MinimumVersion 5.0.0 -MaximumVersion 5.99.99 -Scope CurrentUser -Force "Pester: $(Get-Module Pester | % Version)" - name: run tests shell: powershell run: | Import-Module Pester -PassThru $ErrorActionPreference = 'Continue' $res = Invoke-Pester -Path test -Output Detailed -PassThru -ErrorAction SilentlyContinue if (!$res -or ($res.FailedCount -gt 0)) { $Error | Format-List * -Force exit 1 } ================================================ FILE: .github/workflows/pwsh.yml ================================================ name: PowerShell Core on: push: branches: [ main, master ] pull_request: branches: [ main, master ] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: strategy: fail-fast: false matrix: os: ['windows-latest', 'macos-latest', 'ubuntu-latest'] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - name: install dependencies shell: pwsh run: | "Git version: $(git --version)" "PSVersion: $($PSVersionTable.PSVersion)" "Host name: $($Host.Name)" Set-PSRepository -Name PSGallery -InstallationPolicy Trusted Install-Module Pester -MinimumVersion 5.0.0 -MaximumVersion 5.99.99 -Scope CurrentUser -Force "Pester: $(Get-Module Pester | % Version)" - name: run tests shell: pwsh run: | Import-Module Pester -PassThru $ErrorActionPreference = 'Continue' $res = Invoke-Pester -Path test -Output Detailed -PassThru -ErrorAction SilentlyContinue if (!$res -or ($res.FailedCount -gt 0)) { $Error | Format-List * -Force exit 1 } ================================================ FILE: .gitignore ================================================ VERSION *.log *.nupkg obj .history ================================================ FILE: .vscode/extensions.json ================================================ { // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ "EditorConfig.EditorConfig", "ms-vscode.PowerShell", "DavidAnson.vscode-markdownlint" ] } ================================================ FILE: .vscode/launch.json ================================================ { "version": "0.2.0", "configurations": [ { "type": "PowerShell", "request": "launch", "name": "PowerShell Interactive Session", "cwd": "${workspaceRoot}" }, { "type": "PowerShell", "request": "launch", "name": "PowerShell Pester Tests", "script": "Invoke-Pester", "args": [], "cwd": "${workspaceRoot}/test" }, { "type": "PowerShell", "request": "launch", "name": "PowerShell Launch (current file)", "script": "${file}", "args": [], "cwd": "${file}" }, { "type": "PowerShell", "request": "attach", "name": "PowerShell Attach to Host Process", "processId": "${command:PickPSHostProcess}", "runspaceId": 1 } ] } ================================================ FILE: .vscode/settings.json ================================================ { "editor.insertSpaces": true, "editor.rulers": [ 120 ], "editor.tabSize": 4, "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true, "powershell.codeFormatting.preset": "Stroustrup", "powershell.scriptAnalysis.settingsPath": "PSScriptAnalyzerSettings.psd1", "markdownlint.config": { "MD013": false, "MD024": false, "MD029": false, "MD033": { "allowed_elements": [ "kbd" ] }, "MD034": false, "MD038": false } } ================================================ FILE: .vscode/tasks.json ================================================ { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "windows": { "options": { "shell": { "executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "args": [ "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command" ] } } }, "linux": { "options": { "shell": { "executable": "/usr/bin/pwsh", "args": [ "-NoProfile", "-Command" ] } } }, "osx": { "options": { "shell": { "executable": "/usr/local/bin/pwsh", "args": [ "-NoProfile", "-Command" ] } } }, // Associate with test task runner "tasks": [ { "label": "Test", "type": "shell", "command": "Import-Module Pester -MinimumVersion 5.0.0; $c = [PesterConfiguration]::Default; $c.Run.Path = 'test'; $c.Output.Verbosity = 'Detailed'; Invoke-Pester -Configuration $c", "group": { "kind": "test", "isDefault": true }, "problemMatcher": [ "$pester" ] }, { "label": "TestCurrentFile", "type": "shell", "command": "Clear-Host; Import-Module Pester -MinimumVersion 5.0.0; $c = [PesterConfiguration]::Default; $c.Run.Path = '${file}'; $c.Output.Verbosity = 'Detailed'; Invoke-Pester -Configuration $c", "group": { "kind": "test", "isDefault": false }, "problemMatcher": [ "$pester" ] } ] } ================================================ FILE: CHANGELOG.md ================================================ # posh-git Release History ## 1.1.0 - March 31, 2022 ### Added - `Remove-PoshGitFromProfile` ([git-for-windows/build-extra#401](https://github.com/git-for-windows/build-extra/pull/401)) ([PR #877](https://github.com/dahlbyk/posh-git/pull/877)) Thanks @dscho ### Fixed - Fix branch status when cherry-picking, merging, reverting ([#818](https://github.com/dahlbyk/posh-git/issues/818)) ([PR #828](https://github.com/dahlbyk/posh-git/pull/828)) ([PR #894](https://github.com/dahlbyk/posh-git/pull/894)) Thanks @NihilityT - Fix tab expansion for PowerShell aliases to `git.exe` ([#854](https://github.com/dahlbyk/posh-git/issues/854)) ([PR #855](https://github.com/dahlbyk/posh-git/pull/855)) - Fix tab expansion for symbolic refs ([#867](https://github.com/dahlbyk/posh-git/issues/867)) ([PR #868](https://github.com/dahlbyk/posh-git/pull/868)) Thanks @dmringo - Fix status inside `.github` directories ([#873](https://github.com/dahlbyk/posh-git/issues/873)) ([PR #874](https://github.com/dahlbyk/posh-git/pull/874)) ([PR #893](https://github.com/dahlbyk/posh-git/pull/893)) Thanks @jethas-bennettjones - Version warning no longer flags VFS for Git ([#860](https://github.com/dahlbyk/posh-git/issues/860)) ([PR #884](https://github.com/dahlbyk/posh-git/pull/884)) Thanks @shmuelie - Version warning no longer flags Git for Windows release candidates ([#845](https://github.com/dahlbyk/posh-git/issues/845)) ([PR #885](https://github.com/dahlbyk/posh-git/pull/885)) Thanks @dscho ### Chores - Migrated to GitHub Actions ([PR #878](https://github.com/dahlbyk/posh-git/pull/878)) Thanks @dscho - Fixed Scoop install instructions ([PR #862](https://github.com/dahlbyk/posh-git/pull/862)) Thanks @cjstewart88 - Fix typo ([PR #887](https://github.com/dahlbyk/posh-git/pull/887)) Thanks @bennett1412 ## 1.0.0 - March 10, 2021 - Released to PowerShell Gallery. - Fate of Chocolatey to be determined. ## 1.0.0-beta5 - February 14, 2021 ### Added - Added --renormalize to the "git add" parameters for tab-completion. ([PR #791](https://github.com/dahlbyk/posh-git/pull/791)) Thanks @dannoe - Pass PowerShell aliases down to posh-git TabExpansion autocomplete function ([#257](https://github.com/dahlbyk/posh-git/issues/257)) ([PR #779](https://github.com/dahlbyk/posh-git/pull/779)) Thanks @csc027 - Autocomplete user-defined pretty formats ([#786](https://github.com/dahlbyk/posh-git/issues/786)) ([PR #802](https://github.com/dahlbyk/posh-git/pull/802)) Thanks @rdnlsmith - Add warning for cygwin version of git, which does not play nicely ([#771](https://github.com/dahlbyk/posh-git/issues/771)) ([PR #807](https://github.com/dahlbyk/posh-git/pull/807)) - Add BeforePath and AfterPath settings ([#821](https://github.com/dahlbyk/posh-git/pull/821)) ([PR #822](https://github.com/dahlbyk/posh-git/pull/822)) Thanks @breisfeld Brad Reisfeld ### Changed - `$GitPromptSettings.DefaultPromptAbbreviateHomeDirectory` defaults to `$false` on Windows. When enabled, `~` is always substituted for the user's home path. Before this change, when directly in your home dir, this setting would result in the full path displayed in the prompt e.g. `C:\Users\Keith`. After this change, `~` will be displayed. The new behavior is consistent with how the home path is displayed in prompts in other shells on Linux. ([#746](https://github.com/dahlbyk/posh-git/pull/746)) ([PR #801](https://github.com/dahlbyk/posh-git/pull/801)) - Adds `--no-optional-locks` when invoking git to reduce conflicts with other tools. ([PR #774](https://github.com/dahlbyk/posh-git/pull/774)) Thanks @jessehouwing ### Fixed - Put 'CACHE ERROR' in prompt when [GitStatusCache](https://github.com/cmarcusreid/git-status-cache) returns failure. ([PR #759](https://github.com/dahlbyk/posh-git/pull/759)) Thanks @cmarcusreid - Honor EnableStashStatus when using cache ([#761](https://github.com/dahlbyk/posh-git/pull/761)) ([PR #762](https://github.com/dahlbyk/posh-git/pull/762)) - Register-ArgumentCompleter based tab-expansion in beta4 doesn't work with PowerShell aliases to git,tgit,gitk. ([#769](https://github.com/dahlbyk/posh-git/issues/769)) ([PR #770](https://github.com/dahlbyk/posh-git/pull/770)) - Module now works in (and is tested in) strict mode ([PR #783](https://github.com/dahlbyk/posh-git/pull/783)) - "git help --all" output breaks $GitTabSettings.AllCommands = $true. ([#788](https://github.com/dahlbyk/posh-git/issues/788)) ([PR #790](https://github.com/dahlbyk/posh-git/pull/790)) Thanks @dannoe - Fixed non-ascii git branch name garbled ([PR #796](https://github.com/dahlbyk/posh-git/pull/796)) Thanks @LittleboyHarry - Fix tab completion of branches in different worktrees ([PR #805](https://github.com/dahlbyk/posh-git/pull/805)) Thanks @wallybh - Using `DarkYellow` no longer results in extra `$Errors` ([PR #806](https://github.com/dahlbyk/posh-git/pull/806)) ## 1.0.0-beta4 - March 14, 2020 ### Added - Branch name completion to the new `Remove-GitBranch` command. ([PR #678](https://github.com/dahlbyk/posh-git/pull/678)) ([PR #705](https://github.com/dahlbyk/posh-git/pull/705)) - `$global:GitPromptValues` with the following properties to enable users to have access to the information necessary to display error status in their prompt as well as debug their prompt customizations: - `DollarQuestion` - `IsAdmin` - `LastExitCode` - `LastPrompt` ([PR #684](https://github.com/dahlbyk/posh-git/pull/684)) - Added tab-completion for new `git restore` and `git switch` commands. ([#691](https://github.com/dahlbyk/posh-git/issues/691)) ([PR #702](https://github.com/dahlbyk/posh-git/pull/702)) ([PR #743](https://github.com/dahlbyk/posh-git/pull/743)) Thanks @pinkfloydx33 for the direction and motivation to add this. - Added tab-completion for `git merge --allow-unrelated-histories` ([#632](https://github.com/dahlbyk/posh-git/issues/632)) ([PR #633](https://github.com/dahlbyk/posh-git/pull/633)) - Abbreviate path from git root with new setting `DefaultPromptAbbreviateGitDirectory` ([#719](https://github.com/dahlbyk/posh-git/issues/719)) ([PR #720](https://github.com/dahlbyk/posh-git/pull/720)) ([PR #729](https://github.com/dahlbyk/posh-git/pull/729)) Thanks Philippe Elsass (@elsassph) - Two new properties have been added to `$global:GitTabSettings` - `EnableLogging` - default is `$false` but when set to `$true`, tab expansion functions log to a file. - `LogPath` - the path to the log file that is appended to (created if necessary) when `EnableLogging` is `$true`. ### Changed - Module parameter changed from a [switch] that forced the posh-git prompt to be used to a bools. The use of this parameters during import is now simplified to: `Import-Module posh-git -Args 1`. - Shortened up the Windows title text to work better with Windows Terminal tabs. Now only displays '32-bit' in 32-bit PowerShell, otherwise assumption is you're running 64-bit. Also display only PowerShell major.minor version number. ([PR #707](https://github.com/dahlbyk/posh-git/pull/707)) - Switched from overriding `TabExpansion` function to using `Register-ArgumentCompleter` for tab-completion on PowerShell >= 6.0. `posh-git` can be configured to use the old `TabExpansion` function on PowerShell >= 6.0 by importing the module using the following arguments: `Import-Module posh-git -ArgumentList 0,1` before importing `posh-git`. ([#609](https://github.com/dahlbyk/posh-git/pull/609)) ([PR #711](https://github.com/dahlbyk/posh-git/pull/711)) Thanks Andrew Bradley (@cspotcode) - ([#733](https://github.com/dahlbyk/posh-git/issues/733)) ([PR #741](https://github.com/dahlbyk/posh-git/pull/741)) - Fix conflict with `TabExpansionPlusPlus` module's `Register-ArgumentCompleter` command. ([#715](https://github.com/dahlbyk/posh-git/issues/715)) ([PR #714](https://github.com/dahlbyk/posh-git/pull/714)) Thanks Kris Borowinski (@kborowinski) - Update Ubuntu build system from 14.04 to 16.04 ([PR #677](https://github.com/dahlbyk/posh-git/pull/677)) Thanks @ExE-Boss - `$GitPromptSettings.BeforeIndex` is always displayed even if there is nothing in the index. The intent of this separator setting is to provide a separator between the branch name / branch status and the following section of index/working/summary/stash indicators. ([PR #723](https://github.com/dahlbyk/posh-git/pull/723)) - posh-git no longers sets missing `HOME` environment variable. ([#718](https://github.com/dahlbyk/posh-git/issues/718)) ([PR #722](https://github.com/dahlbyk/posh-git/pull/722)) - `Get-GitStatus -Force` now also overrides `$GitPromptSettings.EnableFileStatus` as well as `$GitPromptSettings.EnablePromptStatus`. ([#657](https://github.com/dahlbyk/posh-git/issues/657)) ([PR #721](https://github.com/dahlbyk/posh-git/pull/721)) ### Fixed - Remove `.exe` suffix from PowerTab integration to enable PowerTab integration to work with `git`, `git.cmd` or `git.exe` ([#606](https://github.com/dahlbyk/posh-git/pull/606/)) - `BranchBehindAndAheadDisplay` minimal/compact bug ([#670](https://github.com/dahlbyk/posh-git/issues/670)) ([PR #671](https://github.com/dahlbyk/posh-git/pull/671)) - Fix(status): Only reset changed colors ([PR #673](https://github.com/dahlbyk/posh-git/pull/673)) Thanks @ExE-Boss - Fix rebase step count ([PR #674](https://github.com/dahlbyk/posh-git/pull/674)) Thanks Saša Kajfeš (@skajfes) - Fix typo in cherry-pick parameters - `´continue` ([PR #675](https://github.com/dahlbyk/posh-git/pull/675)) Thanks @KexyBiscuit - Prompt error in remote PSSession ([#708](https://github.com/dahlbyk/posh-git/issues/708)) ([PR #727](https://github.com/dahlbyk/posh-git/pull/727) - Typos - ([PR #665](https://github.com/dahlbyk/posh-git/pull/665)) Thanks @SJMakin - ([PR #716](https://github.com/dahlbyk/posh-git/pull/716)) Thanks @theaquamarine ## 1.0.0-beta3 - March 10, 2019 ### Removed - BREAKING: Removed SSH agent functionality from `posh-git` and put into another module focused solely on Git SSH support. See [posh-sshell](https://github.com/dahlbyk/posh-sshell). ([#338](https://github.com/dahlbyk/posh-git/issues/338)) ([PR #585](https://github.com/dahlbyk/posh-git/pull/585)) - BREAKING: Removed `PoshGitTextSpan.CustomAnsi` property - now just put your custom VT sequences in the `PoshGitTextSpan.Text` property. Be sure to terminate your VT sequences with `"$([char]27)[0m"` or ``` "`e[0m" ``` on PowerShell Core. ([PR #616](https://github.com/dahlbyk/posh-git/pull/616)) ### Added - Added `Remove-GitBranch` command. Partially addresses #79. These commands currently only delete local branches. ([#79](https://github.com/dahlbyk/posh-git/issues/79)) ([PR #663](https://github.com/dahlbyk/posh-git/pull/663)) - Added support for vsts-cli Git integration. ([#549](https://github.com/dahlbyk/posh-git/issues/549)) ([PR #581](https://github.com/dahlbyk/posh-git/pull/581)) Thanks David Gardiner (@flcdrg) - Enhance prompt function to show username/hostname in SSH connection. Adds `Get-PromptConnectionInfo` command. ([#591](https://github.com/dahlbyk/posh-git/issues/591)) ([PR #595](https://github.com/dahlbyk/posh-git/pull/595)) - Show Rebase Progress (plus `|REVERTING`). ([#102](https://github.com/dahlbyk/posh-git/issues/102)) ([PR #599](https://github.com/dahlbyk/posh-git/pull/599)) - Set `$Env:COLUMNS` on prompt if necessary. ([PR #607](https://github.com/dahlbyk/posh-git/pull/607)) - Tab-completion for `git log --stat`, `git log --patch` and `git diff --staged`. ([PR #620](https://github.com/dahlbyk/posh-git/pull/620)) Thanks Vasily Korytov (@chillum) - Tab-completion for `git update-git-for-windows` on Windows Git >= 2.16.2. ([PR #642](https://github.com/dahlbyk/posh-git/pull/642)) - `New-GitPromptSettings` to provide an easy way to create a `PoshGitPromptSettings` object which can be used to reset to the default settings. ([PR #659](https://github.com/dahlbyk/posh-git/pull/659)) ### Changed - Eliminate traiing `=` on the tab-completion of `--force-with-lease=`. ([PR #622](https://github.com/dahlbyk/posh-git/pull/622)) Thanks Chuck Lu (@chucklu) - Expand `PathStatusSeparator` string when composing prompt. This allows you to use string interpolation in the `$GitPromptSettings.PathStatusSeparator.Text` setting. ([PR #630](https://github.com/dahlbyk/posh-git/pull/630)) Thanks Jason Shirk (@lzybkr) ### Fixed - `WindowTitle` set by user overwritten by module. No longer updates Window title if `WindowTitle` setting set to `$null`. ([#594](https://github.com/dahlbyk/posh-git/issues/594)) ([PR #597](https://github.com/dahlbyk/posh-git/pull/597)) - `Write-VcsStatus` behaving differently. When `AnsiConsole -eq $false`, `Write-VcsStatus` must not emit any strings. ([#612](https://github.com/dahlbyk/posh-git/issues/612)) ([PR #617](https://github.com/dahlbyk/posh-git/pull/617)) - On Windows PowerShell, defer `Add-Type` until `Set-ConsoleMode` executed. This improves module load time. ([#637](https://github.com/dahlbyk/posh-git/issues/637)) ([PR #638](https://github.com/dahlbyk/posh-git/pull/638)) ([PR #662](https://github.com/dahlbyk/posh-git/pull/662)) - How do you manually install this? Added manual instructions for git clone of posh-git to README.md. ([#648](https://github.com/dahlbyk/posh-git/issues/648)) ([PR #649](https://github.com/dahlbyk/posh-git/pull/649)) Thanks Kyle Spier-Swenson (@MrStonedOne) ## 1.0.0-beta2 - May 13, 2018 The 1.0.0 release is targeted specifically at Windows PowerShell 5.x and (cross-platform) PowerShell Core 6.x, both of which support classes, enabling the enhanced structure of `$GitPromptSettings`, and writing prompt strings using [ANSI escape sequences][ansi-esc-code] / [Console Virtual Terminal Sequences][console-vt-seq] (supported since Windows 10 version 1511). Consequently, this release introduces BREAKING changes with 0.x. If you are still on Windows PowerShell 2.0, 3.0 or 4.0, please continue to use the 0.x version of posh-git. ### Changed - Renamed `$GitPromptSettings` values - `BeforeText` to `BeforeStatus` - `DelimText` to `DelimStatus` - `AfterText` to `AfterStatus` - `BeforeIndexText` to `BeforeIndex` - `BeforeStashText` to `BeforeStash` - `AfterStashText` to `AfterStash` - Split `$GirPromptSettings.DefaultPromptSuffix` and `$GitPromptSettings.DefaultPromptDebugSuffix` into: 1. `DefaultPromptBeforeSuffix` (`''`) 2. `DefaultPromptDebug` (`' [DBG]:'`), which is rendered if a debugger is attached 3. `DefaultPromptSuffix` (`'$(">" * ($nestedPromptLevel + 1)) '`), which is rendered last (or returned from `prompt`, for terminals that don't support escape sequences) - Renamed `$GitPromptSettings.EnableWindowTitle` to `$GitPromptSettings.WindowTitle` with significant improvements: - `$Host.UI.RawUI.WindowTitle` is now set on every `prompt`, not just when inside a Git repo. - To prevent setting `WindowTitle`, set `$GitPromptSettigs.WindowTitle` to `$null`. - The `WindowTitle` update has been moved from the `Write-GitStatus` command to the built-in `prompt` function. - `$GitPromptSettings.WindowTitle` is now fully customizable: - As a `string`, it will be processed with [`ExpandString`][invokecommand-expandstring]. - As a `ScriptBlock` (default), it will be executed with two parameters: `$GitStatus` and `$IsAdmin`. - When a color setting is specified by a `string` (color name), it is parsed as an HTML color (on platforms that support `System.Drawing.ColorTranslator`) before being parsed as a `ConsoleColor`. To force using `ConsoleColors`, use static member syntax (e.g. `[ConsoleColor]::Cyan`). ([PR #536](https://github.com/dahlbyk/posh-git/pull/536)) - `PoshGitVcsPrompt` errors now show details if `$GitPromptSettings.Debug` ([PR #560](https://github.com/dahlbyk/posh-git/pull/560)) ### Added - New command `Get-PromptPath` which formats the path displayed in the prompt and window title. This command honors `$GitPromptSettings.DefaultPromptAbbreviateHomeDirectory`. - A path exactly matching `$HOME` is now shown in full, rather than abbreviated to `~` ([PR #567](https://github.com/dahlbyk/posh-git/pull/567)) - New `$GitPromptSettings` values (default): - `PathStatusSeparator` (` `) - `DefaultPromptPath` (`'$(Get-PromptPath)'`) - `DefaultPromptWriteStatusFirst` (`$false`) - `DefaultPromptTimingFormat` (`' {0}ms'`) - `RepoName` property has been addded to the `$global:GitStatus` object returned by `Get-GitStatus`. - Added `$GitPromptSettings.UntrackedFilesMode`; accepted values are `$null` (inherit `status.showUntrackedFiles`), "all", "no", and "normal" ([#556](https://github.com/dahlbyk/posh-git/pull/556)) ([PR #557](https://github.com/dahlbyk/posh-git/pull/557)) Thanks David Snedecor (@TheSned) - Exported `Expand-GitCommand` for use with custom tab expansion ([#562](https://github.com/dahlbyk/posh-git/pull/562)) ([PR #563](https://github.com/dahlbyk/posh-git/pull/563)) ### Fixed - Fixed `$GitPromptSettings.EnablePromptStatus` should not affect `Get-GitStatus` by adding `-Force` parameter. ([#475](https://github.com/dahlbyk/posh-git/issues/475)) ([PR #535](https://github.com/dahlbyk/posh-git/pull/535)) - Fixed PowerShell Core bug where we were using `Get-Content -Encoding Byte` when processing profile scripts during Chocolatey install/uninstall. That encoding doesn't exist in PowerShell Core. Switched to `Get-Content -AsByteStream` on PowerShell Core. ([PR #532](https://github.com/dahlbyk/posh-git/pull/532)) - Fixed ANSI rendering bug: when both `ForegroundColor` and `BackgroundColor` colors are `$null` (default), we were emitting an unnecessary terminating escape sequence `"$([char]27)[0m"`. ([PR #532](https://github.com/dahlbyk/posh-git/pull/532)) - Fixed issue where setting `ForegroundColor` or `BackgroundColor` to 0 (Black) rendered the default color instead. - Updated Git subcommand lists ([#561](https://github.com/dahlbyk/posh-git/issues/561)) ([PR #571](https://github.com/dahlbyk/posh-git/pull/571)) ## 1.0.0-beta1 - January 10, 2018 ### Removed - Drop support for PowerShell 2.0 ([#163](https://github.com/dahlbyk/posh-git/issues/163)) ([PR #427](https://github.com/dahlbyk/posh-git/pull/427)) - Drop support for PowerShell 3.0 and 4.0 ([#328](https://github.com/dahlbyk/posh-git/issues/328)) ([PR #513](https://github.com/dahlbyk/posh-git/pull/513)) - Remove public `Enable-GitColors`, `Get-AliasPattern`, `Get-GitBranch` and `Invoke-NullCoalescing` and its `??` alias ([#93](https://github.com/dahlbyk/posh-git/issues/93)) ([PR #427](https://github.com/dahlbyk/posh-git/pull/427)) ### Changed - Changed the `$GitPromptSettings` hashtable to a stongly typed object. Here is one example of the impact of this change: ```powershell $GitPromptSettings.LocalWorkingStatusSymbol = '#' $GitPromptSettings.LocalWorkingStatusForegroundColor = [ConsoleColor]::DarkRed ``` Changes to: ```powershell $GitPromptSettings.LocalWorkingStatusSymbol.Text = '#' $GitPromptSettings.LocalWorkingStatusSymbol.ForegroundColor = [ConsoleColor]::DarkRed ``` - Changed `Write-VcsStatus`, `Write-GitStatus` and `Write-Prompt` to return a string rather than write to host when the host supports ANSI escape sequences. ### Added - Implement support for ANSI escape sequences for colored output - support System.ConsoleColor, System.Drawing.HtmlColor (if available on the platform) and 24-bit color. NOTE: this is a breaking change since `Write-VcsStatus`, `Write-GitStatus` and `Write-Prompt` will now return a string rather than write to the host when the host supports ANSI escape sequences. ([#304](https://github.com/dahlbyk/posh-git/pull/304)) ([#447](https://github.com/dahlbyk/posh-git/pull/447)) ([#455](https://github.com/dahlbyk/posh-git/pull/455)) - $GitPromptSettings is now a strongly typed object using PS classes ([#344](https://github.com/dahlbyk/posh-git/issues/344)) ([PR #513](https://github.com/dahlbyk/posh-git/pull/513)) - Provide more granular commands than just `Write-GitStatus`. ([#345](https://github.com/dahlbyk/posh-git/issues/345)) ([PR #513](https://github.com/dahlbyk/posh-git/pull/513)) We now export the commands that `Write-GitStatus` uses internally which are: - Write-GitBranchName - Write-GitBranchStatus - Write-GitIndexStatus - Write-GitStashCount - Write-GitWorkingDirStatus - Write-GitWorkingDirStatusSummary ### Fixed - Fixed Get-AuthenticodeSignature not on PS Core. ([PR #487](https://github.com/dahlbyk/posh-git/pull/487)) - Provide DefaultPromptPrefixColor and DefaultPromptSuffixColor options ([#474](https://github.com/dahlbyk/posh-git/issues/474)) ([PR #520](https://github.com/dahlbyk/posh-git/pull/520)) - Fixed posh-git prompt makes PowerShell transcripts less readable ([#282](https://github.com/dahlbyk/posh-git/issues/282)) ([PR #447](https://github.com/dahlbyk/posh-git/pull/447)) ## 0.7.3 - April 19, 2018 - posh-git now exports the variable `$GitPromptScriptBlock` which contains the code for the default prompt. ([#501](https://github.com/dahlbyk/posh-git/issues/501)) ([PR #513](https://github.com/dahlbyk/posh-git/pull/513)) If you need to execute your own logic in your own prompt function but still want to display the default posh-git prompt, you can execute this scriptblock from your prompt function e.g.: ```powershell # profile.ps1 function prompt { Set-NodeVersion &$GitPromptScriptBlock } Import-Module posh-git ``` - Fixed 'Write-Prompt' to be able use Black as foreground color. ([#470](https://github.com/dahlbyk/posh-git/pull/470)) ([PR #468](https://github.com/dahlbyk/posh-git/pull/468)) Thanks Vladimir Poleh (@vladimir-poleh) - Pass "git.exe" instead of "git" to Get-Command. ([PR #478](https://github.com/dahlbyk/posh-git/pull/478)) ([PR #479](https://github.com/dahlbyk/posh-git/pull/479)) Thanks Mike Sigsworth (@mikesigs) - Squash ssh agent warnings if `-Quiet`. ([PR #484](https://github.com/dahlbyk/posh-git/pull/484)) Thanks Refael Ackermann (@refack) - Fixed directory names that contain [brackets] cause GitPrompt to fail. ([PR #502](https://github.com/dahlbyk/posh-git/pull/502)) Thanks Duncan Smart (@duncansmart) - Added `Add-PoshGitToProfile -AllUsers` support ([PR #504](https://github.com/dahlbyk/posh-git/pull/504)) - Fixed duplicated branch completion for git checkout ([#505](https://github.com/dahlbyk/posh-git/issues/505)) ([PR #506](https://github.com/dahlbyk/posh-git/pull/506)) ([PR #512](https://github.com/dahlbyk/posh-git/pull/512)) Thanks Christoph Bergmeister (@bergmeister) - Fixed PSScriptAnalyzer warnings in the source ([PR #509](https://github.com/dahlbyk/posh-git/pull/509)) Thanks Christoph Bergmeister (@bergmeister) - Fixed errors added to $Error collection by `Get-GitStatus` command ([#500](https://github.com/dahlbyk/posh-git/issues/500)) ([PR #514](https://github.com/dahlbyk/posh-git/pull/514)) - Added custom path rendering in prompt ([#469](https://github.com/dahlbyk/posh-git/issues/469)) ([PR #520](https://github.com/dahlbyk/posh-git/pull/520)) - Clean up wording for work dir local status in help file ([PR #516](https://github.com/dahlbyk/posh-git/pull/516)) - Added `$GitPromptSettings.AdminTitlePrefixText` (default: `'Administrator: '`) ([#537](https://github.com/dahlbyk/posh-git/pull/537)) ([PR #538](https://github.com/dahlbyk/posh-git/pull/538)) Thanks Eric Jorgensen (@nebosite) - Added `$GitPromptSettings.UntrackedFilesMode`; accepted values are `$null` (inherit `status.showUntrackedFiles`), "all", "no", and "normal" ([#556](https://github.com/dahlbyk/posh-git/pull/556)) ([PR #557](https://github.com/dahlbyk/posh-git/pull/557)) Thanks David Snedecor (@TheSned) - `PoshGitVcsPrompt` errors now show details if `$GitPromptSettings.Debug` ([PR #560](https://github.com/dahlbyk/posh-git/pull/560)) - Exported `Expand-GitCommand` for use with custom tab expansion ([#562](https://github.com/dahlbyk/posh-git/pull/562)) ([PR #563](https://github.com/dahlbyk/posh-git/pull/563)) - Add code coverage to Coveralls.io ([#416](https://github.com/dahlbyk/posh-git/pull/416)) ([PR #461](https://github.com/dahlbyk/posh-git/pull/461)) Thanks Jan De Dobbeleer (@JanJoris) ## 0.7.1 - March 14, 2017 - Fixed tab completion issues with duplicate aliases ([#164](https://github.com/dahlbyk/posh-git/issues/164)) ([#421](https://github.com/dahlbyk/posh-git/issues/421)) ([PR #422](https://github.com/dahlbyk/posh-git/pull/422)) - `Add-PoshGitToProfile` will no longer modify a signed `$PROFILE` script; it also learned `-Confirm` ([PR #428](https://github.com/dahlbyk/posh-git/pull/428)) - Overwrite pre-0.7 posh-git prompt on import ([PR #425](https://github.com/dahlbyk/posh-git/pull/425)) - Fix Chocolatey deprecation warning with dependency on 0.9.10 ([PR #426](https://github.com/dahlbyk/posh-git/pull/426)) - Don't rerun Pageant if there are no keys to add ([PR #441](https://github.com/dahlbyk/posh-git/pull/441)) - Improve (and hide for Chocolatey) profile.example.ps1 deprecation messaging ([#442](https://github.com/dahlbyk/posh-git/issues/442)) ([PR #444](https://github.com/dahlbyk/posh-git/pull/444)) - Quote tab completion for remote names containing special characters ([PR #446](https://github.com/dahlbyk/posh-git/pull/446)) - Fix signed $PROFILE detection for PowerShell v2 ([#448](https://github.com/dahlbyk/posh-git/issues/448)) ([PR #450](https://github.com/dahlbyk/posh-git/pull/450)) - Create $PROFILE parent directory if missing ([PR #449](https://github.com/dahlbyk/posh-git/pull/449)) ([PR #452](https://github.com/dahlbyk/posh-git/pull/452)) - Add -verbose parameter to install.ps1 ([PR #451](https://github.com/dahlbyk/posh-git/pull/451)) - Write-Prompt now correctly handles $null color parameters; the prompt now also uses DefaultForegroundColor when appropriate ([PR #454](https://github.com/dahlbyk/posh-git/pull/454)) - Add error handling to Write-GitStatus ([PR #170](https://github.com/dahlbyk/posh-git/pull/170)) ([PR #453](https://github.com/dahlbyk/posh-git/pull/453)) ## 0.7.0 - February 14, 2017 This release has focused on improving the "getting started" experience by adding an `Add-PoshGitToProfile` command that modifies the user's PowerShell profile script to import the posh-git module whenever PowerShell starts. When posh-git is imported, it will automatically install a posh-git prompt that displays Git status summary information. Work was also done to improve performance of `Get-GitStatus` when inside large Git repositories. Work was begun to eliminate some obvious crashes on PowerShell on .NET Core but more work remains to be done. - Performance of `Get-GitStatus` on large repos has been improved ([PR #319](https://github.com/dahlbyk/posh-git/pull/319)) - Fix prompt and tab completion with non-ASCII characters ([#64](https://github.com/dahlbyk/posh-git/issues/64)) ([PR #223](https://github.com/dahlbyk/posh-git/pull/223)) ([PR #359](https://github.com/dahlbyk/posh-git/pull/359)) ([#374](https://github.com/dahlbyk/posh-git/issues/374)) ([#389](https://github.com/dahlbyk/posh-git/issues/389)) ([PR #397](https://github.com/dahlbyk/posh-git/pull/397)) ([PR #403](https://github.com/dahlbyk/posh-git/pull/403)) - Fix incorrect tab expansion for `git push --option ` ([#234](https://github.com/dahlbyk/posh-git/issues/234)) ([PR #379](https://github.com/dahlbyk/posh-git/pull/379)) - Fix support for bare repository ([#291](https://github.com/dahlbyk/posh-git/issues/291)) ([PR #370](https://github.com/dahlbyk/posh-git/pull/370)) - Fix syntax error on setenv calls ([PR #297](https://github.com/dahlbyk/posh-git/pull/297)) - Fix temp path issue with ~ in 8.3 filenames ([#298](https://github.com/dahlbyk/posh-git/issues/298)) ([PR #299](https://github.com/dahlbyk/posh-git/pull/299)) - Fix problem on open source PowerShell, missing `WindowsPrincipal`/`WindowsIdentity` ([#301](https://github.com/dahlbyk/posh-git/issues/301)) ([PR #312](https://github.com/dahlbyk/posh-git/pull/312)) - Fix/simplify Chocolatey install and add uninstall ([#358](https://github.com/dahlbyk/posh-git/issues/358)) - Remove invalid branch from tab expansion when `HEAD` is detached ([PR #367](https://github.com/dahlbyk/posh-git/pull/367)) - Fix PowerShell Core error on `EnvironmentVariableTarget` ([#317](https://github.com/dahlbyk/posh-git/issues/317)) ([#369](https://github.com/dahlbyk/posh-git/issues/369)) ([PR #318](https://github.com/dahlbyk/posh-git/pull/318)) - Fewer errors generated in global `$Error` collection ([PR #370](https://github.com/dahlbyk/posh-git/pull/370)) - Remove error thrown by `git symbolic-ref` and `git describe` ([PR #307](https://github.com/dahlbyk/posh-git/pull/307)) - Export command Write-VcsStatus to improve module auto-loading ([PR #284](https://github.com/dahlbyk/posh-git/pull/284)) - Update module import so that it sets the prompt function *iff* the user does not have a customized prompt function ([#217](https://github.com/dahlbyk/posh-git/issues/217)) ([PR #349](https://github.com/dahlbyk/posh-git/pull/349)) - Update profile.example.ps1 to remove prompt function and tweak how module is imported ([PR #349](https://github.com/dahlbyk/posh-git/pull/349)) - Add tab completion for AVH git-flow commands ([PR #231](https://github.com/dahlbyk/posh-git/pull/231)) - Add new commmand Add-PoshGitToProfile ([PR #361](https://github.com/dahlbyk/posh-git/pull/361)) - Add about_posh-git help topic ([PR #298](https://github.com/dahlbyk/posh-git/pull/287)) - Add new settings for default posh-git prompt: - `DefaultPromptPrefix` ([PR #393](https://github.com/dahlbyk/posh-git/pull/393)) - `DefaultPromptSuffix` (default includes nested prompt level, ([PR #363](https://github.com/dahlbyk/posh-git/pull/363))) - `DefaultPromptDebugSuffix` - `DefaultPromptEnableTiming` ([PR #371](https://github.com/dahlbyk/posh-git/pull/371)) - `DefaultPromptAbbreviateHomeDirectory` ([#386](https://github.com/dahlbyk/posh-git/issues/386)) - Add ahead/behind count to prompt ([PR #256](https://github.com/dahlbyk/posh-git/pull/256)) - Add `BranchBehindAndAheadDisplay` setting to control count display (Full (default), Compact, Minimal) - Fix empty `Git-SshPath` issue ([PR #268](https://github.com/dahlbyk/posh-git/pull/268)) - Add new settings for prompt status summary text: `FileAddedText`, `FileModifiedText`, `FileRemovedText` and `FileConflictText` ([PR #277](https://github.com/dahlbyk/posh-git/pull/277)) - Add tags to 'push' tab-completion ([PR #286](https://github.com/dahlbyk/posh-git/pull/286)) - Add new branch status to indicate upstream is gone ([PR #326](https://github.com/dahlbyk/posh-git/pull/326)) - Add tab completion support for shorthand force-push syntax (`git push +`) ([#173](https://github.com/dahlbyk/posh-git/issues/173)) ([PR #174](https://github.com/dahlbyk/posh-git/pull/174)) ([PR #343](https://github.com/dahlbyk/posh-git/pull/343)) - Add tab completion of unique remote branch names for `git checkout ` ([#177](https://github.com/dahlbyk/posh-git/issues/177)) ([PR #251](https://github.com/dahlbyk/posh-git/pull/251)) ([PR #352](https://github.com/dahlbyk/posh-git/pull/352)) - Add `git worktree` tab completion ([PR #366](https://github.com/dahlbyk/posh-git/pull/366)) - Add alias support for TortoiseGit commands ([PR #394](https://github.com/dahlbyk/posh-git/pull/394)) - Add support for tab-completion of Git parameters, long and short ([PR #395](https://github.com/dahlbyk/posh-git/pull/395)) - Switch `$GitPromptSettings` type from `PSObject` to `PSCustomObject`. On PowerShell v5 and higher, this preserves the definition order of properties in `$GitPromptSettings` making it easier to find properties. - Fix prompt status in worktree ([#407](https://github.com/dahlbyk/posh-git/issues/407)) ([PR #408](https://github.com/dahlbyk/posh-git/pull/408)) - Quote tab completion for items containing special characters ([#293](https://github.com/dahlbyk/posh-git/issues/293)) ([PR #413](https://github.com/dahlbyk/posh-git/pull/413)) ## Thank You Thank you to the following folks who contributed their time and scripting skills to make posh-git better: - Keith Hill (@rkeithhill) - [Pester test infrastructure](https://github.com/dahlbyk/posh-git/commits/master/test?author=rkeithhill) - Triage of open issues and PRs - Many README and help improvements - Many of the fixes enumerated above - Marcus Reid (@cmarcusreid) - Use [GitStatusCache](https://github.com/cmarcusreid/git-status-cache) when it's installed ([PR #208](https://github.com/dahlbyk/posh-git/pull/208)) - Report UpstreamGone from GitStatusCache response ([PR #372](https://github.com/dahlbyk/posh-git/pull/372)) - Jason Shirk (@lzybkr) - Speed up `Get-GitStatus` ([PR #319](https://github.com/dahlbyk/posh-git/pull/319)) - Use `PSCustomObject` case for sorted output of `$GitPromptSettings` ([PR #382](https://github.com/dahlbyk/posh-git/pull/382)) - Ralf Müller (@seamlessintegrations) - Add support for tab-completion of Git parameters ([PR #395](https://github.com/dahlbyk/posh-git/pull/395)) - Aksel Kvitberg (@Flueworks) - Add Worktree tab completion ([PR #366](https://github.com/dahlbyk/posh-git/pull/366)) - Eric Amodio (@eamodio) - Add aliasing support for TortoiseGit commands ([PR #394](https://github.com/dahlbyk/posh-git/pull/394)) - Kevin Shaw (@shawmanz32na) - Add DefaultPromptAbbreviateHomeDirectory setting ([PR #387](https://github.com/dahlbyk/posh-git/pull/387)) - KanjiBates (@KanjiBates) - Fix link to git-scm.com in README.md ([PR #396](https://github.com/dahlbyk/posh-git/pull/396)) - Joel Rowley (@hjoelr) - Fix syntax error on setenv calls ([PR #297](https://github.com/dahlbyk/posh-git/pull/297)) - Hui Sun (@JimAmuro) - Fix [#298](https://github.com/dahlbyk/posh-git/issues/298) remove-item error after startup ([PR #299](https://github.com/dahlbyk/posh-git/pull/299)) - Josh (@joshgo) - Add tags to 'push' tab-completion ([PR #286](https://github.com/dahlbyk/posh-git/pull/286)) - Rebecca Turner (@9999years) - Add new settings for prompt FileAddedText, FileModifiedText, FileRemovedText and FileConflictText ([PR #277](https://github.com/dahlbyk/posh-git/pull/277)) - Jack (@Jackbennett) - Export command Write-VcsStatus to improve module auto-loading ([PR #284](https://github.com/dahlbyk/posh-git/pull/284)) - Brendan Forster (@shiftkey) - Improvements to README.md ([PR #273](https://github.com/dahlbyk/posh-git/pull/273)) ([PR #274](https://github.com/dahlbyk/posh-git/pull/274)) - Paul Marston (@paulmarsy) - Update README.md to reflect recent changes to the Git prompt ([PR #221](https://github.com/dahlbyk/posh-git/pull/221)) - Add error handling to Write-VcsStatus ([PR #170](https://github.com/dahlbyk/posh-git/pull/170)) - INOMATA Kentaro (@matarillo) - Fix branch names using UTF8 characters do not display correctly ([PR #223](https://github.com/dahlbyk/posh-git/pull/223)) - Luis Vita (@Ivita) - Fix typo in git commit parameter --amend in tab exansion ([PR #405](https://github.com/dahlbyk/posh-git/pull/405)) - Skeept (@skeept) - Fix debug prompt breaking posh-git prompt on PowerShell v4 ([PR #406](https://github.com/dahlbyk/posh-git/pull/406)) - @theaquamarine - Fix [#249](https://github.com/dahlbyk/posh-git/issues/249), handling of multiple Pageant keys ([PR #255](https://github.com/dahlbyk/posh-git/pull/255)) - Jan De Dobbeleer (@JanJoris) - Remove errors thrown by `git symbolic-ref` and `git describe` ([PR #307](https://github.com/dahlbyk/posh-git/pull/307)) - Dan Smith (@dozius) - Add tab completion for AVH git-flow commands ([PR #231](https://github.com/dahlbyk/posh-git/pull/231)) - @drawfour - Add ahead/behind count to prompt ([PR #256](https://github.com/dahlbyk/posh-git/pull/256)) - Dan Turner (@dan-turner) - Add tab completion support for shorthand force-push syntax (`git push +`) ([PR #174](https://github.com/dahlbyk/posh-git/pull/174)) - Mark Hillebrand (@mah) - Add tab completion of unique remote branch names for `git checkout ` ([PR #251](https://github.com/dahlbyk/posh-git/pull/251)) - Jeff Yates (@somewhatabstract) - Don't rerun Pageant if there are no keys to add ([PR #441](https://github.com/dahlbyk/posh-git/pull/441)) - Tolga Balci (@tolgabalci) - Create $PROFILE parent directory if missing ([PR #449](https://github.com/dahlbyk/posh-git/pull/449)) - Add -verbose parameter to install.ps1 ([PR #451](https://github.com/dahlbyk/posh-git/pull/451)) [ansi-esc-code]: https://en.wikipedia.org/wiki/ANSI_escape_code [console-vt-seq]: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences [invokecommand-expandstring]: https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.commandinvocationintrinsics.expandstring ================================================ FILE: ISSUE_TEMPLATE.md ================================================ ### System Details - posh-git version/path: - PowerShell version: - Git version: - Operating system name and version: ### Issue Description I am experiencing a problem with... ================================================ FILE: LICENSE.txt ================================================ Copyright (c) 2010-2018 Keith Dahlby, Keith Hill, and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: PSScriptAnalyzerSettings.psd1 ================================================ @{ # Use Severity when you want to limit the generated diagnostic records to a # subset of: Error, Warning and Information. # Uncomment the following line if you only want Errors and Warnings but # not Information diagnostic records. Severity = @('Error','Warning') # Use IncludeRules when you want to run only a subset of the default rule set. #IncludeRules = @('PSAvoidDefaultValueSwitchParameter', # 'PSMissingModuleManifestField', # 'PSReservedCmdletChar', # 'PSReservedParams', # 'PSShouldProcess', # 'PSUseApprovedVerbs', # 'PSUseDeclaredVarsMoreThanAssigments') # Use ExcludeRules when you want to run most of the default set of rules except # for a few rules you wish to "exclude". Note: if a rule is in both IncludeRules # and ExcludeRules, the rule will be excluded. ExcludeRules = @('PSAvoidUsingWriteHost', 'PSAvoidGlobalVars', 'PSAvoidUsingInvokeExpression', 'PSReviewUnusedParameter') # You can use the following entry to supply parameters to rules that take parameters. # For instance, the PSAvoidUsingCmdletAliases rule takes a whitelist for aliases you # want to allow. Rules = @{ # Do not flag 'cd' alias. # PSAvoidUsingCmdletAliases = @{Whitelist = @('cd')} # Check if your script uses cmdlets that are compatible on PowerShell Core, version 6.0.0-alpha, on Linux. # PSUseCompatibleCmdlets = @{Compatibility = @("core-6.0.0-alpha-linux")} } } ================================================ FILE: README.md ================================================ # posh-git [![Join the chat at https://gitter.im/dahlbyk/posh-git][gitter-img]][gitter] [![PowerShell Gallery][psgallery-img]][psgallery-site] [![posh-git on Chocolatey][choco-img]][choco-site] Table of contents: - [Overview](#overview) - [Versions](#versions) - [Installation](#installation) - [Using posh-git](#using-posh-git) - [Git status summary information](#git-status-summary-information) - [Customization variables](#customization-variables) - [Customizing the posh-git prompt](#customizing-the-posh-git-prompt) - [Based on work by](#based-on-work-by) ## Overview posh-git is a PowerShell module that integrates Git and PowerShell by providing Git status summary information that can be displayed in the PowerShell prompt, e.g.: ![C:\Users\Keith\GitHub\posh-git [main ≡ +0 ~1 -0 | +0 ~1 -0 !]> ][prompt-def-long] posh-git also provides tab completion support for common git commands, branch names, paths and more. For example, with posh-git, PowerShell can tab complete git commands like `checkout` by typing `git ch` and pressing the tab key. That will tab complete to `git checkout` and if you keep pressing tab, it will cycle through other command matches such as `cherry` and `cherry-pick`. You can also tab complete remote names and branch names e.g.: `git pull or ma` tab completes to `git pull origin main`. ## Versions ### posh-git v1.0 | Windows (AppVeyor) | Linux/macOS (Travis) | Code Coverage Status | |--------------------|----------------------|----------------------| | [![master build status][av-master-img]][av-master-site] | [![master build status][tv-master-img]][tv-master-site] | [![master build coverage][cc-master-img]][cc-master-site] | [README][main-readme] • [CHANGELOG][main-change] - Supports Windows PowerShell 5.x - Supports PowerShell Core 6+ on all platforms - Supports [ANSI escape sequences][ansi-esc-code] for color customization - Includes breaking changes from v0.x ([roadmap](https://github.com/dahlbyk/posh-git/issues/328)) - **All SSH commands removed** from `posh-git` and moved into the new module [posh-sshell][posh-sshell-url] #### Releases - v1.1.0 ( [README][v1.1-readme] • [CHANGELOG][v1.1-change] ) - v1.0.0 ( [README][v1-readme] • [CHANGELOG][v1-change] ) - v1.0.0-beta5 ( [README][v1b5-readme] • [CHANGELOG][v1b5-change] ) - v1.0.0-beta4 ( [README][v1b4-readme] • [CHANGELOG][v1b4-change] ) - v1.0.0-beta3 ( [README][v1b3-readme] • [CHANGELOG][v1b3-change] ) - v1.0.0-beta2 ( [README][v1b2-readme] • [CHANGELOG][v1b2-change] ) - v1.0.0-beta1 ( [README][v1b1-readme] • [CHANGELOG][v1b1-change] ) ### posh-git v0.x | Windows (AppVeyor) | Code Coverage Status | |--------------------|----------------------| | [![v0 build status][av-v0-img]][av-v0-site] | [![v0 build coverage][cc-v0-img]][cc-v0-site] | [README][v0-readme] • [CHANGELOG][v0-change] - Supports Windows PowerShell 3+ - Does not support PowerShell Core - Avoids breaking changes, maintaining v0.x #### Releases - v0.7.3 ( [README][v073-readme] • [CHANGELOG][v073-change] ) - v0.7.1 ( [README][v071-readme] • [CHANGELOG][v071-change] ) - v0.7.0 ( [README][v070-readme] • [CHANGELOG][v070-change] ) ## Installation These installation instructions, as well as the rest of this README, applies only to version 1.x of posh-git. For v0.x installation instructions see this [README][v0-readme]. ### Prerequisites Before installing posh-git make sure the following prerequisites have been met. 1. Windows PowerShell 5.x or PowerShell Core 6.0. You can get PowerShell Core 6.0 for Windows, Linux or macOS from [here][pscore-install]. Check your PowerShell version by executing `$PSVersionTable.PSVersion`. 2. On Windows, script execution policy must be set to either `RemoteSigned` or `Unrestricted`. Check the script execution policy setting by executing `Get-ExecutionPolicy`. If the policy is not set to one of the two required values, run PowerShell as Administrator and execute `Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Confirm`. 3. Git must be installed and available via the PATH environment variable. Check that `git` is accessible from PowerShell by executing `git --version` from PowerShell. If `git` is not recognized as the name of a command, verify that you have Git installed. If not, install Git from [https://git-scm.com](https://git-scm.com). If you have Git installed, make sure the path to git is in your PATH environment variable. ### Installing posh-git via PowerShellGet on Linux, macOS and Windows posh-git is available on the [PowerShell Gallery][psgallery-v1] and can be installed using the built-in PowerShellGet module. 1. Start Windows PowerShell 5.x or PowerShell >= v6 (`pwsh`). 2. Execute one of the following two commands from an elevated PowerShell prompt, depending on whether (A) you've never installed posh-git, or (B) you've already installed a previous version: ```powershell # (A) You've never installed posh-git from the PowerShell Gallery PowerShellGet\Install-Module posh-git -Scope CurrentUser -Force ``` > **NOTE**: If you're asked to trust packages from the PowerShell Gallery, answer `yes` to continue installation of posh-git OR ```powershell # (B) You've already installed a previous version of posh-git from the PowerShell Gallery PowerShellGet\Update-Module posh-git ``` ### Installing posh-git via Chocolatey If you prefer to manage posh-git as a Windows package, you can use [Chocolatey](https://chocolatey.org) to install posh-git. If you don't have Chocolatey, you can install it from the [Chocolatey Install page](https://chocolatey.org/install). With Chocolatey installed, execute the following command to install posh-git: ```powershell choco install poshgit ``` ### Installing posh-git via Scoop Another popular package manager for Windows is [Scoop](https://scoop.sh/), which you can also use to install posh-git. With Scoop installed, execute these commands to install posh-git and import it into your profile: ```powershell scoop bucket add extras scoop install posh-git Add-PoshGitToProfile ``` ### Installing posh-git Manually If you need to test/debug changes prior to contributing here, or would otherwise prefer to install posh-git without the aid of a package manager, you can execute `Import-Module `. For example, if you have git cloned posh-git to `~\git\posh-git` you can import this version of posh-git by executing `Import-Module ~\git\posh-git\src\posh-git.psd1`. ## Using posh-git After you have installed posh-git, you need to configure your PowerShell session to use the posh-git module. ### Step 1: Import posh-git The first step is to import the module into your PowerShell session which will enable git tab completion. You can do this with the command `Import-Module posh-git`. ### Step 2: Import posh-git from your PowerShell profile You do not want to have to manually execute the `Import-Module` command every time you open a new PowerShell prompt. Let's have PowerShell import this module for you in each new PowerShell session. We can do this by either executing the command `Add-PoshGitToProfile` or by editing your PowerShell profile script and adding the command `Import-Module posh-git`. If you want posh-git to be available in all your PowerShell hosts (console, ISE, etc) then execute `Add-PoshGitToProfile -AllHosts`. This will add a line containing `Import-Module posh-git` to the file `$profile.CurrentUserAllHosts`. If you want posh-git to be available in just the current host, then execute `Add-PoshGitToProfile`. This will add the same command but to the file `$profile.CurrentUserCurrentHost`. If you want posh-git to be available for all users on the system, start PowerShell as Administrator or via sudo (`sudo pwsh`) on Linux/macOS then execute `Add-PoshGitToProfile -AllUsers -AllHosts`. This will add the import command to `$profile.AllUsersAllHosts`. If you want to configure posh-git for all users but only for the current host, drop the `-AllHosts` parameter and the command will modify `$profile.AllUsersCurrentHost`. If you'd prefer, you can manually edit the desired PowerShell profile script. Open (or create) your profile script with the command `notepad $profile.CurrentUserAllHosts`. In the profile script, add the following line: ```powershell Import-Module posh-git ``` Save the profile script, then close PowerShell and open a new PowerShell session. Type `git fe` and then press tab. If posh-git has been imported, that command should tab complete to `git fetch`. If you want posh-git to detect your own aliases for git, then you *must* have set the alias *before* importing posh-git. So if you have `Set-Alias g git` then ensure it is executed before `Import-Module posh-git`, and `g checkout` will complete as if you'd typed `git`. ## Git status summary information The Git status summary information provides a wealth of "Git status" information at a glance, all the time in your prompt. By default, the status summary has the following format: [{HEAD-name} S +A ~B -C !D | +E ~F -G !H W] - `[` (`BeforeStatus`) - `{HEAD-name}` is the current branch, or the SHA of a detached HEAD - Cyan means the branch matches its remote - Green means the branch is ahead of its remote (green light to push) - Red means the branch is behind its remote - Yellow means the branch is both ahead of and behind its remote - `S` represents the branch status in relation to the remote (tracked origin) branch. Note: This status information reflects the state of the remote tracked branch after the last `git fetch/pull` of the remote. Execute `git fetch` to update to the latest on the default remote repo. If you have multiple remotes, execute `git fetch --all`. - `≡` = The local branch is at the same commit level as the remote branch (`BranchIdenticalStatus`) - `↑` = The local branch is ahead of the remote branch by the specified number of commits; a `git push` is required to update the remote branch (`BranchAheadStatus`) - `↓` = The local branch is behind the remote branch by the specified number of commits; a `git pull` is required to update the local branch (`BranchBehindStatus`) - `` = The local branch is both ahead of the remote branch by the specified number of commits (a) and behind by the specified number of commits (b); a rebase of the local branch is required before pushing local changes to the remote branch (`BranchBehindAndAheadStatus`). NOTE: this status is only available if `$GitPromptSettings.BranchBehindAndAheadDisplay` is set to `Compact`. - `×` = The local branch is tracking a branch that is gone from the remote (`BranchGoneStatus`) - `ABCD` represent the index; `|` (`DelimStatus`); `EFGH` represent the working directory - `+` = Added files - `~` = Modified files - `-` = Removed files - `!` = Conflicted files - As with `git status` output, index status is displayed in dark green and working directory status in dark red - `W` represents the overall status of the working directory - `!` = There are unstaged changes in the working tree (`LocalWorkingStatusSymbol`) - `~` = There are uncommitted changes i.e. staged changes in the working tree waiting to be committed (`LocalStagedStatusSymbol`) - None = There are no unstaged or uncommitted changes to the working tree (`LocalDefaultStatusSymbol`) - `]` (`AfterStatus`) The symbols and surrounding text can be customized by the corresponding properties on `$GitPromptSettings`. For example, a status of `[main ≡ +0 ~2 -1 | +1 ~1 -0]` corresponds to the following `git status`: ```powershell # On branch main # # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # modified: this-changed.txt # modified: this-too.txt # deleted: gone.ps1 # # Changed but not updated: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: not-staged.ps1 # # Untracked files: # (use "git add ..." to include in what will be committed) # # new.file ``` ## Customization variables posh-git adds variables to your session to let you customize it, including `$GitPromptSettings`, `$GitTabSettings`, and `$TortoiseGitSettings`. For an example of how to configure your PowerShell profile script to import the posh-git module and create a custom prompt function that displays git status info, see the [Customizing Your PowerShell Prompt](#customizing-the-posh-git-prompt) section below. Note on performance: Displaying file status in the git prompt for a very large repo can be prohibitively slow. Rather than turn off file status entirely (`$GitPromptSettings.EnableFileStatus = $false`), you can disable it on a repo-by-repo basis by adding individual repository paths to `$GitPromptSettings.RepositoriesInWhichToDisableFileStatus`. ## Customizing the posh-git prompt When you import the posh-git module, it will replace PowerShell's default prompt function with a new prompt function. The posh-git prompt function will display Git status summary information when the current directory is inside a Git repository. posh-git will not replace the prompt function if it has detected that you have your own, customized prompt function. The prompt function provided by posh-git creates a prompt that looks like this: ![~\GitHub\posh-git [main ≡]> ][prompt-default] You can customize the posh-git prompt function or define your own custom prompt function. The rest of this section covers how to customize posh-git's prompt function using the global variable `$GitPromptSettings`. **If you'd like to make any of following changes permanent, i.e. available whenever you start PowerShell, put the corresponding setting(s) in one of your profile scripts after the line that imports posh-git.** For instance, you can customize the default prompt prefix to display a colored timestamp with these settings: ```text $GitPromptSettings.DefaultPromptPrefix.Text = '$(Get-Date -f "MM-dd HH:mm:ss") ' $GitPromptSettings.DefaultPromptPrefix.ForegroundColor = [ConsoleColor]::Magenta ``` This will change the prompt to: ![02-18 13:45:19 ~\GitHub\posh-git [main ≡]> ][prompt-prefix] If you would prefer not to have any path under your home directory abbreviated with `~`, use the following setting: ```text $GitPromptSettings.DefaultPromptAbbreviateHomeDirectory = $false ``` This will change the prompt to: ![C:\Users\Keith\GitHub\posh-git [main ≡]> ][prompt-no-abbr] If you would like to change the color of the path, you can use the following setting on Windows: ```text $GitPromptSettings.DefaultPromptPath.ForegroundColor = 'Orange' ``` > Note: Setting the ForegroundColor to a color name, other than one of the standard ConsoleColor names, only works on Windows. On Windows, posh-git uses the `[System.Drawing.ColorTranslator]::FromHtml(string colorName)` method to parse a color name as an HTML color. For a complete list of HTML colors, see this [W3Schools page][w3c-colors]. If you are on Linux or macOS and desire an Orange path, you will need to specify the RGB value for Orange e.g.: ```text $GitPromptSettings.DefaultPromptPath.ForegroundColor = 0xFFA500 ``` This will change the prompt to: ![~\GitHub\posh-git [main]> ][prompt-path] If you would like to make your prompt span two lines, with a newline after the Git status summary, use this setting: ```text $GitPromptSettings.DefaultPromptBeforeSuffix.Text = '`n' ``` This will change the prompt to: ![~\GitHub\posh-git [main ≡] > ][prompt-two-line] You can swap the order of the path and the Git status summary with the following setting: ```text $GitPromptSettings.DefaultPromptWriteStatusFirst = $true ``` This will change the prompt to: ![[main ≡] ~\GitHub\posh-git> ][prompt-swap] Finally, you can combine these settings to customize the posh-git prompt fairly significantly. In the `DefaultPromptSuffix` field below, we are prepending the PowerShell history id number before the prompt char `>` e.g.: ```text $GitPromptSettings.DefaultPromptWriteStatusFirst = $true $GitPromptSettings.DefaultPromptBeforeSuffix.Text = '`n$([DateTime]::now.ToString("MM-dd HH:mm:ss"))' $GitPromptSettings.DefaultPromptBeforeSuffix.ForegroundColor = 0x808080 $GitPromptSettings.DefaultPromptSuffix = ' $((Get-History -Count 1).id + 1)$(">" * ($nestedPromptLevel + 1)) ' ``` This will change the prompt to: ![[main ≡] ~\GitHub\posh-git 02-18 14:04:35 38> ][prompt-custom] Finally, the path portion of the prompt can be contained within delimiters. For instance, if you would like the containing characters to be red, curly braces, the following settings can be used: ```powershell $GitPromptSettings.BeforePath = '{' $GitPromptSettings.AfterPath = '}' $GitPromptSettings.BeforePath.ForegroundColor = 'Red' $GitPromptSettings.AfterPath.ForegroundColor = 'Red' ``` With these additional values, the previous prompt would become ![[main ≡] {~\GitHub\posh-git} 02-18 14:04:35 38> ][prompt-custom-wpathdelim] ### Prompt Layouts For reference, the following layouts show the relative position of the various parts of the posh-git prompt. Note that `<>` denotes parts of the prompt that may not appear depending on the status of settings and whether or not the current dir is in a Git repository. To simplify the layout, `DP` is being used as an abbreviation for `DefaultPrompt` settings. Default prompt layout: ```text {DPPrefix}{BeforePath}{DPPath}{AfterPath}{PathStatusSeparator}<{BeforeStatus}{Status}{AfterStatus}>{DPBeforeSuffix}<{DPDebug}><{DPTimingFormat}>{DPSuffix} ``` Prompt layout when DefaultPromptWriteStatusFirst is set to $true: ```text {DPPrefix}<{BeforeStatus}{Status}{AfterStatus}>{PathStatusSeparator}{BeforePath}{DPPath}{AfterPath}{DPBeforeSuffix}<{DPDebug}><{DPTimingFormat}>{DPSuffix} ``` ### Displaying Error Information If you want to display the error status of the last command, you can use the values stored in the `$global:GitPromptValues` object which includes the value of `$LastExitCode` and `$?` (represented by the property `DollarQuestion`). Here is a prompt customization that displays a Red exit code value when `$LastExitCode` is non-zero or a Red `!` if `$?` is `$false`: ```powershell function global:PromptWriteErrorInfo() { if ($global:GitPromptValues.DollarQuestion) { return } if ($global:GitPromptValues.LastExitCode) { "`e[31m(" + $global:GitPromptValues.LastExitCode + ") `e[0m" } else { "`e[31m! `e[0m" } } $global:GitPromptSettings.DefaultPromptBeforeSuffix.Text = '`n$(PromptWriteErrorInfo)$([DateTime]::now.ToString("MM-dd HH:mm:ss"))' ``` When a PowerShell command fails, this is the prompt you will see: ![~\GitHub\posh-git [main ≡] ! 07-01 22:36:31> ][prompt-error1] When an external application returns a non-zero exit code, 1 in this case, you will see the exit code in the prompt: ![~\GitHub\posh-git [main ≡] (1) 07-01 22:32:28> ][prompt-error2] Note that until you run an external application that sets `$LASTEXITCODE` to zero or you manually set the variable to 0, you will see the exit code for any error. In addition to `LastExitCode` and `DollarQuestion`, `$global:GitPromptValues` also has `IsAdmin` and `LastPrompt` properties. The `LastPrompt` property contains the ANSI escaped string that was used for the last prompt. This can be useful for debugging your prompt display particularly when using ANSI/VT sequences. ### $GitPromptScriptBlock If you require even more customization than `$GitPromptSettings` provides, you can create your own prompt function to show whatever information you want. See the [Customizing Your PowerShell Prompt][wiki-custom-prompt] wiki page for details. However, if you need a custom prompt just to perform some non-prompt logic, you can still use posh-git's prompt function to write out the prompt string. This can be done with the `$GitPromptScriptBlock` variable as shown below e.g.: ```powershell # my profile.ps1 function prompt { # Your non-prompt logic here # Have posh-git display its default prompt & $GitPromptScriptBlock } ``` And if you'd like to write prompt text before and/or after the posh-git prompt, you can use posh-git's `Write-Prompt` command as shown below: ```powershell # my profile.ps1 function prompt { # Your non-prompt logic here $prompt = Write-Prompt "Text before posh-git prompt " -ForegroundColor ([ConsoleColor]::Green) $prompt += & $GitPromptScriptBlock $prompt += Write-Prompt "Text after posh-git prompt" -ForegroundColor ([ConsoleColor]::Magenta) if ($prompt) { "$prompt " } else { " " } } ``` ## Based on work by - Keith Dahlby, http://solutionizing.net/ - Mark Embling, http://www.markembling.info/ - Jeremy Skinner, http://www.jeremyskinner.co.uk/ [av-master-site]: https://ci.appveyor.com/project/dahlbyk/posh-git/branch/master [av-master-img]: https://ci.appveyor.com/api/projects/status/eb8erd5afaa01w80/branch/master?svg=true&pendingText=master%20%E2%80%A3%20pending&failingText=master%20%E2%80%A3%20failing&passingText=master%20%E2%80%A3%20passing [av-v0-img]: https://ci.appveyor.com/api/projects/status/eb8erd5afaa01w80/branch/v0?svg=true&pendingText=v0%20%E2%80%A3%20pending&failingText=v0%20%E2%80%A3%20failing&passingText=v0%20%E2%80%A3%20passing [av-v0-site]: https://ci.appveyor.com/project/dahlbyk/posh-git/branch/v0 [tv-master-img]: https://travis-ci.org/dahlbyk/posh-git.svg?branch=master [tv-master-site]: https://travis-ci.org/dahlbyk/posh-git [cc-master-img]: https://coveralls.io/repos/github/dahlbyk/posh-git/badge.svg?branch=master [cc-master-site]: https://coveralls.io/github/dahlbyk/posh-git?branch=master [cc-v0-img]: https://coveralls.io/repos/github/dahlbyk/posh-git/badge.svg?branch=v0 [cc-v0-site]: https://coveralls.io/github/dahlbyk/posh-git?branch=v0 [ansi-esc-code]: https://en.wikipedia.org/wiki/ANSI_escape_code [console-vt-seq]: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences [gitter-img]: https://badges.gitter.im/dahlbyk/posh-git.svg [gitter]: https://gitter.im/dahlbyk/posh-git?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge [pscore-install]: https://github.com/PowerShell/PowerShell#get-powershell [choco-img]: https://img.shields.io/chocolatey/dt/poshgit.svg [choco-site]: https://chocolatey.org/packages/poshgit/ [psgallery-img]: https://img.shields.io/powershellgallery/dt/posh-git.svg [psgallery-site]: https://www.powershellgallery.com/packages/posh-git [psgallery-v1]: https://www.powershellgallery.com/packages/posh-git/1.0.0 [w3c-colors]: https://www.w3schools.com/colors/colors_names.asp [posh-sshell-url]: https://github.com/dahlbyk/posh-sshell [prompt-def-long]: https://github.com/dahlbyk/posh-git/wiki/images/PromptDefaultLong.png "~\GitHub\posh-git [main ≡ +0 ~1 -0 | +0 ~1 -0 !]> " [prompt-default]: https://github.com/dahlbyk/posh-git/wiki/images/PromptDefault.png "~\GitHub\posh-git [main ≡]> " [prompt-prefix]: https://github.com/dahlbyk/posh-git/wiki/images/PromptPrefix.png "02-18 13:45:19 ~\GitHub\posh-git [main ≡]>" [prompt-no-abbr]: https://github.com/dahlbyk/posh-git/wiki/images/PromptNoAbbrevHome.png "C:\Users\Keith\GitHub\posh-git [main ≡]> " [prompt-path]: https://github.com/dahlbyk/posh-git/wiki/images/PromptOrangePath.png "~\GitHub\posh-git [main ≡]> " [prompt-swap]: https://github.com/dahlbyk/posh-git/wiki/images/PromptStatusFirst.png "[main ≡] ~\GitHub\posh-git> " [prompt-two-line]: https://github.com/dahlbyk/posh-git/wiki/images/PromptTwoLine.png "~\GitHub\posh-git [main ≡] > " [prompt-custom]: https://github.com/dahlbyk/posh-git/wiki/images/PromptCustom.png "[main ≡] ~\GitHub\posh-git 02-18 14:04:35 38> " [prompt-custom-wpathdelim]: https://github.com/dahlbyk/posh-git/wiki/images/PromptCustomDelim.png "[main ≡] {~\GitHub\posh-git} 02-18 14:04:35 38> " [prompt-error1]: https://github.com/dahlbyk/posh-git/wiki/images/PromptError1.png "~\GitHub\posh-git [main ≡] ! 07-01 22:36:31> " [prompt-error2]: https://github.com/dahlbyk/posh-git/wiki/images/PromptError2.png "~\GitHub\posh-git [main ≡] (1) 07-01 22:32:28> " [v0-change]: https://github.com/dahlbyk/posh-git/blob/v0/CHANGELOG.md [v0-readme]: https://github.com/dahlbyk/posh-git/blob/v0/README.md [v070-change]: https://github.com/dahlbyk/posh-git/blob/v0.7.0/CHANGELOG.md [v070-readme]: https://github.com/dahlbyk/posh-git/blob/v0.7.0/README.md [v071-change]: https://github.com/dahlbyk/posh-git/blob/v0.7.1/CHANGELOG.md [v071-readme]: https://github.com/dahlbyk/posh-git/blob/v0.7.1/README.md [v073-change]: https://github.com/dahlbyk/posh-git/blob/v0.7.3/CHANGELOG.md [v073-readme]: https://github.com/dahlbyk/posh-git/blob/v0.7.3/README.md [main-change]: https://github.com/dahlbyk/posh-git/blob/master/CHANGELOG.md [main-readme]: https://github.com/dahlbyk/posh-git/blob/master/README.md [v1b1-change]: https://github.com/dahlbyk/posh-git/blob/v1.0.0-beta1/CHANGELOG.md [v1b1-readme]: https://github.com/dahlbyk/posh-git/blob/v1.0.0-beta1/README.md [v1b2-change]: https://github.com/dahlbyk/posh-git/blob/v1.0.0-beta2/CHANGELOG.md [v1b2-readme]: https://github.com/dahlbyk/posh-git/blob/v1.0.0-beta2/README.md [v1b3-change]: https://github.com/dahlbyk/posh-git/blob/v1.0.0-beta3/CHANGELOG.md [v1b3-readme]: https://github.com/dahlbyk/posh-git/blob/v1.0.0-beta3/README.md [v1b4-change]: https://github.com/dahlbyk/posh-git/blob/v1.0.0-beta4/CHANGELOG.md [v1b4-readme]: https://github.com/dahlbyk/posh-git/blob/v1.0.0-beta4/README.md [v1b5-change]: https://github.com/dahlbyk/posh-git/blob/v1.0.0-beta5/CHANGELOG.md [v1b5-readme]: https://github.com/dahlbyk/posh-git/blob/v1.0.0-beta5/README.md [v1-change]: https://github.com/dahlbyk/posh-git/blob/v1.0.0/CHANGELOG.md [v1-readme]: https://github.com/dahlbyk/posh-git/blob/v1.0.0/README.md [v1.1-change]: https://github.com/dahlbyk/posh-git/blob/v1.1.0/CHANGELOG.md [v1.1-readme]: https://github.com/dahlbyk/posh-git/blob/v1.1.0/README.md [wiki-custom-prompt]: https://github.com/dahlbyk/posh-git/wiki/Customizing-Your-PowerShell-Prompt ================================================ FILE: chocolatey/packAndLocalInstall.ps1 ================================================ param ($Remote = 'origin', [switch]$Force) Push-Location $PSScriptRoot $nuspec = [xml](Get-Content poshgit.nuspec) $version = $nuspec.package.metadata.version $tag = "v$version" if ($Force) { git tag -f $tag git push -f $Remote $tag } elseif (!$(git ls-remote $Remote $tag)) { Write-Warning "'$Remote/$tag' not found! Use -Force to create tag at HEAD." return } choco pack poshgit.nuspec choco install -f -y poshgit -pre --version=$version -s . Pop-Location ================================================ FILE: chocolatey/poshgit.nuspec ================================================ poshgit posh-git 1.1.0-alpha Keith Dahlby, Keith Hill, Mark Embling, Jeremy Skinner Keith Dahlby ### posh-git A set of PowerShell scripts which provide Git/PowerShell integration ### Prompt for Git repositories The prompt within Git repositories can show the current branch and the state of files (additions, modifications, deletions) within. ### Tab completion Provides tab completion for common commands when using git. E.g. `git ch<tab>` --> `git checkout` ### Usage See profile.example.ps1 as to how you can integrate the tab completion and/or git prompt into your own profile. Prompt formatting, among other things, can be customized using `$GitPromptSettings`, `$GitTabSettings` and `$TortoiseGitSettings`. Note on performance: displaying file status in the git prompt for a very large repo can be prohibitively slow. Rather than turn off file status entirely, you can disable it on a repo-by-repo basis by adding individual repository paths to `$GitPromptSettings.RepositoriesInWhichToDisableFileStatus`. Provides prompt with Git status summary information and tab completion for Git commands, parameters, remotes and branch names. poshgit posh-git powershell git https://github.com/dahlbyk/posh-git https://github.com/dahlbyk/posh-git/blob/master/LICENSE.txt false ================================================ FILE: chocolatey/tests/InstallChocolatey.Tests.ps1 ================================================ $packageName = "poshgit" cpack function Setup-Environment { Cleanup $env:poshGit = join-path (Resolve-Path .\Tests ) dahlbyk-posh-git-60be436.zip $profileScript = "function Prompt(){ `$host.ui.RawUI.WindowTitle = `"My Prompt`" }" (Set-Content $Profile -value $profileScript -Force) } function Cleanup { Clean-Temp Remove-Item $env:ChocolateyInstall\lib\$packageName* -Recurse -Force } function Clean-Temp { if(Test-Path $env:Temp\Chocolatey\$packageName) {Remove-Item $env:Temp\Chocolatey\$packageName -Recurse -Force} } function RunInstall { cinst $packageName -source (Resolve-Path .) } $binRoot = join-path $env:systemdrive 'tools' if($null -ne $env:chocolatey_bin_root){$binRoot = join-path $env:systemdrive $env:chocolatey_bin_root} $poshgitPath = join-path $binRoot 'poshgit' if(Test-Path $Profile) { $currentProfileScript = (Get-Content $Profile) } function Clean-Environment { Set-Content $Profile -value $currentProfileScript -Force } Describe "Install-Posh-Git" { It "WillRemvePreviousInstallVersion" { Setup-Environment try{ Add-Content $profile -value ". '$poshgitPath\posh-git\profile.example.ps1'" RunInstall $newProfile = (Get-Content $Profile) $pgitDir = [Array](Get-ChildItem "$poshgitPath\*posh-git*\" | Sort-Object -Property LastWriteTime)[-1] ($newProfile -like ". '$poshgitPath\posh-git\profile.example.ps1'").Count.should.be(0) ($newProfile -like ". '$pgitDir\profile.example.ps1'").Count.should.be(1) } catch { write-host (Get-Content $Profile) throw } finally {Clean-Environment} } It "WillNotAddDuplicateCallOnRepeatInstall" { Setup-Environment try{ RunInstall Cleanup RunInstall $newProfile = (Get-Content $Profile) $pgitDir = [Array](Get-ChildItem "$poshgitPath\*posh-git*\" | Sort-Object -Property LastWriteTime)[-1] ($newProfile -like ". '$pgitDir\profile.example.ps1'").Count.should.be(1) } catch { write-host (Get-Content $Profile) throw } finally {Clean-Environment} } It "WillPreserveOldPromptLogic" { Setup-Environment try{ RunInstall . $Profile $host.ui.RawUI.WindowTitle = "bad" Prompt $host.ui.RawUI.WindowTitle.should.be("My Prompt") } catch { write-host (Get-Content function:\prompt) throw } finally { Clean-Environment } } It "WillOutputVcsStatus" { Setup-Environment try{ RunInstall mkdir PoshTest Push-Location PoshTest git init . $Profile $global:wh="" New-Item function:\global:Write-Host -value "param([object] `$object, `$backgroundColor, `$foregroundColor, [switch] `$nonewline) try{Write-Output `$object;[string]`$global:wh += `$object.ToString()} catch{}" Prompt Pop-Location $wh.should.be("$pwd\PoshTest [master]") } catch { write-output (Get-Content $Profile) throw } finally { Clean-Environment if( Test-Path function:\Write-Host ) {Remove-Item function:\Write-Host} if( Test-Path PoshTest ) {Remove-Item PoshTest -Force -Recurse} } } It "WillSucceedOnEmptyProfile" { Setup-Environment try{ Remove-Item $Profile -Force RunInstall mkdir PoshTest Push-Location PoshTest git init . $Profile $global:wh="" New-Item function:\global:Write-Host -value "param([object] `$object, `$backgroundColor, `$foregroundColor, [switch] `$nonewline) try{Write-Output `$object;[string]`$global:wh += `$object.ToString()} catch{}" Prompt Pop-Location $wh.should.be("$pwd\PoshTest [master]") } catch { write-output (Get-Content $Profile) throw } finally { Clean-Environment if( Test-Path function:\Write-Host ) {Remove-Item function:\Write-Host} if( Test-Path PoshTest ) {Remove-Item PoshTest -Force -Recurse} } } It "WillSucceedOnProfileWithPromptWithWriteHost" { Cleanup Setup-Environment try{ Remove-Item $Profile -Force Add-Content $profile -value "function prompt {Write-Host 'Hi'}" -Force RunInstall mkdir PoshTest Push-Location PoshTest git init . $Profile $global:wh="" New-Item function:\global:Write-Host -value "param([object] `$object, `$backgroundColor, `$foregroundColor, [switch] `$nonewline) try{Write-Output `$object;[string]`$global:wh += `$object.ToString()} catch{}" Prompt Remove-Item function:\global:Write-Host Pop-Location $wh.should.be("$pwd\PoshTest [master]") } catch { write-output (Get-Content $Profile) throw } finally { Clean-Environment if( Test-Path function:\Write-Host ) {Remove-Item function:\Write-Host} if( Test-Path PoshTest ) {Remove-Item PoshTest -Force -Recurse} } } It "WillSucceedOnUpdatingFrom040" { Cleanup Setup-Environment try{ Remove-Item $Profile -Force Add-Content $profile -value ". 'C:\tools\poshgit\dahlbyk-posh-git-60be436\profile.example.ps1'" -Force RunInstall mkdir PoshTest Push-Location PoshTest git init write-output (Get-Content function:\prompt) . $Profile $global:wh="" New-Item function:\global:Write-Host -value "param([object] `$object, `$backgroundColor, `$foregroundColor, [switch] `$nonewline) try{Write-Output `$object;[string]`$global:wh += `$object.ToString()} catch{}" Prompt Remove-Item function:\global:Write-Host Pop-Location $wh.should.be("$pwd\PoshTest [master]") } catch { write-output (Get-Content $Profile) throw } finally { Clean-Environment if( Test-Path function:\Write-Host ) {Remove-Item function:\Write-Host} if( Test-Path PoshTest ) {Remove-Item PoshTest -Force -Recurse} } } } ================================================ FILE: chocolatey/tools/chocolateyInstall.ps1 ================================================ try { $poshgitPath = join-path (Get-ToolsLocation) 'poshgit' try { if (test-path($poshgitPath)) { Write-Host "Attempting to remove existing `'$poshgitPath`'." remove-item $poshgitPath -recurse -force } } catch { Write-Host "Could not remove `'$poshgitPath`'" } $version = "v$Env:chocolateyPackageVersion" if ($version -eq 'v') { $version = 'master' } $poshGitInstall = if ($env:poshGit) { $env:poshGit } else { "https://github.com/dahlbyk/posh-git/zipball/$version" } $zip = Install-ChocolateyZipPackage 'poshgit' $poshGitInstall $poshgitPath $currentVersionPath = Get-ChildItem "$poshgitPath\*posh-git*\" | Sort-Object -Property LastWriteTime | Select-Object -Last 1 if (Test-Path $PROFILE) { $oldProfile = @(Get-Content $PROFILE) . $currentVersionPath\src\Utils.ps1 $oldProfileEncoding = Get-FileEncoding $PROFILE $newProfile = @() foreach($line in $oldProfile) { if ($line -like '*PoshGitPrompt*') { continue } if ($line -like '. *posh-git*profile.example.ps1*') { $line = ". '$currentVersionPath\profile.example.ps1' choco" } if ($line -like 'Import-Module *\src\posh-git.psd1*') { $line = "Import-Module '$currentVersionPath\src\posh-git.psd1'" } $newProfile += $line } Set-Content -path $profile -value $newProfile -Force -Encoding $oldProfileEncoding } $installer = Join-Path $currentVersionPath 'install.ps1' & $installer } catch { try { if ($oldProfile ) { Set-Content -path $PROFILE -value $oldProfile -Force -Encoding $oldProfileEncoding } } catch {} throw } ================================================ FILE: chocolatey/tools/chocolateyUninstall.ps1 ================================================ try { $poshgitPath = join-path (Get-ToolsLocation) 'poshgit' $currentVersionPath = Get-ChildItem "$poshgitPath\*posh-git*\" | Sort-Object -Property LastWriteTime | Select-Object -Last 1 if(Test-Path $PROFILE) { $oldProfile = @(Get-Content $PROFILE) . $currentVersionPath\src\Utils.ps1 $oldProfileEncoding = Get-FileEncoding $PROFILE $newProfile = @() foreach($line in $oldProfile) { if ($line -like '*PoshGitPrompt*') { continue; } if ($line -like '*Load posh-git example profile*') { continue; } if($line -like '. *posh-git*profile.example.ps1*') { continue; } if($line -like 'Import-Module *\src\posh-git.psd1*') { continue; } $newProfile += $line } Set-Content -path $profile -value $newProfile -Force -Encoding $oldProfileEncoding } try { if (test-path($poshgitPath)) { Write-Host "Attempting to remove existing `'$poshgitPath`'." remove-item $poshgitPath -recurse -force } } catch { Write-Host "Could not remove `'$poshgitPath`'" } } catch { try { if($oldProfile){ Set-Content -path $PROFILE -value $oldProfile -Force -Encoding $oldProfileEncoding } } catch {} throw } ================================================ FILE: install.ps1 ================================================ param([switch]$WhatIf = $false, [switch]$Force = $false, [switch]$Verbose = $false) $installDir = Split-Path $MyInvocation.MyCommand.Path -Parent Import-Module $installDir\src\posh-git.psd1 Add-PoshGitToProfile -WhatIf:$WhatIf -Force:$Force -Verbose:$Verbose ================================================ FILE: profile.example.ps1 ================================================ # Import the posh-git module, first via installed posh-git module. # If the module isn't installed, then attempt to load it from the cloned posh-git Git repo. $poshGitModule = Get-Module posh-git -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1 if ($poshGitModule) { $poshGitModule | Import-Module } elseif (Test-Path -LiteralPath ($modulePath = Join-Path (Split-Path $MyInvocation.MyCommand.Path -Parent) (Join-Path src 'posh-git.psd1'))) { Import-Module $modulePath } else { throw "Failed to import posh-git." } # Settings for the prompt are in GitPrompt.ps1, so add any desired settings changes here. # Example: # $Global:GitPromptSettings.BranchBehindAndAheadDisplay = "Compact" if ($args[0] -ne 'choco') { Write-Warning "posh-git's profile.example.ps1 will be removed in a future version." Write-Warning "Consider using `Add-PoshGitToProfile` instead." } ================================================ FILE: src/AnsiUtils.ps1 ================================================ # Color codes from https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx $ConsoleColorToAnsi = @( 30 # Black 34 # DarkBlue 32 # DarkGreen 36 # DarkCyan 31 # DarkRed 35 # DarkMagenta 33 # DarkYellow 37 # Gray 90 # DarkGray 94 # Blue 92 # Green 96 # Cyan 91 # Red 95 # Magenta 93 # Yellow 97 # White ) $AnsiDefaultColor = 39 $AnsiEscape = [char]27 + "[" [Reflection.Assembly]::LoadWithPartialName('System.Drawing') > $null $ColorTranslatorType = 'System.Drawing.ColorTranslator' -as [Type] $ColorType = 'System.Drawing.Color' -as [Type] function EscapeAnsiString([string]$AnsiString) { if ($PSVersionTable.PSVersion.Major -ge 6) { $res = $AnsiString -replace "$([char]27)", '`e' } else { $res = $AnsiString -replace "$([char]27)", '$([char]27)' } $res } function Test-VirtualTerminalSequece([psobject[]]$Object, [switch]$Force) { foreach ($obj in $Object) { if (($Force -or $global:GitPromptSettings.AnsiConsole) -and ($obj -is [string])) { $obj.Contains($AnsiEscape) } else { $false } } } function Get-VirtualTerminalSequence ($color, [int]$offset = 0) { # Don't output ANSI escape sequences if the `$color` parameter is `$null`, # they would be broken anyway if ($null -eq $color) { return $null; } if ($color -is [byte]) { return "${AnsiEscape}$(38 + $offset);5;${color}m" } if ($color -is [int]) { $r = ($color -shr 16) -band 0xff $g = ($color -shr 8) -band 0xff $b = $color -band 0xff return "${AnsiEscape}$(38 + $offset);2;${r};${g};${b}m" } # Force 'DarkYellow' to ConsoleColor, since it is not an HTML color if ($color -eq [System.ConsoleColor]::DarkYellow) { $color = [System.ConsoleColor]::DarkYellow } elseif ($color -is [String]) { try { if ($ColorTranslatorType) { $color = $ColorTranslatorType::FromHtml($color) } } catch { Write-Debug $_ } } if ($ColorType -and ($color -is $ColorType)) { return "${AnsiEscape}$(38 + $offset);2;$($color.R);$($color.G);$($color.B)m" } if (($color -is [System.ConsoleColor]) -and ($color -ge 0) -and ($color -le 15)) { return "${AnsiEscape}$($ConsoleColorToAnsi[$color] + $offset)m" } return "${AnsiEscape}$($AnsiDefaultColor + $offset)m" } function Get-ForegroundVirtualTerminalSequence($Color) { return Get-VirtualTerminalSequence $Color } function Get-BackgroundVirtualTerminalSequence($Color) { return Get-VirtualTerminalSequence $Color 10 } ================================================ FILE: src/CheckRequirements.ps1 ================================================ $global:GitMissing = $false $script:GitCygwin = $false $script:GitVersion = $requiredVersion = [System.Version]'2.15' if (!(Get-Command git -TotalCount 1 -ErrorAction SilentlyContinue)) { Write-Warning "git command could not be found. Please create an alias or add it to your PATH." $global:GitMissing = $true return } function Test-GitVersion ($version = $([string](git --version 2> $null))) { if ($version -notmatch '(?\d+(?:\.\d+)+)(?(?[-.]rc\d+)?\.windows|\.vfs)?') { Write-Warning "posh-git could not parse Git version ($version)" $script:GitVersion = $version return $false } # On Windows, check if Git is not "Git for Windows" if ((($PSVersionTable.PSVersion.Major -le 5) -or $IsWindows) -and !$Matches['g4w']) { $script:GitCygwin = $true if (!$Env:POSHGIT_CYGWIN_WARNING) { Write-Warning 'You appear to have an unsupported Git distribution; setting $GitPromptSettings.AnsiConsole = $false. posh-git recommends Git for Windows.' } } $script:GitVersion = [System.Version]$Matches['ver'] return $GitVersion -ge $requiredVersion } if (!(Test-GitVersion)) { Write-Warning "posh-git requires Git $requiredVersion or better. You have $GitVersion." } ================================================ FILE: src/ConsoleMode.ps1 ================================================ # Hack! https://gist.github.com/lzybkr/f2059cb2ee8d0c13c65ab933b75e998c # Always skip setting the console mode on non-Windows platforms. if (($PSVersionTable.PSVersion.Major -ge 6) -and !$IsWindows) { function Set-ConsoleMode { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] param() } return } $consoleModeSource = @" using System; using System.Runtime.InteropServices; public class NativeConsoleMethods { [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr GetStdHandle(int handleId); [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode); [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode); public static uint GetConsoleMode(bool input = false) { var handle = GetStdHandle(input ? -10 : -11); uint mode; if (GetConsoleMode(handle, out mode)) { return mode; } return 0xffffffff; } public static uint SetConsoleMode(bool input, uint mode) { var handle = GetStdHandle(input ? -10 : -11); if (SetConsoleMode(handle, mode)) { return GetConsoleMode(input); } return 0xffffffff; } } "@ [Flags()] enum ConsoleModeInputFlags { ENABLE_PROCESSED_INPUT = 0x0001 ENABLE_LINE_INPUT = 0x0002 ENABLE_ECHO_INPUT = 0x0004 ENABLE_WINDOW_INPUT = 0x0008 ENABLE_MOUSE_INPUT = 0x0010 ENABLE_INSERT_MODE = 0x0020 ENABLE_QUICK_EDIT_MODE = 0x0040 ENABLE_EXTENDED_FLAGS = 0x0080 ENABLE_AUTO_POSITION = 0x0100 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0200 } [Flags()] enum ConsoleModeOutputFlags { ENABLE_PROCESSED_OUTPUT = 0x0001 ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 } function Set-ConsoleMode { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] param( [Parameter(ParameterSetName = "ANSI")] [switch] $ANSI, [Parameter(ParameterSetName = "Mode")] [uint32] $Mode, [switch] $StandardInput ) begin { # Module import is speeded up by deferring the Add-Type until the first time this function is called. # Add the NativeConsoleMethods type but only once per session. if (!('NativeConsoleMethods' -as [System.Type])) { Add-Type $consoleModeSource } } end { if ($ANSI) { $outputMode = [NativeConsoleMethods]::GetConsoleMode($false) $null = [NativeConsoleMethods]::SetConsoleMode($false, $outputMode -bor [ConsoleModeOutputFlags]::ENABLE_VIRTUAL_TERMINAL_PROCESSING) if ($StandardInput) { $inputMode = [NativeConsoleMethods]::GetConsoleMode($true) $null = [NativeConsoleMethods]::SetConsoleMode($true, $inputMode -bor [ConsoleModeInputFlags]::ENABLE_VIRTUAL_TERMINAL_PROCESSING) } } else { [NativeConsoleMethods]::SetConsoleMode($StandardInput, $Mode) } } } ================================================ FILE: src/GitParamTabExpansion.ps1 ================================================ # Variable is used in GitTabExpansion.ps1 $shortGitParams = @{ add = 'n v f i p e u A N' bisect = '' blame = 'b L l t S p M C h c f n s e w' branch = 'd D l f m M r a v vv q t u' checkout = 'q f b B t l m p' cherry = 'v' 'cherry-pick' = 'e x r m n s S X' clean = 'd f i n q e x X' clone = 'l s q v n o b u c' commit = 'a p C c z F m t s n e i o u v q S' config = 'f l z e' diff = 'p u s U z B M C D l S G O R a b w W' difftool = 'd y t x g' fetch = 'a f k p n t u q v' grep = 'a i I w v h H E G P F n l L O z c p C A B W f e q' help = 'a g i m w' init = 'q' log = 'L n i E F g c c m r t' merge = 'e n s X q v S m' mergetool = 't y' mv = 'f k n v' notes = 'f m F C c n s q v' prune = 'n v' pull = 'q v e n s X r a f k u' push = 'n f u q v' rebase = 'm s X S q v n C f i p x' remote = 'v' reset = 'q p' restore = 's p W S q m' revert = 'e m n S s X' rm = 'f n r q' shortlog = 'n s e w' stash = 'p k u a q' status = 's b u z' submodule = 'q b f n N' switch = 'c C d f m q t' tag = 'a s u f d v n l m F' whatchanged = 'p' } # Variable is used in GitTabExpansion.ps1 $longGitParams = @{ add = 'dry-run verbose force interactive patch edit update all no-ignore-removal no-all ignore-removal intent-to-add refresh ignore-errors ignore-missing renormalize' bisect = 'no-checkout term-old term-new' blame = 'root show-stats reverse porcelain line-porcelain incremental encoding= contents date score-debug show-name show-number show-email abbrev' branch = 'color no-color list abbrev= no-abbrev column no-column merged no-merged contains set-upstream track no-track set-upstream-to= unset-upstream edit-description delete create-reflog force move all verbose quiet' checkout = 'quiet force ours theirs track no-track detach orphan ignore-skip-worktree-bits merge conflict= patch' 'cherry-pick' = 'edit mainline no-commit signoff gpg-sign ff allow-empty allow-empty-message keep-redundant-commits strategy= strategy-option= continue quit abort' clean = 'force interactive dry-run quiet exclude=' clone = 'local no-hardlinks shared reference quiet verbose progress no-checkout bare mirror origin branch upload-pack template= config depth single-branch no-single-branch recursive recurse-submodules separate-git-dir=' commit = 'all patch reuse-message reedit-message fixup squash reset-author short branch porcelain long null file author date message template signoff no-verify allow-empty allow-empty-message cleanup= edit no-edit amend no-post-rewrite include only untracked-files verbose quiet dry-run status no-status gpg-sign no-gpg-sign' config = 'replace-all add get get-all get-regexp get-urlmatch global system local file blob remove-section rename-section unset unset-all list bool int bool-or-int path null get-colorbool get-color edit includes no-includes' describe = 'dirty all tags contains abbrev candidates= exact-match debug long match always first-parent' diff = 'cached patch no-patch unified= raw patch-with-raw minimal patience histogram diff-algorithm= stat numstat shortstat dirstat summary patch-with-stat name-only name-status submodule color no-color word-diff word-diff-regex color-words no-renames check full-index binary apprev break-rewrites find-renames find-copies find-copies-harder irreversible-delete diff-filter= pickaxe-all pickaxe-regex relative text ignore-space-at-eol ignore-space-change ignore-all-space ignore-blank-lines inter-hunk-context= function-context exit-code quiet ext-diff no-ext-diff textconv no-textconv ignore-submodules src-prefix dst-prefix no-prefix staged' difftool = 'dir-diff no-prompt prompt tool= tool-help no-symlinks symlinks extcmd= gui' fetch = 'all append depth= unshallow update-shallow dry-run force keep multiple prune no-tags tags recurse-submodules= no-recurse-submodules submodule-prefix= recurse-submodules-default= update-head-ok upload-pack quiet verbose progress' gc = 'aggressive auto prune= no-prune quiet force' grep = 'cached no-index untracked no-exclude-standard exclude-standard text textconv no-textconv ignore-case max-depth word-regexp invert-match full-name extended-regexp basic-regexp perl-regexp fixed-strings line-number files-with-matches open-file-in-pager null count color no-color break heading show-function context after-context before-context function-context and or not all-match quiet' help = 'all guides info man web' init = 'quiet bare template= separate-git-dir= shared=' log = 'follow no-decorate decorate source use-mailmap full-diff log-size max-count skip since after until before author committer grep-reflog grep all-match regexp-ignore-case basic-regexp extended-regexp fixed-strings perl-regexp remove-empty merges no-merges min-parents max-parents no-min-parents no-max-parents first-parent not all branches tags remote glob= exclude= ignore-missing bisect stdin cherry-mark cherry-pick left-only right-only cherry walk-reflogs merge boundary simplify-by-decoration full-history dense sparse simplify-merges ancestry-path date-order author-date-order topo-order reverse objects objects-edge unpacked no-walk= do-walk pretty format= abbrev-commit no-abbrev-commit oneline encoding= notes no-notes standard-notes no-standard-notes show-signature relative-date date= parents children left-right graph show-linear-break patch stat' merge = 'commit no-commit edit no-edit ff no-ff ff-only log no-log stat no-stat squash no-squash strategy strategy-option verify-signatures no-verify-signatures summary no-summary quiet verbose progress no-progress gpg-sign rerere-autoupdate no-rerere-autoupdate abort allow-unrelated-histories' mergetool = 'tool= tool-help no-prompt prompt' mv = 'force dry-run verbose' notes = 'force message file reuse-message reedit-message ref ignore-missing stdin dry-run strategy= commit abort quiet verbose' prune = 'dry-run verbose expire' pull = 'quiet verbose recurse-submodules= no-recurse-submodules= commit no-commit edit no-edit ff no-ff ff-only log no-log stat no-stat squash no-squash strategy= strategy-option= verify-signatures no-verify-signatures summary no-summary rebase= no-rebase all append depth= unshallow update-shallow force keep no-tags update-head-ok upload-pack progress' push = 'all prune mirror dry-run porcelain delete tags follow-tags receive-pack= exec= force-with-lease no-force-with-lease force repo= set-upstream thin no-thin quiet verbose progress recurse-submodules= verify no-verify' rebase = 'onto continue abort keep-empty skip edit-todo merge strategy= strategy-option= gpg-sign quiet verbose stat no-stat no-verify verify force-rebase fork-point no-fork-point ignore-whitespace whitespace= committer-date-is-author-date ignore-date interactive preserve-merges exec root autosquash no-autosquash autostash no-autostash no-ff' reflog = 'stale-fix expire= expire-unreachable= all updateref rewrite verbose' remote = 'verbose' reset = 'patch quiet soft mixed hard merge keep' restore = 'source= patch worktree staged quiet progress no-progress ours theirs merge conflict= ignore-unmerged ignore-skip-worktree-bits overlay no-overlay' revert = 'edit mainline no-edit no-commit gpg-sign signoff strategy= strategy-option continue quit abort' rm = 'force dry-run cached ignore-unmatch quiet' shortlog = 'numbered summary email format=' show = 'pretty= format= abbrev-commit no-abbrev-commit oneline encoding= expand-tabs no-expand-tabs notes no-notes show-notes no-standard-notes standard-notes show-signature name-only name-status stat shortstat numstat' stash = 'patch no-keep-index keep-index include-untracked all quiet index' status = 'short branch porcelain long untracked-files ignore-submodules ignored column no-column' submodule = 'quiet branch force cached files summary-limit remote no-fetch checkout merge rebase init name reference recursive depth' switch = 'create force-create detach guess no-guess force discard-changes merge conflict= quiet no-progress track no-track orphan ignore-other-worktrees recurse-submodules no-recurse-submodules' tag = 'annotate sign local-user force delete verify list sort column no-column contains points-at message file cleanup' whatchanged = 'since' } $shortVstsGlobal = 'h o' $shortVstsParams = @{ abandon = "i $shortVstsGlobal" create = "d i p r s t $shortVstsGlobal" complete = "i $shortVstsGlobal" list = "i p r s t $shortVstsGlobal" reactivate = "i $shortVstsGlobal" 'set-vote' = "i $shortVstsGlobal" show = "i $shortVstsGlobal" update = "d i $shortVstsGlobal" } $longVstsGlobal = 'debug help output query verbose' $longVstsParams = @{ abandon = "id detect instance $longVstsGlobal" create = "auto-complete delete-source-branch work-items bypass-policy bypass-policy-reason description detect instance merge-commit-message open project repository reviewers source-branch squash target-branch title $longVstsGlobal" complete = "id detect instance $longVstsGlobal" list = " $longVstsGlobal" reactivate = " $longVstsGlobal" 'set-vote' = " $longVstsGlobal" show = " $longVstsGlobal" update = " $longVstsGlobal" } # Variable is used in GitTabExpansion.ps1 $gitParamValues = @{ blame = @{ encoding = 'utf-8 none' } branch = @{ color = 'always never auto' abbrev = '7 8 9 10' } checkout = @{ conflict = 'merge diff3' } 'cherry-pick' = @{ strategy = 'resolve recursive octopus ours subtree' } commit = @{ 'cleanup' = 'strip whitespace verbatim scissors default' } diff = @{ unified = '0 1 2 3 4 5' 'diff-algorithm' = 'default patience minimal histogram myers' color = 'always never auto' 'word-diff' = 'color plain porcelain none' abbrev = '7 8 9 10' 'diff-filter' = 'A C D M R T U X B *' 'inter-hunk-context' = '0 1 2 3 4 5' 'ignore-submodules' = 'none untracked dirty all' } difftool = @{ tool = 'vimdiff vimdiff2 araxis bc3 codecompare deltawalker diffmerge diffuse ecmerge emerge gvimdiff gvimdiff2 kdiff3 kompare meld opendiff p4merge tkdiff xxdiff' } fetch = @{ 'recurse-submodules' = 'yes on-demand no' 'recurse-submodules-default' = 'yes on-demand' } init = @{ shared = 'false true umask group all world everybody o' } log = @{ decorate = 'short full no' 'no-walk' = 'sorted unsorted' pretty = { param($format) gitConfigKeys 'pretty' $format 'oneline short medium full fuller email raw' } format = { param($format) gitConfigKeys 'pretty' $format 'oneline short medium full fuller email raw' } encoding = 'UTF-8' date = 'relative local default iso rfc short raw' } merge = @{ strategy = 'resolve recursive octopus ours subtree' log = '1 2 3 4 5 6 7 8 9' } mergetool = @{ tool = 'vimdiff vimdiff2 araxis bc3 codecompare deltawalker diffmerge diffuse ecmerge emerge gvimdiff gvimdiff2 kdiff3 kompare meld opendiff p4merge tkdiff xxdiff' } notes = @{ strategy = 'manual ours theirs union cat_sort_uniq' } pull = @{ strategy = 'resolve recursive octopus ours subtree' 'recurse-submodules' = 'yes on-demand no' 'no-recurse-submodules' = 'yes on-demand no' rebase = 'false true preserve' } push = @{ 'recurse-submodules' = 'check on-demand' } rebase = @{ strategy = 'resolve recursive octopus ours subtree' } restore = @{ conflict = 'merge diff3' source = { param($ref) gitBranches $ref $true gitTags $ref } } revert = @{ strategy = 'resolve recursive octopus ours subtree' } show = @{ pretty = { param($format) gitConfigKeys 'pretty' $format 'oneline short medium full fuller email raw' } format = { param($format) gitConfigKeys 'pretty' $format 'oneline short medium full fuller email raw' } encoding = 'utf-8' } status = @{ 'untracked-files' = 'no normal all' 'ignore-submodules' = 'none untracked dirty all' } switch = @{ conflict = 'merge diff3' } } ================================================ FILE: src/GitPrompt.ps1 ================================================ # Inspired by Mark Embling # http://www.markembling.info/view/my-ideal-powershell-prompt-with-git-integration $global:GitPromptSettings = [PoshGitPromptSettings]::new() $global:GitPromptValues = [PoshGitPromptValues]::new() # Override some of the normal colors if the background color is set to the default DarkMagenta. $s = $global:GitPromptSettings if ($Host.UI.RawUI.BackgroundColor -eq [ConsoleColor]::DarkMagenta) { $s.LocalDefaultStatusSymbol.ForegroundColor = 'Green' $s.LocalWorkingStatusSymbol.ForegroundColor = 'Red' $s.BeforeIndex.ForegroundColor = 'Green' $s.IndexColor.ForegroundColor = 'Green' $s.WorkingColor.ForegroundColor = 'Red' } <# .SYNOPSIS Creates a new instance of a PoshGitPromptSettings object that can be assigned to $GitPromptSettings. .DESCRIPTION Creates a new instance of a PoshGitPromptSettings object that can be used to reset the $GitPromptSettings back to its default. .INPUTS None .OUTPUTS PoshGitPromptSettings .EXAMPLE PS> $GitPromptSettings = New-GitPromptSettings This will reset the current $GitPromptSettings back to its default. #> function New-GitPromptSettings { [PoshGitPromptSettings]::new() } <# .SYNOPSIS Writes the object to the display or renders it as a string using ANSI/VT sequences. .DESCRIPTION Writes the specified object to the display unless $GitPromptSettings.AnsiConsole is enabled. In this case, the Object is rendered, along with the specified colors, as a string with the appropriate ANSI/VT sequences for colors embedded in the string. If a StringBuilder is provided, the string is appended to the StringBuilder. .EXAMPLE PS C:\> Write-Prompt "PS > " -ForegroundColor Cyan -BackgroundColor Black On a system where $GitPromptSettings.AnsiConsole is set to $false, this will write the above to the display using the Write-Host command. If AnsiConsole is set to $true, this will return a string of the form: "`e[96m`e[40mPS > `e[0m". .EXAMPLE PS C:\> $sb = [System.Text.StringBuilder]::new() PS C:\> $sb | Write-Prompt "PS > " -ForegroundColor Cyan -BackgroundColor Black On a system where $GitPromptSettings.AnsiConsole is set to $false, this will write the above to the display using the Write-Host command. If AnsiConsole is set to $true, this will append the following string to the StringBuilder object piped into the command: "`e[96m`e[40mPS > `e[0m". #> function Write-Prompt { [CmdletBinding(DefaultParameterSetName="Default")] param( # Specifies objects to display in the console or render as a string if # $GitPromptSettings.AnsiConsole is enabled. If the Object is of type # [PoshGitTextSpan] the other color parameters are ignored since a # [PoshGitTextSpan] provides the colors. [Parameter(Mandatory, Position=0)] $Object, # Specifies the foreground color. [Parameter(ParameterSetName="Default")] $ForegroundColor = $null, # Specifies the background color. [Parameter(ParameterSetName="Default")] $BackgroundColor = $null, # Specifies both the background and foreground colors via [PoshGitCellColor] object. [Parameter(ParameterSetName="CellColor")] [ValidateNotNull()] [PoshGitCellColor] $Color, # When specified and $GitPromptSettings.AnsiConsole is enabled, the Object parameter # is written to the StringBuilder along with the appropriate ANSI/VT sequences for # the specified foreground and background colors. [Parameter(ValueFromPipeline = $true)] [System.Text.StringBuilder] $StringBuilder ) if (!$Object -or (($Object -is [PoshGitTextSpan]) -and !$Object.Text)) { return $(if ($StringBuilder) { $StringBuilder } else { "" }) } if ($PSCmdlet.ParameterSetName -eq "CellColor") { $bgColor = $Color.BackgroundColor $fgColor = $Color.ForegroundColor } else { $bgColor = $BackgroundColor $fgColor = $ForegroundColor } $s = $global:GitPromptSettings if ($s) { if ($null -eq $fgColor) { $fgColor = $s.DefaultColor.ForegroundColor } if ($null -eq $bgColor) { $bgColor = $s.DefaultColor.BackgroundColor } if ($s.AnsiConsole) { if ($Object -is [PoshGitTextSpan]) { $str = $Object.ToAnsiString() } else { # If we know which colors were changed, we can reset only these and leave others be. $reset = [System.Collections.Generic.List[string]]::new() $e = [char]27 + "[" $fg = $fgColor if (($null -ne $fg) -and !(Test-VirtualTerminalSequece $fg)) { $fg = Get-ForegroundVirtualTerminalSequence $fg $reset.Add('39') } $bg = $bgColor if (($null -ne $bg) -and !(Test-VirtualTerminalSequece $bg)) { $bg = Get-BackgroundVirtualTerminalSequence $bg $reset.Add('49') } $str = "${Object}" if (Test-VirtualTerminalSequece $str -Force) { $reset.Clear() $reset.Add('0') } $str = "${fg}${bg}" + $str if ($reset.Count -gt 0) { $str += "${e}$($reset -join ';')m" } } return $(if ($StringBuilder) { $StringBuilder.Append($str) } else { $str }) } } if ($Object -is [PoshGitTextSpan]) { $bgColor = $Object.BackgroundColor $fgColor = $Object.ForegroundColor $Object = $Object.Text } $writeHostParams = @{ Object = $Object; NoNewLine = $true; } if ($bgColor -and ($bgColor -ge 0) -and ($bgColor -le 15)) { $writeHostParams.BackgroundColor = $bgColor } if ($fgColor -and ($fgColor -ge 0) -and ($fgColor -le 15)) { $writeHostParams.ForegroundColor = $fgColor } Write-Host @writeHostParams return $(if ($StringBuilder) { $StringBuilder } else { "" }) } <# .SYNOPSIS Writes the Git status for repo. Typically, you use Write-VcsStatus function instead of this one. .DESCRIPTION Writes the Git status for repo. This includes the branch name, branch status with respect to its remote (if exists), index status, working dir status, working dir local status and stash count (optional). Various settings from GitPromptSettngs are used to format and color the Git status. On systems that support ANSI terminal sequences, this method will return a string containing ANSI sequences to color various parts of the Git status string. This string can be written to the host and the ANSI sequences will be interpreted and converted to the specified behaviors which is typically setting the foreground and/or background color of text. .EXAMPLE PS C:\> Write-GitStatus (Get-GitStatus) Writes the Git status for the current repo. .INPUTS System.Management.Automation.PSCustomObject This is PSCustomObject returned by Get-GitStatus .OUTPUTS System.String This command returns a System.String object. #> function Write-GitStatus { param( # The Git status object that provides the status information to be written. # This object is retrieved via the Get-GitStatus command. [Parameter(Position = 0)] $Status ) $s = $global:GitPromptSettings if (!$Status -or !$s) { return } $sb = [System.Text.StringBuilder]::new(150) # When prompt is first (default), place the separator before the status summary if (!$s.DefaultPromptWriteStatusFirst) { $sb | Write-Prompt $s.PathStatusSeparator.Expand() > $null } $sb | Write-Prompt $s.BeforeStatus > $null $sb | Write-GitBranchName $Status -NoLeadingSpace > $null $sb | Write-GitBranchStatus $Status > $null $sb | Write-Prompt $s.BeforeIndex > $null if ($s.EnableFileStatus -and $Status.HasIndex) { $sb | Write-GitIndexStatus $Status > $null if ($Status.HasWorking) { $sb | Write-Prompt $s.DelimStatus > $null } } if ($s.EnableFileStatus -and $Status.HasWorking) { $sb | Write-GitWorkingDirStatus $Status > $null } $sb | Write-GitWorkingDirStatusSummary $Status > $null if ($s.EnableStashStatus -and ($Status.StashCount -gt 0)) { $sb | Write-GitStashCount $Status > $null } $sb | Write-Prompt $s.AfterStatus > $null # When status is first, place the separator after the status summary if ($s.DefaultPromptWriteStatusFirst) { $sb | Write-Prompt $s.PathStatusSeparator.Expand() > $null } if ($sb.Length -gt 0) { $sb.ToString() } } <# .SYNOPSIS Formats the branch name text according to $GitPromptSettings. .DESCRIPTION Formats the branch name text according the $GitPromptSettings: BranchNameLimit and TruncatedBranchSuffix. .EXAMPLE PS C:\> $branchName = Format-GitBranchName (Get-GitStatus).Branch Gets the branch name formatted as specified by the user's $GitPromptSettings. .INPUTS System.String This is the branch name as a string. .OUTPUTS System.String This command returns a System.String object. #> function Format-GitBranchName { param( # The branch name to format according to the GitPromptSettings: # BranchNameLimit and TruncatedBranchSuffix. [Parameter(Position=0)] [string] $BranchName ) $s = $global:GitPromptSettings if (!$s -or !$BranchName) { return "$BranchName" } $res = $BranchName if (($s.BranchNameLimit -gt 0) -and ($BranchName.Length -gt $s.BranchNameLimit)) { $res = "{0}{1}" -f $BranchName.Substring(0, $s.BranchNameLimit), $s.TruncatedBranchSuffix } $res } <# .SYNOPSIS Gets the colors to use for the branch status. .DESCRIPTION Gets the colors to use for the branch status. This color is typically used for the branch name as well. The default color is specified by $GitPromptSettins.BranchColor. But depending on the Git status object passed in, the colors could be changed to match that of one these other $GitPromptSettings: BranchBehindAndAheadStatusSymbol, BranchBehindStatusSymbol or BranchAheadStatusSymbol. .EXAMPLE PS C:\> $branchStatusColor = Get-GitBranchStatusColor (Get-GitStatus) Returns a PoshGitTextSpan with the foreground and background colors for the branch status. .INPUTS System.Management.Automation.PSCustomObject This is PSCustomObject returned by Get-GitStatus .OUTPUTS PoshGitTextSpan A PoshGitTextSpan with colors reflecting those to be used by branch status symbols. #> function Get-GitBranchStatusColor { param( # The Git status object that provides branch status information. # This object is retrieved via the Get-GitStatus command. [Parameter(Position = 0)] $Status ) $s = $global:GitPromptSettings if (!$s) { return [PoshGitTextSpan]::new() } $branchStatusTextSpan = [PoshGitTextSpan]::new($s.BranchColor) if (($Status.BehindBy -ge 1) -and ($Status.AheadBy -ge 1)) { # We are both behind and ahead of remote $branchStatusTextSpan = [PoshGitTextSpan]::new($s.BranchBehindAndAheadStatusSymbol) } elseif ($Status.BehindBy -ge 1) { # We are behind remote $branchStatusTextSpan = [PoshGitTextSpan]::new($s.BranchBehindStatusSymbol) } elseif ($Status.AheadBy -ge 1) { # We are ahead of remote $branchStatusTextSpan = [PoshGitTextSpan]::new($s.BranchAheadStatusSymbol) } $branchStatusTextSpan.Text = '' $branchStatusTextSpan } <# .SYNOPSIS Writes the branch name given the current Git status. .DESCRIPTION Writes the branch name given the current Git status which can retrieved via the Get-GitStatus command. Branch name can be affected by the $GitPromptSettings: BranchColor, BranchNameLimit, TruncatedBranchSuffix and Branch*StatusSymbol colors. .EXAMPLE PS C:\> Write-GitBranchName (Get-GitStatus) Writes the name of the current branch. .INPUTS System.Management.Automation.PSCustomObject This is PSCustomObject returned by Get-GitStatus .OUTPUTS System.String, System.Text.StringBuilder This command returns a System.String object unless the -StringBuilder parameter is supplied. In this case, it returns a System.Text.StringBuilder. #> function Write-GitBranchName { param( # The Git status object that provides the status information to be written. # This object is retrieved via the Get-GitStatus command. [Parameter(Position = 0)] $Status, # If specified the branch name is written into the provided StringBuilder object. [Parameter(ValueFromPipeline = $true)] [System.Text.StringBuilder] $StringBuilder, # If specified, suppresses the output of the leading space character. [Parameter()] [switch] $NoLeadingSpace ) $s = $global:GitPromptSettings if (!$Status -or !$s) { return $(if ($StringBuilder) { $StringBuilder } else { "" }) } $str = "" # Use the branch status colors (or CustomAnsi) to display the branch name $branchNameTextSpan = Get-GitBranchStatusColor $Status $branchNameTextSpan.Text = Format-GitBranchName $Status.Branch if (!$NoLeadingSpace) { $branchNameTextSpan.Text = " " + $branchNameTextSpan.Text } if ($StringBuilder) { $StringBuilder | Write-Prompt $branchNameTextSpan > $null } else { $str = Write-Prompt $branchNameTextSpan } return $(if ($StringBuilder) { $StringBuilder } else { $str }) } <# .SYNOPSIS Writes the branch status text given the current Git status. .DESCRIPTION Writes the branch status text given the current Git status which can retrieved via the Get-GitStatus command. Branch status includes information about the upstream branch, how far behind and/or ahead the local branch is from the remote. .EXAMPLE PS C:\> Write-GitBranchStatus (Get-GitStatus) Writes the status of the current branch to the host. .INPUTS System.Management.Automation.PSCustomObject This is PSCustomObject returned by Get-GitStatus .OUTPUTS System.String, System.Text.StringBuilder This command returns a System.String object unless the -StringBuilder parameter is supplied. In this case, it returns a System.Text.StringBuilder. #> function Write-GitBranchStatus { param( # The Git status object that provides the status information to be written. # This object is retrieved via the Get-GitStatus command. [Parameter(Position = 0)] $Status, # If specified the branch status is written into the provided StringBuilder object. [Parameter(ValueFromPipeline = $true)] [System.Text.StringBuilder] $StringBuilder, # If specified, suppresses the output of the leading space character. [Parameter()] [switch] $NoLeadingSpace ) $s = $global:GitPromptSettings if (!$Status -or !$s) { return $(if ($StringBuilder) { $StringBuilder } else { "" }) } $branchStatusTextSpan = Get-GitBranchStatusColor $Status if (!$Status.Upstream) { $branchStatusTextSpan.Text = $s.BranchUntrackedText } elseif ($Status.UpstreamGone -eq $true) { # Upstream branch is gone $branchStatusTextSpan.Text = $s.BranchGoneStatusSymbol.Text } elseif (($Status.BehindBy -eq 0) -and ($Status.AheadBy -eq 0)) { # We are aligned with remote $branchStatusTextSpan.Text = $s.BranchIdenticalStatusSymbol.Text } elseif (($Status.BehindBy -ge 1) -and ($Status.AheadBy -ge 1)) { # We are both behind and ahead of remote if ($s.BranchBehindAndAheadDisplay -eq "Full") { $branchStatusTextSpan.Text = ("{0}{1} {2}{3}" -f $s.BranchBehindStatusSymbol.Text, $Status.BehindBy, $s.BranchAheadStatusSymbol.Text, $status.AheadBy) } elseif ($s.BranchBehindAndAheadDisplay -eq "Compact") { $branchStatusTextSpan.Text = ("{0}{1}{2}" -f $Status.BehindBy, $s.BranchBehindAndAheadStatusSymbol.Text, $Status.AheadBy) } else { $branchStatusTextSpan.Text = $s.BranchBehindAndAheadStatusSymbol.Text } } elseif ($Status.BehindBy -ge 1) { # We are behind remote if (($s.BranchBehindAndAheadDisplay -eq "Full") -Or ($s.BranchBehindAndAheadDisplay -eq "Compact")) { $branchStatusTextSpan.Text = ("{0}{1}" -f $s.BranchBehindStatusSymbol.Text, $Status.BehindBy) } else { $branchStatusTextSpan.Text = $s.BranchBehindStatusSymbol.Text } } elseif ($Status.AheadBy -ge 1) { # We are ahead of remote if (($s.BranchBehindAndAheadDisplay -eq "Full") -or ($s.BranchBehindAndAheadDisplay -eq "Compact")) { $branchStatusTextSpan.Text = ("{0}{1}" -f $s.BranchAheadStatusSymbol.Text, $Status.AheadBy) } else { $branchStatusTextSpan.Text = $s.BranchAheadStatusSymbol.Text } } else { # This condition should not be possible but defaulting the variables to be safe $branchStatusTextSpan.Text = "?" } $str = "" if ($branchStatusTextSpan.Text) { $textSpan = [PoshGitTextSpan]::new($branchStatusTextSpan) if (!$NoLeadingSpace) { $textSpan.Text = " " + $branchStatusTextSpan.Text } if ($StringBuilder) { $StringBuilder | Write-Prompt $textSpan > $null } else { $str = Write-Prompt $textSpan } } return $(if ($StringBuilder) { $StringBuilder } else { $str }) } <# .SYNOPSIS Writes the index status text given the current Git status. .DESCRIPTION Writes the index status text given the current Git status. .EXAMPLE PS C:\> Write-GitIndexStatus (Get-GitStatus) Writes the Git index status to the host. .INPUTS System.Management.Automation.PSCustomObject This is PSCustomObject returned by Get-GitStatus .OUTPUTS System.String, System.Text.StringBuilder This command returns a System.String object unless the -StringBuilder parameter is supplied. In this case, it returns a System.Text.StringBuilder. #> function Write-GitIndexStatus { param( # The Git status object that provides the status information to be written. # This object is retrieved via the Get-GitStatus command. [Parameter(Position = 0)] $Status, # If specified the index status is written into the provided StringBuilder object. [Parameter(ValueFromPipeline = $true)] [System.Text.StringBuilder] $StringBuilder, # If specified, suppresses the output of the leading space character. [Parameter()] [switch] $NoLeadingSpace ) $s = $global:GitPromptSettings if (!$Status -or !$s) { return $(if ($StringBuilder) { $StringBuilder } else { "" }) } $str = "" if ($Status.HasIndex) { if ($s.ShowStatusWhenZero -or $Status.Index.Added) { $indexStatusText = " " if ($NoLeadingSpace) { $indexStatusText = "" $NoLeadingSpace = $false } $indexStatusText += "$($s.FileAddedText)$($Status.Index.Added.Count)" if ($StringBuilder) { $StringBuilder | Write-Prompt $indexStatusText -Color $s.IndexColor > $null } else { $str += Write-Prompt $indexStatusText -Color $s.IndexColor } } if ($s.ShowStatusWhenZero -or $status.Index.Modified) { $indexStatusText = " " if ($NoLeadingSpace) { $indexStatusText = "" $NoLeadingSpace = $false } $indexStatusText += "$($s.FileModifiedText)$($status.Index.Modified.Count)" if ($StringBuilder) { $StringBuilder | Write-Prompt $indexStatusText -Color $s.IndexColor > $null } else { $str += Write-Prompt $indexStatusText -Color $s.IndexColor } } if ($s.ShowStatusWhenZero -or $Status.Index.Deleted) { $indexStatusText = " " if ($NoLeadingSpace) { $indexStatusText = "" $NoLeadingSpace = $false } $indexStatusText += "$($s.FileRemovedText)$($Status.Index.Deleted.Count)" if ($StringBuilder) { $StringBuilder | Write-Prompt $indexStatusText -Color $s.IndexColor > $null } else { $str += Write-Prompt $indexStatusText -Color $s.IndexColor } } if ($Status.Index.Unmerged) { $indexStatusText = " " if ($NoLeadingSpace) { $indexStatusText = "" $NoLeadingSpace = $false } $indexStatusText += "$($s.FileConflictedText)$($Status.Index.Unmerged.Count)" if ($StringBuilder) { $StringBuilder | Write-Prompt $indexStatusText -Color $s.IndexColor > $null } else { $str += Write-Prompt $indexStatusText -Color $s.IndexColor } } } return $(if ($StringBuilder) { $StringBuilder } else { $str }) } <# .SYNOPSIS Writes the working directory status text given the current Git status. .DESCRIPTION Writes the working directory status text given the current Git status. .EXAMPLE PS C:\> Write-GitWorkingDirStatus (Get-GitStatus) Writes the Git working directory status to the host. .INPUTS System.Management.Automation.PSCustomObject This is PSCustomObject returned by Get-GitStatus .OUTPUTS System.String, System.Text.StringBuilder This command returns a System.String object unless the -StringBuilder parameter is supplied. In this case, it returns a System.Text.StringBuilder. #> function Write-GitWorkingDirStatus { param( # The Git status object that provides the status information to be written. # This object is retrieved via the Get-GitStatus command. [Parameter(Position = 0)] $Status, # If specified the working dir status is written into the provided StringBuilder object. [Parameter(ValueFromPipeline = $true)] [System.Text.StringBuilder] $StringBuilder, # If specified, suppresses the output of the leading space character. [Parameter()] [switch] $NoLeadingSpace ) $s = $global:GitPromptSettings if (!$Status -or !$s) { return $(if ($StringBuilder) { $StringBuilder } else { "" }) } $str = "" if ($Status.HasWorking) { if ($s.ShowStatusWhenZero -or $Status.Working.Added) { $workingStatusText = " " if ($NoLeadingSpace) { $workingStatusText = "" $NoLeadingSpace = $false } $workingStatusText += "$($s.FileAddedText)$($Status.Working.Added.Count)" if ($StringBuilder) { $StringBuilder | Write-Prompt $workingStatusText -Color $s.WorkingColor > $null } else { $str += Write-Prompt $workingStatusText -Color $s.WorkingColor } } if ($s.ShowStatusWhenZero -or $Status.Working.Modified) { $workingStatusText = " " if ($NoLeadingSpace) { $workingStatusText = "" $NoLeadingSpace = $false } $workingStatusText += "$($s.FileModifiedText)$($Status.Working.Modified.Count)" if ($StringBuilder) { $StringBuilder | Write-Prompt $workingStatusText -Color $s.WorkingColor > $null } else { $str += Write-Prompt $workingStatusText -Color $s.WorkingColor } } if ($s.ShowStatusWhenZero -or $Status.Working.Deleted) { $workingStatusText = " " if ($NoLeadingSpace) { $workingStatusText = "" $NoLeadingSpace = $false } $workingStatusText += "$($s.FileRemovedText)$($Status.Working.Deleted.Count)" if ($StringBuilder) { $StringBuilder | Write-Prompt $workingStatusText -Color $s.WorkingColor > $null } else { $str += Write-Prompt $workingStatusText -Color $s.WorkingColor } } if ($Status.Working.Unmerged) { $workingStatusText = " " if ($NoLeadingSpace) { $workingStatusText = "" $NoLeadingSpace = $false } $workingStatusText += "$($s.FileConflictedText)$($Status.Working.Unmerged.Count)" if ($StringBuilder) { $StringBuilder | Write-Prompt $workingStatusText -Color $s.WorkingColor > $null } else { $str += Write-Prompt $workingStatusText -Color $s.WorkingColor } } } return $(if ($StringBuilder) { $StringBuilder } else { $str }) } <# .SYNOPSIS Writes the working directory status summary text given the current Git status. .DESCRIPTION Writes the working directory status summary text given the current Git status. If there are any unstaged commits, the $GitPromptSettings.LocalWorkingStatusSymbol will be output. If not, then if are any staged but uncommmited changes, the $GitPromptSettings.LocalStagedStatusSymbol will be output. If not, then $GitPromptSettings.LocalDefaultStatusSymbol will be output. .EXAMPLE PS C:\> Write-GitWorkingDirStatusSummary (Get-GitStatus) Outputs the Git working directory status summary text. .INPUTS System.Management.Automation.PSCustomObject This is PSCustomObject returned by Get-GitStatus .OUTPUTS System.String, System.Text.StringBuilder This command returns a System.String object unless the -StringBuilder parameter is supplied. In this case, it returns a System.Text.StringBuilder. #> function Write-GitWorkingDirStatusSummary { param( # The Git status object that provides the status information to be written. # This object is retrieved via the Get-GitStatus command. [Parameter(Position = 0)] $Status, # If specified the working dir local status is written into the provided StringBuilder object. [Parameter(ValueFromPipeline = $true)] [System.Text.StringBuilder] $StringBuilder, # If specified, suppresses the output of the leading space character. [Parameter()] [switch] $NoLeadingSpace ) $s = $global:GitPromptSettings if (!$Status -or !$s) { return $(if ($StringBuilder) { $StringBuilder } else { "" }) } $str = "" # No uncommited changes $localStatusSymbol = $s.LocalDefaultStatusSymbol if ($Status.HasWorking) { # We have un-staged files in the working tree $localStatusSymbol = $s.LocalWorkingStatusSymbol } elseif ($Status.HasIndex) { # We have staged but uncommited files $localStatusSymbol = $s.LocalStagedStatusSymbol } if ($localStatusSymbol.Text) { $textSpan = [PoshGitTextSpan]::new($localStatusSymbol) if (!$NoLeadingSpace) { $textSpan.Text = " " + $localStatusSymbol.Text } if ($StringBuilder) { $StringBuilder | Write-Prompt $textSpan > $null } else { $str += Write-Prompt $textSpan } } return $(if ($StringBuilder) { $StringBuilder } else { $str }) } <# .SYNOPSIS Writes the stash count given the current Git status. .DESCRIPTION Writes the stash count given the current Git status. .EXAMPLE PS C:\> Write-GitStashCount (Get-GitStatus) Writes the Git stash count to the host. .INPUTS System.Management.Automation.PSCustomObject This is PSCustomObject returned by Get-GitStatus .OUTPUTS System.String, System.Text.StringBuilder This command returns a System.String object unless the -StringBuilder parameter is supplied. In this case, it returns a System.Text.StringBuilder. #> function Write-GitStashCount { param( # The Git status object that provides the status information to be written. # This object is retrieved via the Get-GitStatus command. [Parameter(Position = 0)] $Status, # If specified the working dir local status is written into the provided StringBuilder object. [Parameter(ValueFromPipeline = $true)] [System.Text.StringBuilder] $StringBuilder ) $s = $global:GitPromptSettings if (!$Status -or !$s) { return $(if ($StringBuilder) { $StringBuilder } else { "" }) } $str = "" if ($Status.StashCount -gt 0) { $stashText = "$($Status.StashCount)" if ($StringBuilder) { $StringBuilder | Write-Prompt $s.BeforeStash > $null $StringBuilder | Write-Prompt $stashText -Color $s.StashColor > $null $StringBuilder | Write-Prompt $s.AfterStash > $null } else { $str += Write-Prompt $s.BeforeStash $str += Write-Prompt $stashText -Color $s.StashColor $str += Write-Prompt $s.AfterStash } } return $(if ($StringBuilder) { $StringBuilder } else { $str }) } if (!(Test-Path Variable:Global:VcsPromptStatuses)) { $global:VcsPromptStatuses = @() } <# .SYNOPSIS Writes all version control prompt statuses configured in $global:VcsPromptStatuses. .DESCRIPTION Writes all version control prompt statuses configured in $global:VcsPromptStatuses. By default, this includes the PoshGit prompt status. .EXAMPLE PS C:\> Write-VcsStatus Writes all version control prompt statuses that have been configured with the global variable $VcsPromptStatuses #> function Global:Write-VcsStatus { Set-ConsoleMode -ANSI $OFS = "" $sb = [System.Text.StringBuilder]::new(256) foreach ($promptStatus in $global:VcsPromptStatuses) { [void]$sb.Append("$(& $promptStatus)") } if ($sb.Length -gt 0) { $sb.ToString() } } # Add scriptblock that will execute for Write-VcsStatus $PoshGitVcsPrompt = { try { $global:GitStatus = Get-GitStatus Write-GitStatus $GitStatus } catch { $s = $global:GitPromptSettings if ($s) { $errorText = "PoshGitVcsPrompt error: $_" $sb = [System.Text.StringBuilder]::new() # When prompt is first (default), place the separator before the status summary if (!$s.DefaultPromptWriteStatusFirst) { $sb | Write-Prompt $s.PathStatusSeparator.Expand() > $null } $sb | Write-Prompt $s.BeforeStatus > $null $sb | Write-Prompt $errorText -Color $s.ErrorColor > $null if ($s.Debug) { if (!$s.AnsiConsole) { Write-Host } Write-Verbose "PoshGitVcsPrompt error details: $($_ | Format-List * -Force | Out-String)" -Verbose } $sb | Write-Prompt $s.AfterStatus > $null if ($sb.Length -gt 0) { $sb.ToString() } } } } $global:VcsPromptStatuses += $PoshGitVcsPrompt ================================================ FILE: src/GitTabExpansion.ps1 ================================================ # Initial implementation by Jeremy Skinner # http://www.jeremyskinner.co.uk/2010/03/07/using-git-with-windows-powershell/ $Global:GitTabSettings = New-Object PSObject -Property @{ AllCommands = $false KnownAliases = @{ '!f() { exec vsts code pr "$@"; }; f' = 'vsts.pr' } EnableLogging = $false LogPath = Join-Path ([System.IO.Path]::GetTempPath()) posh-git_tabexp.log RegisteredCommands = "" } $subcommands = @{ bisect = "start bad good skip reset visualize replay log run" notes = 'add append copy edit get-ref list merge prune remove show' 'vsts.pr' = 'create update show list complete abandon reactivate reviewers work-items set-vote policies' reflog = "show delete expire" remote = " add rename remove set-head set-branches get-url set-url show prune update " rerere = "clear forget diff remaining status gc" stash = 'push save list show apply clear drop pop create branch' submodule = "add status init deinit update summary foreach sync" svn = " init fetch clone rebase dcommit log find-rev set-tree commit-diff info create-ignore propget proplist show-ignore show-externals branch tag blame migrate mkdirs reset gc " tfs = " list-remote-branches clone quick-clone bootstrap init clone fetch pull quick-clone unshelve shelve-list labels rcheckin checkin checkintool shelve shelve-delete branch info cleanup cleanup-workspaces help verify autotag subtree reset-remote checkout " flow = "init feature bugfix release hotfix support help version" worktree = "add list lock move prune remove unlock" } $gitflowsubcommands = @{ init = 'help' feature = 'list start finish publish track diff rebase checkout pull help delete' bugfix = 'list start finish publish track diff rebase checkout pull help delete' release = 'list start finish track publish help delete' hotfix = 'list start finish track publish help delete' support = 'list start help' config = 'list set base' } function script:gitCmdOperations($commands, $command, $filter) { $commands[$command].Trim() -split '\s+' | Where-Object { $_ -like "$filter*" } } $script:someCommands = @('add','am','annotate','archive','bisect','blame','branch','bundle','checkout','cherry', 'cherry-pick','citool','clean','clone','commit','config','describe','diff','difftool','fetch', 'format-patch','gc','grep','gui','help','init','instaweb','log','merge','mergetool','mv', 'notes','prune','pull','push','rebase','reflog','remote','rerere','reset','restore','revert','rm', 'shortlog','show','stash','status','submodule','svn','switch','tag','whatchanged', 'worktree') if ((($PSVersionTable.PSVersion.Major -eq 5) -or $IsWindows) -and ($script:GitVersion -ge [System.Version]'2.16.2')) { $script:someCommands += 'update-git-for-windows' } $script:gitCommandsWithLongParams = $longGitParams.Keys -join '|' $script:gitCommandsWithShortParams = $shortGitParams.Keys -join '|' $script:gitCommandsWithParamValues = $gitParamValues.Keys -join '|' $script:vstsCommandsWithShortParams = $shortVstsParams.Keys -join '|' $script:vstsCommandsWithLongParams = $longVstsParams.Keys -join '|' # The regular expression here is roughly follows this pattern: # # *()*+<$args>* # # The delimiters inside the parameter list and between some of the elements are non-newline whitespace characters ([^\S\r\n]). # In those instances, newlines are only allowed if they preceded by a non-newline whitespace character. # # Begin anchor (^|[;`n]) # Whitespace (\s*) # Git Command (?$(GetAliasPattern git)) # Parameters (?(([^\S\r\n]|[^\S\r\n]``\r?\n)+\S+)*) # $args Anchor (([^\S\r\n]|[^\S\r\n]``\r?\n)+\`$args) # Whitespace (\s|``\r?\n)* # End Anchor ($|[|;`n]) $script:GitProxyFunctionRegex = "(^|[;`n])(\s*)(?$(Get-AliasPattern git))(?(([^\S\r\n]|[^\S\r\n]``\r?\n)+\S+)*)(([^\S\r\n]|[^\S\r\n]``\r?\n)+\`$args)(\s|``\r?\n)*($|[|;`n])" try { if ($null -ne (git help -a 2>&1 | Select-String flow)) { $script:someCommands += 'flow' } } catch { Write-Debug "Search for 'flow' in 'git help' output failed with error: $_" } filter quoteStringWithSpecialChars { if ($_ -and ($_ -match '\s+|#|@|\$|;|,|''|\{|\}|\(|\)')) { $str = $_ -replace "'", "''" "'$str'" } else { $_ } } function script:gitCommands($filter, $includeAliases) { $cmdList = @() if (-not $global:GitTabSettings.AllCommands) { $cmdList += $someCommands -like "$filter*" } else { $cmdList += git help --all | Where-Object { $_ -match '^\s{2,}\S.*' } | ForEach-Object { $_.Split(' ', [StringSplitOptions]::RemoveEmptyEntries) } | Where-Object { $_ -like "$filter*" } } if ($includeAliases) { $cmdList += gitAliases $filter } $cmdList | Sort-Object } function script:gitRemotes($filter) { git remote | Where-Object { $_ -like "$filter*" } | quoteStringWithSpecialChars } function script:gitBranches($filter, $includeHEAD = $false, $prefix = '') { if ($filter -match "^(?\S*\.{2,3})(?.*)") { $prefix += $matches['from'] $filter = $matches['to'] } $branches = @(git branch --no-color | ForEach-Object { if (($_ -notmatch "^\* \(HEAD detached .+\)$") -and ($_ -match "^[\*\+]?\s*(?\S+)(?: -> .+)?")) { $matches['ref'] } }) + @(git branch --no-color -r | ForEach-Object { if ($_ -match "^ (?\S+)(?: -> .+)?") { $matches['ref'] } }) + @(if ($includeHEAD) { 'HEAD','FETCH_HEAD','ORIG_HEAD','MERGE_HEAD' }) $branches | Where-Object { $_ -ne '(no branch)' -and $_ -like "$filter*" } | ForEach-Object { $prefix + $_ } | quoteStringWithSpecialChars } function script:gitRemoteUniqueBranches($filter) { git branch --no-color -r | ForEach-Object { if ($_ -match "^ (?[^/]+)/(?\S+)(?! -> .+)?$") { $matches['branch'] } } | Group-Object -NoElement | Where-Object { $_.Count -eq 1 } | Select-Object -ExpandProperty Name | Where-Object { $_ -like "$filter*" } | quoteStringWithSpecialChars } function script:gitConfigKeys($section, $filter, $defaultOptions = '') { $completions = @($defaultOptions -split ' ') git config --name-only --get-regexp ^$section\..* | ForEach-Object { $completions += ($_ -replace "$section\.","") } return $completions | Where-Object { $_ -like "$filter*" } | Sort-Object | quoteStringWithSpecialChars } function script:gitTags($filter, $prefix = '') { git tag | Where-Object { $_ -like "$filter*" } | ForEach-Object { $prefix + $_ } | quoteStringWithSpecialChars } function script:gitFeatures($filter, $command) { $featurePrefix = git config --local --get "gitflow.prefix.$command" $branches = @(git branch --no-color | ForEach-Object { if ($_ -match "^\*?\s*$featurePrefix(?.*)") { $matches['ref'] } }) $branches | Where-Object { $_ -ne '(no branch)' -and $_ -like "$filter*" } | ForEach-Object { $featurePrefix + $_ } | quoteStringWithSpecialChars } function script:gitRemoteBranches($remote, $ref, $filter, $prefix = '') { git branch --no-color -r | Where-Object { $_ -like " $remote/$filter*" } | ForEach-Object { $prefix + $ref + ($_ -replace " $remote/","") } | quoteStringWithSpecialChars } function script:gitStashes($filter) { (git stash list) -replace ':.*','' | Where-Object { $_ -like "$filter*" } | quoteStringWithSpecialChars } function script:gitTfsShelvesets($filter) { (git tfs shelve-list) | Where-Object { $_ -like "$filter*" } | quoteStringWithSpecialChars } function script:gitFiles($filter, $files) { $files | Sort-Object | Where-Object { $_ -like "$filter*" } | quoteStringWithSpecialChars } function script:gitIndex($GitStatus, $filter) { gitFiles $filter $GitStatus.Index } function script:gitAddFiles($GitStatus, $filter) { gitFiles $filter (@($GitStatus.Working.Unmerged) + @($GitStatus.Working.Modified) + @($GitStatus.Working.Added)) } function script:gitCheckoutFiles($GitStatus, $filter) { gitFiles $filter (@($GitStatus.Working.Unmerged) + @($GitStatus.Working.Modified) + @($GitStatus.Working.Deleted)) } function script:gitDeleted($GitStatus, $filter) { gitFiles $filter $GitStatus.Working.Deleted } function script:gitDiffFiles($GitStatus, $filter, $staged) { if ($staged) { gitFiles $filter $GitStatus.Index.Modified } else { gitFiles $filter (@($GitStatus.Working.Unmerged) + @($GitStatus.Working.Modified) + @($GitStatus.Index.Modified)) } } function script:gitMergeFiles($GitStatus, $filter) { gitFiles $filter $GitStatus.Working.Unmerged } function script:gitRestoreFiles($GitStatus, $filter, $staged) { if ($staged) { gitFiles $filter (@($GitStatus.Index.Added) + @($GitStatus.Index.Modified) + @($GitStatus.Index.Deleted)) } else { gitFiles $filter (@($GitStatus.Working.Unmerged) + @($GitStatus.Working.Modified) + @($GitStatus.Working.Deleted)) } } function script:gitAliases($filter) { git config --get-regexp ^alias\. | ForEach-Object{ if ($_ -match "^alias\.(?\S+) .*") { $alias = $Matches['alias'] if ($alias -like "$filter*") { $alias } } } | Sort-Object -Unique } function script:expandGitAlias($cmd, $rest) { $alias = git config "alias.$cmd" if ($alias) { $known = $Global:GitTabSettings.KnownAliases[$alias] if ($known) { return "git $known$rest" } return "git $alias$rest" } else { return "git $cmd$rest" } } function script:expandLongParams($hash, $cmd, $filter) { $hash[$cmd].Trim() -split ' ' | Where-Object { $_ -like "$filter*" } | Sort-Object | ForEach-Object { -join ("--", $_) } } function script:expandShortParams($hash, $cmd, $filter) { $hash[$cmd].Trim() -split ' ' | Where-Object { $_ -like "$filter*" } | Sort-Object | ForEach-Object { -join ("-", $_) } } function script:expandParamValues($cmd, $param, $filter) { $paramValues = $gitParamValues[$cmd][$param] $completions = if ($paramValues -is [scriptblock]) { & $paramValues $filter } else { $paramValues.Trim() -split ' ' | Where-Object { $_ -like "$filter*" } | Sort-Object } $completions | ForEach-Object { -join ("--", $param, "=", $_) } } function Expand-GitCommand($Command) { # Parse all Git output as UTF8, including tab completion output - https://github.com/dahlbyk/posh-git/pull/359 $res = Invoke-Utf8ConsoleCommand { GitTabExpansionInternal $Command $Global:GitStatus } $res } function GitTabExpansionInternal($lastBlock, $GitStatus = $null) { $ignoreGitParams = '(?\s+-(?:[aA-zZ0-9]+|-[aA-zZ0-9][aA-zZ0-9-]*)(?:=\S+)?)*' if ($lastBlock -match "^$(Get-AliasPattern git) (?\S+)(? .*)$") { $lastBlock = expandGitAlias $Matches['cmd'] $Matches['args'] } # Handles tgit (tortoisegit) if ($lastBlock -match "^$(Get-AliasPattern tgit) (?\S*)$") { # Need return statement to prevent fall-through. return $Global:TortoiseGitSettings.TortoiseGitCommands.Keys.GetEnumerator() | Sort-Object | Where-Object { $_ -like "$($matches['cmd'])*" } } # Handles gitk if ($lastBlock -match "^$(Get-AliasPattern gitk).* (?\S*)$") { return gitBranches $matches['ref'] $true } switch -regex ($lastBlock -replace "^$(Get-AliasPattern git) ","") { # Handles git "^(?$($subcommands.Keys -join '|'))\s+(?\S*)$" { gitCmdOperations $subcommands $matches['cmd'] $matches['op'] } # Handles git flow "^flow (?$($gitflowsubcommands.Keys -join '|'))\s+(?\S*)$" { gitCmdOperations $gitflowsubcommands $matches['cmd'] $matches['op'] } # Handles git flow "^flow (?\S*)\s+(?\S*)\s+(?\S*)$" { gitFeatures $matches['name'] $matches['command'] } # Handles git remote (rename|rm|set-head|set-branches|set-url|show|prune) "^remote.* (?:rename|rm|set-head|set-branches|set-url|show|prune).* (?\S*)$" { gitRemotes $matches['remote'] } # Handles git stash (show|apply|drop|pop|branch) "^stash (?:show|apply|drop|pop|branch).* (?\S*)$" { gitStashes $matches['stash'] } # Handles git bisect (bad|good|reset|skip) "^bisect (?:bad|good|reset|skip).* (?\S*)$" { gitBranches $matches['ref'] $true } # Handles git tfs unshelve "^tfs +unshelve.* (?\S*)$" { gitTfsShelvesets $matches['shelveset'] } # Handles git branch -d|-D|-m|-M # Handles git branch "^branch.* (?\S*)$" { gitBranches $matches['branch'] } # Handles git (commands & aliases) "^(?\S*)$" { gitCommands $matches['cmd'] $TRUE } # Handles git help (commands only) "^help (?\S*)$" { gitCommands $matches['cmd'] $FALSE } # Handles git push remote : # Handles git push remote +: "^push${ignoreGitParams}\s+(?[^\s-]\S*).*\s+(?\+?)(?[^\s\:]*\:)(?\S*)$" { gitRemoteBranches $matches['remote'] $matches['ref'] $matches['branch'] -prefix $matches['force'] } # Handles git push remote # Handles git push remote + # Handles git pull remote "^(?:push|pull)${ignoreGitParams}\s+(?[^\s-]\S*).*\s+(?\+?)(?[^\s\:]*)$" { gitBranches $matches['ref'] -prefix $matches['force'] gitTags $matches['ref'] -prefix $matches['force'] } # Handles git pull # Handles git push # Handles git fetch "^(?:push|pull|fetch)${ignoreGitParams}\s+(?\S*)$" { gitRemotes $matches['remote'] } # Handles git reset HEAD # Handles git reset HEAD -- "^reset.* HEAD(?:\s+--)? (?\S*)$" { gitIndex $GitStatus $matches['path'] } # Handles git "^commit.*-C\s+(?\S*)$" { gitBranches $matches['ref'] $true } # Handles git add "^add.* (?\S*)$" { gitAddFiles $GitStatus $matches['files'] } # Handles git checkout -- "^checkout.* -- (?\S*)$" { gitCheckoutFiles $GitStatus $matches['files'] } # Handles git restore -s / --source= - must come before the next regex case "^restore.* (?-i)(-s\s*|(?--source=))(?\S*)$" { gitBranches $matches['ref'] $true $matches['source'] gitTags $matches['ref'] break } # Handles git restore "^restore(?:.* (?(?:(?-i)-S|--staged))|.*) (?\S*)$" { gitRestoreFiles $GitStatus $matches['files'] $matches['staged'] } # Handles git rm "^rm.* (?\S*)$" { gitDeleted $GitStatus $matches['index'] } # Handles git diff/difftool "^(?:diff|difftool)(?:.* (?(?:--cached|--staged))|.*) (?\S*)$" { gitDiffFiles $GitStatus $matches['files'] $matches['staged'] } # Handles git merge/mergetool "^(?:merge|mergetool).* (?\S*)$" { gitMergeFiles $GitStatus $matches['files'] } # Handles git checkout|switch "^(?:checkout|switch).* (?\S*)$" { & { gitBranches $matches['ref'] $true gitRemoteUniqueBranches $matches['ref'] gitTags $matches['ref'] # Return only unique branches (to eliminate duplicates where the branch exists locally and on the remote) } | Select-Object -Unique } # Handles git worktree add "^worktree add.* (?\S+) (?\S*)$" { gitBranches $matches['ref'] } # Handles git "^(?:cherry|cherry-pick|diff|difftool|log|merge|rebase|reflog\s+show|reset|revert|show).* (?\S*)$" { gitBranches $matches['ref'] $true gitTags $matches['ref'] } # Handles git --= "^(?$gitCommandsWithParamValues).* --(?[^=]+)=(?\S*)$" { expandParamValues $matches['cmd'] $matches['param'] $matches['value'] } # Handles git -- "^(?$gitCommandsWithLongParams).* --(?\S*)$" { expandLongParams $longGitParams $matches['cmd'] $matches['param'] } # Handles git - "^(?$gitCommandsWithShortParams).* -(?\S*)$" { expandShortParams $shortGitParams $matches['cmd'] $matches['shortparam'] } # Handles git pr alias "vsts\.pr\s+(?\S*)$" { gitCmdOperations $subcommands 'vsts.pr' $matches['op'] } # Handles git pr -- "vsts\.pr\s+(?$vstsCommandsWithLongParams).*--(?\S*)$" { expandLongParams $longVstsParams $matches['cmd'] $matches['param'] } # Handles git pr - "vsts\.pr\s+(?$vstsCommandsWithShortParams).*-(?\S*)$" { expandShortParams $shortVstsParams $matches['cmd'] $matches['shortparam'] } } } function Expand-GitProxyFunction($command) { # Make sure the incoming command matches: , so we can extract the alias/command # name and the arguments being passed in. if ($command -notmatch '^(?\S+)([^\S\r\n]|[^\S\r\n]`\r?\n)+(?([^\S\r\n]|[^\S\r\n]`\r?\n|\S)*)$') { return $command } # Store arguments for replacement later $arguments = $matches['args'] # Get the command name; if an alias exists, get the actual command name $commandName = $matches['command'] if (Test-Path -Path Alias:\$commandName) { $commandName = Get-Item -Path Alias:\$commandName | Select-Object -ExpandProperty 'ResolvedCommandName' } # Extract definition of git usage if (Test-Path -Path Function:\$commandName) { $definition = Get-Item -Path Function:\$commandName | Select-Object -ExpandProperty 'Definition' if ($definition -match $script:GitProxyFunctionRegex) { # Clean up the command by removing extra delimiting whitespace and backtick preceding newlines return (("$($matches['cmd'].TrimStart()) $($matches['params']) $arguments") -replace '`\r?\n', ' ' -replace '\s+', ' ') } } return $command } function WriteTabExpLog([string] $Message) { if (!$global:GitTabSettings.EnableLogging) { return } $timestamp = Get-Date -Format HH:mm:ss "[$timestamp] $Message" | Out-File -Append $global:GitTabSettings.LogPath } if (!$UseLegacyTabExpansion -and ($PSVersionTable.PSVersion.Major -ge 6)) { $cmdNames = "git","tgit","gitk" # Create regex pattern from $cmdNames: ^(git|git\.exe|tgit|tgit\.exe|gitk|gitk\.exe)$ $cmdNamesPattern = "^($($cmdNames -join '|'))(\.exe)?$" $cmdNames += Get-Alias | Where-Object { $_.Definition -match $cmdNamesPattern } | Foreach-Object Name if ($EnableProxyFunctionExpansion) { $funcNames += Get-ChildItem -Path Function:\ | Where-Object { $_.Definition -match $script:GitProxyFunctionRegex } | Foreach-Object Name $cmdNames += $funcNames # Create regex pattern from $funcNames e.g.: ^(Git-Checkout|Git-Switch)$ $funcNamesPattern = "^($($funcNames -join '|'))$" $cmdNames += Get-Alias | Where-Object { $_.Definition -match $funcNamesPattern } | Foreach-Object Name } $global:GitTabSettings.RegisteredCommands = $cmdNames -join ", " Microsoft.PowerShell.Core\Register-ArgumentCompleter -CommandName $cmdNames -Native -ScriptBlock { param($wordToComplete, $commandAst, $cursorPosition) # The PowerShell completion has a habit of stripping the trailing space when completing: # git checkout # The Expand-GitCommand expects this trailing space, so pad with a space if necessary. $padLength = $cursorPosition - $commandAst.Extent.StartOffset $textToComplete = $commandAst.ToString().PadRight($padLength, ' ').Substring(0, $padLength) if ($EnableProxyFunctionExpansion) { $textToComplete = Expand-GitProxyFunction($textToComplete) } WriteTabExpLog "Expand: command: '$($commandAst.Extent.Text)', padded: '$textToComplete', padlen: $padLength" Expand-GitCommand $textToComplete } } else { $PowerTab_RegisterTabExpansion = if (Get-Module -Name powertab) { Get-Command Register-TabExpansion -Module powertab -ErrorAction SilentlyContinue } if ($PowerTab_RegisterTabExpansion) { & $PowerTab_RegisterTabExpansion git -Type Command { param($Context, [ref]$TabExpansionHasOutput, [ref]$QuoteSpaces) $line = $Context.Line $lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart() if ($EnableProxyFunctionExpansion) { $lastBlock = Expand-GitProxyFunction($lastBlock) } $TabExpansionHasOutput.Value = $true WriteTabExpLog "PowerTab expand: '$lastBlock'" Expand-GitCommand $lastBlock } return } function TabExpansion($line, $lastWord) { $lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart() if ($EnableProxyFunctionExpansion) { $lastBlock = Expand-GitProxyFunction($lastBlock) } $msg = "Legacy expand: '$lastBlock'" switch -regex ($lastBlock) { # Execute git tab completion for all git-related commands "^$(Get-AliasPattern git) (.*)" { WriteTabExpLog $msg; Expand-GitCommand $lastBlock } "^$(Get-AliasPattern tgit) (.*)" { WriteTabExpLog $msg; Expand-GitCommand $lastBlock } "^$(Get-AliasPattern gitk) (.*)" { WriteTabExpLog $msg; Expand-GitCommand $lastBlock } } } } # Handles Remove-GitBranch -Name parameter auto-completion using the built-in mechanism for cmdlet parameters Microsoft.PowerShell.Core\Register-ArgumentCompleter -CommandName Remove-GitBranch -ParameterName Name -ScriptBlock { param($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) gitBranches $WordToComplete $true } ================================================ FILE: src/GitUtils.ps1 ================================================ # Inspired by Mark Embling # http://www.markembling.info/view/my-ideal-powershell-prompt-with-git-integration <# .SYNOPSIS Gets the path to the current repository's .git dir. .DESCRIPTION Gets the path to the current repository's .git dir. Or if the repository is a bare repository, the root directory of the bare repository. .EXAMPLE PS C:\GitHub\posh-git\tests> Get-GitDirectory Returns C:\GitHub\posh-git\.git .INPUTS None. .OUTPUTS System.String #> function Get-GitDirectory { $pathInfo = Microsoft.PowerShell.Management\Get-Location if (!$pathInfo -or ($pathInfo.Provider.Name -ne 'FileSystem')) { $null } elseif ($Env:GIT_DIR) { $Env:GIT_DIR -replace '\\|/', [System.IO.Path]::DirectorySeparatorChar } else { $currentDir = Get-Item -LiteralPath $pathInfo -Force while ($currentDir) { $gitDirPath = Join-Path $currentDir.FullName .git if (Test-Path -LiteralPath $gitDirPath -PathType Container) { return $gitDirPath } # Handle the worktree case where .git is a file if (Test-Path -LiteralPath $gitDirPath -PathType Leaf) { $gitDirPath = Invoke-Utf8ConsoleCommand { git rev-parse --git-dir 2>$null } if ($gitDirPath) { return $gitDirPath } } $headPath = Join-Path $currentDir.FullName HEAD if (Test-Path -LiteralPath $headPath -PathType Leaf) { $refsPath = Join-Path $currentDir.FullName refs $objsPath = Join-Path $currentDir.FullName objects if ((Test-Path -LiteralPath $refsPath -PathType Container) -and (Test-Path -LiteralPath $objsPath -PathType Container)) { $bareDir = Invoke-Utf8ConsoleCommand { git rev-parse --git-dir 2>$null } if ($bareDir -and (Test-Path -LiteralPath $bareDir -PathType Container)) { $resolvedBareDir = (Resolve-Path $bareDir).Path return $resolvedBareDir } } } $currentDir = $currentDir.Parent } } } function Get-GitBranch($branch = $null, $gitDir = $(Get-GitDirectory), [switch]$isDotGitOrBare, [Diagnostics.Stopwatch]$sw) { if (!$gitDir) { return } Invoke-Utf8ConsoleCommand { dbg 'Finding branch' $sw $r = ''; $b = ''; $c = '' $step = ''; $total = '' if (Test-Path $gitDir/rebase-merge) { dbg 'Found rebase-merge' $sw if (Test-Path $gitDir/rebase-merge/interactive) { dbg 'Found rebase-merge/interactive' $sw $r = '|REBASE-i' } else { $r = '|REBASE-m' } $b = "$(Get-Content $gitDir/rebase-merge/head-name)" $step = "$(Get-Content $gitDir/rebase-merge/msgnum)" $total = "$(Get-Content $gitDir/rebase-merge/end)" } else { if (Test-Path $gitDir/rebase-apply) { dbg 'Found rebase-apply' $sw $step = "$(Get-Content $gitDir/rebase-apply/next)" $total = "$(Get-Content $gitDir/rebase-apply/last)" if (Test-Path $gitDir/rebase-apply/rebasing) { dbg 'Found rebase-apply/rebasing' $sw $r = '|REBASE' } elseif (Test-Path $gitDir/rebase-apply/applying) { dbg 'Found rebase-apply/applying' $sw $r = '|AM' } else { $r = '|AM/REBASE' } } elseif (Test-Path $gitDir/MERGE_HEAD) { dbg 'Found MERGE_HEAD' $sw $r = '|MERGING' } elseif (Test-Path $gitDir/CHERRY_PICK_HEAD) { dbg 'Found CHERRY_PICK_HEAD' $sw $r = '|CHERRY-PICKING' } elseif (Test-Path $gitDir/REVERT_HEAD) { dbg 'Found REVERT_HEAD' $sw $r = '|REVERTING' } elseif (Test-Path $gitDir/BISECT_LOG) { dbg 'Found BISECT_LOG' $sw $r = '|BISECTING' } if ($step -and $total) { $r += " $step/$total" } $b = Invoke-NullCoalescing ` $b ` $branch ` { dbg 'Trying symbolic-ref' $sw; git --no-optional-locks symbolic-ref HEAD -q 2>$null } ` { '({0})' -f (Invoke-NullCoalescing ` { dbg 'Trying describe' $sw switch ($Global:GitPromptSettings.DescribeStyle) { 'contains' { git --no-optional-locks describe --contains HEAD 2>$null } 'branch' { git --no-optional-locks describe --contains --all HEAD 2>$null } 'describe' { git --no-optional-locks describe HEAD 2>$null } default { git --no-optional-locks tag --points-at HEAD 2>$null } } } ` { dbg 'Falling back on parsing HEAD' $sw $ref = $null if (Test-Path $gitDir/HEAD) { dbg 'Reading from .git/HEAD' $sw $ref = Get-Content $gitDir/HEAD 2>$null } else { dbg 'Trying rev-parse' $sw $ref = git --no-optional-locks rev-parse HEAD 2>$null } if ($ref -match 'ref: (?.+)') { return $Matches['ref'] } elseif ($ref -and $ref.Length -ge 7) { return $ref.Substring(0, 7) + '...' } else { return 'unknown' } } ) } } if ($isDotGitOrBare -or !$b) { dbg 'Inside git directory?' $sw $revParseOut = git --no-optional-locks rev-parse --is-inside-git-dir 2>$null if ('true' -eq $revParseOut) { dbg 'Inside git directory' $sw $revParseOut = git --no-optional-locks rev-parse --is-bare-repository 2>$null if ('true' -eq $revParseOut) { $c = 'BARE:' } else { $b = 'GIT_DIR!' } } } "$c$($b -replace 'refs/heads/','')$r" } } function GetUniquePaths($pathCollections) { $hash = New-Object System.Collections.Specialized.OrderedDictionary foreach ($pathCollection in $pathCollections) { foreach ($path in $pathCollection) { $hash[$path] = 1 } } $hash.Keys } $castStringSeq = [Linq.Enumerable].GetMethod("Cast").MakeGenericMethod([string]) <# .SYNOPSIS Gets a Git status object that is used by `Write-GitStatus`. .DESCRIPTION The `Get-GitStatus` cmdlet gets the status of the current Git repo. The status object returned by this cmdlet provides the information displayed in the various sections of the posh-git prompt. The following properties in $GitPromptSettings control what information is returned in the status object: EnableFileStatus = $true # Or $false if Git not installed EnableFileStatusFromCache = # Or $true if GitStatusCachePoshClient installed EnablePromptStatus = $true EnableStashStatus = $false UntrackedFilesMode = Default # Other enum values: No, Normal, All The `Force` parameter can be used to override the EnableFileStatus and EnablePromptStatus properties to ensure that both file and prompt status information is returned in the status object. .EXAMPLE PS C:\> $s = Get-GitStatus; Write-GitStatus $s Gets a Git status object. Then passes the object to Write-GitStatus which writes out a posh-git prompt (or returns a string in ANSI mode) with the information contained in the status object. .EXAMPLE PS C:\> $s = Get-GitStatus -Force Gets a Git status object that always returns all status information even if $GitPromptSettings has disabled `EnableFileStatus` and/or `EnablePromptStatus`. .INPUTS None .OUTPUTS System.Management.Automation.PSObject .LINK Write-GitStatus #> function Get-GitStatus { param( # The path of a directory within a Git repository that you want to get # the Git status. [Parameter(Position = 0)] $GitDir = (Get-GitDirectory), # If specified, overrides $GitPromptSettings.EnableFileStatus and # $GitPromptSettings.EnablePromptStatus when they are set to $false. [Parameter()] [switch] $Force ) $settings = if ($global:GitPromptSettings) { $global:GitPromptSettings } else { [PoshGitPromptSettings]::new() } $promptStatusEnabled = $Force -or $settings.EnablePromptStatus if ($promptStatusEnabled -and $GitDir) { if ($settings.Debug) { $sw = [Diagnostics.Stopwatch]::StartNew(); Write-Host '' } else { $sw = $null } $branch = $null $aheadBy = 0 $behindBy = 0 $gone = $false $upstream = $null $indexAdded = New-Object System.Collections.Generic.List[string] $indexModified = New-Object System.Collections.Generic.List[string] $indexDeleted = New-Object System.Collections.Generic.List[string] $indexUnmerged = New-Object System.Collections.Generic.List[string] $filesAdded = New-Object System.Collections.Generic.List[string] $filesModified = New-Object System.Collections.Generic.List[string] $filesDeleted = New-Object System.Collections.Generic.List[string] $filesUnmerged = New-Object System.Collections.Generic.List[string] $stashCount = 0 $fileStatusEnabled = $Force -or $settings.EnableFileStatus # Optimization: short-circuit to avoid InDotGitOrBareRepoDir and InDisabledRepository if !$fileStatusEnabled if ($fileStatusEnabled -and !$($isDotGitOrBare = InDotGitOrBareRepoDir $GitDir) -and !$(InDisabledRepository)) { if ($null -eq $settings.EnableFileStatusFromCache) { $settings.EnableFileStatusFromCache = $null -ne (Get-Module GitStatusCachePoshClient) } if ($settings.EnableFileStatusFromCache) { dbg 'Getting status from cache' $sw $cacheResponse = Get-GitStatusFromCache if ($cacheResponse.Error) { # git-status-cache failed; set $global:GitStatusCacheLoggingEnabled = $true, call Restart-GitStatusCache, # and check %temp%\GitStatusCache_[timestamp].log for details. dbg "Cache returned an error: $($cacheResponse.Error)" $sw $branch = "CACHE ERROR" $behindBy = 1 } else { dbg 'Parsing status' $sw $indexAdded.AddRange($castStringSeq.Invoke($null, (, @($cacheResponse.IndexAdded)))) $indexModified.AddRange($castStringSeq.Invoke($null, (, @($cacheResponse.IndexModified)))) foreach ($indexRenamed in $cacheResponse.IndexRenamed) { $indexModified.Add($indexRenamed.Old) } $indexDeleted.AddRange($castStringSeq.Invoke($null, (, @($cacheResponse.IndexDeleted)))) $indexUnmerged.AddRange($castStringSeq.Invoke($null, (, @($cacheResponse.Conflicted)))) $filesAdded.AddRange($castStringSeq.Invoke($null, (, @($cacheResponse.WorkingAdded)))) $filesModified.AddRange($castStringSeq.Invoke($null, (, @($cacheResponse.WorkingModified)))) foreach ($workingRenamed in $cacheResponse.WorkingRenamed) { $filesModified.Add($workingRenamed.Old) } $filesDeleted.AddRange($castStringSeq.Invoke($null, (, @($cacheResponse.WorkingDeleted)))) $filesUnmerged.AddRange($castStringSeq.Invoke($null, (, @($cacheResponse.Conflicted)))) $branch = $cacheResponse.Branch $upstream = $cacheResponse.Upstream $gone = $cacheResponse.UpstreamGone $aheadBy = $cacheResponse.AheadBy $behindBy = $cacheResponse.BehindBy if ($settings.EnableStashStatus -and $cacheResponse.Stashes) { $stashCount = $cacheResponse.Stashes.Length } if ($cacheResponse.State) { $branch += "|" + $cacheResponse.State } } } else { dbg 'Getting status' $sw switch ($settings.UntrackedFilesMode) { "No" { $untrackedFilesOption = "-uno" } "All" { $untrackedFilesOption = "-uall" } default { $untrackedFilesOption = "-unormal" } } $status = Invoke-Utf8ConsoleCommand { git --no-optional-locks '-c' core.quotepath=false '-c' color.status=false status $untrackedFilesOption --short --branch 2>$null } if ($settings.EnableStashStatus) { dbg 'Getting stash count' $sw $stashCount = $null | git --no-optional-locks stash list 2>$null | measure-object | Select-Object -expand Count } dbg 'Parsing status' $sw switch -regex ($status) { '^(?[^#])(?.) (?.*?)(?: -> (?.*))?$' { if ($sw) { dbg "Status: $_" $sw } $path1 = $matches['path1'] # Even with core.quotePath=false, paths with spaces are wrapped in "" # https://github.com/git/git/commit/dbfdc625a5aad10c47e3ffa446d0b92e341a7b44 # https://github.com/git/git/commit/f3fc4a1b8680c114defd98ce6f2429f8946a5dc1 if ($path1 -like '"*"') { $path1 = $path1.Substring(1, $path1.Length - 2) } switch ($matches['index']) { 'A' { $null = $indexAdded.Add($path1); break } 'M' { $null = $indexModified.Add($path1); break } 'R' { $null = $indexModified.Add($path1); break } 'C' { $null = $indexModified.Add($path1); break } 'D' { $null = $indexDeleted.Add($path1); break } 'U' { $null = $indexUnmerged.Add($path1); break } } switch ($matches['working']) { '?' { $null = $filesAdded.Add($path1); break } 'A' { $null = $filesAdded.Add($path1); break } 'M' { $null = $filesModified.Add($path1); break } 'D' { $null = $filesDeleted.Add($path1); break } 'U' { $null = $filesUnmerged.Add($path1); break } } continue } '^## (?\S+?)(?:\.\.\.(?\S+))?(?: \[(?:ahead (?\d+))?(?:, )?(?:behind (?\d+))?(?gone)?\])?$' { if ($sw) { dbg "Status: $_" $sw } $branch = $matches['branch'] $upstream = $matches['upstream'] $aheadBy = [int]$matches['ahead'] $behindBy = [int]$matches['behind'] $gone = [string]$matches['gone'] -eq 'gone' continue } '^## Initial commit on (?\S+)$' { if ($sw) { dbg "Status: $_" $sw } $branch = $matches['branch'] continue } default { if ($sw) { dbg "Status: $_" $sw } } } } } $branch = Get-GitBranch -Branch $branch -GitDir $GitDir -IsDotGitOrBare:$isDotGitOrBare -sw $sw dbg 'Building status object' $sw # This collection is used twice, so create the array just once $filesAdded = $filesAdded.ToArray() $indexPaths = @(GetUniquePaths $indexAdded, $indexModified, $indexDeleted, $indexUnmerged) $workingPaths = @(GetUniquePaths $filesAdded, $filesModified, $filesDeleted, $filesUnmerged) $index = (, $indexPaths) | Add-Member -Force -PassThru NoteProperty Added $indexAdded.ToArray() | Add-Member -Force -PassThru NoteProperty Modified $indexModified.ToArray() | Add-Member -Force -PassThru NoteProperty Deleted $indexDeleted.ToArray() | Add-Member -Force -PassThru NoteProperty Unmerged $indexUnmerged.ToArray() $working = (, $workingPaths) | Add-Member -Force -PassThru NoteProperty Added $filesAdded | Add-Member -Force -PassThru NoteProperty Modified $filesModified.ToArray() | Add-Member -Force -PassThru NoteProperty Deleted $filesDeleted.ToArray() | Add-Member -Force -PassThru NoteProperty Unmerged $filesUnmerged.ToArray() $result = New-Object PSObject -Property @{ GitDir = $GitDir RepoName = Split-Path (Split-Path $GitDir -Parent) -Leaf Branch = $branch AheadBy = $aheadBy BehindBy = $behindBy UpstreamGone = $gone Upstream = $upstream HasIndex = [bool]$index Index = $index HasWorking = [bool]$working Working = $working HasUntracked = [bool]$filesAdded StashCount = $stashCount } dbg 'Finished' $sw if ($sw) { $sw.Stop() } return $result } } function InDisabledRepository { $currentLocation = Get-Location foreach ($repo in $Global:GitPromptSettings.RepositoriesInWhichToDisableFileStatus) { if ($currentLocation -like "$repo*") { return $true } } return $false } function InDotGitOrBareRepoDir([string][ValidateNotNullOrEmpty()]$GitDir) { # A UNC path has no drive so it's better to use the ProviderPath e.g. "\\server\share". # However for any path with a drive defined, it's better to use the Path property. # In this case, ProviderPath is "\LocalMachine\My"" whereas Path is "Cert:\LocalMachine\My". # The latter is more desirable. $pathInfo = Microsoft.PowerShell.Management\Get-Location $currentPath = if ($pathInfo.Drive) { $pathInfo.Path } else { $pathInfo.ProviderPath } $separator = [System.IO.Path]::DirectorySeparatorChar $res = "$currentPath$separator".StartsWith("$GitDir$separator", (Get-PathStringComparison)) $res } function Get-AliasPattern($cmd) { $aliases = @($cmd) + @(Get-Alias | Where-Object { $_.Definition -match "^$cmd(\.exe)?$" } | Foreach-Object Name) "($($aliases -join '|'))" } <# .SYNOPSIS Deletes the specified Git branches. .DESCRIPTION Deletes the specified Git branches that have been merged into the commit specified by the Commit parameter (HEAD by default). You must either specify a branch name via the Name parameter, which accepts wildard characters, or via the Pattern parameter, which accepts a regular expression. The following branches are always excluded from deletion: * The current branch * develop * master The default set of excluded branches can be overridden with the ExcludePattern parameter. Consider always running this command FIRST with the WhatIf parameter. This will show you which branches will be deleted. This gives you a chance to adjust your branch name wildcard pattern or regular expression if you are using the Pattern parameter. IMPORTANT: Be careful using this command. Even though by default this command deletes only merged branches, most, if not all, of your historical branches have been merged. But that doesn't mean you want to delete them. That is why this command excludes `develop` and `master` by default. But you may use different names e.g. `development` or have other historical branches you don't want to delete. In these cases, you can either: * Specify a narrower branch name wildcard such as "user/$env:USERNAME/*". * Specify an updated ExcludeParameter e.g. '(^\*)|(^. (develop|master|v\d+)$)' which adds any branch matching the pattern 'v\d+' to the exclusion list. If necessary, you can delete the specified branches REGARDLESS of their merge status by using the IncludeUnmerged parameter. BE VERY CAREFUL using the IncludeUnmerged parameter together with the Force parameter, since you will not be given the opportunity to confirm each branch deletion. The following Git commands are executed by this command: git branch --merged $Commit | Where-Object { $_ -notmatch $ExcludePattern } | Where-Object { $_.Trim() -like $Name } | Foreach-Object { git branch --delete $_.Trim() } If the IncludeUnmerged parameter is specified, execution changes to: git branch | Where-Object { $_ -notmatch $ExcludePattern } | Where-Object { $_.Trim() -like $Name } | Foreach-Object { git branch --delete $_.Trim() } If the DeleteForce parameter is specified, the Foreach-Object changes to: Foreach-Object { git branch --delete --force $_.Trim() } If the Pattern parameter is used instead of the Name parameter, the second Where-Object changes to: Where-Object { $_ -match $Pattern } Recovering Deleted Branches If you wind up deleting a branch you didn't intend to, you can easily recover it with the info provided by Git during the delete. For instance, let's say you realized you didn't want to delete the branch 'feature/exp1'. In the output of this command, you should see a deletion entry for this branch that looks like: Deleted branch feature/exp1 (was 08f9000). To recover this branch, execute the following Git command: # git branch git branch feature/exp1 08f9000 .EXAMPLE PS> Remove-GitBranch -Name "user/${env:USERNAME}/*" -WhatIf Shows the merged branches that would be deleted by the specified branch name without actually deleting. Remove the WhatIf parameter when you are happy with the list of branches that will be deleted. .EXAMPLE PS> Remove-GitBranch "feature/*" -Force Deletes the merged branches that match the specified wildcard. Using the Force parameter skips all confirmation prompts. Name is a positional parameter. The first argument is assumed to be the value of the Name parameter. .EXAMPLE PS> Remove-GitBranch "bugfix/*" -Force -DeleteForce Deletes the merged branches that match the specified wildcard. Using the Force parameter skips all confirmation prompts while the DeleteForce parameter uses the --force option in the underlying Git command. .EXAMPLE PS> Remove-GitBranch -Pattern 'user/(dahlbyk|hillr)/.*' Deletes the merged branches that match the specified regular expression. .EXAMPLE PS> Remove-GitBranch -Name * -ExcludePattern '(^\*)|(^. (develop|master|v\d+)$)' Deletes merged branches except the current branch, develop, master and branches that also match the pattern 'v\d+' e.g. v1, v1.0, v1.x. BE VERY CAREFUL SPECIYING SUCH A BROAD BRANCH NAME WILDCARD! .EXAMPLE PS> Remove-GitBranch "feature/*" -IncludeUnmerged -WhatIf Shows the branches, both merged and unmerged, that match the specified wildcard that would be deleted without actually deleting them. Once you've verified the list of branches looks correct, remove the WhatIf parameter to actually delete the branches. #> function Remove-GitBranch { [CmdletBinding(DefaultParameterSetName = "Wildcard", SupportsShouldProcess, ConfirmImpact = "Medium")] param( # Specifies a regular expression pattern for the branches that will be deleted. Certain branches are always excluded from deletion e.g. the current branch as well as the develop and master branches. See the ExcludePattern parameter to modify that pattern. [Parameter(Position = 0, Mandatory, ParameterSetName = "Wildcard")] [ValidateNotNullOrEmpty()] [string] $Name, # Specifies a regular expression for the branches that will be deleted. Certain branches are always excluded from deletion e.g. the current branch as well as the develop and master branches. See the ExcludePattern parameter to modify that pattern. [Parameter(Position = 0, Mandatory, ParameterSetName = "Pattern")] [ValidateNotNull()] [string] $Pattern, # Specifies a regular expression used to exclude merged branches from being deleted. The default pattern excludes the current branch, develop and master branches. [Parameter()] [ValidateNotNull()] [string] $ExcludePattern = '(^\*)|(^. (develop|master)$)', # Branches whose tips are reachable from the specified commit will be deleted. The default commit is HEAD. This parameter is ignored if the IncludeUnmerged parameter is specified. [Parameter()] [string] $Commit = "HEAD", # Specifies that unmerged branches are also eligible to be deleted. [Parameter()] [switch] $IncludeUnmerged, # Deletes the specified branches without prompting for confirmation. By default, Remove-GitBranch prompts for confirmation before deleting branches. [Parameter()] [switch] $Force, # Deletes the specified branches by adding the --force parameter to the git branch --delete command e.g. git branch --delete --force . This is also the equivalent of using the -D parameter on the git branch command. [Parameter()] [switch] $DeleteForce ) if ($IncludeUnmerged) { $branches = git branch } else { $branches = git branch --merged $Commit } $filteredBranches = $branches | Where-Object { $_ -notmatch $ExcludePattern } if ($PSCmdlet.ParameterSetName -eq "Wildcard") { $branchesToDelete = $filteredBranches | Where-Object { $_.Trim() -like $Name } } else { $branchesToDelete = $filteredBranches | Where-Object { $_ -match $Pattern } } $action = if ($DeleteForce) { "delete with force" } else { "delete" } $yesToAll = $noToAll = $false foreach ($branch in $branchesToDelete) { $targetBranch = $branch.Trim() if ($PSCmdlet.ShouldProcess($targetBranch, $action)) { if ($Force -or $yesToAll -or $PSCmdlet.ShouldContinue( "Are you REALLY sure you want to $action `"$targetBranch`"?", "Confirm branch deletion", [ref]$yesToAll, [ref]$noToAll)) { if ($noToAll) { return } if ($DeleteForce) { Invoke-Utf8ConsoleCommand { git branch --delete --force $targetBranch } } else { Invoke-Utf8ConsoleCommand { git branch --delete $targetBranch } } } } } } function Update-AllBranches($Upstream = 'master', [switch]$Quiet) { $head = git rev-parse --abbrev-ref HEAD git checkout -q $Upstream $branches = Invoke-Utf8ConsoleCommand { (git branch --no-color --no-merged) } | Where-Object { $_ -notmatch '^\* ' } foreach ($line in $branches) { $branch = $line.SubString(2) if (!$Quiet) { Write-Host "Rebasing $branch onto $Upstream..." } git rebase -q $Upstream $branch > $null 2> $null if ($LASTEXITCODE) { git rebase --abort Write-Warning "Rebase failed for $branch" } } git checkout -q $head } ================================================ FILE: src/PoshGitTypes.ps1 ================================================ enum BranchBehindAndAheadDisplayOptions { Full; Compact; Minimal } enum UntrackedFilesMode { Default; No; Normal; All } class PoshGitCellColor { [psobject]$BackgroundColor [psobject]$ForegroundColor PoshGitCellColor() { $this.ForegroundColor = $null $this.BackgroundColor = $null } PoshGitCellColor([psobject]$ForegroundColor) { $this.ForegroundColor = $ForegroundColor $this.BackgroundColor = $null } PoshGitCellColor([psobject]$ForegroundColor, [psobject]$BackgroundColor) { $this.ForegroundColor = $ForegroundColor $this.BackgroundColor = $BackgroundColor } hidden [string] ToString($color) { $ansiTerm = "$([char]27)[0m" $colorSwatch = " " $str = "" if (!$color) { $str = "" } elseif (Test-VirtualTerminalSequece $color -Force) { if ($global:GitPromptSettings.AnsiConsole) { # Use '#' for FG color swatch since we are just applying the VT seqs as-is and # a " " swatch char won't show anything for a FG color. if ($color -eq $this.ForegroundColor) { $colorSwatch = "#" } $str = "${color}$colorSwatch${ansiTerm} " } $str = "`"$(EscapeAnsiString $color)`"" } else { if ($global:GitPromptSettings.AnsiConsole) { $bg = Get-BackgroundVirtualTerminalSequence $color $str = "${bg}${colorSwatch}${ansiTerm} " } if ($color -is [int]) { $str += "0x{0:X6}" -f $color } else { $str += $color.ToString() } } return $str } [string] ToEscapedString() { if (!$global:GitPromptSettings.AnsiConsole) { return "" } $str = "" if ($this.ForegroundColor) { if (Test-VirtualTerminalSequece $this.ForegroundColor) { $str += EscapeAnsiString $this.ForegroundColor } else { $seq = Get-ForegroundVirtualTerminalSequence $this.ForegroundColor $str += EscapeAnsiString $seq } } if ($this.BackgroundColor) { if (Test-VirtualTerminalSequece $this.BackgroundColor) { $str += EscapeAnsiString $this.BackgroundColor } else { $seq = Get-BackgroundVirtualTerminalSequence $this.BackgroundColor $str += EscapeAnsiString $seq } } return $str } [string] ToString() { $str = "ForegroundColor: " $str += $this.ToString($this.ForegroundColor) + ", " $str += "BackgroundColor: " $str += $this.ToString($this.BackgroundColor) return $str } } class PoshGitTextSpan { [string]$Text [psobject]$BackgroundColor [psobject]$ForegroundColor PoshGitTextSpan() { $this.Text = "" $this.ForegroundColor = $null $this.BackgroundColor = $null } PoshGitTextSpan([string]$Text) { $this.Text = $Text $this.ForegroundColor = $null $this.BackgroundColor = $null } PoshGitTextSpan([string]$Text, [psobject]$ForegroundColor) { $this.Text = $Text $this.ForegroundColor = $ForegroundColor $this.BackgroundColor = $null } PoshGitTextSpan([string]$Text, [psobject]$ForegroundColor, [psobject]$BackgroundColor) { $this.Text = $Text $this.ForegroundColor = $ForegroundColor $this.BackgroundColor = $BackgroundColor } PoshGitTextSpan([PoshGitTextSpan]$PoshGitTextSpan) { $this.Text = $PoshGitTextSpan.Text $this.ForegroundColor = $PoshGitTextSpan.ForegroundColor $this.BackgroundColor = $PoshGitTextSpan.BackgroundColor } PoshGitTextSpan([PoshGitCellColor]$PoshGitCellColor) { $this.Text = '' $this.ForegroundColor = $PoshGitCellColor.ForegroundColor $this.BackgroundColor = $PoshGitCellColor.BackgroundColor } [PoshGitTextSpan] Expand() { if (!$this.Text) { return $this } $execContext = Get-Variable ExecutionContext -ValueOnly $expandedText = $execContext.SessionState.InvokeCommand.ExpandString($this.Text) $newTextSpan = [PoshGitTextSpan]::new($expandedText, $this.ForegroundColor, $this.BackgroundColor) return $newTextSpan } # This method is used by Write-Prompt to render an instance of a PoshGitTextSpan when # $GitPromptSettings.AnsiConsole is $true. It is also used by the default ToString() # implementation to display any ANSI seqs when AnsiConsole is $true. [string] ToAnsiString() { # If we know which colors were changed, we can reset only these and leave others be. $reset = [System.Collections.Generic.List[string]]::new() $e = [char]27 + "[" $fg = $this.ForegroundColor if (($null -ne $fg) -and !(Test-VirtualTerminalSequece $fg)) { $fg = Get-ForegroundVirtualTerminalSequence $fg $reset.Add('39') } $bg = $this.BackgroundColor if (($null -ne $bg) -and !(Test-VirtualTerminalSequece $bg)) { $bg = Get-BackgroundVirtualTerminalSequence $bg $reset.Add('49') } $txt = $this.Text $str = "${fg}${bg}${txt}" # ALWAYS terminate a VT sequence in case the host supports VT (regardless of AnsiConsole setting), # or the host display can get messed up. if (Test-VirtualTerminalSequece $txt -Force) { $reset.Clear() $reset.Add('0') } if ($reset.Count -gt 0) { $str += "${e}$($reset -join ';')m" } return $str } [string] ToEscapedString() { $str = EscapeAnsiString $this.ToAnsiString() return $str } # This method is used when displaying the "value" of PoshGitTextSpan e.g. when evaluating $GitPromptSettings [string] ToString() { $sep = " " if ($this.Text.Length -lt 2) { $sep = " " * (3 - $this.Text.Length) } if ($global:GitPromptSettings.AnsiConsole) { $txt = $this.ToAnsiString() if (Test-VirtualTerminalSequece $txt) { $escAnsi = "ANSI: `"$(EscapeAnsiString $txt)`"" $str = "Text: `"$txt`e[39;49m`",${sep}${escAnsi}" } else { $str = "Text: `"$txt`"" } } else { $txt = $this.Text if (Test-VirtualTerminalSequece $txt -Force) { $txt = EscapeAnsiString $txt } $color = [PoshGitCellColor]::new($this.ForegroundColor, $this.BackgroundColor) $str = "Text: `"$txt`",${sep}$($color.ToString())" } return $str } } class PoshGitPromptSettings { [bool]$AnsiConsole = ($Host.UI.SupportsVirtualTerminal -or ($Env:ConEmuANSI -eq "ON")) -and !$script:GitCygwin [bool]$SetEnvColumns = $true [PoshGitCellColor]$DefaultColor = [PoshGitCellColor]::new() [PoshGitCellColor]$BranchColor = [PoshGitCellColor]::new([ConsoleColor]::Cyan) [PoshGitCellColor]$IndexColor = [PoshGitCellColor]::new([ConsoleColor]::DarkGreen) [PoshGitCellColor]$WorkingColor = [PoshGitCellColor]::new([ConsoleColor]::DarkRed) [PoshGitCellColor]$StashColor = [PoshGitCellColor]::new([ConsoleColor]::Red) [PoshGitCellColor]$ErrorColor = [PoshGitCellColor]::new([ConsoleColor]::Red) [PoshGitTextSpan]$PathStatusSeparator = ' ' [PoshGitTextSpan]$BeforeStatus = [PoshGitTextSpan]::new('[', [ConsoleColor]::Yellow) [PoshGitTextSpan]$DelimStatus = [PoshGitTextSpan]::new(' |', [ConsoleColor]::Yellow) [PoshGitTextSpan]$AfterStatus = [PoshGitTextSpan]::new(']', [ConsoleColor]::Yellow) [PoshGitTextSpan]$BeforePath = [PoshGitTextSpan]::new('', [ConsoleColor]::Yellow) [PoshGitTextSpan]$AfterPath = [PoshGitTextSpan]::new('', [ConsoleColor]::Yellow) [PoshGitTextSpan]$BeforeIndex = [PoshGitTextSpan]::new('', [ConsoleColor]::DarkGreen) [PoshGitTextSpan]$BeforeStash = [PoshGitTextSpan]::new(' (', [ConsoleColor]::Red) [PoshGitTextSpan]$AfterStash = [PoshGitTextSpan]::new(')', [ConsoleColor]::Red) [PoshGitTextSpan]$LocalDefaultStatusSymbol = [PoshGitTextSpan]::new('', [ConsoleColor]::DarkGreen) [PoshGitTextSpan]$LocalWorkingStatusSymbol = [PoshGitTextSpan]::new('!', [ConsoleColor]::DarkRed) [PoshGitTextSpan]$LocalStagedStatusSymbol = [PoshGitTextSpan]::new('~', [ConsoleColor]::Cyan) [PoshGitTextSpan]$BranchGoneStatusSymbol = [PoshGitTextSpan]::new([char]0x00D7, [ConsoleColor]::DarkCyan) # × Multiplication sign [PoshGitTextSpan]$BranchIdenticalStatusSymbol = [PoshGitTextSpan]::new([char]0x2261, [ConsoleColor]::Cyan) # ≡ Three horizontal lines [PoshGitTextSpan]$BranchAheadStatusSymbol = [PoshGitTextSpan]::new([char]0x2191, [ConsoleColor]::Green) # ↑ Up arrow [PoshGitTextSpan]$BranchBehindStatusSymbol = [PoshGitTextSpan]::new([char]0x2193, [ConsoleColor]::Red) # ↓ Down arrow [PoshGitTextSpan]$BranchBehindAndAheadStatusSymbol = [PoshGitTextSpan]::new([char]0x2195, [ConsoleColor]::Yellow) # ↕ Up & Down arrow [BranchBehindAndAheadDisplayOptions]$BranchBehindAndAheadDisplay = [BranchBehindAndAheadDisplayOptions]::Full [string]$FileAddedText = '+' [string]$FileModifiedText = '~' [string]$FileRemovedText = '-' [string]$FileConflictedText = '!' [string]$BranchUntrackedText = '' [bool]$EnableStashStatus = $false [bool]$ShowStatusWhenZero = $true [bool]$AutoRefreshIndex = $true [UntrackedFilesMode]$UntrackedFilesMode = [UntrackedFilesMode]::Default [bool]$EnablePromptStatus = !$global:GitMissing [bool]$EnableFileStatus = $true [Nullable[bool]]$EnableFileStatusFromCache = $null [string[]]$RepositoriesInWhichToDisableFileStatus = @() [string]$DescribeStyle = '' [psobject]$WindowTitle = {param($GitStatus, [bool]$IsAdmin) "$(if ($IsAdmin) {'Admin: '})$(if ($GitStatus) {"$($GitStatus.RepoName) [$($GitStatus.Branch)]"} else {Get-PromptPath}) - PowerShell $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor) $(if ([IntPtr]::Size -eq 4) {'32-bit '})($PID)"} [PoshGitTextSpan]$DefaultPromptPrefix = '$(Get-PromptConnectionInfo -Format "[{1}@{0}]: ")' [PoshGitTextSpan]$DefaultPromptPath = '$(Get-PromptPath)' [PoshGitTextSpan]$DefaultPromptBeforeSuffix = '' [PoshGitTextSpan]$DefaultPromptDebug = [PoshGitTextSpan]::new(' [DBG]:', [ConsoleColor]::Magenta) [PoshGitTextSpan]$DefaultPromptSuffix = '$(">" * ($nestedPromptLevel + 1)) ' [bool]$DefaultPromptAbbreviateHomeDirectory = ($PSVersionTable.PSVersion.Major -gt 5) -and !$IsWindows [bool]$DefaultPromptAbbreviateGitDirectory = $false [bool]$DefaultPromptWriteStatusFirst = $false [bool]$DefaultPromptEnableTiming = $false [PoshGitTextSpan]$DefaultPromptTimingFormat = ' {0}ms' [int]$BranchNameLimit = 0 [string]$TruncatedBranchSuffix = '...' [bool]$Debug = $false } class PoshGitPromptValues { [int]$LastExitCode [bool]$DollarQuestion [bool]$IsAdmin [string]$LastPrompt } ================================================ FILE: src/TortoiseGit.ps1 ================================================ # TortoiseGit function private:Get-TortoiseGitPath { if ((Test-Path "C:\Program Files\TortoiseGit\bin\TortoiseGitProc.exe") -eq $true) { # TortoiseGit 1.8.0 renamed TortoiseProc to TortoiseGitProc. return "C:\Program Files\TortoiseGit\bin\TortoiseGitProc.exe" } return "C:\Program Files\TortoiseGit\bin\TortoiseProc.exe" } $Global:TortoiseGitSettings = new-object PSObject -Property @{ TortoiseGitPath = (Get-TortoiseGitPath) TortoiseGitCommands = @{ "about" = "about"; "add" = "add"; "blame" = "blame"; "cat" = "cat"; "cleanup" = "cleanup"; "clean" = "cleanup"; "commit" = "commit"; "conflicteditor" = "conflicteditor"; "createpatch" = "createpatch"; "patch" = "createpatch"; "diff" = "diff"; "export" = "export"; "help" = "help"; "ignore" = "ignore"; "log" = "log"; "merge" = "merge"; "pull" = "pull"; "push" = "push"; "rebase" = "rebase"; "refbrowse" = "refbrowse"; "reflog" = "reflog"; "remove" = "remove"; "rm" = "remove"; "rename" = "rename"; "mv" = "rename"; "repocreate" = "repocreate"; "init" = "repocreate"; "repostatus" = "repostatus"; "status" = "repostatus"; "resolve" = "resolve"; "revert" = "revert"; "settings" = "settings"; "config" = "settings"; "stash" = "stash"; "stashapply" = "stashapply"; "stashsave" = "stashsave"; "subadd" = "subadd"; "subsync" = "subsync"; "subupdate" = "subupdate"; "switch" = "switch"; "checkout" = "switch"; "fetch" = "sync"; "sync" = "sync"; } } function tgit { if ($args) { # Replace any aliases with actual TortoiseGit commands if ($Global:TortoiseGitSettings.TortoiseGitCommands.ContainsKey($args[0])) { $args[0] = $Global:TortoiseGitSettings.TortoiseGitCommands.Get_Item($args[0]) } if ($args[0] -eq "help") { # Replace the built-in help behaviour with just a list of commands $Global:TortoiseGitSettings.TortoiseGitCommands.Values.GetEnumerator() | Sort-Object | Get-Unique return } $newArgs = @() $newArgs += "/command:" + $args[0] $cmd = $args[0] if ($args.length -gt 1) { $args[1..$args.length] | ForEach-Object { $newArgs += $_ } } & $Global:TortoiseGitSettings.TortoiseGitPath $newArgs } } ================================================ FILE: src/Utils.ps1 ================================================ # Need this variable as long as we support PS v2 $ModuleBasePath = Split-Path $MyInvocation.MyCommand.Path -Parent # Store error records generated by stderr output when invoking an executable # This can be accessed from the user's session by executing: # PS> $m = Get-Module posh-git # PS> & $m Get-Variable invokeErrors -ValueOnly $invokeErrors = New-Object System.Collections.ArrayList 256 # General Utility Functions function Invoke-NullCoalescing { $result = $null foreach ($arg in $args) { if ($arg -is [ScriptBlock]) { $result = & $arg } else { $result = $arg } if ($result) { break } } $result } Set-Alias ?? Invoke-NullCoalescing -Force function Invoke-Utf8ConsoleCommand([ScriptBlock]$cmd) { $currentEncoding = [Console]::OutputEncoding $errorCount = $global:Error.Count try { # A native executable that writes to stderr AND has its stderr redirected will generate non-terminating # error records if the user has set $ErrorActionPreference to Stop. Override that value in this scope. $ErrorActionPreference = 'Continue' try { [Console]::OutputEncoding = [Text.Encoding]::UTF8 } catch [System.IO.IOException] {} & $cmd } finally { try { [Console]::OutputEncoding = $currentEncoding } catch [System.IO.IOException] {} # Clear out stderr output that was added to the $Error collection, putting those errors in a module variable if ($global:Error.Count -gt $errorCount) { $numNewErrors = $global:Error.Count - $errorCount $invokeErrors.InsertRange(0, $global:Error.GetRange(0, $numNewErrors)) if ($invokeErrors.Count -gt 256) { $invokeErrors.RemoveRange(256, ($invokeErrors.Count - 256)) } $global:Error.RemoveRange(0, $numNewErrors) } } } function Test-Administrator { # PowerShell 5.x only runs on Windows so use .NET types to determine isAdminProcess # Or if we are on v6 or higher, check the $IsWindows pre-defined variable. if (($PSVersionTable.PSVersion.Major -le 5) -or $IsWindows) { $currentUser = [Security.Principal.WindowsPrincipal]([Security.Principal.WindowsIdentity]::GetCurrent()) return $currentUser.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } # Must be Linux or OSX, so use the id util. Root has userid of 0. return 0 -eq (id -u) } <# .SYNOPSIS Configures your PowerShell profile (startup) script to import the posh-git module when PowerShell starts. .DESCRIPTION Checks if your PowerShell profile script is not already importing posh-git and if not, adds a command to import the posh-git module. This will cause PowerShell to load posh-git whenever PowerShell starts. .PARAMETER AllHosts By default, this command modifies the CurrentUserCurrentHost profile script. By specifying the AllHosts switch, the command updates the CurrentUserAllHosts profile (or AllUsersAllHosts, given -AllUsers). .PARAMETER AllUsers By default, this command modifies the CurrentUserCurrentHost profile script. By specifying the AllUsers switch, the command updates the AllUsersCurrentHost profile (or AllUsersAllHosts, given -AllHosts). Requires elevated permissions. .PARAMETER Force Do not check if the specified profile script is already importing posh-git. Just add Import-Module posh-git command. .EXAMPLE PS C:\> Add-PoshGitToProfile Updates your profile script for the current PowerShell host to import the posh-git module when the current PowerShell host starts. .EXAMPLE PS C:\> Add-PoshGitToProfile -AllHosts Updates your profile script for all PowerShell hosts to import the posh-git module whenever any PowerShell host starts. .INPUTS None. .OUTPUTS None. #> function Add-PoshGitToProfile { [CmdletBinding(SupportsShouldProcess)] param( [Parameter()] [switch] $AllHosts, [Parameter()] [switch] $AllUsers, [Parameter()] [switch] $Force, [Parameter(ValueFromRemainingArguments)] [psobject[]] $TestParams ) if ($AllUsers -and !(Test-Administrator)) { throw 'Adding posh-git to an AllUsers profile requires an elevated host.' } $underTest = $false $profileName = $(if ($AllUsers) { 'AllUsers' } else { 'CurrentUser' }) ` + $(if ($AllHosts) { 'AllHosts' } else { 'CurrentHost' }) Write-Verbose "`$profileName = '$profileName'" $profilePath = $PROFILE.$profileName Write-Verbose "`$profilePath = '$profilePath'" # Under test, we override some variables using $args as a backdoor. if (($TestParams.Count -gt 0) -and ($TestParams[0] -is [string])) { $profilePath = [string]$TestParams[0] $underTest = $true if ($TestParams.Count -gt 1) { $ModuleBasePath = [string]$TestParams[1] } } if (!$profilePath) { $profilePath = $PROFILE } if (!$Force) { # Search the user's profiles to see if any are using posh-git already, there is an extra search # ($profilePath) taking place to accommodate the Pester tests. $importedInProfile = Test-PoshGitImportedInScript $profilePath if (!$importedInProfile -and !$underTest) { $importedInProfile = Test-PoshGitImportedInScript $PROFILE } if (!$importedInProfile -and !$underTest) { $importedInProfile = Test-PoshGitImportedInScript $PROFILE.CurrentUserCurrentHost } if (!$importedInProfile -and !$underTest) { $importedInProfile = Test-PoshGitImportedInScript $PROFILE.CurrentUserAllHosts } if (!$importedInProfile -and !$underTest) { $importedInProfile = Test-PoshGitImportedInScript $PROFILE.AllUsersCurrentHost } if (!$importedInProfile -and !$underTest) { $importedInProfile = Test-PoshGitImportedInScript $PROFILE.AllUsersAllHosts } if ($importedInProfile) { Write-Warning "Skipping add of posh-git import to file '$profilePath'." Write-Warning "posh-git appears to already be imported in one of your profile scripts." Write-Warning "If you want to force the add, use the -Force parameter." return } } if (!$profilePath) { Write-Warning "Skipping add of posh-git import to profile; no profile found." Write-Verbose "`$PROFILE = '$PROFILE'" Write-Verbose "CurrentUserCurrentHost = '$($PROFILE.CurrentUserCurrentHost)'" Write-Verbose "CurrentUserAllHosts = '$($PROFILE.CurrentUserAllHosts)'" Write-Verbose "AllUsersCurrentHost = '$($PROFILE.AllUsersCurrentHost)'" Write-Verbose "AllUsersAllHosts = '$($PROFILE.AllUsersAllHosts)'" return } # If the profile script exists and is signed, then we should not modify it if (Test-Path -LiteralPath $profilePath) { if (!(Get-Command Get-AuthenticodeSignature -ErrorAction SilentlyContinue)) { Write-Verbose "Platform doesn't support script signing, skipping test for signed profile." } else { $sig = Get-AuthenticodeSignature $profilePath if ($null -ne $sig.SignerCertificate) { Write-Warning "Skipping add of posh-git import to profile; '$profilePath' appears to be signed." Write-Warning "Add the command 'Import-Module posh-git' to your profile and resign it." return } } } # Check if the location of this module file is in the PSModulePath if (Test-InPSModulePath $ModuleBasePath) { $profileContent = "`nImport-Module posh-git" } else { $modulePath = Join-Path $ModuleBasePath posh-git.psd1 $profileContent = "`nImport-Module '$modulePath'" } # Make sure the PowerShell profile directory exists $profileDir = Split-Path $profilePath -Parent if (!(Test-Path -LiteralPath $profileDir)) { if ($PSCmdlet.ShouldProcess($profileDir, "Create current user PowerShell profile directory")) { New-Item $profileDir -ItemType Directory -Force -Verbose:$VerbosePreference > $null } } if ($PSCmdlet.ShouldProcess($profilePath, "Add 'Import-Module posh-git' to profile")) { Add-Content -LiteralPath $profilePath -Value $profileContent -Encoding UTF8 } } <# .SYNOPSIS Modifies your PowerShell profile (startup) script so that it does not import the posh-git module when PowerShell starts. .DESCRIPTION Checks if your PowerShell profile script is importing posh-git and if it does, removes the command to import the posh-git module. This will cause PowerShell to no longer load posh-git whenever PowerShell starts. .PARAMETER AllHosts By default, this command modifies the CurrentUserCurrentHost profile script. By specifying the AllHosts switch, the command updates the CurrentUserAllHosts profile (or AllUsersAllHosts, given -AllUsers). .PARAMETER AllUsers By default, this command modifies the CurrentUserCurrentHost profile script. By specifying the AllUsers switch, the command updates the AllUsersCurrentHost profile (or AllUsersAllHosts, given -AllHosts). Requires elevated permissions. .EXAMPLE PS C:\> Remove-PoshGitFromProfile Updates your profile script for the current PowerShell host to stop importing the posh-git module when the current PowerShell host starts. .EXAMPLE PS C:\> Remove-PoshGitFromProfile -AllHosts Updates your profile script for all PowerShell hosts to no longer import the posh-git module whenever any PowerShell host starts. .INPUTS None. .OUTPUTS None. #> function Remove-PoshGitFromProfile { [CmdletBinding(SupportsShouldProcess)] param( [Parameter()] [switch] $AllHosts, [Parameter()] [switch] $AllUsers, [Parameter(ValueFromRemainingArguments)] [psobject[]] $TestParams ) if ($AllUsers -and !(Test-Administrator)) { throw 'Removing posh-git from an AllUsers profile requires an elevated host.' } $underTest = $false $profileName = $(if ($AllUsers) { 'AllUsers' } else { 'CurrentUser' }) ` + $(if ($AllHosts) { 'AllHosts' } else { 'CurrentHost' }) Write-Verbose "`$profileName = '$profileName'" $profilePath = $PROFILE.$profileName Write-Verbose "`$profilePath = '$profilePath'" # Under test, we override some variables using $args as a backdoor. if (($TestParams.Count -gt 0) -and ($TestParams[0] -is [string])) { $profilePath = [string]$TestParams[0] $underTest = $true if ($TestParams.Count -gt 1) { $ModuleBasePath = [string]$TestParams[1] } } if (!$profilePath) { $profilePath = $PROFILE } if (!$profilePath) { Write-Warning "Skipping removal of posh-git import from profile; no profile found." Write-Verbose "`$PROFILE = '$PROFILE'" Write-Verbose "CurrentUserCurrentHost = '$($PROFILE.CurrentUserCurrentHost)'" Write-Verbose "CurrentUserAllHosts = '$($PROFILE.CurrentUserAllHosts)'" Write-Verbose "AllUsersCurrentHost = '$($PROFILE.AllUsersCurrentHost)'" Write-Verbose "AllUsersAllHosts = '$($PROFILE.AllUsersAllHosts)'" return } if (Test-Path -LiteralPath $profilePath) { # If the profile script exists and is signed, then we should not modify it if (!(Get-Command Get-AuthenticodeSignature -ErrorAction SilentlyContinue)) { Write-Verbose "Platform doesn't support script signing, skipping test for signed profile." } else { $sig = Get-AuthenticodeSignature $profilePath if ($null -ne $sig.SignerCertificate) { Write-Warning "Skipping removal of posh-git import from profile; '$profilePath' appears to be signed." Write-Warning "Remove the command 'Import-Module posh-git' from your profile and resign it." return } } $oldProfile = @(Get-Content $profilePath) $oldProfileEncoding = Get-FileEncoding $profilePath $newProfile = @() foreach($line in $oldProfile) { if ($line -like '*PoshGitPrompt*') { continue; } if ($line -like '*Load posh-git example profile*') { continue; } if($line -like '. *posh-git*profile.example.ps1*') { continue; } if($line -like 'Import-Module *\posh-git.psd1*') { continue; } $newProfile += $line } Set-Content -path $profilePath -value $newProfile -Force -Encoding $oldProfileEncoding } } <# .SYNOPSIS Gets the file encoding of the specified file. .DESCRIPTION Gets the file encoding of the specified file. .PARAMETER Path Path to the file to check. The file must exist. .EXAMPLE PS C:\> Get-FileEncoding $profile Get's the file encoding of the profile file. .INPUTS None. .OUTPUTS [System.String] .NOTES Adapted from http://www.west-wind.com/Weblog/posts/197245.aspx #> function Get-FileEncoding($Path) { if ($PSVersionTable.PSVersion.Major -ge 6) { $bytes = [byte[]](Get-Content $Path -AsByteStream -ReadCount 4 -TotalCount 4) } else { $bytes = [byte[]](Get-Content $Path -Encoding byte -ReadCount 4 -TotalCount 4) } if (!$bytes) { return 'utf8' } switch -regex ('{0:x2}{1:x2}{2:x2}{3:x2}' -f $bytes[0],$bytes[1],$bytes[2],$bytes[3]) { '^efbbbf' { return 'utf8' } '^2b2f76' { return 'utf7' } '^fffe' { return 'unicode' } '^feff' { return 'bigendianunicode' } '^0000feff' { return 'utf32' } default { return 'ascii' } } } <# .SYNOPSIS Gets a StringComparison enum value appropriate for comparing paths on the OS platform. .DESCRIPTION Gets a StringComparison enum value appropriate for comparing paths on the OS platform. .EXAMPLE PS C:\> $pathStringComparison = Get-PathStringComparison .INPUTS None .OUTPUTS [System.StringComparison] #> function Get-PathStringComparison { # File system paths are case-sensitive on Linux and case-insensitive on Windows and macOS if (($PSVersionTable.PSVersion.Major -ge 6) -and $IsLinux) { [System.StringComparison]::Ordinal } else { [System.StringComparison]::OrdinalIgnoreCase } } function Get-PromptPath { $settings = $global:GitPromptSettings $stringComparison = Get-PathStringComparison # A UNC path has no drive so it's better to use the ProviderPath e.g. "\\server\share". # However for any path with a drive defined, it's better to use the Path property. # In this case, ProviderPath is "\LocalMachine\My"" whereas Path is "Cert:\LocalMachine\My". # The latter is more desirable. $pathInfo = $ExecutionContext.SessionState.Path.CurrentLocation $currentPath = if ($pathInfo.Drive) { $pathInfo.Path } else { $pathInfo.ProviderPath } if (!$settings -or !$currentPath) { return $currentPath } $abbrevHomeDir = $settings.DefaultPromptAbbreviateHomeDirectory $abbrevGitDir = $settings.DefaultPromptAbbreviateGitDirectory # Look up the git root if ($abbrevGitDir) { $gitPath = Get-GitDirectory # Up one level from `.git` if ($gitPath) { $gitPath = Split-Path $gitPath -Parent } } # Abbreviate path under a git repository as ":" if ($abbrevGitDir -and $gitPath -and $currentPath.StartsWith($gitPath, $stringComparison)) { $gitName = Split-Path $gitPath -Leaf $relPath = if ($currentPath -eq $gitPath) { "" } else { $currentPath.SubString($gitPath.Length + 1) } $currentPath = "$gitName`:$relPath" } # Abbreviate path under the user's home dir as "~" elseif ($abbrevHomeDir -and $currentPath.StartsWith($Home, $stringComparison)) { $currentPath = "~" + $currentPath.SubString($Home.Length) } return $currentPath } <# .SYNOPSIS Gets a string with current machine name and user name when connected with SSH .PARAMETER Format Format string to use for displaying machine name ({0}) and user name ({1}). Default: "[{1}@{0}]: ", i.e. "[user@machine]: " .INPUTS None .OUTPUTS [String] #> function Get-PromptConnectionInfo($Format = '[{1}@{0}]: ') { if ($GitPromptSettings -and (Test-Path Env:SSH_CONNECTION)) { $MachineName = [System.Environment]::MachineName $UserName = [System.Environment]::UserName $Format -f $MachineName,$UserName } } function Get-PSModulePath { $modulePaths = $Env:PSModulePath -split ';' $modulePaths } function Test-InPSModulePath { param ( [Parameter(Position=0, Mandatory=$true)] [ValidateNotNull()] [string] $Path ) $modulePaths = Get-PSModulePath if (!$modulePaths) { return $false } $pathStringComparison = Get-PathStringComparison $Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) $inModulePath = @($modulePaths | Where-Object { $Path.StartsWith($_.TrimEnd([System.IO.Path]::DirectorySeparatorChar), $pathStringComparison) }).Count -gt 0 if ($inModulePath -and ('src' -eq (Split-Path $Path -Leaf))) { Write-Warning 'posh-git repository structure is incompatible with %PSModulePath%.' Write-Warning 'Importing with absolute path instead.' return $false } $inModulePath } function Test-PoshGitImportedInScript { param ( [Parameter(Position=0)] [string] $Path ) if (!$Path -or !(Test-Path -LiteralPath $Path)) { return $false } $match = (@(Get-Content $Path -ErrorAction SilentlyContinue) -match 'posh-git').Count -gt 0 if ($match) { Write-Verbose "posh-git found in '$Path'" } $match } function dbg($Message, [Diagnostics.Stopwatch]$Stopwatch) { if ($Stopwatch) { Write-Verbose ('{0:00000}:{1}' -f $Stopwatch.ElapsedMilliseconds,$Message) -Verbose # -ForegroundColor Yellow } } ================================================ FILE: src/WindowTitle.ps1 ================================================ $HostSupportsSettingWindowTitle = $null $OriginalWindowTitle = $null function Test-WindowTitleIsWriteable { if ($null -eq $HostSupportsSettingWindowTitle) { # Probe $Host.UI.RawUI.WindowTitle to see if it can be set without errors try { $script:OriginalWindowTitle = $Host.UI.RawUI.WindowTitle if (!$script:OriginalWindowTitle) { # Set a reasonable title to revert to on uninstall $Host.UI.RawUI.WindowTitle = 'PowerShell'; $script:OriginalWindowTitle = $Host.UI.RawUI.WindowTitle } $newTitle = "${OriginalWindowTitle} " $Host.UI.RawUI.WindowTitle = $newTitle $script:HostSupportsSettingWindowTitle = ($Host.UI.RawUI.WindowTitle -eq $newTitle) $Host.UI.RawUI.WindowTitle = $OriginalWindowTitle Write-Debug "HostSupportsSettingWindowTitle: $HostSupportsSettingWindowTitle" Write-Debug "OriginalWindowTitle: $OriginalWindowTitle" } catch { $script:OriginalWindowTitle = $null $script:HostSupportsSettingWindowTitle = $false Write-Debug "HostSupportsSettingWindowTitle error: $_" } } return $HostSupportsSettingWindowTitle } function Reset-WindowTitle { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] param() $settings = $global:GitPromptSettings # Revert to original WindowTitle, but only if posh-git is currently configured to set it if ($HostSupportsSettingWindowTitle -and $OriginalWindowTitle -and $settings.WindowTitle) { Write-Debug "Resetting WindowTitle: '$OriginalWindowTitle'" $Host.UI.RawUI.WindowTitle = $OriginalWindowTitle } } function Set-WindowTitle { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] param($GitStatus, $IsAdmin) $settings = $global:GitPromptSettings # Update the host's WindowTitle if host supports it and user has not disabled $GitPromptSettings.WindowTitle if ($settings.WindowTitle -and (Test-WindowTitleIsWriteable)) { try { if ($settings.WindowTitle -is [scriptblock]) { # ensure results returned by scriptblock are flattened into a string $windowTitleText = "$(& $settings.WindowTitle $GitStatus $IsAdmin)" } else { $windowTitleText = $ExecutionContext.SessionState.InvokeCommand.ExpandString("$($settings.WindowTitle)") } Write-Debug "Setting WindowTitle: $windowTitleText" $Host.UI.RawUI.WindowTitle = "$windowTitleText" } catch { Write-Debug "Error occurred during evaluation of `$GitPromptSettings.WindowTitle: $_" } } } ================================================ FILE: src/en-US/about_posh-git.help.txt ================================================ TOPIC posh-git SHORT DESCRIPTION posh-git integrates Git and PowerShell providing tab completion of Git commands, branch names, paths and more. It also provides Git status summary information that can be displayed in the PowerShell prompt. LONG DESCRIPTION posh-git integrates Git and PowerShell. Tab completion is supported for Git subcommands, branch and remote names. Git also provides commands to display colored Git status summary information. If you would like fine grained control over how the Git status summary information is displayed in your prompt function, you can get the raw status summary information via the Get-GitStatus command. Then you can display the information in your prompt however you would like. posh-git will install a prompt function if it detects the user does not have their own, customized prompt. This prompt displays Git status summary information when the current directory is located in a Git repository. RELEASE NOTES See https://github.com/dahlbyk/posh-git/blob/master/CHANGELOG.md GIT TAB COMPLETION You can tab complete most common Git subcommands e.g.: C:\GitHub\posh-git> git ch --> git checkout You can also tab complete branch names and even remote names such as origin and upstream. For instance, type the following inside of a Git repo to see tab completion in action: C:\GitHub\posh-git> git fe or ma The above will expand to: C:\GitHub\posh-git> git fetch origin master And like tab completion in other parts of PowerShell, you can press tab multiple times to cycle through all matches. For instance, type "git ch" and press the tab key multiple times to cycle through "checkout", "cherry" and "cherry-pick". POWERSHELL PROMPT PowerShell generates its prompt by executing a function named "prompt", if one exists. posh-git will install its prompt function if it detects the user does not have their own, customized prompt function. The posh-git prompt displays the current working directory followed by git status summary information if the current directory is located in a Git repository, e.g.: C:\GitHub\posh-git [master ≡]> You can customize the posh-git prompt using various settings in the $GitPromptSettings variable. For details see: https://github.com/dahlbyk/posh-git/blob/master/README.md#customizing-the-posh-git-prompt GIT STATUS SUMMARY [{HEAD-name} S +A ~B -C !D | +E ~F -G !H W] * [ (BeforeStatus) * {HEAD-name} is the current branch, or the SHA of a detached HEAD * Cyan means the branch matches its remote * Green means the branch is ahead of its remote (green light to push) * Red means the branch is behind its remote * Yellow means the branch is both ahead of and behind its remote * S represents the branch status in relation to remote (tracked origin) branch. Note: This status information reflects the state of the remote tracked branch after the last `git fetch/pull` of the remote. Execute `git fetch` to update to the latest on the default remote repo. If you have multiple remotes, execute `git fetch --all`. * ≡ = Local branch is at the same commit level as the remote branch (BranchIdenticalStatus). * ↑ = Local branch is ahead of the remote branch by the specified number of commits; a 'git push' is required to update the remote branch (BranchAheadStatus). * ↓ = Local branch is behind the remote branch by the specified number of commits; a 'git pull' is required to update the local branch (BranchBehindStatus). * = Local branch is both ahead of the remote branch by the specified number of commits () and behind by the specified number of commits (); a rebase of the local branch is required before pushing local changes to the remote branch (BranchBehindAndAheadStatus). NOTE: this status is only available if $GitPromptSettings.BranchBehindAndAheadDisplay is set to 'Compact'. * × = The local branch is tracking a branch that is gone from the remote (BranchGoneStatus). * ABCD represents the index | EFGH represents the working directory * + = Added files * ~ = Modified files * - = Removed files * ! = Conflicted files * Index status is dark green and working directory status is dark red reflecting the colors used by 'git status'. * W represents the overall status of the working directory * ! = There are unstaged changes (LocalWorkingStatusSymbol) * ~ = There are uncommitted changes i.e. staged changes waiting to be committed (LocalStagedStatusSymbol) * None = There are no unstaged or uncommitted changes (LocalDefaultStatusSymbol) * ] (AfterStatus) The (symbols) and surrounding text can be customized by the corresponding properties of the global variable $GitPromptSettings. For example, a status summary of [master ≡ +0 ~2 -1 | +1 ~1 -0 !] corresponds to the following 'git status': # On branch master # # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # modified: this-changed.txt # modified: this-too.txt # deleted: gone.ps1 # # Changed but not updated: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: not-staged.ps1 # # Untracked files: # (use "git add ..." to include in what will be committed) # # new.file USAGE AND CUSTOMIZATION You need to import the posh-git module into your PowerShell session to use it. Execute "Import-Module posh-git" to do this. After posh-git has been imported, you can execute the command "Add-PoshGitToProfile" to have your PowerShell profile updated to import posh-git whenever PowerShell starts. When posh-git is imported it will provide a basic prompt function that displays Git status summary information, unless you have your own, custom prompt function. Prompt formatting, among other things, can be customized using the global variables: $GitPromptSettings, $GitTabSettings and $TortoiseGitSettings. To see the available settings, simply type the variable name at the PowerShell prompt (after posh-git has been imported) and press Enter. For more information on customizing the posh-git default prompt or creating your own prompt function see: https://github.com/dahlbyk/posh-git/blob/master/README.md#customizing-the-posh-git-prompt PERFORMANCE Displaying Git status in your prompt for a very large repository can be prohibitively slow. Rather than turn off Git status entirely, you can disable it on a repo-by-repo basis by adding individual repository paths to $GitPromptSettings.RepositoriesInWhichToDisableFileStatus. PRIMARY COMMANDS Get-GitStatus: Returns information about the current Git repository as well as the index and working directory. Remove-GitBranch: Deletes the specified Git branches that have been merged into the commit specified by the Commit parameter (HEAD by default). You must either specify a branch name via the Name parameter, which accepts wildard characters, or via the Pattern parameter, which accepts a regular expression. Write-GitStatus: Given a status object returned by Get-GitStatus, this command formats it as described above in the GIT STATUS PROMPT section. On a host that supports virtual terminal sequences and where $GitPromptSettings.AnsiConsole is set to $true, the prompt text is returned by the function as a string which may contain ANSI escape sequences. Otherise, the prompt text is written directly to the host. Write-VcsStatus: Gets the Git repository information and formats it, as described above in the GIT STATUS PROMPT section. On a host that supports virtual terminal sequences and where $GitPromptSettings.AnsiConsole is set to $true, the prompt text is returned by the function as a string which may contain ANSI escape sequences. Otherise, the prompt text is written directly to the host. This command can be used with other version control systems that append a scriptblock, which uses Write-Prompt to supply their prompt text, to $global:VcsPromptStatuses. BASED ON WORK BY: Keith Dahlby, http://solutionizing.net/ Mark Embling, http://www.markembling.info/ Jeremy Skinner, http://www.jeremyskinner.co.uk/ ================================================ FILE: src/posh-git.psd1 ================================================ @{ # Script module or binary module file associated with this manifest. RootModule = 'posh-git.psm1' # Version number of this module. ModuleVersion = '1.1.0.1' # ID used to uniquely identify this module GUID = '74c9fd30-734b-4c89-a8ae-7727ad21d1d5' # Author of this module Author = 'Keith Dahlby, Keith Hill, and contributors' # Copyright statement for this module Copyright = '(c) 2010-2021 Keith Dahlby, Keith Hill, and contributors' # Description of the functionality provided by this module Description = 'Provides prompt with Git status summary information and tab completion for Git commands, parameters, remotes and branch names.' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '5.0' # Functions to export from this module FunctionsToExport = @( 'Add-PoshGitToProfile', 'Expand-GitCommand', 'Format-GitBranchName', 'Get-GitBranchStatusColor', 'Get-GitStatus', 'Get-GitDirectory', 'Get-PromptConnectionInfo', 'Get-PromptPath', 'New-GitPromptSettings', 'Remove-GitBranch', 'Remove-PoshGitFromProfile', 'Update-AllBranches', 'Write-GitStatus', 'Write-GitBranchName', 'Write-GitBranchStatus', 'Write-GitIndexStatus', 'Write-GitStashCount', 'Write-GitWorkingDirStatus', 'Write-GitWorkingDirStatusSummary', 'Write-Prompt', 'Write-VcsStatus', 'TabExpansion', 'tgit' ) # Cmdlets to export from this module CmdletsToExport = @() # Variables to export from this module VariablesToExport = @('GitPromptScriptBlock') # Aliases to export from this module AliasesToExport = @() # Private data to pass to the module specified in RootModule/ModuleToProcess. # This may also contain a PSData hashtable with additional module metadata used by PowerShell. PrivateData = @{ PSData = @{ # Tags applied to this module. These help with module discovery in online galleries. Tags = @('git', 'prompt', 'tab', 'tab-completion', 'tab-expansion', 'tabexpansion', 'PSEdition_Core') # A URL to the license for this module. LicenseUri = 'https://github.com/dahlbyk/posh-git/blob/master/LICENSE.txt' # A URL to the main website for this project. ProjectUri = 'https://github.com/dahlbyk/posh-git' # ReleaseNotes of this module ReleaseNotes = 'https://github.com/dahlbyk/posh-git/blob/master/CHANGELOG.md' # OVERRIDE THIS FIELD FOR PUBLISHED RELEASES - LEAVE AT 'alpha' FOR CLONED/LOCAL REPO USAGE Prerelease = 'alpha' } } } ================================================ FILE: src/posh-git.psm1 ================================================ param([bool]$ForcePoshGitPrompt, [bool]$UseLegacyTabExpansion, [bool]$EnableProxyFunctionExpansion) if (Test-Path Env:\POSHGIT_ENABLE_STRICTMODE) { # Set strict mode to latest to help catch scripting errors in the module. This is done by the Pester tests. Set-StrictMode -Version Latest } . $PSScriptRoot\CheckRequirements.ps1 > $null . $PSScriptRoot\ConsoleMode.ps1 . $PSScriptRoot\Utils.ps1 . $PSScriptRoot\AnsiUtils.ps1 . $PSScriptRoot\WindowTitle.ps1 . $PSScriptRoot\PoshGitTypes.ps1 . $PSScriptRoot\GitUtils.ps1 . $PSScriptRoot\GitPrompt.ps1 . $PSScriptRoot\GitParamTabExpansion.ps1 . $PSScriptRoot\GitTabExpansion.ps1 . $PSScriptRoot\TortoiseGit.ps1 $IsAdmin = Test-Administrator # Get the default prompt definition. $initialSessionState = [System.Management.Automation.Runspaces.Runspace]::DefaultRunspace.InitialSessionState if (!$initialSessionState -or !$initialSessionState.PSObject.Properties.Match('Commands') -or !$initialSessionState.Commands['prompt']) { $defaultPromptDef = "`$(if (test-path variable:/PSDebugContext) { '[DBG]: ' } else { '' }) + 'PS ' + `$(Get-Location) + `$(if (`$nestedpromptlevel -ge 1) { '>>' }) + '> '" } else { $defaultPromptDef = $initialSessionState.Commands['prompt'].Definition } # The built-in posh-git prompt function in ScriptBlock form. $GitPromptScriptBlock = { $origDollarQuestion = $global:? $origLastExitCode = $global:LASTEXITCODE if (!$global:GitPromptValues) { $global:GitPromptValues = [PoshGitPromptValues]::new() } $global:GitPromptValues.DollarQuestion = $origDollarQuestion $global:GitPromptValues.LastExitCode = $origLastExitCode $global:GitPromptValues.IsAdmin = $IsAdmin $settings = $global:GitPromptSettings if (!$settings) { return "<`$GitPromptSettings not found> " } if ($settings.DefaultPromptEnableTiming) { $sw = [System.Diagnostics.Stopwatch]::StartNew() } if ($settings.SetEnvColumns) { # Set COLUMNS so git knows how wide the terminal is $Env:COLUMNS = $Host.UI.RawUI.WindowSize.Width } # Construct/write the prompt text $prompt = '' # Write default prompt prefix $prompt += Write-Prompt $settings.DefaultPromptPrefix.Expand() # Get the current path - formatted correctly $promptPath = $settings.DefaultPromptPath.Expand() # Write the delimited path and Git status summary information if ($settings.DefaultPromptWriteStatusFirst) { $prompt += Write-VcsStatus $prompt += Write-Prompt $settings.BeforePath.Expand() $prompt += Write-Prompt $promptPath $prompt += Write-Prompt $settings.AfterPath.Expand() } else { $prompt += Write-Prompt $settings.BeforePath.Expand() $prompt += Write-Prompt $promptPath $prompt += Write-Prompt $settings.AfterPath.Expand() $prompt += Write-VcsStatus } # Write default prompt before suffix text $prompt += Write-Prompt $settings.DefaultPromptBeforeSuffix.Expand() # If stopped in the debugger, the prompt needs to indicate that by writing default prompt debug if ((Test-Path Variable:/PSDebugContext) -or [runspace]::DefaultRunspace.Debugger.InBreakpoint) { $prompt += Write-Prompt $settings.DefaultPromptDebug.Expand() } # Get the prompt suffix text $promptSuffix = $settings.DefaultPromptSuffix.Expand() # When using Write-Host, we return a single space from this function to prevent PowerShell from displaying "PS>" # So to avoid two spaces at the end of the suffix, remove one here if it exists if (!$settings.AnsiConsole -and $promptSuffix.Text.EndsWith(' ')) { $promptSuffix.Text = $promptSuffix.Text.Substring(0, $promptSuffix.Text.Length - 1) } # This has to be *after* the call to Write-VcsStatus, which populates $global:GitStatus Set-WindowTitle $global:GitStatus $IsAdmin # If prompt timing enabled, write elapsed milliseconds if ($settings.DefaultPromptEnableTiming) { $timingInfo = [PoshGitTextSpan]::new($settings.DefaultPromptTimingFormat) $sw.Stop() $timingInfo.Text = $timingInfo.Text -f $sw.ElapsedMilliseconds $prompt += Write-Prompt $timingInfo } $prompt += Write-Prompt $promptSuffix # When using Write-Host, return at least a space to avoid "PS>" being unexpectedly displayed if (!$settings.AnsiConsole) { $prompt += " " } else { # If using ANSI, set this global to help debug ANSI issues $global:GitPromptValues.LastPrompt = EscapeAnsiString $prompt } $global:LASTEXITCODE = $origLastExitCode $prompt } $currentPromptDef = if ($funcInfo = Get-Command prompt -ErrorAction SilentlyContinue) { $funcInfo.Definition } # If prompt matches pre-0.7 posh-git prompt, ignore it $collapsedLegacyPrompt = '$realLASTEXITCODE = $LASTEXITCODE;Write-Host($pwd.ProviderPath) -nonewline;Write-VcsStatus;$global:LASTEXITCODE = $realLASTEXITCODE;return "> "' if ($currentPromptDef -and (($currentPromptDef.Trim() -replace '[\r\n\t]+\s*',';') -eq $collapsedLegacyPrompt)) { Write-Warning 'Replacing old posh-git prompt. Did you copy profile.example.ps1 into $PROFILE?' $currentPromptDef = $null } if (!$currentPromptDef) { # HACK: If prompt is missing, create a global one we can overwrite with Set-Item function global:prompt { ' ' } } # If there is no prompt function or the prompt function is the default, replace the current prompt function definition if ($ForcePoshGitPrompt -or !$currentPromptDef -or ($currentPromptDef -eq $defaultPromptDef)) { # Set the posh-git prompt as the default prompt Set-Item Function:\prompt -Value $GitPromptScriptBlock } # Install handler for removal/unload of the module $ExecutionContext.SessionState.Module.OnRemove = { $global:VcsPromptStatuses = $global:VcsPromptStatuses | Where-Object { $_ -ne $PoshGitVcsPrompt } Reset-WindowTitle # Check if the posh-git prompt function itself has been replaced. If so, do not restore the prompt function $promptDef = if ($funcInfo = Get-Command prompt -ErrorAction SilentlyContinue) { $funcInfo.Definition } if ($promptDef -eq $GitPromptScriptBlock) { Set-Item Function:\prompt -Value ([scriptblock]::Create($defaultPromptDef)) return } Write-Warning 'If your prompt function uses any posh-git commands, it will cause posh-git to be re-imported every time your prompt function is invoked.' } $exportModuleMemberParams = @{ Function = @( 'Add-PoshGitToProfile', 'Expand-GitCommand', 'Format-GitBranchName', 'Get-GitBranchStatusColor', 'Get-GitDirectory', 'Get-GitStatus', 'Get-PromptConnectionInfo', 'Get-PromptPath', 'New-GitPromptSettings', 'Remove-GitBranch', 'Remove-PoshGitFromProfile', 'Update-AllBranches', 'Write-GitStatus', 'Write-GitBranchName', 'Write-GitBranchStatus', 'Write-GitIndexStatus', 'Write-GitStashCount', 'Write-GitWorkingDirStatus', 'Write-GitWorkingDirStatusSummary', 'Write-Prompt', 'Write-VcsStatus', 'TabExpansion', 'tgit' ) Variable = @( 'GitPromptScriptBlock' ) } Export-ModuleMember @exportModuleMemberParams ================================================ FILE: test/Ansi.Tests.ps1 ================================================ BeforeAll { . $PSScriptRoot\Shared.ps1 } Describe 'ANSI Tests' { Context 'Returns correct ANSI sequence for specified colors' { It 'Setting BackgroundColor to 0x0 results in Black background' { $ts = & $module.NewBoundScriptBlock({[PoshGitTextSpan]::new("TEST", 0xFF0000, 0)}) $ansiStr = $ts.toAnsiString() $ansiStr | Should -BeExactly "${csi}38;2;255;0;0m${csi}48;2;0;0;0mTEST${csi}39;49m" } It 'Setting ForegroundColor to 0x0 results in Black foreground' { $ts = & $module.NewBoundScriptBlock({[PoshGitTextSpan]::new("TEST", 0, 0xFFFFFF)}) $ansiStr = $ts.toAnsiString() $ansiStr | Should -BeExactly "${csi}38;2;0;0;0m${csi}48;2;255;255;255mTEST${csi}39;49m" } } } ================================================ FILE: test/CheckRequirements.Tests.ps1 ================================================ # Not using BeforeAll because discovery doesn't like InModuleScope without posh-git loaded . $PSScriptRoot\Shared.ps1 Describe 'Test-GitVersion' { InModuleScope 'posh-git' { It 'Returns true for Git for Windows newer than 2.15 (<_>)' -ForEach @( 'git version 2.33.0.rc2.windows.1', 'git version 2.33.0-rc2.windows.1', 'git version 2.31.0.vfs.0.1', 'git version 2.15.0.windows.0', 'git version 2.100.0.windows.0', 'git version 3.0.0.windows.0' ) { Mock Write-Warning {} Test-GitVersion $_ | Should -Be $true Should -Not -Invoke Write-Warning } It 'Returns false for Git for Windows older than 2.15 (<_>)' -ForEach @( 'git version 0.1.0.windows', 'git version 1.99.0.windows', 'git version 2.14.0.windows' ) { Mock Write-Warning {} Test-GitVersion $_ | Should -Be $false Should -Not -Invoke Write-Warning } It 'Returns false for unparseable version (<_>)' -ForEach @( 'git version 1' ) { Mock Write-Warning {} -Verifiable -ParameterFilter { $Message -like '*could not parse*' } Test-GitVersion $_ | Should -Be $false Should -InvokeVerifiable Write-Warning } # TODO: Test Cygwin warning and POSHGIT_CYGWIN_WARNING } } ================================================ FILE: test/DefaultPrompt.Tests.ps1 ================================================ BeforeAll { . $PSScriptRoot\Shared.ps1 [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '')] $SkipWindowTitleTests = !(& $module Test-WindowTitleIsWriteable) } Describe 'Default Prompt Tests - NO ANSI' { BeforeAll { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $prompt = Get-Item Function:\prompt $OFS = '' } BeforeEach { # Ensure these settings start out set to the default values $global:GitPromptSettings = New-GitPromptSettings $GitPromptSettings.AnsiConsole = $false } Context 'Prompt with no Git summary' { It 'Returns the expected prompt string' { Set-Location $HOME -ErrorAction Stop $res = [string](&$prompt *>&1) $res | Should -BeExactly "$(Get-PromptConnectionInfo)$(GetHomePath)> " } It 'Returns the expected prompt string with changed DefaultPromptPrefix' { Set-Location $Home -ErrorAction Stop $GitPromptSettings.DefaultPromptPrefix.Text = 'PS ' $res = [string](&$prompt *>&1) $res | Should -BeExactly "PS $(GetHomePath)> " } It 'Returns the expected prompt string with expanded DefaultPromptPrefix' { Set-Location $Home -ErrorAction Stop $GitPromptSettings.DefaultPromptPrefix.Text = '[$(hostname)] ' $res = [string](&$prompt *>&1) $res | Should -BeExactly "[$(hostname)] $(GetHomePath)> " } It 'Returns the expected prompt string with changed DefaultPromptSuffix' { Set-Location $Home -ErrorAction Stop $GitPromptSettings.DefaultPromptSuffix.Text = '`n> ' $res = [string](&$prompt *>&1) $res | Should -BeExactly "$(Get-PromptConnectionInfo)$(GetHomePath)`n> " } It 'Returns the expected prompt string with expanded DefaultPromptSuffix' { Set-Location $Home -ErrorAction Stop $GitPromptSettings.DefaultPromptSuffix.Text = ' - $(6*7)> ' $res = [string](&$prompt *>&1) $res | Should -BeExactly "$(Get-PromptConnectionInfo)$(GetHomePath) - 42> " } It 'Returns the expected prompt string with DefaultPromptAbbreviateHomeDirectory enabled' { Set-Location $Home -ErrorAction Stop $GitPromptSettings.DefaultPromptAbbreviateHomeDirectory = $true $res = [string](&$prompt *>&1) $res | Should -BeExactly "$(Get-PromptConnectionInfo)$(GetHomePath)> " } It 'Returns the expected prompt string with DefaultPromptAbbreviateHomeDirectory disabled' { Set-Location $Home -ErrorAction Stop $GitPromptSettings.DefaultPromptAbbreviateHomeDirectory = $false $res = [string](&$prompt *>&1) $res | Should -BeExactly "$(Get-PromptConnectionInfo)$(GetHomePath)> " } It 'Returns the expected prompt string with prefix, suffix and abbrev home set' { Set-Location $Home -ErrorAction Stop $GitPromptSettings.DefaultPromptPrefix.Text = '[$(hostname)] ' $GitPromptSettings.DefaultPromptSuffix.Text = ' - $(6*7)> ' $GitPromptSettings.DefaultPromptAbbreviateHomeDirectory = $true $res = [string](&$prompt *>&1) $res | Should -BeExactly "[$(hostname)] $(GetHomePath) - 42> " } It 'Returns the expected prompt string with prompt timing enabled' { Set-Location $Home -ErrorAction Stop $GitPromptSettings.DefaultPromptEnableTiming = $true $res = [string](&$prompt *>&1) $escapedHome = [regex]::Escape("$(Get-PromptConnectionInfo)$(GetHomePath)") $res | Should -Match "$escapedHome \d+ms> " } } Context 'Prompt with Git summary' { BeforeAll { Set-Location $PSScriptRoot } It 'Returns the expected prompt string with status' { Mock -ModuleName posh-git -CommandName git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master A test/Foo.Tests.ps1 D test/Bar.Tests.ps1 M test/Baz.Tests.ps1 '@ } $res = [string](&$prompt *>&1) Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $path = GetHomeRelPath $PSScriptRoot $res | Should -BeExactly "$(Get-PromptConnectionInfo)$path [master +1 ~0 -0 | +0 ~1 -1 !]> " } It 'Returns the expected prompt string with changed PathStatusSeparator' { Mock -ModuleName posh-git -CommandName git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master '@ } $GitPromptSettings.PathStatusSeparator.Text = ' !! ' $res = [string](&$prompt *>&1) Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $path = GetHomeRelPath $PSScriptRoot $res | Should -BeExactly "$(Get-PromptConnectionInfo)$path !! [master]> " } It 'Returns the expected prompt string with expanded PathStatusSeparator' { Mock -ModuleName posh-git -CommandName git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master '@ } $GitPromptSettings.PathStatusSeparator.Text = ' - $(6*7) ' $res = [string](&$prompt *>&1) Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $path = GetHomeRelPath $PSScriptRoot $res | Should -BeExactly "$(Get-PromptConnectionInfo)$path - 42 [master]> " } It 'Returns the expected prompt string with DefaultPromptAbbreviateGitDirectory disabled' { Mock -ModuleName posh-git -CommandName git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master '@ } $GitPromptSettings.DefaultPromptAbbreviateGitDirectory = $false $res = [string](&$prompt *>&1) Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $path = GetGitRelPath $PSScriptRoot # Restore default $GitPromptSettings.DefaultPromptAbbreviateGitDirectory = $false $res | Should -BeExactly "$(Get-PromptConnectionInfo)$path [master]> " } It 'Returns the expected prompt string with DefaultPromptAbbreviateGitDirectory enabled (root)' { Mock -ModuleName posh-git -CommandName git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master '@ } $GitPromptSettings.DefaultPromptAbbreviateGitDirectory = $true $gitRootPath = Split-Path $PSScriptRoot -Parent Set-Location $gitRootPath $res = [string](&$prompt *>&1) Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $path = GetGitRelPath $gitRootPath # Restore default Set-Location $PSScriptRoot $GitPromptSettings.DefaultPromptAbbreviateGitDirectory = $false $res | Should -BeExactly "$(Get-PromptConnectionInfo)$path [master]> " } It 'Returns the expected prompt string with DefaultPromptAbbreviateGitDirectory enabled (subfolder)' { Mock -ModuleName posh-git -CommandName git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master '@ } $GitPromptSettings.DefaultPromptAbbreviateGitDirectory = $true $res = [string](&$prompt *>&1) Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $path = GetGitRelPath $PSScriptRoot # Restore default $GitPromptSettings.DefaultPromptAbbreviateGitDirectory = $false $res | Should -BeExactly "$(Get-PromptConnectionInfo)$path [master]> " } } } Describe 'Default Prompt Tests - ANSI' { BeforeAll { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $prompt = Get-Item Function:\prompt $OFS = '' } BeforeEach { # Ensure these settings start out set to the default values $global:GitPromptSettings = New-GitPromptSettings $GitPromptSettings.AnsiConsole = $true } Context 'Prompt with no Git summary' { It 'Returns the expected prompt string' { Set-Location $HOME -ErrorAction Stop $res = &$prompt $res | Should -BeExactly "$(Get-PromptConnectionInfo)$(GetHomePath)> " } It 'Returns the expected prompt string with changed DefaultPromptSuffix' { Set-Location $Home -ErrorAction Stop $GitPromptSettings.DefaultPromptSuffix.Text = '`n> ' $GitPromptSettings.DefaultPromptSuffix.ForegroundColor = [ConsoleColor]::DarkBlue $GitPromptSettings.DefaultPromptSuffix.BackgroundColor = 0xFF6000 # Orange $res = &$prompt $res | Should -BeExactly "$(Get-PromptConnectionInfo)$(GetHomePath)${csi}34m${csi}48;2;255;96;0m`n> ${csi}39;49m" } It 'Returns the expected prompt string with expanded DefaultPromptSuffix' { Set-Location $Home -ErrorAction Stop $GitPromptSettings.DefaultPromptSuffix.Text = ' - $(6*7)> ' $GitPromptSettings.DefaultPromptSuffix.ForegroundColor = [ConsoleColor]::DarkBlue $GitPromptSettings.DefaultPromptSuffix.BackgroundColor = 0xFF6000 # Orange $res = &$prompt $res | Should -BeExactly "$(Get-PromptConnectionInfo)$(GetHomePath)${csi}34m${csi}48;2;255;96;0m - 42> ${csi}39;49m" } It 'Returns the expected prompt string with changed DefaultPromptPrefix' { Set-Location $Home -ErrorAction Stop $GitPromptSettings.DefaultPromptPrefix.Text = 'PS ' $GitPromptSettings.DefaultPromptPrefix.BackgroundColor = [ConsoleColor]::White $res = &$prompt $res | Should -BeExactly "${csi}107mPS ${csi}49m$(GetHomePath)> " } It 'Returns the expected prompt string with expanded DefaultPromptPrefix' { Set-Location $Home -ErrorAction Stop $GitPromptSettings.DefaultPromptPrefix.Text = '[$(hostname)] ' $GitPromptSettings.DefaultPromptPrefix.BackgroundColor = 0xF5F5F5 $res = &$prompt $res | Should -BeExactly "${csi}48;2;245;245;245m[$(hostname)] ${csi}49m$(GetHomePath)> " } It 'Returns the expected prompt path colors' { Set-Location $Home -ErrorAction Stop $GitPromptSettings.DefaultPromptAbbreviateHomeDirectory = $true $GitPromptSettings.DefaultPromptPath.ForegroundColor = [ConsoleColor]::DarkCyan $GitPromptSettings.DefaultPromptPath.BackgroundColor = [ConsoleColor]::DarkRed $res = &$prompt $res | Should -BeExactly "$(Get-PromptConnectionInfo)${csi}36m${csi}41m$(GetHomePath)${csi}39;49m> " } It 'Returns the expected prompt string with prefix, suffix and abbrev home set' { Set-Location $Home -ErrorAction Stop $GitPromptSettings.DefaultPromptPrefix.Text = '[$(hostname)] ' $GitPromptSettings.DefaultPromptPrefix.ForegroundColor = 0xF5F5F5 $GitPromptSettings.DefaultPromptSuffix.Text = ' - $(6*7)> ' $GitPromptSettings.DefaultPromptSuffix.ForegroundColor = [ConsoleColor]::DarkBlue $GitPromptSettings.DefaultPromptAbbreviateHomeDirectory = $true $res = &$prompt $res | Should -BeExactly "${csi}38;2;245;245;245m[$(hostname)] ${csi}39m$(GetHomePath)${csi}34m - 42> ${csi}39m" } It 'Returns the expected prompt string with prompt timing enabled' { Set-Location $Home -ErrorAction Stop $GitPromptSettings.DefaultPromptEnableTiming = $true $GitPromptSettings.DefaultPromptTimingFormat.ForegroundColor = [System.ConsoleColor]::Magenta $res = &$prompt $escapedHome = [regex]::Escape((GetHomePath)) $rexcsi = [regex]::Escape($csi) $res | Should -Match "$escapedHome${rexcsi}95m \d+ms${rexcsi}39m> " } } Context 'Prompt with Git summary' { BeforeAll { Set-Location $PSScriptRoot } It 'Returns the expected prompt string with status' { Mock -ModuleName posh-git git { if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master A test/Foo.Tests.ps1 D test/Bar.Tests.ps1 M test/Baz.Tests.ps1 '@ } $res = &$prompt Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $path = GetHomeRelPath $PSScriptRoot $res | Should -BeExactly "$(Get-PromptConnectionInfo)$path ${csi}93m[${csi}39m${csi}96mmaster${csi}39m${csi}32m +1${csi}39m${csi}32m ~0${csi}39m${csi}32m -0${csi}39m${csi}93m |${csi}39m${csi}31m +0${csi}39m${csi}31m ~1${csi}39m${csi}31m -1${csi}39m${csi}31m !${csi}39m${csi}93m]${csi}39m> " } It 'Returns the expected prompt string with changed PathStatusSeparator' { Mock -ModuleName posh-git -CommandName git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master '@ } $GitPromptSettings.PathStatusSeparator.Text = ' !! ' $GitPromptSettings.PathStatusSeparator.BackgroundColor = [ConsoleColor]::White $res = [string](&$prompt *>&1) Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $path = GetHomeRelPath $PSScriptRoot $res | Should -BeExactly "$(Get-PromptConnectionInfo)$path${csi}107m !! ${csi}49m${csi}93m[${csi}39m${csi}96mmaster${csi}39m${csi}93m]${csi}39m> " } It 'Returns the expected prompt string with expanded PathStatusSeparator' { Mock -ModuleName posh-git -CommandName git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master '@ } $GitPromptSettings.PathStatusSeparator.Text = ' [$(hostname)] ' $GitPromptSettings.PathStatusSeparator.BackgroundColor = [ConsoleColor]::White $res = [string](&$prompt *>&1) Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $path = GetHomeRelPath $PSScriptRoot $res | Should -BeExactly "$(Get-PromptConnectionInfo)$path${csi}107m [$(hostname)] ${csi}49m${csi}93m[${csi}39m${csi}96mmaster${csi}39m${csi}93m]${csi}39m> " } } } Describe 'Default Prompt WindowTitle Tests' -Skip:$SkipWindowTitleTests { BeforeAll { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $homePath = [regex]::Escape((GetHomePath)) [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $repoAdminRegex = '^Admin: posh-git \[master\] - PowerShell \d+\.\d+ (\d\d-bit )?\(\d+\)$' [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $repoRegex = '^posh-git \[master\] - PowerShell \d+\.\d+ (\d\d-bit )?\(\d+\)$' [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $nonRepoAdminRegex = '^Admin: ' + $homePath + ' - PowerShell \d+\.\d+ (\d\d-bit )?\(\d+\)$' [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $nonRepoRegex = '^' + $homePath + ' - PowerShell \d+\.\d+ (\d\d-bit )?\(\d+\)$' } BeforeEach { # Ensure these settings start out set to the default values as the module only grabs # $global:PreviousWindowTitle once when the module and that happens just once for this whole test file. $defaultTitle = if ($IsWindows) { "Windows PowerShell" } else { "PowerShell-$($PSVersionTable.PSVersion)" } $Host.UI.RawUI.WindowTitle = $defaultTitle $global:PreviousWindowTitle = $defaultTitle $global:GitPromptSettings = New-GitPromptSettings } Context 'In a Git repo' { BeforeAll { Mock -ModuleName posh-git -CommandName git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master A test/Foo.Tests.ps1 D test/Bar.Tests.ps1 M test/Baz.Tests.ps1 '@ } } It 'Default GitPromptSettings.WindowTitle sets the expected Window title text' { Set-Location $PSScriptRoot & $GitPromptScriptBlock 6>&1 Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $title = $Host.UI.RawUI.WindowTitle if (& $module { $IsAdmin }) { $title | Should -Match $repoAdminRegex } else { $title | Should -Match $repoRegex } } It 'Custom GitPromptSettings.WindowTitle scriptblock sets the expected Window title text' { Set-Location $PSScriptRoot $GitPromptSettings.WindowTitle = { param($s, $admin) "$(if ($admin) {'daboss:'} else {'loser:'}) poshgit == $($s.RepoName) / $($s.Branch)" } & $GitPromptScriptBlock 6>&1 Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $title = $Host.UI.RawUI.WindowTitle if (& $module { $IsAdmin }) { $title | Should -Match '^daboss: poshgit == posh-git / master$' } else { $title | Should -Match '^loser: poshgit == posh-git / master$' } } It 'Custom GitPromptSettings.WindowTitle single quoted string sets the expected Window title text' { Set-Location $PSScriptRoot $GitPromptSettings.WindowTitle = '$(if ($IsAdmin) {"daboss:"} else {"loser:"}) poshgit == $($GitStatus.RepoName) / $($GitStatus.Branch)' & $GitPromptScriptBlock 6>&1 Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $title = $Host.UI.RawUI.WindowTitle if (& $module { $IsAdmin }) { $title | Should -Match '^daboss: poshgit == posh-git / master$' } else { $title | Should -Match '^loser: poshgit == posh-git / master$' } } It 'Does not set Window title when GitPromptSettings.WindowText is $null' { Set-Location $PSScriptRoot $GitPromptSettings.WindowTitle = $null & $GitPromptScriptBlock 6>&1 Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $title = $Host.UI.RawUI.WindowTitle $title | Should -Match '^(Windows )?PowerShell' } It 'Does not set Window title when GitPromptSettings.WindowText is $false' { Set-Location $PSScriptRoot $GitPromptSettings.WindowTitle = $false & $GitPromptScriptBlock 6>&1 Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $title = $Host.UI.RawUI.WindowTitle $title | Should -Match '^(Windows )?PowerShell' } It 'Does not set Window title when GitPromptSettings.WindowText is ""' { Set-Location $PSScriptRoot $GitPromptSettings.WindowTitle = '' & $GitPromptScriptBlock 6>&1 Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $title = $Host.UI.RawUI.WindowTitle $title | Should -Match '^(Windows )?PowerShell' } } Context 'Not in a Git repo' { It 'Does not display posh-git status info in Window title when not in a Git repo' { Set-Location $Home & $GitPromptScriptBlock 6>&1 $title = $Host.UI.RawUI.WindowTitle if (& $module { $IsAdmin }) { $title | Should -Match $nonRepoAdminRegex } else { $title | Should -Match $nonRepoRegex } } } Context 'Moving in and out of a Git repo' { BeforeAll { Mock -ModuleName posh-git -CommandName git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master A test/Foo.Tests.ps1 D test/Bar.Tests.ps1 M test/Baz.Tests.ps1 '@ } } It 'Displays the correct Window title as we move in and out of a Git repo' { Set-Location $Home & $GitPromptScriptBlock 6>&1 $title = $Host.UI.RawUI.WindowTitle if (& $module { $IsAdmin }) { $title | Should -Match $nonRepoAdminRegex } else { $title | Should -Match $nonRepoRegex } Set-Location $PSScriptRoot & $GitPromptScriptBlock 6>&1 Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $title = $Host.UI.RawUI.WindowTitle if (& $module { $IsAdmin }) { $title | Should -Match $repoAdminRegex } else { $title | Should -Match $repoRegex } Set-Location $Home & $GitPromptScriptBlock 6>&1 $title = $Host.UI.RawUI.WindowTitle if (& $module { $IsAdmin }) { $title | Should -Match $nonRepoAdminRegex } else { $title | Should -Match $nonRepoRegex } } # This test must be the last test in this file Context 'Removing the posh-git module' { It 'Correctly reverts the Window Title back to original state' { Set-Item function:\prompt -Value ([Runspace]::DefaultRunspace.InitialSessionState.Commands['prompt']).Definition $originalTitle = & $module { $OriginalWindowTitle } $originalTitle | Should -Not -BeNullOrEmpty Remove-Module posh-git -Force *>$null $title = $Host.UI.RawUI.WindowTitle $title | Should -eq $originalTitle } } } } ================================================ FILE: test/Get-GitBranch.Tests.ps1 ================================================ BeforeAll { . $PSScriptRoot\Shared.ps1 } Describe 'Get-GitBranch Tests' { Context 'Get-GitBranch GIT_DIR Tests' { It 'Returns GIT_DIR! when in .git dir of the repo' { $repoRoot = (Resolve-Path $PSScriptRoot\..).Path Set-Location $repoRoot\.git -ErrorAction Stop InModuleScope posh-git { InDotGitOrBareRepoDir (Get-Location) | Should -Be $true Get-GitBranch -IsDotGitOrBare | Should -BeExactly 'GIT_DIR!' } } It 'Returns correct path when in a child folder of the .git dir of the repo' { $repoRoot = (Resolve-Path $PSScriptRoot\..).Path Set-Location $repoRoot\.git\hooks -ErrorAction Stop InModuleScope posh-git { InDotGitOrBareRepoDir (Get-Location) | Should -Be $true Get-GitBranch -IsDotGitOrBare | Should -BeExactly 'GIT_DIR!' } } } } ================================================ FILE: test/Get-GitDirectory.Tests.ps1 ================================================ BeforeAll { . $PSScriptRoot\Shared.ps1 } Describe 'Get-GitDiretory Tests' { Context "Test normal repository" { BeforeAll { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $origPath = Get-Location } AfterAll { Set-Location $origPath } It 'Returns $null for not a Git repo' { Set-Location $env:windir Get-GitDirectory | Should -BeNullOrEmpty } It 'Returns $null for not a filesystem path' { Set-Location Alias:\ Get-GitDirectory | Should -BeNullOrEmpty } It 'Returns correct path when in the root of repo' { $repoRoot = (Resolve-Path $PSScriptRoot\..).Path Set-Location $repoRoot Get-GitDirectory | Should -BeExactly (MakeNativePath $repoRoot\.git) } It 'Returns correct path when under a child folder of the root of repo' { $repoRoot = (Resolve-Path $PSScriptRoot\..).Path Set-Location $PSScriptRoot Get-GitDirectory | Should -BeExactly (Join-Path $repoRoot .git) } } Context 'Test worktree' { BeforeEach { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $origPath = Get-Location $temp = [System.IO.Path]::GetTempPath() $repoPath = Join-Path $temp ([IO.Path]::GetRandomFileName()) $worktreePath = Join-Path $temp ([IO.Path]::GetRandomFileName()) &$gitbin init $repoPath Set-Location $repoPath # Git rid of Git warnings about configuring user for the commit we do below &$gitbin config user.email "you@example.com" &$gitbin config user.name "Pester User" 'foo' > ./README.md &$gitbin add ./README.md # Quoting is a hack due to our use of the global:git function and how it converts args for invoke-expression &$gitbin commit -m "`"initial commit.`"" if (Test-Path $worktreePath) { Remove-Item $worktreePath -Recurse -Force } New-Item $worktreePath -ItemType Directory > $null &$gitbin worktree add -b test-worktree $worktreePath master 2>$null } AfterEach { Set-Location $origPath if (Test-Path $repoPath) { Remove-Item $repoPath -Recurse -Force } if (Test-Path $worktreePath) { Remove-Item $worktreePath -Recurse -Force } } It 'Returns the correct dir when under a worktree' { Set-Location $worktreePath $worktreeBaseName = Split-Path $worktreePath -Leaf $path = GetMacOSAdjustedTempPath $repoPath Get-GitDirectory | Should -BeExactly (MakeGitPath $path\.git\worktrees\$worktreeBaseName) } } Context 'Test bare repository' { BeforeAll { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $origPath = Get-Location $temp = [System.IO.Path]::GetTempPath() $bareRepoName = "test.git" $bareRepoPath = Join-Path $temp $bareRepoName if (Test-Path $bareRepoPath) { Remove-Item $bareRepoPath -Recurse -Force } &$gitbin init --bare $bareRepoPath } AfterAll { Set-Location $origPath if (Test-Path $bareRepoPath) { Remove-Item $bareRepoPath -Recurse -Force } } It 'Returns correct path when in the root of bare repo' { Set-Location $bareRepoPath Get-GitDirectory | Should -BeExactly (MakeNativePath $bareRepoPath) } It 'Returns correct path when under a child folder of the root of bare repo' { Set-Location $bareRepoPath\hooks -ErrorVariable Stop $path = GetMacOSAdjustedTempPath $bareRepoPath Get-GitDirectory | Should -BeExactly (MakeNativePath $path) } } Context "Test GIT_DIR environment variable" { AfterAll { Remove-Item Env:\GIT_DIR -ErrorAction SilentlyContinue } It 'Returns the value in GIT_DIR env var' { $env:GIT_DIR = MakeNativePath '/xyzzy/posh-git/.git' Get-GitDirectory | Should -BeExactly $env:GIT_DIR } } } ================================================ FILE: test/Get-GitStatus.Tests.ps1 ================================================ # For info on Pester mocking see - http://www.powershellmagazine.com/2014/09/30/pester-mock-and-testdrive/ BeforeAll { . $PSScriptRoot\Shared.ps1 } Describe 'Get-GitStatus Tests' { Context 'Get-GitStatus Working Directory Tests' { BeforeAll { Set-Location $PSScriptRoot } It 'Returns the correct branch name' { Mock -ModuleName posh-git git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding @' ## rkeithill/more-status-tests '@ } $status = Get-GitStatus Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $status.Branch | Should -Be "rkeithill/more-status-tests" $status.HasIndex | Should -Be $false $status.HasUntracked | Should -Be $false $status.HasWorking | Should -Be $false $status.Working.Added.Count | Should -Be 0 $status.Working.Deleted.Count | Should -Be 0 $status.Working.Modified.Count | Should -Be 0 $status.Working.Unmerged.Count | Should -Be 0 $status.Index.Added.Count | Should -Be 0 $status.Index.Deleted.Count | Should -Be 0 $status.Index.Modified.Count | Should -Be 0 $status.Index.Unmerged.Count | Should -Be 0 } It 'Returns the correct number of added untracked working files' { Mock -ModuleName posh-git git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master ?? test/Foo.Tests.ps1 ?? test/Bar.Tests.ps1 '@ } $status = Get-GitStatus Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $status.HasIndex | Should -Be $false $status.HasUntracked | Should -Be $true $status.HasWorking | Should -Be $true $status.Working.Added.Count | Should -Be 2 $status.Working.Deleted.Count | Should -Be 0 $status.Working.Modified.Count | Should -Be 0 $status.Working.Unmerged.Count | Should -Be 0 $status.Working.Added[0] | Should -Be "test/Foo.Tests.ps1" $status.Working.Added[1] | Should -Be "test/Bar.Tests.ps1" } It 'Returns the correct number of added working files' { Mock -ModuleName posh-git git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master A test/Foo.Tests.ps1 A test/Bar.Tests.ps1 '@ } $status = Get-GitStatus Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $status.HasIndex | Should -Be $false $status.HasUntracked | Should -Be $true $status.HasWorking | Should -Be $true $status.Working.Added.Count | Should -Be 2 $status.Working.Deleted.Count | Should -Be 0 $status.Working.Modified.Count | Should -Be 0 $status.Working.Unmerged.Count | Should -Be 0 $status.Working.Added[0] | Should -Be "test/Foo.Tests.ps1" $status.Working.Added[1] | Should -Be "test/Bar.Tests.ps1" } It 'Returns the correct number of deleted working files' { Mock -ModuleName posh-git git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master D test/Foo.Tests.ps1 D test/Bar.Tests.ps1 '@ } $status = Get-GitStatus Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $status.HasIndex | Should -Be $false $status.HasUntracked | Should -Be $false $status.HasWorking | Should -Be $true $status.Working.Added.Count | Should -Be 0 $status.Working.Deleted.Count | Should -Be 2 $status.Working.Modified.Count | Should -Be 0 $status.Working.Unmerged.Count | Should -Be 0 $status.Working.Deleted[0] | Should -Be "test/Foo.Tests.ps1" $status.Working.Deleted[1] | Should -Be "test/Bar.Tests.ps1" } It 'Returns the correct number of modified working files' { Mock -ModuleName posh-git git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master M test/Foo.Tests.ps1 M test/Bar.Tests.ps1 '@ } $status = Get-GitStatus Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $status.HasIndex | Should -Be $false $status.HasUntracked | Should -Be $false $status.HasWorking | Should -Be $true $status.Working.Added.Count | Should -Be 0 $status.Working.Deleted.Count | Should -Be 0 $status.Working.Modified.Count | Should -Be 2 $status.Working.Unmerged.Count | Should -Be 0 $status.Working.Modified[0] | Should -Be "test/Foo.Tests.ps1" $status.Working.Modified[1] | Should -Be "test/Bar.Tests.ps1" } It 'Returns the correct number of unmerged working files' { Mock -ModuleName posh-git git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master U test/Foo.Tests.ps1 U test/Bar.Tests.ps1 '@ } $status = Get-GitStatus Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $status.HasIndex | Should -Be $false $status.HasUntracked | Should -Be $false $status.HasWorking | Should -Be $true $status.Working.Added.Count | Should -Be 0 $status.Working.Deleted.Count | Should -Be 0 $status.Working.Modified.Count | Should -Be 0 $status.Working.Unmerged.Count | Should -Be 2 $status.Working.Unmerged[0] | Should -Be "test/Foo.Tests.ps1" $status.Working.Unmerged[1] | Should -Be "test/Bar.Tests.ps1" } It 'Returns the correct number of mixed working files' { Mock -ModuleName posh-git git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master ? test/Untracked.Tests.ps1 A test/Added.Tests.ps1 D test/Deleted.Tests.ps1 M test/Modified.Tests.ps1 U test/Unmerged.Tests.ps1 '@ } $status = Get-GitStatus Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $status.HasIndex | Should -Be $false $status.HasUntracked | Should -Be $true $status.HasWorking | Should -Be $true $status.Working.Added.Count | Should -Be 2 $status.Working.Deleted.Count | Should -Be 1 $status.Working.Modified.Count | Should -Be 1 $status.Working.Unmerged.Count | Should -Be 1 $status.Working.Added[0] | Should -Be "test/Untracked.Tests.ps1" $status.Working.Added[1] | Should -Be "test/Added.Tests.ps1" $status.Working.Deleted[0] | Should -Be "test/Deleted.Tests.ps1" $status.Working.Modified[0] | Should -Be "test/Modified.Tests.ps1" $status.Working.Unmerged[0] | Should -Be "test/Unmerged.Tests.ps1" } It 'Returns the correct number of added index files' { Mock -ModuleName posh-git git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master A test/Foo.Tests.ps1 A test/Bar.Tests.ps1 '@ } $status = Get-GitStatus Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $status.HasIndex | Should -Be $true $status.HasUntracked | Should -Be $false $status.HasWorking | Should -Be $false $status.Index.Added.Count | Should -Be 2 $status.Index.Deleted.Count | Should -Be 0 $status.Index.Modified.Count | Should -Be 0 $status.Index.Unmerged.Count | Should -Be 0 $status.Index.Added[0] | Should -Be "test/Foo.Tests.ps1" $status.Index.Added[1] | Should -Be "test/Bar.Tests.ps1" } It 'Returns the correct number of deleted index files' { Mock -ModuleName posh-git git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master D test/Foo.Tests.ps1 D test/Bar.Tests.ps1 '@ } $status = Get-GitStatus Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $status.HasIndex | Should -Be $true $status.HasUntracked | Should -Be $false $status.HasWorking | Should -Be $false $status.Index.Added.Count | Should -Be 0 $status.Index.Deleted.Count | Should -Be 2 $status.Index.Modified.Count | Should -Be 0 $status.Index.Unmerged.Count | Should -Be 0 $status.Index.Deleted[0] | Should -Be "test/Foo.Tests.ps1" $status.Index.Deleted[1] | Should -Be "test/Bar.Tests.ps1" } It 'Returns the correct number of copied index files' { Mock -ModuleName posh-git git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master C test/Foo.Tests.ps1 C test/Bar.Tests.ps1 '@ } $status = Get-GitStatus Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $status.HasIndex | Should -Be $true $status.HasUntracked | Should -Be $false $status.HasWorking | Should -Be $false $status.Index.Added.Count | Should -Be 0 $status.Index.Deleted.Count | Should -Be 0 $status.Index.Modified.Count | Should -Be 2 $status.Index.Unmerged.Count | Should -Be 0 $status.Index.Modified[0] | Should -Be "test/Foo.Tests.ps1" $status.Index.Modified[1] | Should -Be "test/Bar.Tests.ps1" } It 'Returns the correct number of modified index files' { Mock -ModuleName posh-git git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master M test/Foo.Tests.ps1 M test/Bar.Tests.ps1 '@ } $status = Get-GitStatus Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $status.HasIndex | Should -Be $true $status.HasUntracked | Should -Be $false $status.HasWorking | Should -Be $false $status.Index.Added.Count | Should -Be 0 $status.Index.Deleted.Count | Should -Be 0 $status.Index.Modified.Count | Should -Be 2 $status.Index.Unmerged.Count | Should -Be 0 $status.Index.Modified[0] | Should -Be "test/Foo.Tests.ps1" $status.Index.Modified[1] | Should -Be "test/Bar.Tests.ps1" } It 'Returns the correct number of modified index files for a rename' { Mock -ModuleName posh-git git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master R README.md -> README2.md '@ } $status = Get-GitStatus Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $status.HasIndex | Should -Be $true $status.HasUntracked | Should -Be $false $status.HasWorking | Should -Be $false $status.Index.Added.Count | Should -Be 0 $status.Index.Deleted.Count | Should -Be 0 $status.Index.Modified.Count | Should -Be 1 $status.Index.Unmerged.Count | Should -Be 0 $status.Index.Modified[0] | Should -Be "README.md" } It 'Returns the correct number of unmerged index files' { Mock -ModuleName posh-git git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master U test/Foo.Tests.ps1 U test/Bar.Tests.ps1 '@ } $status = Get-GitStatus Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $status.HasIndex | Should -Be $true $status.HasUntracked | Should -Be $false $status.HasWorking | Should -Be $false $status.Index.Added.Count | Should -Be 0 $status.Index.Deleted.Count | Should -Be 0 $status.Index.Modified.Count | Should -Be 0 $status.Index.Unmerged.Count | Should -Be 2 $status.Index.Unmerged[0] | Should -Be "test/Foo.Tests.ps1" $status.Index.Unmerged[1] | Should -Be "test/Bar.Tests.ps1" } It 'Returns the correct number of mixed index files' { Mock -ModuleName posh-git git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master A test/Added.Tests.ps1 D test/Deleted.Tests.ps1 C test/Copied.Tests.ps1 R README.md -> README2.md M test/Modified.Tests.ps1 U test/Unmerged.Tests.ps1 '@ } $status = Get-GitStatus Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $status.HasIndex | Should -Be $true $status.HasUntracked | Should -Be $false $status.HasWorking | Should -Be $false $status.Index.Added.Count | Should -Be 1 $status.Index.Deleted.Count | Should -Be 1 $status.Index.Modified.Count | Should -Be 3 $status.Index.Unmerged.Count | Should -Be 1 $status.Index.Added[0] | Should -Be "test/Added.Tests.ps1" $status.Index.Deleted[0] | Should -Be "test/Deleted.Tests.ps1" $status.Index.Modified[0] | Should -Be "test/Copied.Tests.ps1" $status.Index.Modified[1] | Should -Be "README.md" $status.Index.Modified[2] | Should -Be "test/Modified.Tests.ps1" $status.Index.Unmerged[0] | Should -Be "test/Unmerged.Tests.ps1" } It 'Returns the correct number of mixed index and working files' { Mock -ModuleName posh-git git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master A test/Added.Tests.ps1 D test/Deleted.Tests.ps1 C test/Copied.Tests.ps1 R README.md -> README2.md M test/Modified.Tests.ps1 U test/Unmerged.Tests.ps1 ? test/Untracked.Tests.ps1 A test/Added.Tests.ps1 D test/Deleted.Tests.ps1 M test/Modified.Tests.ps1 U test/Unmerged.Tests.ps1 '@ } $status = Get-GitStatus Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $status.HasIndex | Should -Be $true $status.HasUntracked | Should -Be $true $status.HasWorking | Should -Be $true $status.Working.Added.Count | Should -Be 2 $status.Working.Deleted.Count | Should -Be 1 $status.Working.Modified.Count | Should -Be 1 $status.Working.Unmerged.Count | Should -Be 1 $status.Working.Added[0] | Should -Be "test/Untracked.Tests.ps1" $status.Working.Added[1] | Should -Be "test/Added.Tests.ps1" $status.Working.Deleted[0] | Should -Be "test/Deleted.Tests.ps1" $status.Working.Modified[0] | Should -Be "test/Modified.Tests.ps1" $status.Working.Unmerged[0] | Should -Be "test/Unmerged.Tests.ps1" $status.Index.Added.Count | Should -Be 1 $status.Index.Deleted.Count | Should -Be 1 $status.Index.Modified.Count | Should -Be 3 $status.Index.Unmerged.Count | Should -Be 1 $status.Index.Added[0] | Should -Be "test/Added.Tests.ps1" $status.Index.Deleted[0] | Should -Be "test/Deleted.Tests.ps1" $status.Index.Modified[0] | Should -Be "test/Copied.Tests.ps1" $status.Index.Modified[1] | Should -Be "README.md" $status.Index.Modified[2] | Should -Be "test/Modified.Tests.ps1" $status.Index.Unmerged[0] | Should -Be "test/Unmerged.Tests.ps1" } } Context 'Branch progress suffix' { BeforeEach { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $repoPath = NewGitTempRepo -MakeInitialCommit } AfterEach { Set-Location $PSScriptRoot RemoveGitTempRepo $repoPath } It('Shows CHERRY-PICKING') { git checkout -qb test Write-Output 1 > test.txt git add test.txt git commit -qam 'first' 2> $null git checkout -qb conflict Write-Output 2 > test.txt git commit -qam 'second' 2> $null $status = Get-GitStatus $status.Branch | Should -Be conflict git cherry-pick test $status = Get-GitStatus $status.Branch | Should -Be 'conflict|CHERRY-PICKING' } It('Shows MERGING') { git checkout -qb test Write-Output 1 > test.txt git add test.txt git commit -qam 'first' 2> $null Write-Output 2 > test.txt git commit -qam 'second' 2> $null git checkout HEAD~ -qb conflict Write-Output 3 > test.txt git commit -qam 'third' 2> $null $status = Get-GitStatus $status.Branch | Should -Be conflict git merge test $status = Get-GitStatus $status.Branch | Should -Be 'conflict|MERGING' } It('Shows REVERTING') { git checkout -qb test Write-Output 1 > test.txt git add test.txt git commit -qam 'first' 2> $null git checkout -qb conflict Write-Output 2 > test.txt git commit -qam 'second' 2> $null $status = Get-GitStatus $status.Branch | Should -Be conflict git revert test $status = Get-GitStatus $status.Branch | Should -Be 'conflict|REVERTING' } } Context 'In .git' { BeforeEach { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $repoPath = NewGitTempRepo } AfterEach { Set-Location $PSScriptRoot RemoveGitTempRepo $repoPath } It('Does not have files') { New-Item "$repoPath/test.txt" -ItemType File $status = Get-GitStatus $status.HasUntracked | Should -Be $true $status.HasWorking | Should -Be $true $status.Working.Added.Count | Should -Be 1 Set-Location "$repoPath/.git" -ErrorAction Stop $status = Get-GitStatus $status.HasUntracked | Should -Be $false $status.HasWorking | Should -Be $false $status.Working.Added.Count | Should -Be 0 Set-Location "$repoPath/.git/refs" -ErrorAction Stop $status = Get-GitStatus $status.HasUntracked | Should -Be $false $status.HasWorking | Should -Be $false $status.Working.Added.Count | Should -Be 0 } } Context 'In .github' { BeforeEach { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $repoPath = NewGitTempRepo New-Item -Type Directory -Force "$repoPath/.github/workflows" } AfterEach { Set-Location $PSScriptRoot RemoveGitTempRepo $repoPath } It('Files are not ignored') { New-Item "$repoPath/test.txt" -ItemType File $status = Get-GitStatus $status.HasUntracked | Should -Be $true $status.HasWorking | Should -Be $true $status.Working.Added.Count | Should -Be 1 Set-Location "$repoPath/.github" -ErrorAction Stop $status = Get-GitStatus $status.HasUntracked | Should -Be $true $status.HasWorking | Should -Be $true $status.Working.Added.Count | Should -Be 1 Set-Location "$repoPath/.github/workflows" -ErrorAction Stop $status = Get-GitStatus $status.HasUntracked | Should -Be $true $status.HasWorking | Should -Be $true $status.Working.Added.Count | Should -Be 1 } } } ================================================ FILE: test/GitParamTabExpansion.Tests.ps1 ================================================ BeforeAll { . $PSScriptRoot\Shared.ps1 } Describe 'ParamsTabExpansion Tests' { Context 'Push Parameters TabExpansion Tests' { It 'Tab completes all long push parameters' { $result = & $module GitTabExpansionInternal 'git push --' $result -contains '--all' | Should -Be $true $result -contains '--delete' | Should -Be $true $result -contains '--dry-run' | Should -Be $true $result -contains '--exec=' | Should -Be $true $result -contains '--follow-tags' | Should -Be $true $result -contains '--force' | Should -Be $true $result -contains '--force-with-lease' | Should -Be $true $result -contains '--mirror' | Should -Be $true $result -contains '--no-force-with-lease' | Should -Be $true $result -contains '--no-thin' | Should -Be $true $result -contains '--no-verify' | Should -Be $true $result -contains '--porcelain' | Should -Be $true $result -contains '--progress' | Should -Be $true $result -contains '--prune' | Should -Be $true $result -contains '--quiet' | Should -Be $true $result -contains '--receive-pack=' | Should -Be $true $result -contains '--recurse-submodules=' | Should -Be $true $result -contains '--repo=' | Should -Be $true $result -contains '--set-upstream' | Should -Be $true $result -contains '--tags' | Should -Be $true $result -contains '--thin' | Should -Be $true $result -contains '--verbose' | Should -Be $true $result -contains '--verify' | Should -Be $true } It 'Tab completes all short push parameters' { $result = & $module GitTabExpansionInternal 'git push -' $result -contains '-f' | Should -Be $true $result -contains '-n' | Should -Be $true $result -contains '-q' | Should -Be $true $result -contains '-u' | Should -Be $true $result -contains '-v' | Should -Be $true } It 'Tab completes push parameters values' { $result = & $module GitTabExpansionInternal 'git push --recurse-submodules=' $result -contains '--recurse-submodules=check' | Should -Be $true $result -contains '--recurse-submodules=on-demand' | Should -Be $true } } Context 'Pretty/Format TabCompletion Tests - No Custom Formats' { It 'Tab completes default formats for log --pretty' { $result = & $module GitTabExpansionInternal 'git log --pretty=' $result -contains '--pretty=oneline' | Should -Be $true $result -contains '--pretty=short' | Should -Be $true $result -contains '--pretty=medium' | Should -Be $true $result -contains '--pretty=full' | Should -Be $true $result -contains '--pretty=fuller' | Should -Be $true $result -contains '--pretty=email' | Should -Be $true $result -contains '--pretty=raw' | Should -Be $true } It 'Tab completes default formats for log --format' { $result = & $module GitTabExpansionInternal 'git log --format=' $result -contains '--format=oneline' | Should -Be $true $result -contains '--format=short' | Should -Be $true $result -contains '--format=medium' | Should -Be $true $result -contains '--format=full' | Should -Be $true $result -contains '--format=fuller' | Should -Be $true $result -contains '--format=email' | Should -Be $true $result -contains '--format=raw' | Should -Be $true } It 'Tab completes default formats for show --pretty' { $result = & $module GitTabExpansionInternal 'git show --pretty=' $result -contains '--pretty=oneline' | Should -Be $true $result -contains '--pretty=short' | Should -Be $true $result -contains '--pretty=medium' | Should -Be $true $result -contains '--pretty=full' | Should -Be $true $result -contains '--pretty=fuller' | Should -Be $true $result -contains '--pretty=email' | Should -Be $true $result -contains '--pretty=raw' | Should -Be $true } It 'Tab completes default formats for show --format' { $result = & $module GitTabExpansionInternal 'git show --format=' $result -contains '--format=oneline' | Should -Be $true $result -contains '--format=short' | Should -Be $true $result -contains '--format=medium' | Should -Be $true $result -contains '--format=full' | Should -Be $true $result -contains '--format=fuller' | Should -Be $true $result -contains '--format=email' | Should -Be $true $result -contains '--format=raw' | Should -Be $true } } Context 'Pretty/Format TabCompletion Tests - With Custom Formats' { BeforeEach { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $repoPath = NewGitTempRepo # Test with custom formats &$gitbin config pretty.birdseye "%C(auto)%h%d %s %C(bold blue)<%aN> %C(green)(%cr)%Creset" &$gitbin config pretty.test2 "%h%d %s <%aN> (%cr)" } AfterEach { RemoveGitTempRepo $repoPath } It 'Tab completes default and custom formats for log --pretty' { $result = & $module GitTabExpansionInternal 'git log --pretty=' $result -contains '--pretty=oneline' | Should -Be $true $result -contains '--pretty=short' | Should -Be $true $result -contains '--pretty=medium' | Should -Be $true $result -contains '--pretty=full' | Should -Be $true $result -contains '--pretty=fuller' | Should -Be $true $result -contains '--pretty=email' | Should -Be $true $result -contains '--pretty=raw' | Should -Be $true $result -contains '--pretty=birdseye' | Should -Be $true $result -contains '--pretty=test2' | Should -Be $true } It 'Tab completes default and custom formats for log --format' { $result = & $module GitTabExpansionInternal 'git log --format=' $result -contains '--format=oneline' | Should -Be $true $result -contains '--format=short' | Should -Be $true $result -contains '--format=medium' | Should -Be $true $result -contains '--format=full' | Should -Be $true $result -contains '--format=fuller' | Should -Be $true $result -contains '--format=email' | Should -Be $true $result -contains '--format=raw' | Should -Be $true $result -contains '--format=birdseye' | Should -Be $true $result -contains '--format=test2' | Should -Be $true } It 'Tab completes default and custom formats for show --pretty' { $result = & $module GitTabExpansionInternal 'git show --pretty=' $result -contains '--pretty=oneline' | Should -Be $true $result -contains '--pretty=short' | Should -Be $true $result -contains '--pretty=medium' | Should -Be $true $result -contains '--pretty=full' | Should -Be $true $result -contains '--pretty=fuller' | Should -Be $true $result -contains '--pretty=email' | Should -Be $true $result -contains '--pretty=raw' | Should -Be $true $result -contains '--pretty=birdseye' | Should -Be $true $result -contains '--pretty=test2' | Should -Be $true } It 'Tab completes default and custom formats for show --format' { $result = & $module GitTabExpansionInternal 'git show --format=' $result -contains '--format=oneline' | Should -Be $true $result -contains '--format=short' | Should -Be $true $result -contains '--format=medium' | Should -Be $true $result -contains '--format=full' | Should -Be $true $result -contains '--format=fuller' | Should -Be $true $result -contains '--format=email' | Should -Be $true $result -contains '--format=raw' | Should -Be $true $result -contains '--format=birdseye' | Should -Be $true $result -contains '--format=test2' | Should -Be $true } } } ================================================ FILE: test/GitParamTabExpansionVsts.Tests.ps1 ================================================ BeforeAll { . $PSScriptRoot\Shared.ps1 } Describe 'ParamsTabExpansion VSTS Tests' { Context 'Push Parameters TabExpansion Tests' { # Create a git alias for 'pr', as if we'd installed vsts-cli BeforeEach { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $repoPath = NewGitTempRepo # Test with non-standard vsts pr alias name &$gitbin config alias.test-vsts-pr "!f() { exec vsts code pr \`"`$`@\`"; }; f" } AfterEach { RemoveGitTempRepo $repoPath } It 'Tab completes empty for git pr oops parameters values' { $result = & $module GitTabExpansionInternal 'git test-vsts-pr oops --' $result | Should -Be @() } It 'Tab completes empty for git pr oops short parameter values' { $result = & $module GitTabExpansionInternal 'git test-vsts-pr oops -' $result | Should -Be @() } It 'Tab completes git pr create parameters values' { $result = & $module GitTabExpansionInternal 'git test-vsts-pr create --' $result -contains '--auto-complete' | Should -Be $true } It 'Tab completes git pr create auto-complete parameters values' { $result = & $module GitTabExpansionInternal 'git test-vsts-pr create --auto-complete --' $result -contains '--delete-source-branch' | Should -Be $true } It 'Tab completes git pr show all parameters values' { $result = & $module GitTabExpansionInternal 'git test-vsts-pr show --' $result -contains '--' | Should -Be $false $result -contains '--debug' | Should -Be $true $result -contains '--help' | Should -Be $true $result -contains '--output' | Should -Be $true $result -contains '--query' | Should -Be $true $result -contains '--verbose' | Should -Be $true } It 'Tab completes git pr create all short push parameters' { $result = & $module GitTabExpansionInternal 'git test-vsts-pr create -' $result -contains '-d' | Should -Be $true $result -contains '-i' | Should -Be $true $result -contains '-p' | Should -Be $true $result -contains '-r' | Should -Be $true $result -contains '-s' | Should -Be $true $result -contains '-h' | Should -Be $true $result -contains '-o' | Should -Be $true } } } ================================================ FILE: test/GitPrompt.Tests.ps1 ================================================ BeforeAll { . $PSScriptRoot\Shared.ps1 } Describe 'Write-VcsStatus Tests' { BeforeAll { Mock -ModuleName posh-git -CommandName git { $OFS = " " if ($args -contains 'rev-parse') { $res = Invoke-Expression "&$gitbin $args" return $res } Convert-NativeLineEnding -SplitLines @' ## master A test/Foo.Tests.ps1 D test/Bar.Tests.ps1 M test/Baz.Tests.ps1 '@ } } Context 'AnsiConsole disabled' { BeforeAll { # Ensure these settings start out set to the default values $global:GitPromptSettings = New-GitPromptSettings $GitPromptSettings.AnsiConsole = $false } It 'Returns no output from Write-VcsStatus' { # Verify that we are getting write-host output first $OFS = '' $res = Write-VcsStatus 6>&1 Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 "$res" | Should -BeExactly " [master +1 ~0 -0 ~]" # Verify that there is no return value i.e. we get $null $res = Write-VcsStatus 6>$null $res | Should -BeExactly $null } } Context 'AnsiConsole enabled' { BeforeAll { # Ensure these settings start out set to the default values $global:GitPromptSettings = New-GitPromptSettings $GitPromptSettings.AnsiConsole = $true } It 'Returns status output from Write-VcsStatus as string' { $res = Write-VcsStatus Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $res | Should -BeExactly " ${csi}93m[${csi}39m${csi}96mmaster${csi}39m${csi}32m +1${csi}39m${csi}32m ~0${csi}39m${csi}32m -0${csi}39m${csi}96m ~${csi}39m${csi}93m]${csi}39m" } } } ================================================ FILE: test/GitProxyFunctionExpansion.Tests.ps1 ================================================ BeforeAll { . $PSScriptRoot\Shared.ps1 } Describe 'Proxy Function Expansion Tests' { Context 'Proxy Function Name TabExpansion Tests' { BeforeEach { if (Test-Path -Path Function:\Invoke-GitFunction) { Rename-Item -Path Function:\Invoke-GitFunction -NewName Invoke-GitFunctionBackup } if (Test-Path -Path Alias:\igf) { Rename-Item -Path Alias:\igf -NewName igfbackup } New-Alias -Name 'igf' -Value Invoke-GitFunction -Scope 'Global' } AfterEach { if (Test-Path -Path Function:\Invoke-GitFunction) { Remove-Item -Path Function:\Invoke-GitFunction } if (Test-Path -Path Function:\Invoke-GitFunctionBackup) { Rename-Item Function:\Invoke-GitFunctionBackup Invoke-GitFunction } if (Test-Path -Path Alias:\igf) { Remove-Item -Path Alias:\igf } if (Test-Path -Path Alias:\igfbackup) { Rename-Item -Path Alias:\igfbackup -NewName igf } } It 'Expands a proxy function with parameters' { function global:Invoke-GitFunction { git checkout $args } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction -b newbranch' $result | Should -Be 'git checkout -b newbranch' $result | Should -Be (& $module Expand-GitProxyFunction 'igf -b newbranch') } It 'Expands a multiline proxy function' { function global:Invoke-GitFunction { git checkout $args } $result = & $module Expand-GitProxyFunction "Invoke-GitFunction ```r`n-b ```r`nnewbranch" $result | Should -Be 'git checkout -b newbranch' $result | Should -Be (& $module Expand-GitProxyFunction "igf ```r`n-b ```r`nnewbranch") } It 'Does not expand the proxy function name if there is no preceding whitespace before backtick newlines' { function global:Invoke-GitFunction { git checkout $args } & $module Expand-GitProxyFunction "Invoke-GitFunction```r`n-b```r`nnewbranch" | Should -Be "Invoke-GitFunction```r`n-b```r`nnewbranch" & $module Expand-GitProxyFunction "igf```r`n-b```r`nnewbranch" | Should -Be "igf```r`n-b```r`nnewbranch" } It 'Does not expand the proxy function name if there is no preceding non-newline whitespace before any backtick newlines' { function global:Invoke-GitFunction { git checkout $args } & $module Expand-GitProxyFunction "Invoke-GitFunction ```r`n-b```r`nnewbranch" | Should -Be "Invoke-GitFunction ```r`n-b```r`nnewbranch" & $module Expand-GitProxyFunction "igf ```r`n-b```r`nnewbranch" | Should -Be "igf ```r`n-b```r`nnewbranch" } It 'Does not expand the proxy function name if the preceding whitespace before backtick newlines are newlines' { function global:Invoke-GitFunction { git checkout $args } & $module Expand-GitProxyFunction "Invoke-GitFunction`r`n```r`n-b`r`n```r`nnewbranch" | Should -Be "Invoke-GitFunction`r`n```r`n-b`r`n```r`nnewbranch" & $module Expand-GitProxyFunction "igf`r`n```r`n-b`r`n```r`nnewbranch" | Should -Be "igf`r`n```r`n-b`r`n```r`nnewbranch" } It 'Does not expand the proxy function if there is no trailing space' { function global:Invoke-GitFunction { git checkout $args } & $module Expand-GitProxyFunction 'Invoke-GitFunction' | Should -Be 'Invoke-GitFunction' & $module Expand-GitProxyFunction 'igf' | Should -Be 'igf' } } Context 'Proxy Function Definition Expansion Tests' { BeforeEach { if (Test-Path -Path Function:\Invoke-GitFunction) { Rename-Item -Path Function:\Invoke-GitFunction -NewName Invoke-GitFunctionBackup } if (Test-Path -Path Alias:\igf) { Rename-Item -Path Alias:\igf -NewName igfbackup } New-Alias -Name 'igf' -Value Invoke-GitFunction -Scope 'Global' } AfterEach { if (Test-Path -Path Function:\Invoke-GitFunction) { Remove-Item -Path Function:\Invoke-GitFunction } if (Test-Path -Path Function:\Invoke-GitFunctionBackup) { Rename-Item Function:\Invoke-GitFunctionBackup Invoke-GitFunction } if (Test-Path -Path Alias:\igf) { Remove-Item -Path Alias:\igf } if (Test-Path -Path Alias:\igfbackup) { Rename-Item -Path Alias:\igfbackup -NewName igf } } It 'Expands a single line function' { function global:Invoke-GitFunction { git checkout $args } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout ' $result | Should -Be (& $module Expand-GitProxyFunction 'igf ' ) } It 'Expands a single line function with short parameter' { function global:Invoke-GitFunction { git checkout -b $args } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout -b ' $result | Should -Be (& $module Expand-GitProxyFunction 'igf ' ) } It 'Expands a single line function with long parameter' { function global:Invoke-GitFunction { git checkout --detach $args } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout --detach ' $result | Should -Be (& $module Expand-GitProxyFunction 'igf ' ) } It 'Expands a single line with piped function suffix' { function global:Invoke-GitFunction { git checkout --detach $args | Write-Host } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout --detach ' $result | Should -Be (& $module Expand-GitProxyFunction 'igf ' ) } It 'Expands the first line in function' { function global:Invoke-GitFunction { git checkout $args $a = 5 Write-Host $null } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout ' $result | Should -Be (& $module Expand-GitProxyFunction 'igf ' ) } It 'Expands the middle line in function' { function global:Invoke-GitFunction { $a = 5 git checkout $args Write-Host $null } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout ' $result | Should -Be (& $module Expand-GitProxyFunction 'igf ' ) } It 'Expands the last line in function' { function global:Invoke-GitFunction { $a = 5 Write-Host $null git checkout $args } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout ' $result | Should -Be (& $module Expand-GitProxyFunction 'igf ' ) } It 'Expands semicolon delimited functions' { function global:Invoke-GitFunction { $a = 5; git checkout $args; Write-Host $null; } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout ' $result | Should -Be (& $module Expand-GitProxyFunction 'igf ' ) } It 'Expands mixed semicolon delimited and newline functions' { function global:Invoke-GitFunction { $a = 5; Write-Host $null git checkout $args; Write-Host $null; } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout ' $result | Should -Be (& $module Expand-GitProxyFunction 'igf ' ) } It 'Expands mixed semicolon delimited and newline multiline functions' { function global:Invoke-GitFunction { $a = 5; Write-Host $null git ` checkout ` $args; Write-Host $null; } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout ' $result | Should -Be (& $module Expand-GitProxyFunction 'igf ' ) } It 'Expands simultaneously semicolon delimited and newline functions' { function global:Invoke-GitFunction { $a = 5; Write-Host $null; git checkout $args; Write-Host $null; } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout ' $result | Should -Be (& $module Expand-GitProxyFunction 'igf ' ) } It 'Expands multiline function' { function global:Invoke-GitFunction { git ` checkout ` $args } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout ' $result | Should -Be (& $module Expand-GitProxyFunction 'igf ' ) } It 'Expands multiline function that terminates with semicolon on new line' { function global:Invoke-GitFunction { git ` checkout ` $args ` ; } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout ' $result | Should -Be (& $module Expand-GitProxyFunction 'igf ' ) } It 'Expands multiline function with short parameter' { function global:Invoke-GitFunction { git ` checkout ` -b ` $args } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout -b ' $result | Should -Be (& $module Expand-GitProxyFunction 'igf ' ) } It 'Expands multiline function with long parameter' { function global:Invoke-GitFunction { git ` checkout ` --detach ` $args } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout --detach ' $result | Should -Be (& $module Expand-GitProxyFunction 'igf ' ) } It 'Does not expand a single line with piped function prefix' { function global:Invoke-GitFunction { "master" | git checkout --detach $args } & $module Expand-GitProxyFunction 'Invoke-GitFunction ' | Should -Be 'Invoke-GitFunction ' & $module Expand-GitProxyFunction 'igf ' | Should -Be 'igf ' } It 'Does not expand function if $args is not present' { function global:Invoke-GitFunction { git checkout } & $module Expand-GitProxyFunction 'Invoke-GitFunction ' | Should -Be 'Invoke-GitFunction ' & $module Expand-GitProxyFunction 'igf ' | Should -Be 'igf ' } It 'Does not expand function if $args is not attached to the git function' { function global:Invoke-GitFunction { $a = 5 git checkout Write-Host $args } & $module Expand-GitProxyFunction 'Invoke-GitFunction ' | Should -Be 'Invoke-GitFunction ' & $module Expand-GitProxyFunction 'igf ' | Should -Be 'igf ' } It 'Does not expand multiline function if $args is not attached to the git function' { function global:Invoke-GitFunction { $a = 5 git ` checkout Write-Host $args } & $module Expand-GitProxyFunction 'Invoke-GitFunction ' | Should -Be 'Invoke-GitFunction ' & $module Expand-GitProxyFunction 'igf ' | Should -Be 'igf ' } It 'Does not expand multiline function backtick newlines are not preceded with whitespace' { function global:Invoke-GitFunction { $a = 5 git` checkout` $args Write-Host $null } & $module Expand-GitProxyFunction 'Invoke-GitFunction ' | Should -Be 'Invoke-GitFunction ' & $module Expand-GitProxyFunction 'igf ' | Should -Be 'igf ' } } Context 'Proxy Function Parameter Replacement Tests' { BeforeEach { if (Test-Path -Path Function:\Invoke-GitFunction) { Rename-Item -Path Function:\Invoke-GitFunction -NewName Invoke-GitFunctionBackup } function global:Invoke-GitFunction { git checkout $args } } AfterEach { if (Test-Path -Path Function:\Invoke-GitFunction) { Remove-Item -Path Function:\Invoke-GitFunction } if (Test-Path -Path Function:\Invoke-GitFunctionBackup) { Rename-Item Function:\Invoke-GitFunctionBackup Invoke-GitFunction } } It 'Replaces parameter in $args' { $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction master' $result | Should -Be 'git checkout master' } It 'Replaces short parameter in $args' { $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction -b master' $result | Should -Be 'git checkout -b master' } It 'Replaces long parameter in $args' { $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction --detach master' $result | Should -Be 'git checkout --detach master' } It 'Replaces mixed parameters in $args' { $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction -q -f -m --detach master' $result | Should -Be 'git checkout -q -f -m --detach master' } } Context 'Proxy Subcommand TabExpansion Tests' { BeforeEach { if (Test-Path -Path Function:\Invoke-GitFunction) { Rename-Item -Path Function:\Invoke-GitFunction -NewName Invoke-GitFunctionBackup } } AfterEach { if (Test-Path -Path Function:\Invoke-GitFunction) { Remove-Item -Path Function:\Invoke-GitFunction } if (Test-Path -Path Function:\Invoke-GitFunctionBackup) { Rename-Item -Path Function:\Invoke-GitFunctionBackup -NewName Invoke-GitFunction } } It 'Tab completes without subcommands' { function global:Invoke-GitFunction { git whatever $args } $functionText = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result = & $module GitTabExpansionInternal $functionText $result | Should -Be @() } It 'Tab completes bisect subcommands' { function global:Invoke-GitFunction { git bisect $args } $functionText = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result = & $module GitTabExpansionInternal $functionText $result -contains '' | Should -Be $false $result -contains 'start' | Should -Be $true $result -contains 'run' | Should -Be $true $functionText = & $module Expand-GitProxyFunction 'Invoke-GitFunction s' $result2 = & $module GitTabExpansionInternal $functionText $result2 -contains 'start' | Should -Be $true $result2 -contains 'skip' | Should -Be $true } It 'Tab completes remote subcommands' { function global:Invoke-GitFunction { git remote $args } $functionText = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result = & $module GitTabExpansionInternal $functionText $result -contains '' | Should -Be $false $result -contains 'add' | Should -Be $true $result -contains 'set-branches' | Should -Be $true $result -contains 'get-url' | Should -Be $true $result -contains 'update' | Should -Be $true $functionText = & $module Expand-GitProxyFunction 'Invoke-GitFunction s' $result2 = & $module GitTabExpansionInternal $functionText $result2 -contains 'set-branches' | Should -Be $true $result2 -contains 'set-head' | Should -Be $true $result2 -contains 'set-url' | Should -Be $true } } } ================================================ FILE: test/ModuleManifest.Tests.ps1 ================================================ BeforeAll { . $PSScriptRoot\Shared.ps1 } Describe 'Module Manifest Tests' { It 'Passes Test-ModuleManifest' { Test-ModuleManifest -Path $moduleManifestPath | Should -Not -BeNullOrEmpty $? | Should -Be $true } } ================================================ FILE: test/Shared.ps1 ================================================ # Define these variables since they are not defined in WinPS 5.x if ($PSVersionTable.PSVersion.Major -lt 6) { [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '')] $IsWindows = $true [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '')] $IsLinux = $false [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '')] $IsMacOS = $false } $modulePath = Convert-Path $PSScriptRoot\..\src $moduleManifestPath = "$modulePath\posh-git.psd1" [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '')] $csi = [char]0x1b + "[" [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '')] $expectedEncoding = if ($PSVersionTable.PSVersion.Major -le 5) { "utf8" } else { "ascii" } if (!(Get-Variable -Name gitbin -Scope global -ErrorAction SilentlyContinue)) { if (($PSVersionTable.PSVersion.Major -le 5) -or $IsWindows) { # On Windows, we can access the git binary via git.exe $global:gitbin = Get-Command -Name git -CommandType Application -TotalCount 1 } else { # On Linux/macOS, we can access the git binary via its path /usr/bin/git $global:gitbin = (Get-Command -Name git -CommandType Application -TotalCount 1).Path } } # We need this or the Git mocks don't work # This must global in order to be accessible in posh-git module scope function global:git { $OFS = ' ' $cmdline = "$args" # Write-Warning "in global git func with: $cmdline" switch ($cmdline) { '--version' { 'git version 2.16.2.windows.1' } 'help' { Get-Content $PSScriptRoot\git-help.txt } default { $res = Invoke-Expression "&$gitbin $cmdline" $res } } } # This must global in order to be accessible in posh-git module scope function global:Convert-NativeLineEnding([string]$content, [switch]$SplitLines) { $tmp = $content -split "`n" | ForEach-Object { $_.TrimEnd("`r") } if ($SplitLines) { $tmp } else { $content = $tmp -join [System.Environment]::NewLine $content } } function GetHomePath() { if ($GitPromptSettings.DefaultPromptAbbreviateHomeDirectory) { "~" } else { $Home } } function GetHomeRelPath([string]$Path) { $separator = [System.IO.Path]::DirectorySeparatorChar if (!("$Path$separator".StartsWith("$Home$separator"))) { # Path not under $Home return $Path } if ($GitPromptSettings.DefaultPromptAbbreviateHomeDirectory) { "~$($Path.Substring($Home.Length))" } else { $Path } } function GetGitRelPath([string]$Path) { $gitPath = Get-GitDirectory if (!$gitPath) { throw "GetGitRelPath Should -be called inside a git repository" } # Up one level from `.git` $gitPath = Split-Path $gitPath -Parent $separator = [System.IO.Path]::DirectorySeparatorChar if (!"$Path$separator".StartsWith("$gitPath$separator")) { # Path not under $gitPath return $Path } if ($GitPromptSettings.DefaultPromptAbbreviateGitDirectory) { $gitName = Split-Path $gitPath -Leaf $relPath = if ($Path -eq $gitPath) { "" } else { $Path.Substring($gitPath.Length + 1) } "$gitName`:$relPath" } else { # Otherwise, honor Home path abbreviation GetHomeRelPath $Path } } function GetMacOSAdjustedTempPath($Path) { if (($PSVersionTable.PSVersion.Major -ge 6) -and $IsMacOS) { # Mac OS's temp folder has a symlink in its path - /var is linked to /private/var return "/private${Path}" } $Path } function MakeNativePath([string]$Path) { $Path -replace '\\|/', [System.IO.Path]::DirectorySeparatorChar } function MakeGitPath([string]$Path) { $Path -replace '\\', '/' } function NewGitTempRepo([switch]$MakeInitialCommit) { Push-Location $temp = [System.IO.Path]::GetTempPath() $repoPath = Join-Path $temp ([IO.Path]::GetRandomFileName()) $initArgs = @() if (&$gitbin config init.defaultBranch) { $initArgs += '--initial-branch', 'master' } &$gitbin init $initArgs $repoPath *>$null Set-Location $repoPath if ($MakeInitialCommit) { &$gitbin config user.email "spaceman.spiff@appveyor.com" &$gitbin config user.name "Spaceman Spiff" 'readme' | Out-File ./README.md -Encoding ascii &$gitbin add ./README.md *>$null &$gitbin commit -m "initial commit." *>$null } $repoPath } function RemoveGitTempRepo($RepoPath) { Pop-Location if ($repoPath -and (Test-Path $repoPath)) { Remove-Item $repoPath -Recurse -Force } } function ResetGitTempRepoWorkingDir($RepoPath, $Branch = 'master') { Set-Location $repoPath &$gitbin checkout -fq $Branch *>$null &$gitbin clean -xdfq *>$null } Remove-Item Function:\prompt Remove-Module posh-git -Force *>$null # For Pester testing, enable strict mode inside the posh-git module $env:POSHGIT_ENABLE_STRICTMODE = 1 # Force the posh-git prompt to be installed. Could be runnng on dev system where user has customized the prompt. [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $module = Import-Module $moduleManifestPath -ArgumentList $true, $false -Force -PassThru ================================================ FILE: test/TabExpansion.Tests.ps1 ================================================ BeforeAll { . $PSScriptRoot\Shared.ps1 } Describe 'TabExpansion function test' -Skip:($PSVersionTable.PSVersion.Major -gt 5) { It 'Windows PowerShell v5 exports a TabExpansion function' { $module.ExportedFunctions.Keys -contains 'TabExpansion' | Should -Be $true } } Describe 'TabExpansion Tests' { Context 'Subcommand TabExpansion Tests' { It 'Tab completes without subcommands' { $result = & $module GitTabExpansionInternal 'git whatever ' $result | Should -Be @() } It 'Tab completes bisect subcommands' { $result = & $module GitTabExpansionInternal 'git bisect ' $result -contains '' | Should -Be $false $result -contains 'start' | Should -Be $true $result -contains 'run' | Should -Be $true $result2 = & $module GitTabExpansionInternal 'git bisect s' $result2 -contains 'start' | Should -Be $true $result2 -contains 'skip' | Should -Be $true } It 'Tab completes remote subcommands' { $result = & $module GitTabExpansionInternal 'git remote ' $result -contains '' | Should -Be $false $result -contains 'add' | Should -Be $true $result -contains 'set-branches' | Should -Be $true $result -contains 'get-url' | Should -Be $true $result -contains 'update' | Should -Be $true $result2 = & $module GitTabExpansionInternal 'git remote s' $result2 -contains 'set-branches' | Should -Be $true $result2 -contains 'set-head' | Should -Be $true $result2 -contains 'set-url' | Should -Be $true } It 'Tab completes update-git-for-windows only on Windows' { $result = & $module GitTabExpansionInternal 'git update-' if ((($PSVersionTable.PSVersion.Major -eq 5) -or $IsWindows)) { $result -contains '' | Should -Be $false $result -contains 'update-git-for-windows' | Should -Be $true } else { $result | Should -BeNullOrEmpty } } } Context 'Fetch/Push/Pull TabExpansion Tests' { BeforeEach { # Ensure master branch exists &$gitbin branch -q master 2>$null # Ensure an origin remote exists &$gitbin remote add origin . 2>$null # Ensure origin/master exists &$gitbin update-ref refs/remotes/origin/master $(git rev-parse master) 2>$null # Ensure origin/HEAD exists &$gitbin symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/master 2>$null } It 'Tab completes all remotes' { (&$gitbin remote) -contains 'origin' | Should -Be $true $result = & $module GitTabExpansionInternal 'git push ' $result -contains 'origin' | Should -Be $true } It 'Tab completes all branches' { $result = & $module GitTabExpansionInternal 'git push origin ' $result -contains 'master' | Should -Be $true $result -contains 'origin/master' | Should -Be $true $result -contains 'origin/HEAD' | Should -Be $true } It 'Tab completes all :branches' { $result = & $module GitTabExpansionInternal 'git push origin :' $result -contains ':master' | Should -Be $true } It 'Tab completes matching remotes' { $result = & $module GitTabExpansionInternal 'git push or' $result | Should -BeExactly 'origin' } It 'Tab completes matching branches' { $result = & $module GitTabExpansionInternal 'git push origin ma' $result | Should -BeExactly 'master' } It 'Tab completes matching remote/branches' { $result = & $module GitTabExpansionInternal 'git push origin origin/ma' $result | Should -BeExactly 'origin/master' } It 'Tab completes matching :branches' { $result = & $module GitTabExpansionInternal 'git push origin :ma' $result | Should -BeExactly ':master' } It 'Tab completes matching ref:branches' { $result = & $module GitTabExpansionInternal 'git push origin HEAD:ma' $result | Should -BeExactly 'HEAD:master' } It 'Tab completes matching +ref:branches' { $result = & $module GitTabExpansionInternal 'git push origin +HEAD:ma' $result | Should -BeExactly '+HEAD:master' } It 'Tab completes matching remote with preceding parameters' { $result = & $module GitTabExpansionInternal 'git push --follow-tags -u or' $result | Should -BeExactly 'origin' } It 'Tab completes all branches with preceding parameters' { $result = & $module GitTabExpansionInternal 'git push --follow-tags -u origin ' $result -contains 'master' | Should -Be $true $result -contains 'origin/master' | Should -Be $true $result -contains 'origin/HEAD' | Should -Be $true } It 'Tab completes matching branch with preceding parameters' { $result = & $module GitTabExpansionInternal 'git push --follow-tags -u origin ma' $result | Should -BeExactly 'master' } It 'Tab completes matching branch with intermixed parameters' { $result = & $module GitTabExpansionInternal 'git push -u origin --follow-tags ma' $result | Should -BeExactly 'master' $result = & $module GitTabExpansionInternal 'git push -u origin --follow-tags ma' $result | Should -BeExactly 'master' } It 'Tab completes matching ref:branch with intermixed parameters' { $result = & $module GitTabExpansionInternal 'git push -u origin --follow-tags HEAD:ma' $result | Should -BeExactly 'HEAD:master' $result = & $module GitTabExpansionInternal 'git push -u origin --follow-tags +HEAD:ma' $result | Should -BeExactly '+HEAD:master' } It 'Tab completes matching multiple push ref specs with intermixed parameters' { $result = & $module GitTabExpansionInternal 'git push -u origin --follow-tags one :two three:four ma' $result | Should -BeExactly 'master' $result = & $module GitTabExpansionInternal 'git push -u origin --follow-tags one :two three:four --crazy-param ma' $result | Should -BeExactly 'master' $result = & $module GitTabExpansionInternal 'git push -u origin --follow-tags one :two three:four HEAD:ma' $result | Should -BeExactly 'HEAD:master' $result = & $module GitTabExpansionInternal 'git push -u origin --follow-tags one :two three:four --crazy-param HEAD:ma' $result | Should -BeExactly 'HEAD:master' $result = & $module GitTabExpansionInternal 'git push -u origin --follow-tags one :two three:four +ma' $result | Should -BeExactly '+master' $result = & $module GitTabExpansionInternal 'git push -u origin --follow-tags one :two three:four --crazy-param +ma' $result | Should -BeExactly '+master' $result = & $module GitTabExpansionInternal 'git push -u origin --follow-tags one :two three:four +HEAD:ma' $result | Should -BeExactly '+HEAD:master' $result = & $module GitTabExpansionInternal 'git push -u origin --follow-tags one :two three:four --crazy-param +HEAD:ma' $result | Should -BeExactly '+HEAD:master' } It 'Tab complete returns empty result for missing remote' { $result = & $module GitTabExpansionInternal 'git push zy' $result | Should -BeNullOrEmpty } It 'Tab complete returns empty result for missing branch' { $result = & $module GitTabExpansionInternal 'git push origin zy' $result | Should -BeNullOrEmpty } It 'Tab complete returns empty result for missing remotebranch' { $result = & $module GitTabExpansionInternal 'git fetch origin/zy' $result | Should -BeNullOrEmpty } It 'Tab completes branch names with - and -- in them' { $branchName = 'branch--for-Pester-tests' if (&$gitbin branch --list -q $branchName) { &$gitbin branch -D $branchName } &$gitbin branch $branchName try { $result = & $module GitTabExpansionInternal 'git push origin branch-' $result | Should -BeExactly $branchName $result = & $module GitTabExpansionInternal 'git push --follow-tags -u origin ' $result -contains $branchName | Should -Be $true } finally { &$gitbin branch -D $branchName } } It 'Tab completes branch names that are symbolic refs' { $branchName = 'symbolic-ref--for-Pester-tests' if (&$gitbin branch --list -q $branchName) { &$gitbin branch -D $branchName } &$gitbin symbolic-ref refs/heads/$branchName refs/heads/master try { $result = & $module GitTabExpansionInternal 'git checkout symbolic-ref--for-Pester-test' $result | Should -BeExactly $branchName } finally { &$gitbin branch -D $branchName } } } Context 'Restore Source Branch TabExpansion Tests' { It 'Tab completes source branches -s' { $result = & $module GitTabExpansionInternal 'git restore -s mas' $result | Should -BeExactly 'master' } It 'Tab completes source branches --source=' { $result = & $module GitTabExpansionInternal 'git restore --source=mas' $result | Should -BeExactly '--source=master' } } Context 'Vsts' { BeforeEach { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $repoPath = NewGitTempRepo # Test with non-standard vsts pr alias name &$gitbin config alias.test-vsts-pr "!f() { exec vsts code pr \`"`$`@\`"; }; f" } AfterEach { RemoveGitTempRepo $repoPath } It 'Tab completes pr options' { $result = & $module GitTabExpansionInternal 'git test-vsts-pr ' $result -contains 'abandon' | Should -Be $true } } Context 'Git Config Alias TabExpansion Tests' { BeforeAll { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $repoPath = NewGitTempRepo -MakeInitialCommit $addedAliases = @() function Add-GlobalTestAlias($Name, $Value) { if (!(&$gitbin config --global "alias.$Name")) { &$gitbin config --global "alias.$Name" $Value $addedAliases += $Name } } } AfterAll { $addedAliases | Where-Object { $_ } | ForEach-Object { &$gitbin config --global --unset "alias.$_" 2>$null } RemoveGitTempRepo $repoPath } It 'Command completion includes unique list of aliases' { $alias = "test-$(New-Guid)" Add-GlobalTestAlias $alias config &$gitbin config alias.$alias help (&$gitbin config --get-all alias.$alias).Count | Should -Be 2 $result = @(& $module GitTabExpansionInternal "git $alias") $result.Count | Should -Be 1 $result[0] | Should -BeExactly $alias } It 'Tab completes when there is one alias of a given name' { $alias = "test-$(New-Guid)" &$gitbin config alias.$alias checkout @(&$gitbin config --get-all alias.$alias).Length | Should -Be 1 $result = & $module GitTabExpansionInternal "git $alias ma" $result | Should -BeExactly 'master' } It 'Tab completes when there are multiple aliases of the same name' { Add-GlobalTestAlias co checkout &$gitbin config alias.co checkout (&$gitbin config --get-all alias.co).Count | Should -BeGreaterThan 1 $result = & $module GitTabExpansionInternal 'git co ma' $result | Should -BeExactly 'master' } } Context 'PowerShell Alias TabExpansion Tests' { BeforeAll { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $repoPath = NewGitTempRepo -MakeInitialCommit New-Alias g git -Scope Global New-Alias ge git.exe -Scope Global } AfterAll { Remove-Item Alias:/g Remove-Item Alias:/ge RemoveGitTempRepo $repoPath } It 'Tab completes PowerShell alias specifying git (with no extension)' { $result = & $module GitTabExpansionInternal "g check" $result | Should -BeExactly 'checkout' $result = & $module GitTabExpansionInternal "g checkout ma" $result | Should -BeExactly 'master' } It 'Tab completes PowerShell alias specifying git.exe' { $result = & $module GitTabExpansionInternal "ge check" $result | Should -BeExactly 'checkout' $result = & $module GitTabExpansionInternal "ge checkout ma" $result | Should -BeExactly 'master' } It 'Get-AliasPattern finds the aliases for the given command' { $result = & $module Get-AliasPattern git $result | Should -BeExactly '(git|g|ge)' } } Context 'PowerShell Special Chars Tests' { BeforeAll { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $repoPath = NewGitTempRepo -MakeInitialCommit } AfterAll { RemoveGitTempRepo $repoPath } AfterEach { ResetGitTempRepoWorkingDir $repoPath } It 'Tab completes remote name with special char as quoted' { &$gitbin remote add '#test' https://github.com/dahlbyk/posh-git.git 2> $null $result = & $module GitTabExpansionInternal 'git push #' $result | Should -BeExactly "'#test'" } It 'Tab completes branch name with special char as quoted' { &$gitbin branch '#develop' 2>$null $result = & $module GitTabExpansionInternal 'git checkout #' $result | Should -BeExactly "'#develop'" } It 'Tab completes git feature branch name with special char as quoted' { &$gitbin branch '#develop' 2>$null $result = & $module GitTabExpansionInternal 'git flow feature list #' $result | Should -BeExactly "'#develop'" } It 'Tab completes a tag name with special char as quoted' { $tag = "v1.0.0;abcdef" &$gitbin tag $tag $result = & $module GitTabExpansionInternal 'git show v1' $result | Should -BeExactly "'$tag'" } It 'Tab completes a tag name with single quote correctly' { &$gitbin tag "v2.0.0'" $result = & $module GitTabExpansionInternal 'git show v2' $result | Should -BeExactly "'v2.0.0'''" } It 'Tab completes add file in working dir with special char as quoted' { $filename = 'foo{bar} (x86).txt'; New-Item $filename -ItemType File $gitStatus = & $module Get-GitStatus $result = & $module GitTabExpansionInternal 'git add ' $gitStatus $result | Should -BeExactly "'$filename'" } It 'Tab completes add file with non-ASCII file name' { &$gitbin config core.quotepath true # Problematic (default) config $fileName = "posh$([char]8226)git.txt" New-Item $fileName -ItemType File $gitStatus = & $module Get-GitStatus $result = & $module GitTabExpansionInternal 'git add ' $gitStatus $result | Should -BeExactly $fileName } } } ================================================ FILE: test/Utils.Tests.ps1 ================================================ BeforeAll { . $PSScriptRoot\Shared.ps1 . $modulePath\Utils.ps1 } Describe 'Utils Function Tests' { Context 'Add-PoshGitToProfile Tests' { BeforeAll { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $newLine = [System.Environment]::NewLine } BeforeEach { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $profilePath = [System.IO.Path]::GetTempFileName() } AfterEach { Remove-Item $profilePath -Recurse -ErrorAction SilentlyContinue } It 'Creates profile file if it does not exist that imports absolute path' { Mock Get-PSModulePath { return @() } Remove-Item -LiteralPath $profilePath Test-Path -LiteralPath $profilePath | Should -Be $false Add-PoshGitToProfile $profilePath Test-Path -LiteralPath $profilePath | Should -Be $true Get-FileEncoding $profilePath | Should -Be $expectedEncoding $content = Get-Content $profilePath $content.Count | Should -Be 2 $nativePath = MakeNativePath $modulePath\posh-git.psd1 @($content)[1] | Should -BeExactly "Import-Module '$nativePath'" } It 'Creates profile file if it does not exist that imports from module path' { $parentDir = Split-Path $profilePath -Parent Mock Get-PSModulePath { return @( 'C:\Users\Keith\Documents\WindowsPowerShell\Modules', 'C:\Program Files\WindowsPowerShell\Modules', 'C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\', "$parentDir") } Remove-Item -LiteralPath $profilePath Test-Path -LiteralPath $profilePath | Should -Be $false Add-PoshGitToProfile $profilePath $parentDir Test-Path -LiteralPath $profilePath | Should -Be $true Get-FileEncoding $profilePath | Should -Be $expectedEncoding $content = Get-Content $profilePath $content.Count | Should -Be 2 @($content)[1] | Should -BeExactly "Import-Module posh-git" } It 'Creates profile file if the profile dir does not exist' { # Use $profilePath as missing parent directory (auto-cleanup) Remove-Item -LiteralPath $profilePath Test-Path -LiteralPath $profilePath | Should -Be $false $childProfilePath = Join-Path $profilePath profile.ps1 Add-PoshGitToProfile $childProfilePath Test-Path -LiteralPath $childProfilePath | Should -Be $true $childProfilePath | Should -FileContentMatch "^Import-Module .*posh-git" } It 'Does not modify profile that already refers to posh-git' { $profileContent = @' Import-Module PSCX Import-Module posh-git '@ Set-Content $profilePath -Value $profileContent -Encoding Ascii $output = Add-PoshGitToProfile $profilePath 3>&1 $output[1] | Should -Match 'posh-git appears' Get-FileEncoding $profilePath | Should -Be 'ascii' $content = Get-Content $profilePath $content.Count | Should -Be 2 $expectedContent = Convert-NativeLineEnding $profileContent $content -join $newline | Should -BeExactly $expectedContent } It 'Adds import from PSModulePath on existing (Unicode) profile file correctly' { $profileContent = @' Import-Module PSCX New-Alias pscore C:\Users\Keith\GitHub\rkeithhill\PowerShell\src\powershell-win-core\bin\Debug\netcoreapp1.1\win10-x64\powershell.exe '@ Set-Content $profilePath -Value $profileContent -Encoding Unicode $moduleBasePath = Split-Path $profilePath -Parent Add-PoshGitToProfile $profilePath $moduleBasePath Test-Path -LiteralPath $profilePath | Should -Be $true Get-FileEncoding $profilePath | Should -Be 'unicode' $content = Get-Content $profilePath $content.Count | Should -Be 5 $expectedContent = Convert-NativeLineEnding $profileContent $expectedContent += "${newLine}${newLine}Import-Module '$(Join-Path $moduleBasePath posh-git).psd1'" $content -join $newLine | Should -BeExactly $expectedContent } } Context 'Remove-PoshGitFromProfile Tests' { BeforeAll { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $newLine = [System.Environment]::NewLine } BeforeEach { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $profilePath = [System.IO.Path]::GetTempFileName() } AfterEach { Remove-Item $profilePath -Recurse -ErrorAction SilentlyContinue } It 'Removes import from the profile correctly' { $profileContent = @' Import-Module PSCX # import posh-git here: '@ Set-Content $profilePath -Value $profileContent -Encoding Ascii $moduleBasePath = Split-Path $profilePath -Parent Add-PoshGitToProfile $profilePath $moduleBasePath $output = Remove-PoshGitFromProfile $profilePath 3>&1 Write-Host "output: $output" $output.Length | Should -Be 0 Get-FileEncoding $profilePath | Should -Be 'ascii' $content = Get-Content $profilePath -Raw $content | Should -Be "$profileContent${newline}" } } Context 'Get-PromptConnectionInfo' { BeforeEach { if (Test-Path Env:SSH_CONNECTION) { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $ssh_connection = $Env:SSH_CONNECTION Remove-Item Env:SSH_CONNECTION } } AfterEach { if ($ssh_connection) { Set-Item Env:SSH_CONNECTION $ssh_connection } elseif (Test-Path Env:SSH_CONNECTION) { Remove-Item Env:SSH_CONNECTION } } It 'Returns null if Env:SSH_CONNECTION is not set' { Get-PromptConnectionInfo | Should -BeExactly $null } It 'Returns null if Env:SSH_CONNECTION is empty' { Set-Item Env:SSH_CONNECTION '' Get-PromptConnectionInfo | Should -BeExactly $null } It 'Returns "[username@hostname]: " if Env:SSH_CONNECTION is set' { Set-Item Env:SSH_CONNECTION 'test' Get-PromptConnectionInfo | Should -BeExactly "[$([System.Environment]::UserName)@$([System.Environment]::MachineName)]: " } It 'Returns formatted string if Env:SSH_CONNECTION is set with -Format' { Set-Item Env:SSH_CONNECTION 'test' Get-PromptConnectionInfo -Format "[{0}]({1}) " | Should -BeExactly "[$([System.Environment]::MachineName)]($([System.Environment]::UserName)) " } } Context 'Test-PoshGitImportedInScript Tests' { BeforeEach { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $profilePath = [System.IO.Path]::GetTempFileName() } AfterEach { Remove-Item $profilePath -ErrorAction SilentlyContinue } It 'Detects Import-Module posh-git in profile script' { $profileContent = "Import-Module posh-git" Set-Content $profilePath -Value $profileContent -Encoding Unicode Test-PoshGitImportedInScript $profilePath | Should -Be $true } It 'Detects chocolatey installed line in profile script' { $profileContent = ". 'C:\tools\poshgit\dahlbyk-posh-git-18d600a\profile.example.ps1" Set-Content $profilePath -Value $profileContent -Encoding Unicode Test-PoshGitImportedInScript $profilePath | Should -Be $true } It 'Returns false when one-line profile script does not import posh-git' { $profileContent = "# Test" Set-Content $profilePath -Value $profileContent -Encoding Unicode Test-PoshGitImportedInScript $profilePath | Should -Be $false } It 'Returns false when profile script does not import posh-git' { $profileContent = "Import-Module Pscx`nImport-Module platyPS`nImport-Module Plaster" Set-Content $profilePath -Value $profileContent -Encoding Unicode Test-PoshGitImportedInScript $profilePath | Should -Be $false } } Context 'Test-InPSModulePath Tests' { It 'Returns false for install not under any PSModulePaths' { Mock Get-PSModulePath { } $path = "C:\Users\Keith\Documents\WindowsPowerShell\Modules\posh-git\0.7.0\" Test-InPSModulePath $path | Should -Be $false Assert-MockCalled Get-PSModulePath } It 'Returns true for install under single PSModulePath' { Mock Get-PSModulePath { return MakeNativePath "$HOME\Documents\WindowsPowerShell\Modules\posh-git\" } $path = MakeNativePath "$HOME\Documents\WindowsPowerShell\Modules\posh-git\0.7.0" Test-InPSModulePath $path | Should -Be $true Assert-MockCalled Get-PSModulePath } It 'Returns true for install under multiple PSModulePaths' { Mock Get-PSModulePath { return (MakeNativePath "$HOME\Documents\WindowsPowerShell\Modules\posh-git\"), (MakeNativePath "$HOME\GitHub\dahlbyk\posh-git\0.6.1.20160330\") } $path = MakeNativePath "$HOME\Documents\WindowsPowerShell\Modules\posh-git\0.7.0" Test-InPSModulePath $path | Should -Be $true Assert-MockCalled Get-PSModulePath } It 'Returns false when current posh-git module location is not under PSModulePaths' { Mock Get-PSModulePath { return (MakeNativePath "$HOME\Documents\WindowsPowerShell\Modules\posh-git\"), (MakeNativePath "$HOME\GitHub\dahlbyk\posh-git\0.6.1.20160330\") } $path = MakeNativePath "\tools\posh-git\dahlbyk-posh-git-18d600a" Test-InPSModulePath $path | Should -Be $false Assert-MockCalled Get-PSModulePath } It 'Returns false when current posh-git module location is under PSModulePath, but in a src directory' { Mock Get-PSModulePath { return MakeNativePath '\GitHub' } $path = MakeNativePath "\GitHub\posh-git\src" Test-InPSModulePath $path | Should -Be $false Assert-MockCalled Get-PSModulePath } } } ================================================ FILE: test/git-help.txt ================================================ usage: git [--version] [--help] [-C ] [-c name=value] [--exec-path[=]] [--html-path] [--man-path] [--info-path] [-p | --paginate | --no-pager] [--no-replace-objects] [--bare] [--git-dir=] [--work-tree=] [--namespace=] [] These are common Git commands used in various situations: start a working area (see also: git help tutorial) clone Clone a repository into a new directory init Create an empty Git repository or reinitialize an existing one work on the current change (see also: git help everyday) add Add file contents to the index mv Move or rename a file, a directory, or a symlink reset Reset current HEAD to the specified state rm Remove files from the working tree and from the index examine the history and state (see also: git help revisions) bisect Use binary search to find the commit that introduced a bug grep Print lines matching a pattern log Show commit logs show Show various types of objects status Show the working tree status grow, mark and tweak your common history branch List, create, or delete branches checkout Switch branches or restore working tree files commit Record changes to the repository diff Show changes between commits, commit and working tree, etc merge Join two or more development histories together rebase Reapply commits on top of another base tip tag Create, list, delete or verify a tag object signed with GPG collaborate (see also: git help workflows) fetch Download objects and refs from another repository pull Fetch from and integrate with another repository or a local branch push Update remote refs along with associated objects 'git help -a' and 'git help -g' list available subcommands and some concept guides. See 'git help ' or 'git help ' to read about a specific subcommand or concept.