diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 57f2de4f596902..3c43b76838a55d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ concurrency: env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - NODE_VERSION: 20 + NODE_VERSION: 22 PDM_VERSION: 2.20.0.post1 # renovate: datasource=pypi depName=pdm DRY_RUN: true TEST_LEGACY_DECRYPTION: true diff --git a/.github/workflows/update-data.yml b/.github/workflows/update-data.yml index d4de7915483334..93b68aafd8baaf 100644 --- a/.github/workflows/update-data.yml +++ b/.github/workflows/update-data.yml @@ -5,7 +5,7 @@ on: workflow_dispatch: env: - NODE_VERSION: 20 + NODE_VERSION: 22 permissions: contents: read diff --git a/.nvmrc b/.nvmrc index 2a393af592b8cd..7af24b7ddbde0c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.18.0 +22.11.0 diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 99bb5ca13822de..02f2d3394cd350 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -394,7 +394,12 @@ If `true`, Renovate removes special characters when slugifying the branch name: - only alphabetic characters are allowed - hyphens `-` are used to separate sections -The default `false` behavior will mean that special characters like `.` may end up in the branch name. +The default `false` behavior will mean that special characters like `.` and `/` may end up in the branch name. + + +!!! note + Renovate will not apply any search/replace to the `branchPrefix` part of the branch name. + If you don't want any `/` in your branch name then you will also need to change `branchPrefix` from the default `renovate/` value to something like `renovate-`. ## branchPrefix diff --git a/docs/usage/examples/self-hosting.md b/docs/usage/examples/self-hosting.md index 705ffd176820d3..edc1b91a386940 100644 --- a/docs/usage/examples/self-hosting.md +++ b/docs/usage/examples/self-hosting.md @@ -436,7 +436,7 @@ COPY self-signed-certificate.crt /usr/local/share/ca-certificates/ RUN update-ca-certificates # Change back to the Ubuntu user -USER 1000 +USER 12021 # OpenSSL ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index 955105f4dce7d7..fd1a19ffe71f40 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -515,7 +515,7 @@ const options: RenovateOptions[] = [ description: 'Change this value to override the default Renovate sidecar image.', type: 'string', - default: 'ghcr.io/containerbase/sidecar:11.11.41', + default: 'ghcr.io/containerbase/sidecar:13.0.4', globalOnly: true, }, { diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index e7e4ea1b89f87a..374c60079c1fbe 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -532,7 +532,7 @@ describe('modules/platform/github/index', () => { } describe('initRepo', () => { - it('should rebase', async () => { + it('should squash', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); const config = await github.initRepo({ repository: 'some/repo' }); @@ -675,7 +675,7 @@ describe('modules/platform/github/index', () => { expect(config).toMatchSnapshot(); }); - it('should squash', async () => { + it('should merge', async () => { httpMock .scope(githubApiHost) .post(`/graphql`) @@ -687,8 +687,8 @@ describe('modules/platform/github/index', () => { nameWithOwner: 'some/repo', hasIssuesEnabled: true, mergeCommitAllowed: true, - rebaseMergeAllowed: false, - squashMergeAllowed: true, + rebaseMergeAllowed: true, + squashMergeAllowed: false, defaultBranchRef: { name: 'master', target: { @@ -704,7 +704,7 @@ describe('modules/platform/github/index', () => { expect(config).toMatchSnapshot(); }); - it('should merge', async () => { + it('should rebase', async () => { httpMock .scope(githubApiHost) .post(`/graphql`) @@ -715,8 +715,8 @@ describe('modules/platform/github/index', () => { isArchived: false, nameWithOwner: 'some/repo', hasIssuesEnabled: true, - mergeCommitAllowed: true, - rebaseMergeAllowed: false, + mergeCommitAllowed: false, + rebaseMergeAllowed: true, squashMergeAllowed: false, defaultBranchRef: { name: 'master', @@ -2882,7 +2882,7 @@ describe('modules/platform/github/index', () => { }, variables: { pullRequestId: 'abcd', - mergeMethod: 'REBASE', + mergeMethod: 'SQUASH', }, }, }; @@ -3510,7 +3510,7 @@ describe('modules/platform/github/index', () => { }, variables: { pullRequestId: 'abcd', - mergeMethod: 'REBASE', + mergeMethod: 'SQUASH', }, }, }; @@ -3683,7 +3683,7 @@ describe('modules/platform/github/index', () => { }); describe('mergePr(prNo) - autodetection', () => { - it('should try rebase first', async () => { + it('should try squash first', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); scope.put('/repos/some/repo/pulls/1235/merge').reply(200); @@ -3702,12 +3702,12 @@ describe('modules/platform/github/index', () => { ).toBeTrue(); }); - it('should try squash after rebase', async () => { + it('should try merge after squash', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); scope .put('/repos/some/repo/pulls/1236/merge') - .reply(400, 'no rebasing allowed'); + .reply(400, 'no squashing allowed'); await github.initRepo({ repository: 'some/repo' }); const pr = { number: 1236, @@ -3723,15 +3723,15 @@ describe('modules/platform/github/index', () => { ).toBeFalse(); }); - it('should try merge after squash', async () => { + it('should try rebase after merge', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); scope - .put('/repos/some/repo/pulls/1237/merge') - .reply(405, 'no rebasing allowed') .put('/repos/some/repo/pulls/1237/merge') .reply(405, 'no squashing allowed') .put('/repos/some/repo/pulls/1237/merge') + .reply(405, 'no merging allowed') + .put('/repos/some/repo/pulls/1237/merge') .reply(200); await github.initRepo({ repository: 'some/repo' }); const pr = { @@ -3753,12 +3753,12 @@ describe('modules/platform/github/index', () => { initRepoMock(scope, 'some/repo'); scope .put('/repos/some/repo/pulls/1237/merge') - .reply(405, 'no rebasing allowed') - .put('/repos/some/repo/pulls/1237/merge') - .replyWithError('no squashing allowed') + .reply(405, 'no squashing allowed') .put('/repos/some/repo/pulls/1237/merge') .replyWithError('no merging allowed') .put('/repos/some/repo/pulls/1237/merge') + .replyWithError('no rebasing allowed') + .put('/repos/some/repo/pulls/1237/merge') .replyWithError('never gonna give you up'); await github.initRepo({ repository: 'some/repo' }); const pr = { diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index 08678c06d37dac..082932f3475698 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -553,12 +553,12 @@ export async function initRepo({ // Base branch may be configured but defaultBranch is always fixed logger.debug(`${repository} default branch = ${config.defaultBranch}`); // GitHub allows administrators to block certain types of merge, so we need to check it - if (repo.rebaseMergeAllowed) { - config.mergeMethod = 'rebase'; - } else if (repo.squashMergeAllowed) { + if (repo.squashMergeAllowed) { config.mergeMethod = 'squash'; } else if (repo.mergeCommitAllowed) { config.mergeMethod = 'merge'; + } else if (repo.rebaseMergeAllowed) { + config.mergeMethod = 'rebase'; } else { // This happens if we don't have Administrator read access, it is not a critical error logger.debug('Could not find allowed merge methods for repo'); @@ -1910,25 +1910,25 @@ export async function mergePr({ } } if (!automerged) { - // We need to guess the merge method and try squash -> rebase -> merge - options.body.merge_method = 'rebase'; + // We need to guess the merge method and try squash -> merge -> rebase + options.body.merge_method = 'squash'; try { logger.debug({ options, url }, `mergePr`); automergeResult = await githubApi.putJson(url, options); } catch (err1) { - logger.debug({ err: err1 }, `Failed to rebase merge PR`); + logger.debug({ err: err1 }, `Failed to squash merge PR`); try { - options.body.merge_method = 'squash'; + options.body.merge_method = 'merge'; logger.debug({ options, url }, `mergePr`); automergeResult = await githubApi.putJson(url, options); } catch (err2) { - logger.debug({ err: err2 }, `Failed to merge squash PR`); + logger.debug({ err: err2 }, `Failed to merge commit PR`); try { - options.body.merge_method = 'merge'; + options.body.merge_method = 'rebase'; logger.debug({ options, url }, `mergePr`); automergeResult = await githubApi.putJson(url, options); } catch (err3) { - logger.debug({ err: err3 }, `Failed to merge commit PR`); + logger.debug({ err: err3 }, `Failed to rebase merge PR`); logger.info({ pr: prNo }, 'All merge attempts failed'); return false; } diff --git a/lib/workers/repository/updates/branch-name.spec.ts b/lib/workers/repository/updates/branch-name.spec.ts index 297d7012bcd4af..a4b4dd5993954e 100644 --- a/lib/workers/repository/updates/branch-name.spec.ts +++ b/lib/workers/repository/updates/branch-name.spec.ts @@ -213,6 +213,22 @@ describe('workers/repository/updates/branch-name', () => { expect(upgrade.branchName).toBe('renovate/jest-42-x'); }); + it('removes slashes from the non-suffix part', () => { + const upgrade: RenovateConfig = { + branchNameStrict: true, + branchName: + '{{{branchPrefix}}}{{{additionalBranchPrefix}}}{{{branchTopic}}}', + branchTopic: + '{{{depNameSanitized}}}-{{{newMajor}}}{{#if isPatch}}.{{{newMinor}}}{{/if}}.x{{#if isLockfileUpdate}}-lockfile{{/if}}', + branchPrefix: 'renovate/', + depNameSanitized: '@foo/jest', + newMajor: '42', + group: {}, + }; + generateBranchName(upgrade); + expect(upgrade.branchName).toBe('renovate/foo-jest-42-x'); + }); + it('hashedBranchLength hashing', () => { const upgrade: RenovateConfig = { branchName: diff --git a/lib/workers/repository/updates/branch-name.ts b/lib/workers/repository/updates/branch-name.ts index bc42966fd0ded7..d151c0772b8c24 100644 --- a/lib/workers/repository/updates/branch-name.ts +++ b/lib/workers/repository/updates/branch-name.ts @@ -11,7 +11,7 @@ const MIN_HASH_LENGTH = 6; const RE_MULTIPLE_DASH = regEx(/--+/g); -const RE_SPECIAL_CHARS_STRICT = regEx(/[`~!@#$%^&*()_=+[\]\\|{};':",.<>?]/g); +const RE_SPECIAL_CHARS_STRICT = regEx(/[`~!@#$%^&*()_=+[\]\\|{};':",.<>?/]/g); /** * Clean git branch name @@ -26,12 +26,20 @@ const RE_SPECIAL_CHARS_STRICT = regEx(/[`~!@#$%^&*()_=+[\]\\|{};':",.<>?]/g); */ function cleanBranchName( branchName: string, + branchPrefix: string, branchNameStrict?: boolean, ): string { let cleanedBranchName = branchName; + let existingBranchPrefix = ''; if (branchNameStrict) { - cleanedBranchName = cleanedBranchName.replace(RE_SPECIAL_CHARS_STRICT, '-'); // massage out all special characters that slip through slugify + if (cleanedBranchName.startsWith(branchPrefix)) { + existingBranchPrefix = branchPrefix; + cleanedBranchName = cleanedBranchName.slice(branchPrefix.length); + } + cleanedBranchName = + existingBranchPrefix + + cleanedBranchName.replace(RE_SPECIAL_CHARS_STRICT, '-'); // massage out all special characters that slip through slugify } return cleanGitRef @@ -125,6 +133,7 @@ export function generateBranchName(update: RenovateConfig): void { } update.branchName = cleanBranchName( update.branchName, + update.branchPrefix!, update.branchNameStrict, ); } diff --git a/package.json b/package.json index 7dfcd6da83e255..c2ba4869a45cc5 100644 --- a/package.json +++ b/package.json @@ -135,11 +135,11 @@ }, "homepage": "https://renovatebot.com", "engines": { - "node": "^20.15.1", + "node": "^20.15.1 || ^22.11.0", "pnpm": "^9.0.0" }, "volta": { - "node": "20.18.0", + "node": "22.11.0", "pnpm": "9.12.3" }, "dependencies": { diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile index 87d980acba86e1..bf755d60107105 100644 --- a/tools/docker/Dockerfile +++ b/tools/docker/Dockerfile @@ -5,21 +5,23 @@ ARG BASE_IMAGE_TYPE=slim # -------------------------------------- # slim image # -------------------------------------- -FROM ghcr.io/renovatebot/base-image:7.41.2@sha256:b104d2a8ae0072f205f29b972769e4c1a00d9957073b628b57c734e4f7376027 AS slim-base +FROM ghcr.io/renovatebot/base-image:9.2.0@sha256:52c241a927ca9f2e339e8d74c860f410c2f182a19819fb2050f89c91a905d99f AS slim-base # -------------------------------------- # full image # -------------------------------------- -FROM ghcr.io/renovatebot/base-image:7.41.2-full@sha256:3365becc7d7b50b210f793e58e4e871ead00976280f3451fd97230417c02f423 AS full-base +FROM ghcr.io/renovatebot/base-image:9.2.0-full@sha256:898a5898727ef6ecbe3d57c6dcf9917b13af91abc11f70e6c9926c7ebf1b0311 AS full-base + +ENV RENOVATE_BINARY_SOURCE=global # -------------------------------------- # build image # -------------------------------------- -FROM --platform=$BUILDPLATFORM ghcr.io/renovatebot/base-image:7.41.2@sha256:b104d2a8ae0072f205f29b972769e4c1a00d9957073b628b57c734e4f7376027 AS build +FROM --platform=$BUILDPLATFORM ghcr.io/renovatebot/base-image:9.2.0@sha256:52c241a927ca9f2e339e8d74c860f410c2f182a19819fb2050f89c91a905d99f AS build # We want a specific node version here # renovate: datasource=node-version -RUN install-tool node 20.18.0 +RUN install-tool node 22.11.0 WORKDIR /usr/local/renovate @@ -102,4 +104,4 @@ LABEL \ org.label-schema.version="${RENOVATE_VERSION}" # Numeric user ID for the ubuntu user. Used to indicate a non-root user to OpenShift -USER 1000 +USER 12021