lib/Parse-GitStatus.ps1
############################################################################## #.SYNOPSIS # Invokes `git status -s` and parses the result into object/array of objects # Called by Git-NumberedStatus # #.DESCRIPTION # Output looks like: # # @{ # state=M(odified), A(dded), R(enamed) # file=The filename as ouputted by git status # staged=$true for staging area, $false for working directory change # fullPath=The full path # relativePath=Relative path to current pwd # oldFile=The original filename in case of a staged rename # } ############################################################################## function Parse-GitStatus($includeNumstat = $false, $extraArgs) { $hasStaged = $false $hasWorkingDir = $false $workingDir = Get-Location $gitRootdir = Get-GitRootLocation # write-host "workingDir=$workingDir" # write-host "gitRootdir=$gitRootdir" # ATTN: git status --porcelain returns paths relative from the repository root folder # git status -s *could* change in the future but returns paths relative to pwd. $allFiles = Invoke-Git status -s $extraArgs | % { $relativePath = $_.Substring(3).Replace("`"", "") if ($relativePath -match " -> ") { $oldFilename = $relativePath.Substring(0, $relativePath.IndexOf(" -> ")) $relativePath = $relativePath.Substring($relativePath.IndexOf(" -> ") + 4) } # write-host "relativePath: $relativePath" $fullPath = Join-Path $workingDir $relativePath # write-host "fullPath: $fullPath" $fullPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($fullPath) # write-host "fullPath2: $fullPath" $gitRootPath = $fullPath.Substring($gitRootdir.ToString().Length + 1) $relativePath = Quote-Path $relativePath $fullPath = Quote-Path $fullPath $gitRootPath = Quote-Path $gitRootPath # write-host "RELATIVE=$relativePath FULL=$fullPath GIT_ROOT=$gitRootPath" $returns = @() $staged = $_[0] -ne " " -and $_[0] -ne "?" if ($staged) { $hasStaged = $true $returns += @{state=$_[0];file=$gitRootPath;staged=$true;fullPath=$fullPath;relativePath=$relativePath;oldFile=$oldFilename} } $isWorkingDir = $_[1] -ne " " if ($isWorkingDir) { $hasWorkingDir = $true $state = If ($_[1] -eq "?") {"A"} Else {$_[1]} $returns += @{state=$state;file=$gitRootPath;staged=$false;fullPath=$fullPath;relativePath=$relativePath} } return $returns } | % {$_} # Include +/- lines for all files if ($includeNumstat) { if ($hasWorkingDir) { Add-GitNumstat $allFiles $false } if ($hasStaged) { Add-GitNumstat $allFiles $true } } return $allFiles } function Quote-Path($path) { if ($path.Contains(" ")) { return "`"$path`"" } return $path } function Add-GitNumstat($allFiles, $staged) { if ($staged) { $numstatResult = Invoke-Git diff --numstat --cached 2>&1 } else { $numstatResult = Invoke-Git diff --numstat 2>&1 } $eolWarnings = $numstatResult | ? { $_ -is [System.Management.Automation.ErrorRecord] } $eolWarnings | % { $line = $_.Exception.Message if ($line.StartsWith('The file will have its original line')) { # Ignore this one } elseif ($line.StartsWith('warning: ')) { # Add EOL notice $match = $line | Select-String -Pattern 'warning: (\w+) will be replaced by (\w+) in (.*)\.' $fromEol = $match.matches.groups[1] $toEol = $match.matches.groups[2] $fileName = $match.matches.groups[3].Value.Replace('/', '\') $matchingStatus = $allFiles | Where {$_.file -eq $fileName} if ($matchingStatus) { $matchingStatus.lineEndings = "$fromEol -> $toEol" } } } $numstatResult = $numstatResult | ? { $_ -isnot [System.Management.Automation.ErrorRecord] } $numstatResult | % { # Output format: +++ `t --- `t fileName $numstat = $_.Trim().Split("`t") $file = Quote-Path $numstat[2] $numstat = @{file=$file;added=$numstat[0];deleted=$numstat[1]} $matchingStatus = $allFiles | Where {$_.staged -eq $staged -and $numstat.file -eq $_.file} if ($matchingStatus) { $matchingStatus.added = $numstat.added $matchingStatus.deleted = $numstat.deleted } } } |