Private/Objects/Sync-NAVGitObjectFolder.ps1

function Sync-NAVGitObjectFolder {
    <#
    .SYNOPSIS
        Mirrors exported object files into the repository: each file goes to the Objects or Tests folder
        (Get-NAVGitObjectTargetFolder). New and changed files are copied byte for byte, a copy left in the other
        folder is removed (the object moved), and - with -RemoveMissing - object files that were not exported are
        deleted. -KeepMissingTests keeps those in the Tests folder (returned as Kept): when the test objects were never
        imported, their absence from the database means nothing. Only files named like object files (TAB18.TXT) are
        touched.
        Returns [pscustomobject]@{ Added; Changed; Moved; Removed; Kept; Unchanged; Tests } (lists of file names).
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string] $SourceFolder,

        [Parameter(Mandatory)]
        [string] $ObjectsPath,

        [Parameter(Mandatory)]
        [string] $TestsPath,

        [Parameter(Mandatory)]
        [hashtable] $TestObjects,

        [switch] $RemoveMissing,

        [switch] $KeepMissingTests
    )

    $result = [ordered]@{ Added = @(); Changed = @(); Moved = @(); Removed = @(); Kept = @(); Unchanged = @(); Tests = @() }
    $folders = @{ Objects = $ObjectsPath; Tests = $TestsPath }
    foreach ($folder in $folders.Values) {
        if (-not (Test-Path -LiteralPath $folder -PathType Container)) {
            New-Item -ItemType Directory -Path $folder -Force | Out-Null
        }
    }

    $exported = @{}
    foreach ($file in (Get-NAVGitObjectFiles -Folder $SourceFolder)) {
        $name = $file.Name.ToUpperInvariant()
        $exported[$name] = $true

        $header = Read-NavObjectHeader -Path $file.FullName
        $target = Get-NAVGitObjectTargetFolder -Header $header -TestObjects $TestObjects
        $other = 'Tests'
        if ($target -eq 'Tests') { $other = 'Objects'; $result.Tests += $name }

        $destination = Join-Path $folders[$target] $file.Name
        $otherCopy = Join-Path $folders[$other] $file.Name
        $movedFromOther = Test-Path -LiteralPath $otherCopy -PathType Leaf

        if (Test-Path -LiteralPath $destination -PathType Leaf) {
            if (Test-NAVGitFileEqual -Path $file.FullName -OtherPath $destination) {
                $result.Unchanged += $name
            }
            else {
                Copy-Item -LiteralPath $file.FullName -Destination $destination -Force
                $result.Changed += $name
            }
        }
        else {
            Copy-Item -LiteralPath $file.FullName -Destination $destination -Force
            if ($movedFromOther) { $result.Moved += $name } else { $result.Added += $name }
        }

        if ($movedFromOther) {
            Remove-Item -LiteralPath $otherCopy -Force
        }
    }

    if ($RemoveMissing) {
        foreach ($target in @('Objects', 'Tests')) {
            foreach ($file in (Get-NAVGitObjectFiles -Folder $folders[$target])) {
                $name = $file.Name.ToUpperInvariant()
                if ($exported.ContainsKey($name)) { continue }
                if ($target -eq 'Tests' -and $KeepMissingTests) {
                    $result.Kept += $name
                    continue
                }
                Remove-Item -LiteralPath $file.FullName -Force
                $result.Removed += $name
            }
        }
    }

    return [pscustomobject]$result
}

function Test-NAVGitFileEqual {
    param(
        [Parameter(Mandatory)] [string] $Path,
        [Parameter(Mandatory)] [string] $OtherPath
    )

    if ((Get-Item -LiteralPath $Path).Length -ne (Get-Item -LiteralPath $OtherPath).Length) {
        return $false
    }
    $a = [System.IO.File]::ReadAllBytes($Path)
    $b = [System.IO.File]::ReadAllBytes($OtherPath)
    for ($i = 0; $i -lt $a.Length; $i++) {
        if ($a[$i] -ne $b[$i]) { return $false }
    }
    return $true
}