function Get-SemverHistory { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string]$Path = (Get-Location).Path ) if ((Split-Path -Path $Path -Leaf) -eq 'sem.ver') { # We have a semver file already } else { if (-not (Get-Item -Path $Path).PSIsContainer) { throw "The Path argument should be a folder or a sem.ver specific file" } $Path = Join-Path -Path $Path -ChildPath "sem.ver" $Path = (Get-ChildItem -Path $Path -Recurse | Select-Object -First 1).FullName } if (-not (Test-Path -Path $Path)) { throw "Could not find the requested path [$Path]" } $semverLog = git log -c -- $Path $commits = @() $commit = $null $inSemverLines = $false foreach ($line in $semverLog) { if ([string]::IsNullOrWhiteSpace($line)) { continue } # This breaks if the comment starts with the word 'commit' on the left margin if ($line.StartsWith("commit") -and [string]::IsNullOrWhiteSpace($Commit.SemverValue)) { if ($null -ne $commit) { $commits += $commit } $commit = @{ Hash = ($line -split 'commit ')[1].Trim() Author = "" Date = "" SemverValue = "" SemverContents = @() Comment = @() } $inSemverLines = $false } elseif ($line.StartsWith("Author:")) { $commit.Author = ($line -split 'Author:')[1].Trim() } elseif ($line.StartsWith("Date:")) { $commit.Date = ($line -split 'Date:')[1].Trim() } else { if ($line.StartsWith("diff --git")) { # We don't care about [diff --git a/sem.ver b/sem.ver] type lines continue } elseif ($line.StartsWith("index") -and ($line -split ' ').Count -eq 3) { # we don't care about [index 7c4d2b4..e1de4ac 100644] type lines continue } elseif ($line.StartsWith("---") -or $line.StartsWith("+++")) { # we don't care about the diff filenames continue } elseif ($line.StartsWith("@@") -and $line.EndsWith("@@")) { $inSemverLines = $true continue } elseif ($inSemverLines) { if ($line.Trim().StartsWith("-") -or $line.StartsWith("\")) { # ignore remove lines continue } elseif ($line.Trim().StartsWith("+")) { # skip the first two characters $line = $line.Substring(2) } $commit.SemverContents += $line } else { $commit.Comment += $line.Trim() } } } # Save the last one too! if ($null -ne $commit) { $commits += $commit } foreach ($commit in $commits) { # Yes, it's a hack. It's cheaper than retrieving the file _at_ the hash tho $jsonString = "$($commit.SemverContents -join '')".Trim() $jsonString = $jsonString.Replace(' ', '').Replace("`t", "") if ([string]::IsNullOrWhiteSpace($jsonString)) { continue } if (($jsonString.IndexOf("Version") -eq -1) -or ($jsonString.StartsWith('{"Major"'))) { $jsonString = "`"Version`": {$jsonString" } if (-not $jsonString.StartsWith("{")) { $jsonString = "{$jsonString" } if ($jsonString.StartsWith("{{")) { $jsonString = $jsonString.Substring(1) $jsonString = "{`"Version`":$jsonString" } if (-not $jsonString.EndsWith("}")) { $jsonString = "$jsonString}" } if (-not $jsonString.EndsWith("}}")) { $jsonString = "$jsonString}" } $jsonString = $jsonString.Replace(' {{', '{') $json = ConvertFrom-Json $jsonString $commit.SemverValue = "$($json.Version.Major).$($json.Version.Minor).$($json.Version.Patch)" $commit.SemverContents = $null } return $commits } function Get-GitHistory { [CmdletBinding()] [OutputType([object[]])] param ( [Parameter(Mandatory = $true, ParameterSetName = 'ByHash')] [ValidateNotNullOrEmpty()] [Alias('From')] [string]$HashStart, [Parameter(Mandatory = $true, ParameterSetName = 'ByHash')] [ValidateNotNullOrEmpty()] [Alias('To')] [string]$HashEnd, [Parameter(Mandatory = $true, ParameterSetName = 'ByObject')] [Alias('FromEntry')] [object]$StartEntry, [Parameter(Mandatory = $true, ParameterSetName = 'ByObject')] [Alias('ToEntry')] [object]$EndEntry ) $logLead = Get-LogLeadName if ($PSCmdlet.ParameterSetName -eq 'ByHash') { if ($HashStart -eq $HashEnd) { Write-Error "$logLead : You must supply two different hashes. These hashes were the same." return } $dateStart = Invoke-CallOperatorWithPathAndParameters git @('log', '--no-walk', '--format="%ai"', $HashStart) $dateEnd = Invoke-CallOperatorWithPathAndParameters git @('log', '--no-walk', '--format="%ai"', $HashEnd) # Put them in the right order if ($dateStart -lt $dateEnd) { $HashStart, $HashEnd = $HashEnd, $HashStart } } else { if ($StartEntry.Date -lt $EndEntry.Date) { $StartEntry, $EndEntry = $EndEntry, $StartEntry } $HashEnd = $EndEntry.Hash $HashStart = $StartEntry.Hash } $lines = Invoke-CallOperatorWithPathAndParameters git @('log', "$HashEnd...$HashStart", '--format="Author: %aN%nDate: %ai%n%B%n-="', '--notes') $results = @() $result = $null $skipAdd = $false foreach ($line in $lines) { if ($line.StartsWith("Author: ")) { if ($null -ne $result) { $results += $result $skipAdd = $false } $result = @{ Author = $line.Substring(8) Date = $null Comments = @() JiraTickets = @() } } elseif ($line.Trim() -eq '-=') { if ($skipAdd -eq $false) { $results += $result } $skipAdd = $false $result = $null } elseif ($line.StartsWith("Date: ")) { $result.Date = [DateTime]($line.Substring(6)) } else { $result.Comments += $line.Trim() if ($line.StartsWith('Merge')) { $skipAdd = $true } } } foreach ($result in $results) { $result.JiraTickets = (Select-String '[A-Z]+-[0-9]+' -InputObject $result.Comments -AllMatches -CaseSensitive).Matches.Value } return $results } function Get-RepoHistoryBySemver { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string]$Path = (Get-Location).Path ) $results = @() Write-Progress -Activity "Getting history for semver" -PercentComplete 1 $semverHistory = Get-SemverHistory -Path $Path for ($i = 0; $i -lt ($semverHistory.Count - 1); $i++) { $from = $semverHistory[$i] $to = $semverHistory[$i + 1] Write-Progress -Activity "Getting history by semver" -PercentComplete ((100 * $i) / ($semverHistory.Count - 1)) -Status "Checking for data between $($from.Hash) and $($to.Hash)" $commitHistory = Get-GitHistory -StartEntry $from -EndEntry $to $results += @{ Version = $from.SemverValue JiraTickets = ($commitHistory.JiraTickets | Sort-Object -Unique) CommitHistory = $commitHistory CommitCount = $commitHistory.Count SemverData = $from } } Write-Progress -Completed -Activity "Getting history by semver" return $results }