From 5375933ceacbcd7ea0295dd5fd786d982042f6af Mon Sep 17 00:00:00 2001 From: Colin O'Dell <colinodell@gmail.com> Date: Sun, 2 Oct 2022 01:56:52 -0400 Subject: [PATCH] feat(git): prune branches sequentially (#18068) --- lib/util/git/error.ts | 6 ++++++ lib/util/git/errors.spec.ts | 12 ++++++++++++ lib/util/git/index.spec.ts | 24 ++++++++++++++++++++++++ lib/util/git/index.ts | 26 +++++++++++++++++++++++--- 4 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 lib/util/git/errors.spec.ts diff --git a/lib/util/git/error.ts b/lib/util/git/error.ts index e2b1aad028..19b8ab6a52 100644 --- a/lib/util/git/error.ts +++ b/lib/util/git/error.ts @@ -138,3 +138,9 @@ export function handleCommitError( // We don't know why this happened, so this will cause bubble up to a branch error throw err; } + +export function bulkChangesDisallowed(err: Error): boolean { + return err.message.includes( + 'remote: Repository policies do not allow pushes that update more than' + ); +} diff --git a/lib/util/git/errors.spec.ts b/lib/util/git/errors.spec.ts new file mode 100644 index 0000000000..39d3f95faa --- /dev/null +++ b/lib/util/git/errors.spec.ts @@ -0,0 +1,12 @@ +import { bulkChangesDisallowed } from './error'; + +describe('util/git/errors', () => { + describe('bulkChangesDisallowed', () => { + it('should match the expected error', () => { + const err = new Error( + "To https://github.com/the-org/st-mono.git\n!\t:refs/renovate/branches/renovate/Dependencies-mobile-ios-minor\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/database-mongodb-4.x\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-add-field-major-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-add-field-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-count-characters-major-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-count-characters-pin-dependencies\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-count-characters-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-counter-major-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-counter-pin-dependencies\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-counter-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-header-major-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-header-pin-dependencies\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-header-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-mega-menu-major-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-mega-menu-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-nav-scroller-major-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-nav-scroller-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-quantity-counter-major-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-scrollspy-major-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-scrollspy-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-show-animation-major-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-show-animation-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-step-form-major-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-sticky-block-major-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-sticky-block-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-switch-major-web-shared-norm\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-switch-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-toggle-state-major-web-shared-norm\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-toggle-state-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-video-bg-major-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-video-bg-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-video-player-major-web-shared-norm\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/hs-video-player-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/infrastructure-mongodbatlas-1.x\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/infrastructure-random-3.x\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/web-blog-vapor-leaf-4.x\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/web-blog-vapor-vapor-4.x\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/web-shared-major-web-shared-dev\t[remote failure] (remote failed to report status)\n!\t:refs/renovate/branches/renovate/web-shared-web-shared-dev\t[remote failure] (remote failed to report status)\nDone\nPushing to https://github.com/the-org/st-mono.git\nPOST git-receive-pack (5863 bytes)\nremote: Repository policies do not allow pushes that update more than 2 branches or tags.\nerror: failed to push some refs to 'https://github.com/the-org/st-mono.git'\n" + ); + expect(bulkChangesDisallowed(err)).toBe(true); + }); + }); +}); diff --git a/lib/util/git/index.spec.ts b/lib/util/git/index.spec.ts index 8822a6eb10..4bf559a99f 100644 --- a/lib/util/git/index.spec.ts +++ b/lib/util/git/index.spec.ts @@ -984,8 +984,13 @@ describe('util/git/index', () => { await git.pushCommitToRenovateRef(commit, 'bbb'); await git.pushCommitToRenovateRef(commit, 'ccc', 'branches'); + + const pushSpy = jest.spyOn(SimpleGit.prototype, 'push'); + + expect(await lsRenovateRefs()).not.toBeEmpty(); await git.clearRenovateRefs(); expect(await lsRenovateRefs()).toBeEmpty(); + expect(pushSpy).toHaveBeenCalledOnce(); }); it('preserves unknown sections by default', async () => { @@ -996,6 +1001,25 @@ describe('util/git/index', () => { await git.clearRenovateRefs(); expect(await lsRenovateRefs()).toEqual(['refs/renovate/foo/bar']); }); + + it('falls back to sequential ref deletion if bulk changes are disallowed', async () => { + const commit = git.getBranchCommit('develop')!; + await git.pushCommitToRenovateRef(commit, 'foo'); + await git.pushCommitToRenovateRef(commit, 'bar'); + await git.pushCommitToRenovateRef(commit, 'baz'); + + const pushSpy = jest.spyOn(SimpleGit.prototype, 'push'); + pushSpy.mockImplementationOnce(() => { + throw new Error( + 'remote: Repository policies do not allow pushes that update more than 2 branches or tags.' + ); + }); + + expect(await lsRenovateRefs()).not.toBeEmpty(); + await git.clearRenovateRefs(); + expect(await lsRenovateRefs()).toBeEmpty(); + expect(pushSpy).toHaveBeenCalledTimes(4); + }); }); describe('listCommitTree', () => { diff --git a/lib/util/git/index.ts b/lib/util/git/index.ts index d357441b60..93cfad07e0 100644 --- a/lib/util/git/index.ts +++ b/lib/util/git/index.ts @@ -36,7 +36,11 @@ import { getCachedConflictResult, setCachedConflictResult, } from './conflicts-cache'; -import { checkForPlatformFailure, handleCommitError } from './error'; +import { + bulkChangesDisallowed, + checkForPlatformFailure, + handleCommitError, +} from './error'; import { getCachedModifiedResult, setCachedModifiedResult, @@ -1149,6 +1153,11 @@ export async function pushCommitToRenovateRef( * * $ git push --delete origin refs/renovate/foo refs/renovate/bar refs/renovate/baz * + * If Step 2 fails because the repo doesn't allow bulk changes, we'll remove them one by one instead: + * + * $ git push --delete origin refs/renovate/foo + * $ git push --delete origin refs/renovate/bar + * $ git push --delete origin refs/renovate/baz */ export async function clearRenovateRefs(): Promise<void> { if (!gitInitialized || !remoteRefsExist) { @@ -1181,8 +1190,19 @@ export async function clearRenovateRefs(): Promise<void> { obsoleteRefs.push(...renovateBranchRefs); if (obsoleteRefs.length) { - const pushOpts = ['--delete', 'origin', ...obsoleteRefs]; - await git.push(pushOpts); + try { + const pushOpts = ['--delete', 'origin', ...obsoleteRefs]; + await git.push(pushOpts); + } catch (err) /* istanbul ignore next */ { + if (bulkChangesDisallowed(err)) { + for (const ref of obsoleteRefs) { + const pushOpts = ['--delete', 'origin', ref]; + await git.push(pushOpts); + } + } else { + throw err; + } + } } remoteRefsExist = false; -- GitLab