A Git-Aware PowerShell Prompt That Earns Its Keep
I spend most of my working day in terminals. On Windows, that means PowerShell Core, where the default prompt is fairly minimal: a path and a greater-than sign, with no information about which branch you’re on, whether you have uncommitted work, or whether there’s a stash you set aside a while ago and haven’t returned to.
So I put together a small replacement.
What It Does
The prompt is built from three segments, each colour-coded to deliver information at a glance:
The server tag — a grey block showing the logon server name (title-cased) followed by a 𝚸 glyph. It’s a small orientation aid when you’re hopping between machines via RDP or remote sessions. You always know where you are.
$LogonServer = ConvertTo-TitleCase -InputString $env:LOGONSERVER
Write-Host -NoNewline "${LogonServer}[`u{1D6B8}] " -BackgroundColor Gray -ForegroundColor Black
The working directory — blue background for normal sessions, dark red when running as Administrator. The colour distinction has helped me catch a few accidental elevated operations. Remove-Item -Recurse doesn’t ask for confirmation, so a permanent visual cue is useful.
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator)
$location = (Get-Location).Path
if ($isAdmin) {
Write-Host -NoNewline "$($location) " -ForegroundColor Gray -BackgroundColor DarkRed
} else {
Write-Host -NoNewline "$($location) " -BackgroundColor Blue -ForegroundColor Black
}
The git segment — this is where it gets interesting. When you’re inside a repository, a fourth block appears with:
- The branch name, prefixed with a 𝚩 glyph
- A background colour that shifts from dark green (clean) to dark yellow (dirty) — the traffic-light metaphor lets you read state at a glance without parsing text
- Individual Unicode indicators for untracked (𝚰), unstaged (𝚳), and staged (𝚱) changes
- A stash counter, when stashes exist, so they don’t get forgotten
Outside a git repository, the segment simply disappears. No noise.
The colour switching and indicator assignment look like this:
$isDirty = $hasUnstagedChanges -or $hasStagedChanges -or $hasUntrackedFiles
$bgColor = if ($isDirty) { "DarkYellow" } else { "DarkGreen" }
$stashCount = (git stash list 2>$null).Count
$stashInfo = if ($stashCount -gt 0) { " `u{1D6B8}:$stashCount " } else { "" }
$unstagedIndicator = if ($hasUnstagedChanges) { "`u{1D6B3} " } else { "" }
$stagedIndicator = if ($hasStagedChanges) { "`u{1D6B1} " } else { "" }
$untrackedIndicator = if ($hasUntrackedFiles) { "`u{1D6B0}" } else { "" }
Write-Host -NoNewline "`u{1D6A9}:" -ForegroundColor Black -BackgroundColor $bgColor
Write-Host -NoNewline "$gitBranch" -ForegroundColor Black -BackgroundColor $bgColor
# ... stash count and status indicators follow
Write-Host -NoNewline "$untrackedIndicator$unstagedIndicator$stagedIndicator" `
-ForegroundColor Black -BackgroundColor $bgColor
Why Not Oh My Posh or Starship?
Both are excellent tools, and for many setups they’re the right choice. They’re also extra dependencies — binaries to install, configure with TOML or JSON, and keep updated. For something as fundamental as my prompt, I prefer a single function in my $PROFILE that I can read end to end and modify in a few seconds. No theme files, no font requirements beyond a Unicode-capable terminal, and one less moving part to debug after an update.
The entire thing is plain PowerShell. It calls git branch --show-current and git status --porcelain directly, parses the two-character status codes, and sets colours accordingly. The --porcelain flag matters here — it gives machine-readable output that’s stable across git versions, unlike the human-readable git status which is localised and can change format.
The Parsing
The git status --porcelain format uses a two-character prefix per file:
- First character: index (staged) status
- Second character: work tree (unstaged) status
??means untracked
The code walks each line and sets three boolean flags. It doesn’t count files or build lists — it only needs to know whether each category has entries, not how many. That keeps it fast even in a repository with thousands of changed files, where you’d rather the prompt didn’t do more work than necessary.
$gitStatusOutput = git status --porcelain 2>$null
$hasUnstagedChanges = $false
$hasStagedChanges = $false
$hasUntrackedFiles = $false
if ($gitStatusOutput) {
foreach ($line in $gitStatusOutput) {
if ($line.StartsWith('??')) {
$hasUntrackedFiles = $true
continue
}
# Second character: work tree (unstaged) status
if ($line.Length -gt 1 -and $line[1] -ne ' ') {
$hasUnstagedChanges = $true
}
# First character: index (staged) status
if ($line.Length -gt 0 -and $line[0] -ne ' ' -and $line[0] -ne '?') {
$hasStagedChanges = $true
}
}
}
The ConvertTo-TitleCase Helper
A small utility at the top normalises the LOGONSERVER environment variable, which Windows stores in upper case with leading backslashes. It finds the first letter, uppercases it, and lowercases the rest — just enough to make the prompt easier to scan.
function ConvertTo-TitleCase {
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$InputString
)
if ([string]::IsNullOrEmpty($InputString)) { return $InputString }
for ($i = 0; $i -lt $InputString.Length; $i++) {
if ([char]::IsLetter($InputString[$i])) {
return $InputString.Substring(0, $i) +
$InputString[$i].ToString().ToUpper() +
$InputString.Substring($i + 1).ToLower()
}
}
return $InputString
}
Living With It
I’ve used variations of this prompt for a while, and the part I appreciate most is the colour coding. A glance at the terminal tells me:
- Am I in a git repo? (Is there a coloured git segment?)
- Is there uncommitted work? (Yellow or green?)
- Did I stage something I forgot to commit? (𝚱 visible?)
- Am I running elevated? (Red path background?)
None of that requires reading — it’s all peripheral.
If you’d like to try it, drop the functions into your PowerShell profile (code $PROFILE) and make sure your terminal supports Unicode. Windows Terminal does out of the box. The Mathematical Bold Capital glyphs (U+1D6A9 and friends) render in most modern terminals and give the prompt a distinctive look without requiring a Nerd Font.
Here’s the full source — drop it into your $PROFILE and you’re done:
function ConvertTo-TitleCase {
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$InputString
)
if ([string]::IsNullOrEmpty($InputString)) {
return $InputString
}
# Find the first letter and capitalize it
for ($i = 0; $i -lt $InputString.Length; $i++) {
if ([char]::IsLetter($InputString[$i])) {
return $InputString.Substring(0, $i) + $InputString[$i].ToString().ToUpper() + $InputString.Substring($i + 1).ToLower()
}
}
# If no letter found, return original string
return $InputString
}
# Enhanced git prompt for PowerShell Core
function prompt {
# Show logon server with a PowerShell icon and convert to title case
$LogonServer = ConvertTo-TitleCase -InputString $env:LOGONSERVER
Write-Host -NoNewline "${LogonServer}[`u{1D6B8}] " -BackgroundColor Gray -ForegroundColor Black
# Check if running as admin
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
# Start with the location part of the prompt
$location = (Get-Location).Path
if ($isAdmin) {
Write-Host -NoNewline "$($location) " -ForegroundColor Gray -BackgroundColor DarkRed
}
else {
Write-Host -NoNewline "$($location) " -BackgroundColor Blue -ForegroundColor Black
}
# Get Git branch and status if in a git repository
if (Get-Command git -ErrorAction SilentlyContinue) {
try {
$gitBranch = git branch --show-current 2>$null
if ($gitBranch) {
# Get all status output
$gitStatusOutput = git status --porcelain 2>$null
# Initialize status flags
$hasUnstagedChanges = $false
$hasStagedChanges = $false
$hasUntrackedFiles = $false
if ($gitStatusOutput) {
foreach ($line in $gitStatusOutput) {
# Check for untracked files (indicated by '??')
if ($line.StartsWith('??')) {
$hasUntrackedFiles = $true
continue
}
# Check if the second character is not a space (indicating unstaged change)
if ($line.Length -gt 1 -and $line[1] -ne ' ') {
$hasUnstagedChanges = $true
}
# Check if the first character is not a space (indicating staged change)
if ($line.Length -gt 0 -and $line[0] -ne ' ' -and $line[0] -ne '?') {
$hasStagedChanges = $true
}
}
}
# Set background color based on whether there are any changes
$isDirty = $hasUnstagedChanges -or $hasStagedChanges -or $hasUntrackedFiles
$bgColor = if ($isDirty) {
"DarkYellow"
}
else {
"DarkGreen"
}
# Check for stashes
$stashCount = (git stash list 2>$null).Count
$stashInfo = if ($stashCount -gt 0) {
" `u{1D6B8}:$stashCount "
}
else {
""
}
$unstagedIndicator = if ($hasUnstagedChanges) {
"`u{1D6B3} "
}
else {
""
}
$stagedIndicator = if ($hasStagedChanges) {
"`u{1D6B1} "
}
else {
""
}
$untrackedIndicator = if ($hasUntrackedFiles) {
"`u{1D6B0}"
}
else {
""
}
# Write opening angle bracket with background color
Write-Host -NoNewline "`u{1D6A9}:" -ForegroundColor Black -BackgroundColor $bgColor
# Write branch name
Write-Host -NoNewline "$gitBranch" -ForegroundColor Black -BackgroundColor $bgColor
# Add stash count if there are stashes
if ($stashCount -gt 0) {
Write-Host -NoNewline $stashInfo -ForegroundColor Black -BackgroundColor $bgColor
}
elseif ($isDirty) {
# Write a space if there are no stashes
Write-Host -NoNewline " " -ForegroundColor Black -BackgroundColor $bgColor
}
# Add status indicators
Write-Host -NoNewline "$untrackedIndicator$unstagedIndicator$stagedIndicator" -ForegroundColor Black -BackgroundColor $bgColor
# Determine if we need a separator
$hasAnyIndicators = $stashCount -gt 0 -or $hasUnstagedChanges -or $hasStagedChanges -or $hasUntrackedFiles
if ($hasAnyIndicators -eq $false) {
Write-Host -NoNewline " " -ForegroundColor Black -BackgroundColor $bgColor
}
}
}
catch {
# Not in a git repository or git command failed
}
}
# Close the bracket
if ($isAdmin) {
Write-Host -NoNewline ">"
}
else {
Write-Host -NoNewline ">"
}
# Return a space for the actual prompt
return " "
}