functions/Get-GitCommitFile.ps1

function Get-GitCommitFile
{
    <#
    .SYNOPSIS
    Gets the files associated with a commit.
 
    .DESCRIPTION
    Gets the files associated with a commit.
 
    .PARAMETER Repo
    The name of a git repository, or the path or a substring of the path of a repository directory or any of its subdirectories or files.
    If the Repo parameter is omitted, the current repository will be used if currently inside a repository; otherwise, nothing is returned.
    For examples of using the Repo parameter, refer to the help text for Get-GitRepo.
 
    .PARAMETER SHA1Hash
    The SHA1 hash of (or a reference to) a commit in the current repository. If omitted, the HEAD commit is used.
 
    .EXAMPLE
    ## Call from outside a repository without parameters ##
 
    PS C:\> $Powdrgit.Path = 'C:\PowdrgitExamples\MyToolbox;C:\PowdrgitExamples\Project1' # to ensure the repository paths are defined
    PS C:\> Get-GitCommitFile
 
    # Nothing was returned because a Repo was not provided.
 
    .EXAMPLE
    ## Call from outside a repository for non-existent repository ##
 
    PS C:\> $Powdrgit.Path = 'C:\PowdrgitExamples\MyToolbox;C:\PowdrgitExamples\Project1' # to ensure the repository paths are defined
    PS C:\> $Powdrgit.ShowWarnings = $true # to ensure warnings are visible
    PS C:\> Get-GitCommitFile -Repo NonExistentRepo
    WARNING: [Get-GitCommitFile]Repository 'NonExistentRepo' not found. Check the repository directory exists and has been added to the $Powdrgit.Path module variable.
 
    .EXAMPLE
    ## Call from outside a repository with Repo parameter ##
 
    PS C:\> $Powdrgit.Path = 'C:\PowdrgitExamples\MyToolbox;C:\PowdrgitExamples\Project1' # to ensure the repository paths are defined
    PS C:\> Set-GitBranch -Repo MyToolbox -BranchName main # checkout the main branch from the current location
    PS C:\> Get-GitCommitFile -Repo MyToolbox | Format-Table -Property RepoName,SHA1Hash,Exists,FullName
 
    RepoName SHA1Hash Exists FullName
    -------- -------- ------ --------
    MyToolbox ba6dfdc703948adbd01590c965932ae8ff692aa0 False
 
    # When SHA1Hash is not specified, the HEAD commit is used.
 
    .EXAMPLE
    ## Call from inside a repository with SHA1Hash parameter ##
 
    PS C:\> $Powdrgit.Path = 'C:\PowdrgitExamples\MyToolbox;C:\PowdrgitExamples\Project1' # to ensure the repository paths are defined
    PS C:\> Set-GitBranch -Repo MyToolbox -BranchName main -SetLocation # move to the repository directory and checkout the main branch
    PS C:\PowdrgitExamples\MyToolbox> $commitHash = Get-GitLog -NoMerges | Where-Object Subject -eq 'Add feature1_File1.txt' | Select-Object -ExpandProperty SHA1Hash # pick a commit from the log
    PS C:\PowdrgitExamples\MyToolbox> Get-GitCommitFile -SHA1Hash $commitHash | Format-Table -Property RepoName,SHA1Hash,Exists,FullName
 
    RepoName SHA1Hash Exists FullName
    -------- -------- ------ --------
    MyToolbox beffe458a0460726f79316fd0dad2d2392a35b64 True C:\PowdrgitExamples\MyToolbox\feature1_File1.txt
 
    .EXAMPLE
    ## Pipe results from Get-GitLog ##
 
    PS C:\> $Powdrgit.Path = 'C:\PowdrgitExamples\MyToolbox;C:\PowdrgitExamples\Project1' # to ensure the repository paths are defined
    PS C:\> Set-GitBranch -Repo MyToolbox -BranchName main -SetLocation # move to the repository directory and checkout the main branch
    PS C:\PowdrgitExamples\MyToolbox> Get-GitLog -NoMerges | Get-GitCommitFile | Format-Table -Property RepoName,SHA1Hash,Exists,FullName
 
    RepoName SHA1Hash Exists FullName
    -------- -------- ------ --------
    MyToolbox beffe458a0460726f79316fd0dad2d2392a35b64 True C:\PowdrgitExamples\MyToolbox\feature1_File1.txt
    MyToolbox 3a987081541ca2f31f575d47287cb3fdf82a1135 False
    MyToolbox 87b1320518c17702d30e463966bc070ce6481459 False
 
    # Commits with no files associated to them are included in the results.
 
    .INPUTS
    [System.String[]]
    Accepts string objects via the SHA1Hash parameter. The output of Get-GitLog and Get-GitCommit can be piped into Get-GitCommitFile.
 
    .OUTPUTS
    [GitCommitFile]
    Returns a custom GitCommitFile object. For details use Get-Member at a command prompt e.g.:
    PS C:\PowdrgitExamples\MyToolbox> Get-GitCommitFile | Get-Member -MemberType Properties
 
    .NOTES
    Author : nmbell
 
    .LINK
    Get-GitCommit
    .LINK
    Get-GitFileHistory
    .LINK
    Get-GitLog
    .LINK
    Get-GitRepo
    .LINK
    about_powdrgit
    .LINK
    https://github.com/nmbell/powdrgit/blob/main/help/about_powdrgit.md
    #>


    # Function alias
    [Alias('ggcf')]

    # Use cmdlet binding
    [CmdletBinding(
      HelpURI = 'https://github.com/nmbell/powdrgit/blob/main/help/Get-GitCommitFile.md'
    )]

    # Declare output type
    [OutputType('GitCommitFile')]

    # Declare parameters
    Param(

        [Parameter(
          Mandatory                       = $false
        , Position                        = 0
        , ValueFromPipeline               = $false
        , ValueFromPipelineByPropertyName = $true
        )]
    # [ArgumentCompleter()]
        [Alias('RepoName','RepoPath')]
        [String[]]
        $Repo

    ,    [Parameter(
            Mandatory                       = $false
        , Position                        = 1
          , ValueFromPipeline               = $true
          , ValueFromPipelineByPropertyName = $true
          )]
        [String]
        $SHA1Hash = 'HEAD'

    )

    BEGIN
    {
        $bk = 'B'

        # Common BEGIN:
        Set-StrictMode -Version 3.0
        $thisFunctionName = $MyInvocation.MyCommand
        $start            = Get-Date
        $indent           = ($Powdrgit.DebugIndentChar[0]+' ')*($PowdrgitCallDepth++)
        $PSDefaultParameterValues += @{ '*:Verbose' = $(If ($DebugPreference -notin 'Ignore','SilentlyContinue') { $DebugPreference } Else { $VerbosePreference }) } # turn on Verbose with Debug
        $warn             = $Powdrgit.ShowWarnings -and !($PSBoundParameters.ContainsKey('WarningAction') -and $PSBoundParameters.WarningAction -eq 'Ignore') # because -WarningAction:Ignore is not implemented correctly
        Write-Debug " $(ts)$indent[$thisFunctionName][$bk]Start: $($start.ToString('yyyy-MM-dd HH:mm:ss.fff'))"

        # Function BEGIN:
        Write-Debug " $(ts)$indent[$thisFunctionName][$bk]Storing current location"
        $startLocation = $PWD.Path

        $gitCommandTemplate = "git diff-tree --root --no-commit-id --name-status -r <SHA1Hash>"

        # Lookup array: https://git-scm.com/docs/git-diff-tree#Documentation/git-diff-tree.txt---diff-filterACDMRTUXB82308203
        $statusKey = @{
            'A' = 'Add'
            'B' = 'Broken'
            'C' = 'Copy'
            'D' = 'Delete'
            'M' = 'Modify'
            'R' = 'Rename'
            'T' = 'Type'
            'U' = 'Unmerged'
            'X' = 'Unknown'
        }
    }

    PROCESS
    {
        $bk = 'P'

        # Find the repository name from current location
        If (!$PSBoundParameters.ContainsKey('Repo')) { $Repo = Get-GitRepo -Current | Select-Object -ExpandProperty RepoPath }

        # Get the repository info
        $validRepos = Get-ValidRepo -Repo $Repo

        # Get the commit files
        ForEach ($validRepo in $validRepos)
        {
            # Go to the repository and get the repository info
            Write-Debug " $(ts)$indent[$thisFunctionName][$bk]Moving to the repository directory: $($validRepo.RepoPath)"
            Set-GitRepo -Repo $validRepo.RepoPath -WarningAction Ignore

            # Validate parameters
            $gitCommandRefType = "git cat-file -t $SHA1Hash"
            $refType = Invoke-GitExpression -Command $gitCommandRefType -SuppressGitErrorStream
            If ($refType -notin 'commit')
            {
                If ($warn) { Write-Warning "[$thisFunctionName]`"$SHA1Hash`" is not a valid commit in repository '$($validRepo.RepoName)'." }
            }
            If ($SHA1Hash -eq 'HEAD')
            {
                $SHA1Hash = Get-GitCommit -Repo $Repo -SHA1Hash HEAD | Select-Object -ExpandProperty SHA1Hash
            }

            # Gather commit files list
            Write-Debug " $(ts)$indent[$thisFunctionName][$bk]Gathering commit files list"
            $gitCommand = $gitCommandTemplate.Replace('<SHA1Hash>',$SHA1Hash)
            $gitResults = Invoke-GitExpression -Command $gitCommand -SuppressGitErrorStream
            Write-Verbose "$(ts)$indent[$thisFunctionName][$bk]Found commit files: $('{0,3}' -f ($gitResults | Measure-Object).Count)"

            # Parse the list
            If ($gitResults)
            {
                $gitResults = $gitResults.Replace('/','\')
                ForEach ($line in $gitResults | Where-Object { $_.Trim() })
                {
                    $linesplit = $line -split "`t"

                    $statusScore = $linesplit[0]
                    $filePath1   = If ($linesplit.Count -gt 1) { $linesplit[1] } Else { $null }
                    $filePath2   = If ($linesplit.Count -gt 2) { $linesplit[2] } Else { $null }

                    $Matches = $null
                    $statusScore -match '(\w)(\d*)' | Out-Null
                    $status = $statusKey[$Matches[1]]
                    $score  = $Matches[2]
                    $thisGitPath = $filePath1*(![Bool]$filePath2)+$filePath2*([Bool]$filePath2)
                    $prevGitPath = $filePath2*(![Bool]$filePath2)+$filePath1*([Bool]$filePath2)
                    $prevFilePath = ($validRepo.RepoPath+'\')*([Bool]$prevGitPath)+$prevGitPath

                    # Output
                    [GitCommitFile]@{
                        'RepoName'         = $validRepo.RepoName
                        'RepoPath'         = $validRepo.RepoPath
                        'SHA1Hash'         = $SHA1Hash
                        'Action'           = $status
                        'Exists'           = Test-Path -Path ($validRepo.RepoPath+'\'+$thisGitPath)
                        'FullName'         = $validRepo.RepoPath+'\'+$thisGitPath
                        'Directory'        = $validRepo.RepoPath+'\'+[System.IO.Path]::GetDirectoryName($thisGitPath)
                        'Name'             = [System.IO.Path]::GetFileName($thisGitPath)
                        'BaseName'         = [System.IO.Path]::GetFileNameWithoutExtension($thisGitPath)
                        'Extension'        = [System.IO.Path]::GetExtension($thisGitPath)
                        'PreviousFilePath' = $prevFilePath
                        'GitPath'          = $thisGitPath
                        'GitDirectory'     = [System.IO.Path]::GetDirectoryName($thisGitPath)
                        'PreviousGitPath'  = $prevGitPath
                        'SimilarityPc'     = $score
                    }
                }
            }
            Else
            {
                # Output an 'empty' object
                [GitCommitFile]@{
                    'RepoName'         = $validRepo.RepoName
                    'RepoPath'         = $validRepo.RepoPath
                    'SHA1Hash'         = $SHA1Hash
                    'Action'           = $null
                    'Exists'           = $null
                    'FullName'         = $null
                    'Directory'        = $null
                    'Name'             = $null
                    'BaseName'         = $null
                    'Extension'        = $null
                    'PreviousFilePath' = $null
                    'GitPath'          = $null
                    'GitDirectory'     = $null
                    'PreviousGitPath'  = $null
                    'SimilarityPc'     = $null
                }
            }
        }
    }

    END
    {
        $bk = 'E'

        # Function END:
        Write-Debug " $(ts)$indent[$thisFunctionName][$bk]Setting location to original directory"
        Set-Location -Path $startLocation

        # Common END:
        $end      = Get-Date
        $duration = New-TimeSpan -Start $start -End $end
        Write-Debug " $(ts)$indent[$thisFunctionName][$bk]Finish: $($end.ToString('yyyy-MM-dd HH:mm:ss.fff')) ($($duration.ToString('d\d\ hh\:mm\:ss\.fff')))"
        $PowdrgitCallDepth--
    }
}