gitazure-devops

Splitt git push to Azure DevOps


I would like ask you for help with git and how to split push to smaller one. I am not experienced in git so I need help.

My problem is that I can't send push bigger than 5GB to Azure DevOps it is reason why I need split push.

I have big push because I am trying reduce repository by removing binary files.

I follow these steps

git clone --bare --mirror

I am using git-filter-repo for reduce repository size.

after that

git reflog expire --expire=now --all

git repack -a -d --depth=250 --window=250 --max-pack-size=2g

Repository was reduced perfectly and packages are not bigger than 2GB. It does not help with push. because when I push my changes by

git push origin --force --mirror --prune

I get error about size.

I found solution with counting commits, but it does not work as I supposed. First it raised error that I am on mirror git. I do not know if I change something wrong way If I unset mirror flag from config. True is that founded code "worked", but it removed some branches. I need solution where I can push everything for all branches.

After my fail i found another code bellow, but I am afraid that it push only one branch (my be I am wrong). This code also need unset mirror flag.

# Split a repository into batches to avoid `pack exceeds maximum allowed size` on git push

REMOTE=origin
BRANCH=$(git rev-parse --abbrev-ref HEAD)
BATCH_SIZE=500

# check if the branch exists on the remote
if git show-ref --quiet --verify refs/remotes/$REMOTE/$BRANCH; then
    # if so, only push the commits that are not on the remote already
    range=$REMOTE/$BRANCH..HEAD
else
    # else push all the commits
    range=HEAD
fi
# count the number of commits to push
n=$(git log --first-parent --format=format:x $range | wc -l)

# push each batch
for i in $(seq $n -$BATCH_SIZE 1); do
    # get the hash of the commit to push
    h=$(git log --first-parent --reverse --format=format:%H --skip $i -n1)
    echo "Pushing $h..."
    git push $REMOTE $h:refs/heads/$BRANCH
done

