@@ -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 ) {
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.
176155ForEach ($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