Types/OpenPackage.Source/Repository.ps1

<#
.SYNOPSIS
    Gets a repository as a package
.DESCRIPTION
    Gets a repository as an open package
#>

param(
# A Repository to package.
# This can be the root of a repo or a link to a portion of the tree.
# If a portion of the tree is provided, will perform a sparse clone of the repository
[Parameter(ValueFromPipelineByPropertyName)]
[Alias('clone_url')]
[string]
$Repository,

# The github branch name.
[Parameter(ValueFromPipelineByPropertyName)]
[string]
$Branch,

# One or more optional sparse filters to a repository.
# If these are provided, only files matching these filters will be downloaded.
[Parameter(ValueFromPipelineByPropertyName)]    
[string[]]
$SparseFilter,


# A list of file wildcards to include.
[Parameter(ValueFromPipelineByPropertyName)]
[SupportsWildcards()]
[string[]]
$Include,

# A list of file wildcards to exclude.
[Parameter(ValueFromPipelineByPropertyName)]
[SupportsWildcards()]
[string[]]
$Exclude,

# The base path within the package.
# Content should be added beneath this base path.
[string]
$BasePath = '/',

# A content type map.
# This maps extensions and URIs to a content type.
[Collections.IDictionary]
$TypeMap = $(
    ([PSCustomObject]@{PSTypeName='OpenPackage.ContentTypeMap'}).TypeMap
),

# The compression option.
[IO.Packaging.CompressionOption]
[Alias('CompressionLevel')]
$CompressionOption = 'Superfast',
        
# If set, will force the redownload of various resources and remove existing files or directories
[switch]
$Force,

# If set, will include hidden files and folders, except for files beneath `.git`
[Alias('IncludeDotFiles')]
[switch]
$IncludeHidden,

# If set, will include the `.git` directory contents if found.
# By default, this content will be excluded.
[Alias('IncludeGitFile','IncludeGitFiles','IncludeGitDirectory')]
[switch]
$IncludeGit,

# If set, will include any content found in `/node_modules`.
# By default, this content will be excluded.
[Alias('IncludeNodeModules')]
[switch]
$IncludeNodeModule,

# If set, will include any content found in `/_site`.
# By default, this content will be excluded.
[Alias('IncludeWebsite')]
[switch]
$IncludeSite
)

if (-not $repository) {
    throw "No repository"
}

$gitApp = $ExecutionContext.SessionState.InvokeCommand.GetCommand('git', 'Application')
if (-not $gitApp) {
    throw "No git"
}

if (-not $env:OpenPackagePath) {
    throw "No Open Package Path"
}



$myAppData = $env:OpenPackagePath -split $(
    if ($IsLinux -or $IsMacOS) {
        ':'
    } else {
        ';'
    }
)

$namedParameters = [Ordered]@{}

foreach ($key in $MyInvocation.MyCommand.Parameters.Keys) {
    $var = $ExecutionContext.SessionState.PSVariable.Get($key)
    if (-not [string]::IsNullOrEmpty($var.value)) {
        $namedParameters[$key] = $var.value
    }
}

$namedParameters.Remove('Repository')
$namedParameters.Remove('Branch')
$namedParameters.Remove('SparseFilter')

$repositoryUrl = $Repository

# See if we are matching a repo or a tree or blob.
$treePattern = '/(?>tree|blob)/(?<branch>[^/]+)/'
if ($Repository -match $treePattern -and -not $NamedParameters.SparseFilter) {
    # If we are matching a tree, turn the file portion into a sparse filter
    $branch = $matches.branch
    $NamedParameters.SparseFilter = 
        $SparseFilter = 
        $RepositoryUrl -replace "^.+?$treePattern" -replace '^/?', '/' -replace '/?$', '/' -replace '(?<!:)//', '/'
    $Repository = $RepositoryUrl -replace "$treePattern.+?$"
    if ($SparseFilter -notmatch '/[^\.]+\.[^\.]+?$') {
        $sparseFilter = $SparseFilter -replace '/$','/**' 
    }
}

