VeilVer.psm1

#Region './Private/Get-GitBlobTags.ps1' -1

function Get-GitBlobTags {
    [CmdletBinding()]
    param (
        # Does not need to exist anymore, but must be a valid path
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path $_ -IsValid }, ErrorMessage = 'Must be a valid path format, but does not need to exist (anymore).')]
        [string]$RelativeRootPath
    )

    # Example tag: VV/demo/docs/Contoso/Doc1.md/v1.0.0
    $TagPattern = "VV/$RelativeRootPath/v*"

    # Get tags with version data split by semicolon, sorted by version in descending order
    $Tags = Invoke-GitCommand 'tag', '--list', '--format=%(refname:short);%(contents)', '--sort=-version:refname', $TagPattern |
        Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
        ForEach-Object {
            $Tag, $Base64Data = $_ -split ';'
            
            $TagVersionString = ($Tag -split '/')[-1].TrimStart('v')
            $TagVersion = [version]$TagVersionString

            # The tag command returns an empty line as part of the tag message, so we need to filter it out
            $JsonData = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Base64Data))
            $VersionData = $JsonData | ConvertFrom-Json

            [pscustomobject]@{
                'Version' = $TagVersion
                'Data' = $VersionData
            }
        }

    if ($Tags.Count -eq 0) {
        return
    }

    Write-Output $Tags
}
#EndRegion './Private/Get-GitBlobTags.ps1' 38
#Region './Private/Get-GitCurrentCommit.ps1' -1

function Get-GitCurrentCommit {
    [CmdletBinding()]
    param ()

    Invoke-GitCommand 'rev-parse', '--verify', 'HEAD'
}
#EndRegion './Private/Get-GitCurrentCommit.ps1' 7
#Region './Private/Get-GitFileHistoryNames.ps1' -1

function Get-GitFileHistoryNames {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Path
    )

    # Get all names that the file has had by looking at commits that have changed the file
    # Automatically sorted by most recent (current) name first
    # Empty format string to only get the file names
    Invoke-GitCommand 'log', '--format=', '--name-only', '--follow', '--', $Path | Select-Object -Unique
}
#EndRegion './Private/Get-GitFileHistoryNames.ps1' 13
#Region './Private/Get-GitRepoRoot.ps1' -1

function Get-GitRepoRoot {
    [CmdletBinding()]
    param()

    Invoke-GitCommand 'rev-parse', '--show-toplevel'
}
#EndRegion './Private/Get-GitRepoRoot.ps1' 7
#Region './Private/Invoke-GitCommand.ps1' -1

function Invoke-GitCommand {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string[]]$Arguments
    )

    Test-GitInstallation -ErrorAction Stop

    Write-Verbose "Invoking 'git $Arguments'."

    & git $Arguments

    if ($LASTEXITCODE -ne 0) {
        throw "Command 'git $Arguments' failed with exit code $LASTEXITCODE."
    }
}
#EndRegion './Private/Invoke-GitCommand.ps1' 18
#Region './Private/Test-GitFileIsModified.ps1' -1

function Test-GitFileIsModified {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path $_ -PathType Leaf }, ErrorMessage = 'Path must exist and be a file.')]
        [string]$Path
    )

    try {
        $null = Invoke-GitCommand 'diff-index', 'HEAD', '--quiet', '--', $Path
    } catch {}
    
    # Returns true if the file has been modified
    $LASTEXITCODE -eq 1
}
#EndRegion './Private/Test-GitFileIsModified.ps1' 16
#Region './Private/Test-GitInstallation.ps1' -1

function Test-GitInstallation {
    [CmdletBinding()]
    param()

    if (-not (Get-Command "git" -ErrorAction SilentlyContinue)) {
        throw 'No installation of git was found, please install git to use this module.'
    }
}
#EndRegion './Private/Test-GitInstallation.ps1' 9
#Region './Public/Get-VVVersion.ps1' -1

function Get-VVVersion {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path $_ -PathType Leaf }, ErrorMessage = 'Path must exist and be a file.')]
        [string]$Path
    )

    # Get all tags based on file names
    $FileNames = Get-GitFileHistoryNames -Path $Path
    Write-Verbose @"
Found the following file path(s) of the file through the commit history:
- $($FileNames -join "`n- ")
"@

    $Tags = $FileNames | ForEach-Object {
        Get-GitBlobTags -RelativeRootPath $_
    }

    if ($Tags.Count -eq 0) {
        Write-Warning "No hidden version tags found for the file '$Path'."
        return
    }

    Write-Output $Tags
}
#EndRegion './Public/Get-VVVersion.ps1' 26
#Region './Public/Set-VVVersion.ps1' -1

function Set-VVVersion {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path $_ -PathType Leaf }, ErrorMessage = 'Path must exist and be a file.')]
        [string]$Path,

        [Parameter(Mandatory)]
        [version]$Version,

        [Parameter(Mandatory)]
        [hashtable]$Metadata
    )

    Push-Location (Get-GitRepoRoot)

    # Get the relative path of the file from the root of the repo and trim start
    $RelativePath = (Resolve-Path $Path -Relative).TrimStart('.\').TrimStart('./')
    $TagName = "VV/$RelativePath/v$Version"

    # Ensure that the file has no pending changes, since we are tagging the file content together with the commit and don't want any discrepancies
    if (Test-GitFileIsModified -Path $RelativePath) {
        Write-Warning "The file '$RelativePath' has been modified. Please commit the changes before setting the version."
        return
    }

    # Set extra metadata for the tag
    if ($Metadata.ContainsKey('Commit')) { Write-Warning "The 'Commit' key is reserved and will be overwritten." }
    $Metadata['Commit'] = Get-GitCurrentCommit
    
    # Assemble metadata, convert to JSON and then to Base64
    $JsonMetadata = $Metadata | ConvertTo-Json -Compress -Depth 20
    $Base64Metadata = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($JsonMetadata))

    # Tag the file with the version data as json in the tag message
    $BlobHash = Invoke-GitCommand 'hash-object', '-t', 'blob', $Path
    Invoke-GitCommand 'tag', '-a', $TagName, $BlobHash, '-m', $Base64Metadata -ErrorAction Stop

    Write-Verbose "Hidden tag '$TagName' has been created for '$RelativePath'."
}
#EndRegion './Public/Set-VVVersion.ps1' 41