diff --git a/lib/util/git/error.ts b/lib/util/git/error.ts index e2b1aad0284ec450b529d8cfc3a0739f98d4f2e4..19b8ab6a528103ebebf9d8360d3a20e798d763bd 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 0000000000000000000000000000000000000000..39d3f95faa0a51e4ce21809f1742765154c705b0 --- /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 8822a6eb1062b4327bf7dbf2860454800d603ba5..4bf559a99f288d54f5c12b2a6ea5990d167ef10e 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 d357441b6033790e845963befc78e59d736c7af7..93cfad07e00a7b4ea2b707a1d4057b068a6ebaad 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;