# push the final partial batch
git push $REMOTE HEAD:refs/heads/$BRANCH`

Please can you confirm me if this code does what I need. It means that pushes will be lower than 5GB and that push all my branches only reduced by filter-repo?

Thank you for your help


Solution

  • I used my split pushes two times in my script. First for backup repository to new one. Second for push changed commits from git filter-repo. I exctracted and rewrite my backup script and added here as solution.

        [string] $RepositoryFolderPathForBareCloneBAK = "C:\Repositories\bare_clone"
        [string] $RepositoryHttpsURL = "URLToRepository"
        [string] $BackupRepositoryHttpsURL = "URLToBackupRepository"
        [string] $remoteName = "origin"
        [int] $maxPushSizeInMB = (5 * 1024) # 5GB - Azure DevOps limit for one Push
        [int] $splitPushCommitsCount = 100
        [boolean] $splitPush = $false
    
        $ALocation = Get-Location
        if ($null -ne (Test-Path $RepositoryFolderPathForBareCloneBAK)) {
            write-host "Remove directory $RepositoryFolderPathForBareCloneBAK" -ForegroundColor Green
            $null = Remove-Item $RepositoryFolderPathForBareCloneBAK -Recurse -Force -ErrorAction Stop | Out-Null
        }
        write-host "Clone bare repository from $RepositoryHttpsURL to $RepositoryFolderPathForBareCloneBAK" -ForegroundColor Green
        git clone --bare --mirror $RepositoryHttpsURL $RepositoryFolderPathForBareCloneBAK
    
        write-host "Backup repository" -ForegroundColor Magenta
    
        write-host "Switch to folder $RepositoryFolderPathForBareCloneBAK" -ForegroundColor green
        Set-Location $RepositoryFolderPathForBareCloneBAK -ErrorAction Stop
    
        $doSplitPush = $splitPush #Split push if it is forced
        if (!($doSplitPush)) {
            # check if repository is lower than limit if not do split push
            write-host "git count-objects -vH" -ForegroundColor Green
            $gitCountResult = (git count-objects -vH)
            $splitRegEx = "^(?<Name>[^\s-]+(-[^\s-]+)*):\s*(?<Value>\d+\.?\d*)\s*(?<Measure>\S+)?$"
            $repositorySize = 0
            $gitCountResult | ForEach-Object {
                write-host $_ -ForegroundColor Green
                if ($_ -match $splitRegEx) {
                    if ($matches['Name'] -eq 'size-pack') {
                        if ("$($matches['Measure'])" -ne '') {
                            if (($matches['Measure']) -eq 'bytes') {
                                $repositorySize = ([math]::Round((([float]($matches['Value'])) / 1024 / 1024)))
                            }
                            elseif (($matches['Measure']) -eq 'KiB') {
                                $repositorySize = ([math]::Round(([float]($matches['Value']) / 1024)))
                            }
                            elseif (($matches['Measure']) -eq 'MiB') {
                                $repositorySize = ([float]($matches['Value']))
                            }
                            elseif (($matches['Measure']) -eq 'GiB') {
                                $repositorySize = (([float]($matches['Value'])) * 1024)
                            }
                            else {
                                $repositorySize = ([float]($matches['Value']))
                            }
                        }
                    }
                }
            }
            $doSplitPush = ($repositorySize -ge $maxPushSizeInMB)
        }
    
        if (!($doSplitPush)) {
            write-host "Push all to remote repository" -ForegroundColor Magenta
            write-host "git push --mirror $BackupRepositoryHttpsURL" -ForegroundColor Green
            git push --mirror $BackupRepositoryHttpsURL
        }
        else {
            write-host "Splited git pushes to remote repository. Push per $($splitPushCommitsCount) commits" -ForegroundColor Magenta
            if ((git config remote.origin.mirror) -eq "true") {
                write-host "git config --unset remote.origin.mirror" -ForegroundColor Green
                git config --unset remote.origin.mirror
            }
            # Split a repository into batches to avoid `pack exceeds maximum allowed size` on git push
            $REMOTE = $remoteName
            $NewREMOTE = "new_$($remoteName)"
            $BATCH_SIZE = $splitPushCommitsCount
            write-host "Get list of remotes" -ForegroundColor green
            $Remotes = git remote -v
    
            if ($Remotes) {
                $RemoteExtists = ($null -ne ($Remotes | Where-Object { $_.IndexOf($BackupRepositoryHttpsURL) -ne -1 } ))
                if (!$RemoteExtists) {
                    if ($null -ne ($Remotes | Where-Object { $_.IndexOf($NewREMOTE) -ne -1 } )) {
                        write-host "Set existing remote $($NewREMOTE) for $($BackupRepositoryHttpsURL)" -ForegroundColor green
                        git remote set-url "$NewREMOTE" "$BackupRepositoryHttpsURL" | Out-Null
                    }
                    else {
                        write-host "Add remote $NewREMOTE for $BackupRepositoryHttpsURL" -ForegroundColor green
                        git remote add "$NewREMOTE" "$BackupRepositoryHttpsURL" | Out-Null
                    }
                }
            }
            git for-each-ref --format="%(refname)" --sort='authordate' | ForEach-Object {
                if ($_.IndexOf("refs/heads/") -ne -1) {
                    $BRANCH = $_.Replace("refs/heads/", '')
                    write-host "Pushing branch $($BRANCH)" -ForegroundColor yellow
                    write-host "Switch $BRANCH" -ForegroundColor green
                    write-host "git symbolic-ref HEAD $($_)" -ForegroundColor green
                    git symbolic-ref HEAD $_ | Out-Null
    
                    # check if the branch exists on the remote
                    if ($null -ne (git show-ref --quiet --verify "refs/remotes/$($NewREMOTE)/$($BRANCH)")) {
                        # check if the branch exists on the remote
                        $range = "$($NewREMOTE)/$($BRANCH)..HEAD"
                    }
                    else {
                        # else push all the commits
                        $range = "HEAD"
                    }
                    write-host "Range $($range)" -ForegroundColor green
                    # count the number of commits to push
                    $n = (git log --first-parent --format="format:x $range").Length
                    write-Host "Count of commits to push $($n)" -ForegroundColor green
                    if ($n -gt 0) {
                        $loopCount = [int] [Math]::Truncate($n / $BATCH_SIZE)
                        # push each batch
                        for ($i = 1; $i -le $loopCount; $i++) {
                            $h = git log --first-parent --reverse --format=format:%H --skip ($n - ($i * $BATCH_SIZE)) -n1
                            write-Host "Skip commits $(($n - ($i * $BATCH_SIZE))) to $($h)" -ForegroundColor green
                            Write-Host "Pushing $($NewREMOTE) --force $($h):refs/heads/$($BRANCH)"
                            git push "$($NewREMOTE)" --force "$($h):refs/heads/$($BRANCH)"
                        }
                        # push last batch
                        Write-Host "Pushing $($NewREMOTE)" --force "HEAD:refs/heads/$($BRANCH)"
                        git push "$($NewREMOTE)" --force "HEAD:refs/heads/$($BRANCH)"
                    }
                    else {
                        write-host "Skiped push no commits" -ForegroundColor Yellow
                    }
                }
            }
            write-host "git push $NewREMOTE --force 'refs/tags/*'" -ForegroundColor Green
            git push $NewREMOTE --force 'refs/tags/*'
            write-host "git push $NewREMOTE --force 'refs/replace/*'" -ForegroundColor Green
            git push $NewREMOTE --force 'refs/replace/*'
        }
        Set-Location $ALocation
        if (Test-Path -Path $RepositoryFolderPathForBareCloneBAK) {
            write-host "Remove directory $RepositoryFolderPathForBareCloneBAK" -ForegroundColor Green
            $null = Remove-Item $RepositoryFolderPathForBareCloneBAK -Recurse -Force -ErrorAction Stop | Out-Null
        }