scripts/win/git/status.ps1

<#
.SYNOPSIS
  Simple, read-only Git status for Borg.
.DESCRIPTION
  Prints a friendly snapshot of the current repository:
  - Repo root, branch, upstream, ahead/behind
  - Staged / Unstaged / Untracked groups
  - Conflict detection + guidance
  No interactivity, no actions.
#>


# helpers
function W-Info($m){ Write-Host "🧭 $m" }
function W-Ok($m){ Write-Host "✅ $m" }
function W-Warn($m){ Write-Host "⚠️ $m" }
function W-Err($m){ Write-Host "❌ $m" }

# git available?
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
  W-Err "git not found in PATH."
  exit 1
}

# ensure inside a git repo
$top = git rev-parse --show-toplevel 2>$null
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($top)) {
  W-Err "Not inside a git repository."
  exit 2
}
Set-Location $top | Out-Null

# branch + upstream + ahead/behind
$branch = (git rev-parse --abbrev-ref HEAD).Trim()
$upstream = (git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>$null)
$ahead = 0; $behind = 0
if ($LASTEXITCODE -eq 0 -and $upstream) {
  $lr = (git rev-list --left-right --count "$upstream...HEAD").Trim().Split(" ")
  if ($lr.Count -eq 2) { $behind = [int]$lr[0]; $ahead = [int]$lr[1] }
}

# parse porcelain v2
$status = git status --porcelain=v2
$staged = New-Object System.Collections.Generic.List[string]
$unstaged = New-Object System.Collections.Generic.List[string]
$untracked = New-Object System.Collections.Generic.List[string]
$conflicts = New-Object System.Collections.Generic.HashSet[string]

foreach ($line in $status) {

  if ($line -match '^\?{2}\s+(.+)$') {
    [void]$untracked.Add($Matches[1])
    continue
  }

  if ($line -match '^u\s+.+\s(.+)$') {
    $p = $Matches[1]
    [void]$unstaged.Add($p)
    [void]$conflicts.Add($p)
    continue
  }

  if ($line -match '^\d+\s+([MADRCU\.])([MADRCU\.])\s+.+\s+(.+)$') {
    $x = $Matches[1]; $y = $Matches[2]; $path = $Matches[3]
    if ($x -ne '.') { [void]$staged.Add($path) }
    if ($y -ne '.') { [void]$unstaged.Add($path) }
    continue
  }
}

# de-dup + sort
$staged    = $staged    | Sort-Object -Unique
$unstaged  = $unstaged  | Sort-Object -Unique
$untracked = $untracked | Sort-Object -Unique

# header
Write-Host ""
W-Info ("Repo : {0}" -f $top)
W-Info ("Branch : {0}{1}" -f $branch, ($(if ($upstream) { " (upstream: $upstream, ↑$ahead ↓$behind)" } else { "" })))
Write-Host ""

function Show-Group([string]$title, $items, [string]$prefix, [bool]$markConflicts = $false) {
  if ($items -and $items.Count -gt 0) {
    Write-Host "• $title ($($items.Count))"
    foreach ($i in $items) {
      if ($markConflicts -and $conflicts.Contains($i)) {
        Write-Host (" {0} <<CONFLICT>> {1}" -f $prefix, $i)
      } else {
        Write-Host (" {0} {1}" -f $prefix, $i)
      }
    }
    Write-Host ""
  }
}

Show-Group "Staged changes"   $staged   "+" $false
Show-Group "Unstaged changes" $unstaged "~" $true
Show-Group "Untracked files"  $untracked "?" $false

# hints
if ($behind -gt 0) {
  W-Warn ("Branch is behind upstream by {0} commit(s). Consider: git pull --rebase" -f $behind)
}
if ($conflicts.Count -gt 0) {
  W-Warn "Unmerged changes detected. Resolve conflicts, then:"
  Write-Host " git add <files> && git rebase --continue (or) git commit"
}

if ($staged.Count -eq 0 -and $unstaged.Count -eq 0 -and $untracked.Count -eq 0) {
  W-Ok "Working tree clean."
}