1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
parameters:
- name: Paths
type: object
default: []
- name: Repositories
type: object
default:
- Name: $(Build.Repository.Name)
Commitish: $(Build.SourceVersion)
WorkingDirectory: $(System.DefaultWorkingDirectory)
- name: SkipCheckoutNone
type: boolean
default: false
- name: TokenToUseForAuth
type: string
default: ''
- name: PreserveAuthToken
type: boolean
default: false
steps:
- ${{ if not(parameters.SkipCheckoutNone) }}:
- checkout: none
- ${{ if ne(parameters.TokenToUseForAuth, '') }}:
- pwsh: |
$base64Token = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("nobody:${{ parameters.TokenToUseForAuth }}"))
Write-Host "##vso[task.setvariable variable=_base64AuthToken;issecret=true;]$base64Token"
git config set --global "http.extraheader" "AUTHORIZATION: basic $base64Token"
displayName: Setup git config auth header
- task: PowerShell@2
${{ if eq(length(parameters.Repositories), 1) }}:
displayName: 'Sparse checkout ${{ parameters.Repositories[0].Name }}'
${{ else }}:
displayName: 'Sparse checkout repositories'
inputs:
targetType: inline
# Define this inline, because of the chicken/egg problem with loading a script when nothing
# has been checked out yet.
script: |
# Setting $PSNativeCommandArgumentPassing to 'Legacy' to use PowerShell
# 7.2 behavior for command argument passing. Newer behaviors will result
# in errors from git.exe.
$PSNativeCommandArgumentPassing = 'Legacy'
function Retry()
{
Run 3 @args
}
function Run()
{
$retries, $command, $arguments = $args
if ($retries -isnot [int]) {
$command, $arguments = $args
$retries = 0
}
Write-Host "==>" $command $arguments
$attempt = 0
$sleep = 5
while ($true) {
$attempt++
& $command $arguments
if (!$LASTEXITCODE) { return }
if ($attempt -gt $retries) { exit $LASTEXITCODE }
Write-Warning "Attempt $attempt failed: $_. Trying again in $sleep seconds..."
Start-Sleep -Seconds $sleep
$sleep *= 2
}
}
function SparseCheckout([Array]$paths, [Hashtable]$repository)
{
$dir = $repository.WorkingDirectory
if (!$dir) {
$dir = "./$($repository.Name)"
}
New-Item $dir -ItemType Directory -Force | Out-Null
Push-Location $dir
if (Test-Path .git/info/sparse-checkout) {
$hasInitialized = $true
Write-Host "Repository $($repository.Name) has already been initialized in $pwd. Skipping this step."
} else {
Write-Host "Repository $($repository.Name) is being initialized in $pwd"
if ($repository.Commitish -match '^refs/pull/\d+/merge$') {
Retry git clone --no-checkout --filter=tree:0 -c remote.origin.fetch=''+$($repository.Commitish):refs/remotes/origin/$($repository.Commitish)'' https://github.com/$($repository.Name) .
} else {
Retry git clone --no-checkout --filter=tree:0 https://github.com/$($repository.Name) .
}
# Turn off git GC for sparse checkout. Note: The devops checkout task does this by default
Run git config gc.auto 0
Run git sparse-checkout init
# Set non-cone mode otherwise path filters will not work in git >= 2.37.0
# See https://github.blog/2022-06-27-highlights-from-git-2-37/#tidbits
# '/*' '!/*/' -> only checkout files in top level directory
# '/eng' -> checkout required eng/ scripts/configs
# '.config' -> required for files like .config/1espt/PipelineAutobaseliningConfig.yml and .config/guardian/.gdnbaselines used by 1es PT scripts
git sparse-checkout set --no-cone '/*' '!/*/' '/eng' '/.config'
}
# Prevent wildcard expansion in Invoke-Expression (e.g. for checkout path '/*')
$quotedPaths = $paths | ForEach-Object { "'$_'" }
$gitsparsecmd = "git sparse-checkout add $quotedPaths"
Write-Host $gitsparsecmd
Invoke-Expression -Command $gitsparsecmd
Write-Host "Set sparse checkout paths to:"
Get-Content .git/info/sparse-checkout
# sparse-checkout commands after initial checkout will auto-checkout again
if (!$hasInitialized) {
# Remove refs/heads/ prefix from branch names
$commitish = $repository.Commitish -replace '^refs/heads/', ''
# use -- to prevent git from interpreting the commitish as a path
# This will use the default branch if repo.Commitish is empty
Retry git -c advice.detachedHead=false checkout $commitish --
} else {
Write-Host "Skipping checkout as repo has already been initialized"
}
Pop-Location
}
# Paths may be sourced as a yaml object literal OR a dynamically generated variable json string.
# If the latter, convertToJson will wrap the 'string' in quotes, so remove them.
$paths = '${{ convertToJson(parameters.Paths) }}'.Trim('"') | ConvertFrom-Json
# Replace windows backslash paths, as Azure Pipelines default directories are sometimes formatted like 'D:\a\1\s'
$repositories = '${{ convertToJson(parameters.Repositories) }}' -replace '\\', '/' | ConvertFrom-Json -AsHashtable
foreach ($repo in $Repositories) {
SparseCheckout $paths $repo
}
pwsh: true
workingDirectory: $(System.DefaultWorkingDirectory)
- ${{ if and(ne(parameters.TokenToUseForAuth, ''), not(parameters.PreserveAuthToken)) }}:
- pwsh: |
git config unset --global "http.extraheader"
displayName: Removing git config auth header
condition: always()
|