# Pick out the owner and repository
$owner, $repositoryName = ($Repository -as [uri]).Segments[1,2] -replace '/$'
if (-not $owner -or -not $repositoryName) {
    Write-Error "Could not identifier owner and repository $reposityUrl"
    return
}

$repoDir = if (
    ($Repository -as [uri]).DnsSafeHost
) {
    ($Repository -as [uri]).DnsSafeHost
} else {
    'repos'
}

# Get the path to our repos
$reposPaths = Join-Path $myAppData $repoDir
if (-not (Test-Path $reposPaths)) {
    # (create it if it did not exist)
    $null = New-Item -ItemType Directory $reposPaths -Force
    if (-not $?) { return }
}

# Get the path to repos of that owner
$ownerDirectory = Join-Path $reposPaths $owner
if (-not (Test-Path $ownerDirectory)) {
    $null = New-Item -ItemType Directory $ownerDirectory -Force
    if (-not $?) { return }
}

# Finally, get the path to the repo itself.
$repoDirectory = Join-Path $ownerDirectory $repositoryName

# If we are using `-Force`, remove the directory
if ($Force -and (Test-path $repoDirectory)) {
    Remove-Item -Path $repoDirectory -Recurse -Force 
}

$namedParameters.Remove('Repository')

# and get read to turn our git activity into progress bars
$gitProgress = @{Id=Get-Random;Status="Cloning $repository"}            
$writeGitProgress = {
    process { 
        $gitProgress.Activity = "$_"
        Write-Progress @gitProgress
    }
}

$checkoutBranch = @()
if ($branch) { $checkoutBranch += $branch}


# If the directory did not exist
if (-not (Test-Path $repoDirectory)) {
    # push to the owner directory and clone it
    Push-Location $ownerDirectory

    # If sparse filters were provided
    if ($SparseFilter) {
        # Perform a sparse clone
        & $gitApp clone --depth 1 --no-checkout --sparse --filter=tree:0 $Repository "$repositoryName" *>&1 |
            . $writeGitProgress
        # and push into the location
        Push-Location -LiteralPath $repoDirectory
        # then set our sparse filter
        $SparseArgs = @($SparseFilter)
        & $gitApp sparse-checkout set --no-cone @SparseArgs *>&1 |
            . $writeGitProgress
        # and checkout.
        & $gitApp checkout @checkoutBranch *>&1 |
            . $writeGitProgress
        # we should now have the files, so prepare an -Include filter
        if (-not $namedParameters.Include) {
            $namedParameters.Include = foreach ($sparse in $SparseFilter) {
                "*$sparse"
            }
        }
        # clean up our progress
        $gitProgress.Completed = $true
        Write-Progress @gitProgress
        # and call ourselves with the repoDirectory
        Get-OpenPackage -FilePath $pwd @namedParameters
        Pop-Location
    } else {
        # If no sparse filters were provided, just clone
        & $gitApp clone $Repository *>&1 |
            . $writeGitProgress

        $gitProgress.Completed = $true
        Write-Progress @gitProgress

        if ($?) {
            Push-Location $repoDirectory
            # and call ourself
            Get-OpenPackage -FilePath $pwd @namedParameters
            Pop-Location
        }
    }

    Pop-Location
    if (-not $?) { return }
}
else {
    # If the directory already exists, go there
    Push-Location $repoDirectory
    # and checkout
    & $gitApp checkout @checkoutBranch | . $writeGitProgress
    # and if a sparse filter was provided
    if ($SparseFilter) {
        if (-not $namedParameters.Include) {
            # try to only include those files
            $namedParameters.Include = foreach ($sparse in $SparseFilter) {
                "*$sparse"
            }
        }
    }
    $gitProgress.Completed = $true
    Write-Progress @gitProgress

    # Call get-openpackage and make a package from this directory
    Get-OpenPackage -FilePath $repoDirectory @namedParameters
    Pop-Location
}