Skip to content

Commit fc1fbe0

Browse files
committed
Add concurrent git clone and fetch operations - #1
1 parent a6dbfc1 commit fc1fbe0

File tree

1 file changed

+72
-42
lines changed

1 file changed

+72
-42
lines changed

backup_github_repositories.ps1

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ Param (
3838
Mandatory=$True,
3939
HelpMessage="The name of a GitHub user that has access to the GitHub API."
4040
)]
41-
[string]$userName,
41+
[String]
42+
$userName,
4243

4344
[Parameter(
4445
Mandatory=$True,
@@ -50,14 +51,18 @@ Param (
5051
Mandatory = $True,
5152
ParameterSetName = 'PlainTextSecret'
5253
)]
53-
[String]$userSecret,
54+
[String]
55+
$userSecret,
5456

55-
[String]$organisationName,
57+
[String]
58+
$organisationName,
5659

57-
[String]$backupDirectory,
60+
[String]
61+
$backupDirectory,
5862

59-
[ValidateRange(0,256)]
60-
[Int]$maxConcurrency=2
63+
[ValidateRange(1,256)]
64+
[Int]
65+
$maxConcurrency=8
6166
)
6267

6368
# Consolidate the user secret, either from the argument or the prompt, in a secure string format.
@@ -79,35 +84,8 @@ if (!$backupDirectory) {
7984
$backupDirectory = $(Join-Path -Path "$PSScriptRoot" -ChildPath $(Get-Date -UFormat "%Y-%m-%d"))
8085
}
8186

82-
# Log a message to the commandline.
83-
function Write-Message([string] $message, [string] $color = 'Yellow') {
84-
85-
Write-Host "${message}" -foregroundcolor $color
86-
}
87-
88-
#
89-
# Clone or fetch a remote GitHub repository into a local directory.
90-
#
91-
# @see https://git-scm.com/docs/git-clone#git-clone---mirror
92-
#
93-
function Backup-GitHubRepository([string] $fullName, [string] $directory) {
94-
95-
Write-Message "Starting backup of https://github.com/${fullName} to ${directory}..." 'DarkYellow'
96-
97-
if (Test-Path "${directory}") {
98-
99-
git --git-dir="${directory}" fetch --all
100-
git --git-dir="${directory}" fetch --tags
101-
return
102-
}
103-
104-
git clone --mirror "[email protected]:${fullName}.git" "${directory}"
105-
}
106-
107-
#
108-
# Calculate the total repositories size in megabytes based on GitHubs 'size' property.
109-
#
110-
function Get-TotalRepositoriesSizeInMegabytes([object] $repositories) {
87+
# Calculates the total repositories size in megabytes based on GitHubs 'size' property.
88+
function Get-TotalRepositoriesSizeInMegabytes([Object] $repositories) {
11189

11290
$totalSizeInKilobytes = 0
11391
ForEach ($repository in $repositories) {
@@ -159,7 +137,7 @@ Do {
159137
$pageNumber++
160138
$paginatedGitHubApiUri = "${gitHubRepositoriesUrl}&page=${pageNumber}"
161139

162-
Write-Message "Requesting '${paginatedGitHubApiUri}'..."
140+
Write-Host "Requesting '${paginatedGitHubApiUri}'..." -ForegroundColor "Yellow"
163141
$paginatedRepositories = Invoke-WebRequest -Uri $paginatedGitHubApiUri -Headers $requestHeaders | `
164142
Select-Object -ExpandProperty Content | `
165143
ConvertFrom-Json
@@ -170,16 +148,68 @@ Do {
170148

171149
# Print a userfriendly message what will happen next.
172150
$totalSizeInMegabytes = Get-TotalRepositoriesSizeInMegabytes -repositories $repositories
173-
Write-Message "Cloning $($repositories.Count) repositories (~${totalSizeInMegabytes} MB) into '${backupDirectory}'..."
151+
Write-Host "Cloning $($repositories.Count) repositories (~${totalSizeInMegabytes} MB) " -NoNewLine
152+
Write-Host "into '${backupDirectory}' with a maximum concurrency of ${maxConcurrency}:"
174153

175154
# Clone each repository into the backup directory.
176155
ForEach ($repository in $repositories) {
177156

178-
# The repository directory is suffixed with a ".git" to indicate a bare repository.
179-
Backup-GitHubRepository -FullName $repository.full_name `
180-
-Directory $(Join-Path -Path $backupDirectory -ChildPath "$($repository.name).git")
157+
while ($true) {
158+
159+
# Handle completed jobs as soon as possible.
160+
$completedJobs = $(Get-Job -State Completed | Where-Object {$_.Name.Contains("backup_github_repositories")})
161+
ForEach ($job in $completedJobs) {
162+
$job | Receive-Job
163+
$job | Remove-Job
164+
}
165+
166+
$concurrencyLimitIsReached = $($(Get-Job -State Running).Count -ge $maxConcurrency)
167+
if ($concurrencyLimitIsReached) {
168+
169+
$pollingFrequencyInMilliseconds = 50
170+
Start-Sleep -Milliseconds $pollingFrequencyInMilliseconds
171+
continue
172+
}
173+
174+
# Clone or fetch a remote GitHub repository into a local directory.
175+
$scriptBlock = {
176+
177+
Param (
178+
[Parameter(Mandatory=$true)]
179+
[String]
180+
$fullName,
181+
182+
[Parameter(Mandatory=$true)]
183+
[String]
184+
$directory
185+
)
186+
187+
if (Test-Path "${directory}") {
188+
189+
git --git-dir="${directory}" fetch --quiet --all
190+
git --git-dir="${directory}" fetch --quiet --tags
191+
Write-Host "[${fullName}] Backup completed with git fetch strategy."
192+
return
193+
}
194+
195+
git clone --quiet --mirror "[email protected]:${fullName}.git" "${directory}"
196+
Write-Host "[${fullName}] Backup completed with git clone strategy."
197+
}
198+
199+
# Suffix the repository directory with a ".git" to indicate a bare repository.
200+
$directory = $(Join-Path -Path $backupDirectory -ChildPath "$($repository.name).git")
201+
202+
Write-Host "[$($repository.full_name)] Starting backup to ${directory}..." -ForegroundColor "DarkYellow"
203+
Start-Job $scriptBlock -Name "backup_github_repositories" `
204+
-ArgumentList $repository.full_name, $directory `
205+
| Out-Null
206+
break
207+
}
181208
}
182209

210+
# Wait for the last jobs to complete and output their results.
211+
Get-Job | Receive-job -AutoRemoveJob -Wait
212+
183213
$stopwatch.Stop()
184-
$durationInSeconds = $stopwatch.Elapsed.TotalSeconds
185-
Write-Message "Successfully finished the backup in ${durationInSeconds} seconds..."
214+
$durationInSeconds = [Math]::Floor([Decimal]($stopwatch.Elapsed.TotalSeconds))
215+
Write-Host "Successfully finished the backup in ${durationInSeconds} seconds." -ForegroundColor "Yellow"

0 commit comments

Comments
 (0)