* test(util/git): skip the per-test clone for tests that need no repo
The spec's `beforeEach` rebuilds a bare clone and calls `initRepo()`
before every test, which costs about 0.7s each. Eight tests never touch
a repository at all -- `createSimpleGit`, `gitRetry`, `validateGitVersion`
and `getUrl` -- yet each paid for that clone. They now sit in a second
root describe with no clone in it, so the file drops from about 129s to
about 121s with the same 144 tests.
The two blocks that are pure functions are marked `describe.concurrent`.
The other two stay sequential: `createSimpleGit` spies on a prototype and
sets the custom env, and `gitRetry` stubs `NODE_ENV`.
This was the outcome of trying `it.concurrent` across the file. It cannot
help there, for two reasons stacked on each other. Vitest still runs
`beforeEach` for every concurrent test, so concurrency does not remove
the clone, it only interleaves several of them. And those interleaved
clones all write the same module singleton (`config`, `git`,
`gitInitialized`) and `GlobalConfig.localDir`, so one test's `initRepo()`
is overwritten by another's before its body runs. The only lever that
works is not running the clone for tests that do not need it.
Co-Authored-By: Claude Fable 5.1 <michael.kriese+claude-code@mend.io>
* test(util/git): share one clone across the read-only tests
The spec now has one root describe with three nested groups, split by how
much git setup a test needs:
- `without a repository`: pure functions, no setup at all.
- `shared clone`: 32 read-only tests. One bare clone of `base` and one
`initRepo()` in `beforeAll`, so each test costs only the git commands
it runs itself.
- `clone per test`: the tests that commit, push, check out or re-init.
They keep the fresh clone and `initRepo()` per test.
The `base` repository moves up to the root describe so both clone groups
can use it. Groups run in file order, and some of the writing tests change
`base` without restoring it, so the shared-clone group has to stay ahead.
`isBranchModified()` stays with the writing tests although it never touches
the repository: it caches per-branch results in `config.branchIsModified`,
which only `initRepo()` clears, and its tests query the same branch with
different ignored authors.
Three describes are split by test: `getFileList`, `getBranchUpdateDate`
and `diffCommitTree` each have one test that writes, which stays behind
under the same describe name with a pointer to the read-only half.
Same 144 tests; the file drops from about 121s to about 100s.
Co-Authored-By: Claude Fable 5.1 <michael.kriese+claude-code@mend.io>
* test(util/git): keep the 30s test timeout for every test
The `{ timeout: 30000 }` option used to sit on the single root describe
and so covered every test in the file. The split left it on the
`clone per test` group only, which put the 32 shared-clone tests on the
5s default even though they still run real git commands. CI is a lot
slower than a local machine, so the option moves up to the root describe
again; nested describes inherit it.
Hooks are unaffected either way: they run under `hookTimeout`, not the
suite option, and the shared `beforeAll` does the same work the old
`beforeEach` did.
Co-Authored-By: Claude Fable 5.1 <michael.kriese+claude-code@mend.io>
* test(util/git): reuse one clone for the local-only writers
A fourth nested group, `reused clone`, holds the 36 tests that write to
the local checkout but never to `origin` or `base`: `checkoutBranch`,
`isBranchModified`, `mergeToLocal`, `isFileModeEnabled`,
`getCommitMessages`, `setGitAuthor`, `getRepoStatus`, `fetchRevSpec` and
single tests of `getBranchUpdateDate`, `mergeBranch`, `deleteBranch` and
`diffCommitTree`.
The group clones once, but `initRepo()` and `syncGit()` run again before
every test. On an existing checkout `syncGit()` takes its fetch path:
`fetch --prune`, `reset --hard`, `checkout` of the default branch,
`clean -fd` and deletion of every other local branch. With the fresh
`config` from `initRepo()` that undoes everything such a test can do to
the repository or the module singleton, including the per-branch
`branchIsModified` cache that kept `isBranchModified()` out of the
shared-clone group. Local git config is the one thing it leaves alone, so
the `beforeEach` re-sets the author that `setGitAuthor()` writes.
That reset costs about 0.35s per test against about 0.87s for a clone, so
the file drops from about 100s to about 87s. Same 144 tests.
Co-Authored-By: Claude Fable 5.1 <michael.kriese+claude-code@mend.io>
* test(util/git): trim the describe comments to the essentials
Review feedback: the block comments read more like a narration of how
the layout came to be than like documentation. Each now states only
what a group's setup is, what a test may or may not do to belong there,
and the ordering constraint about `base`. The explanation of the
`isBranchModified()` cache is gone too, since that test now runs with a
fresh `initRepo()` in the reused-clone group.
Comments only, no test changes.
Co-Authored-By: Claude Fable 5.1 <michael.kriese+claude-code@mend.io>
* test(util/git): move isBranchModified() into the shared-clone group
`isBranchModified()` only reads the repository, but it stores results in
`config.branchIsModified`, which only `initRepo()` cleared. That kept its
16 tests in the reused-clone group at about 0.35s each.
A new `clearBranchIsModifiedCache()` helper, marked `@internal`, clears
that store. The shared-clone `beforeEach` calls it along with the cheap
config setters (`setUserRepoConfig`, `setGitAuthor`,
`setPlatformIgnoredAuthors`), so those tests now share one `initRepo()`
and cost a few milliseconds each. The file drops from about 87s to about
83s.
A dynamic import with `vi.resetModules()` was considered instead: a fresh
module instance has no `config` and `gitInitialized = false`, so every
test would need its own `initRepo()` and `syncGit()` on it, which is the
reused-clone cost again plus re-importing the module graph.
Co-Authored-By: Claude Fable 5.1 <michael.kriese+claude-code@mend.io>
---------
Co-authored-by: Claude Fable 5.1 <michael.kriese+claude-code@mend.io>
We dropped the "bot" from our terminology long ago, but it looks like
we've got some outdated docs + code that reference it.
We should remove this to keep things consistent.
A `fix`, as we're changing the commit author's name, and log lines.
Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Refs #45216
The object counterpart to `prefer-coerce-array`: `x ?? {}` becomes
`coerceObject(x)`. The rule flags a `??` whose default is a bare `{}`
literal, so a populated default or an asserted `({} as Foo)` still
passes; a `x ?? y ?? {}` chain maps onto `coerceObject(x, y)`.
Every hit across `lib/` converted cleanly, so no disable comments were
needed.
* refactor(linters): add prefer-coerce-array rule
Refs #45217
`x ?? []` and `coerceArray(x)` say the same thing, but only the second
one is searchable and consistent. The rule flags a `??` whose default is
a bare `[]` literal; a non-empty default or an asserted `([] as Foo[])`
still passes.
Applying it across `lib/` surfaced three spots the helper cannot cover,
which keep `?? []` with a disable comment or a narrower fix:
- `package-files.ts` destructures a tuple, which `coerceArray()` widens
- `generate.spec.ts` defaults `matchAll()`, an iterator rather than an array
- `mise/upgradeable-tooling.ts` declares a `Record` return, so its
`?? []` fallback was wrong to begin with and becomes `coerceObject()`
* refactor(platform/github): hoist the GHE version header lookup
`Object.entries(...).find()` returns a tuple, and defaulting it with
`?? []` only to destructure the second element reads as an array
default when it is not one. Name the found entry instead, which drops
the `?? []` rather than routing a tuple through `coerceArray()`.
Adds a custom oxlint rule renovate/v8-ignore-no-count that flags the
count form '/* v8 ignore next <N> */'. The count is not honored by
vitest's ast-v8-to-istanbul hint parser, which captures only the
keyword (if/else/next/file); 'next' always exempts exactly the next
AST node. Trailing counts therefore suggest a line range that is never
applied and mislead readers.
The rule is autofixable (drops the count, preserving actual behavior)
and has been applied to all existing occurrences.
* feat(lint): add renovate/prefer-is-helpers and renovate/prefer-is-object rules
Flag ad-hoc type checks that have @sindresorhus/is equivalents:
- .filter(Boolean) -> .filter(isTruthy)
- typeof x === 'string' -> isString(x)
- x === null || x === undefined -> isNullOrUndefined(x) (and negation)
The typeof x === 'object' shape lives in a separate suggestion-only rule
(renovate/prefer-is-object, kept at warn) because isObject()/isPlainObject()
are not drop-in equivalents: null, arrays and functions behave differently.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: fix renovate/prefer-is-helpers violations
Replace ad-hoc type checks with @sindresorhus/is helpers across lib/:
- 13x .filter(Boolean) -> .filter(isTruthy)
- 36x typeof x ==/!== 'string' -> isString(x) / !isString(x)
- 6x x === null || x === undefined -> isNullOrUndefined(x) (and negation)
Promote renovate/prefer-is-helpers to error now that the codebase is clean.
renovate/prefer-is-object stays at warn: its 15 remaining sites guard
parsed YAML/JSON or unknown values where isObject()/isPlainObject() are
not provably equivalent (null/array/function semantics).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: enforce renovate/prefer-is-object at error level
Resolve the 15 remaining warnings and raise the rule from warn to error:
- Migrate 10 sites to isObject() where the guarded value provably cannot
be a function (parsed YAML/JSON, typed exec command arguments, or
test-controlled fixtures): helm-values, woodpecker, bun, pre-commit
managers, exec utils, file package cache, and the github http spec.
In the bun manager this also fixes a latent crash where a null
`workspaces` value would throw on the `in` operator.
- Keep 5 sites as reasoned oxlint-disable comments where excluding
functions is load-bearing: the byte-exact fingerprint canonicalizer
(2), the circularity walker, the zod result guard, and the generic
hasKey helper.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: add regex support for gitIgnoredAuthors configuration
* feat: add tests for gitIgnoredAuthors regex handling in isBranchModified
* refactor: simplify ignored authors matching in isBranchModified function
* refactor: use for-of loop for ignored author filtering
* fix(git): treat non-regex gitIgnoredAuthors as exact strings
* fix(git): remove duplicate import of matchRegexOrGlobList
* fix(git): reorder import statements for consistency
* feat(git): enhance gitIgnoredAuthors handling with regex validation and logging
* feat(git): enhance gitIgnoredAuthors support for glob patterns and improve regex handling
* fix(git): add support for escaped glob patterns in matchRegexOrGlobList
* feat(git): update gitIgnoredAuthors documentation and improve regex handling for glob patterns
* fix(git): improve escaping of brackets and backslashes in glob pattern handling
* feat(git): normalize GitHub bot email addresses in ignored authors handling
* feat(tests): add custom author with brackets to file list in index.spec.ts
* feat(git): refactor ignored author handling to support regex normalization
* feat(git): enhance ignored authors handling to support regex patterns
* chore: retrigger ci
* fix(github): include commitTrailers in platform-native commits
The GitHub platform-native commit path (platformCommit) recreates the
commit via the REST API using only message, so trailers from
commitTrailers / :gitSignOff were dropped even though prepareCommit
wrote them into the local commit.
* test: use codeBlock for multi-line commit trailer expectations
* Remove unnecessary comment
* refactor(gerrit): format commit message with shared helper
Use formatCommitMessage for Gerrit reserved trailers so the final
message is built the same way as the git and GitHub platform-native
paths, then clear trailers so prepareCommit does not append them twice.
* Revert "refactor(gerrit): format commit message with shared helper"
This reverts commit d230fa3802.
* refactor(git): fold commit trailers into the message before scm
Remove CommitFilesConfig.trailers so platforms only ever see a fully
formatted message. Workers call formatCommitMessage once; prepareCommit
and GitHub platform-native commits pass message through as-is. Gerrit
parses the existing trailer block to merge Renovate-Branch/Change-Id.
* refactor(git): drop formatCommitMessage helper
Trailers are already folded into message at the worker boundary, so the
shared formatter only re-joined strings. Inline that at the few call sites
and keep splitCommitMessage for Gerrit's reserved-trailer merge.
* revert: keep the minimal commitTrailers platform-native fix
Drop the later refactors (fold trailers into message, Gerrit changes,
splitCommitMessage). Keep formatCommitMessage shared by prepareCommit
and GitHub platform-native commits only; Gerrit is unchanged.
* feat(config): add commitTrailers option
Renovate's only way to add git trailers today is `commitBody`, but it's
broken on Gerrit: Renovate appends its own `Renovate-Branch`/`Change-Id`
block after the body, so user trailers end up in a non-final message
block and are not real trailers per git-interpret-trailers rules.
This adds a `commitTrailers: string[]` option (each entry a full
`Key: value` line), standardizes Gerrit's own trailers through the same
mechanism so user trailers and platform trailers merge into one final
block, and verifies the result at runtime with `git interpret-trailers`
(commitlint's trailer-exists rule uses the same approach). The
`:gitSignOff` preset is migrated from `commitBody` to `commitTrailers`
so sign-off works correctly on Gerrit too.
* Simplify a bit
* Simplify docs even more
* Simplify another bit
* Add comment about rawExec
* Drop runtime commit trailer verification
Bypass simple-git only to emit a proactive warn log is not worth the
extra surface area. Config validation already enforces Key: value form.
* test(git): cover string commit message with trailers
Codecov patch failed on the partial ternary branch that normalizes a
string message before appending trailers.
* test(gerrit): fix trailers test after scm rework
Align the commitTrailers merge test with the post-rework Gerrit SCM:
use fakeSha helpers and drop the removed force flag.
* Improve wording on gitSignOff docs
* Validate compiled commitTrailers as well
* Improve trailers example
* Use isString instead
* Update lib/util/git/index.ts
Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>
* Update lib/workers/repository/onboarding/branch/create.ts
Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>
* Format code
* Fix type-check
* Fix tests
---------
Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>
* feat(lint): add renovate/prefer-fs-util and renovate/prefer-luxon rules
Two shared-util substitution rules, both at warn level:
- renovate/prefer-fs-util flags value imports of fs, node:fs,
fs/promises, node:fs/promises, fs-extra and fs-extra/esm in lib/
source files outside lib/util/fs/, pointing to the scoped helpers
from lib/util/fs. Spec files and type-only imports are exempt.
A config-only change via no-restricted-imports was considered but
rejected: that rule is at error severity globally and per-path warn
severity is not expressible, so the 13 existing call sites would
break the baseline.
- renovate/prefer-luxon flags new Date(...) and Date.now() in lib/
source files, pointing to Luxon per best-practices. Date/time
utility modules (lib/util/date, lib/util/pretty-time,
lib/util/timestamp) and spec files (vi.setSystemTime etc.) are
exempt.
Baseline: 13 prefer-fs-util and 45 prefer-luxon warnings, zero errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: use Luxon instead of new Date() at remaining call sites
Fixes all renovate/prefer-luxon violations for `new Date(...)`:
- `new Date().toISOString()` -> `DateTime.utc().toISO()` (byte-identical
output) in the http cache provider, schedule logging and PR cache.
- `new Date()` -> `DateTime.now().toJSDate()` for the git commit-date
fallback and the rubygems `syncedAt` markers (same `Date` values).
- rust-release-channel now computes the Rust 1.0 release date and nightly
version dates via `DateTime.utc(...)`; previously `new Date(y, m, d)`
used the local timezone while the epoch used UTC, so the comparison was
timezone-dependent at the boundary date. Tests run with TZ=UTC, where
behavior is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: use lib/util/fs helpers in repository worker
Fixes renovate/prefer-fs-util violations outside bootstrap code:
- `fs.ensureDir()` -> `ensureDir()` from lib/util/fs (same fs-extra call
underneath; it no-ops on an empty dir name, so the repository worker
spec now simulates the init failure via a rejected `initRepo` instead
of relying on `fs.ensureDir('')` throwing).
- `fs.remove(privateCacheDir())` -> `cachePathExists()` + `rmCache()`,
keeping the remove-if-present semantics while asserting cacheDir
containment.
Two call sites keep raw imports with reasoned disable comments:
- lib/util/modules.ts synchronously enumerates Renovate's own installed
module directories at import time.
- lib/util/cache/package/impl/sqlite.ts receives cacheDir as an explicit
argument during global initialization, before GlobalConfig (which the
scoped cache helpers resolve against) contains cacheDir.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(lint): promote prefer-fs-util and prefer-luxon to error
Both rules are narrowed to what is actually accepted practice before
being promoted:
prefer-luxon no longer flags `Date.now()`: it returns UTC-based epoch
milliseconds used for elapsed-time measurement and TTL arithmetic, which
is timezone-independent by construction. The mined reviewer feedback
behind this rule ("use Luxon for date arithmetic outside util") targets
timezone-dependent date construction/formatting, which cannot occur with
a raw millis number; the rule keeps flagging `new Date(...)`.
prefer-fs-util exempts code that legitimately needs raw fs access
because the scoped lib/util/fs helpers resolve against GlobalConfig
localDir/cacheDir, which does not exist yet or does not apply there:
- lib/logger/: log-file streams at user-configured system paths during
bootstrap; lib/util/fs also depends on the logger (import cycle).
- lib/workers/global/: creates baseDir/cacheDir themselves and
reads/writes user-supplied paths before GlobalConfig is seeded.
- lib/config-validator.ts: standalone CLI without GlobalConfig.
- lib/util/git/: owns the localDir lifecycle (clone, emptyDir, symlinks)
and private-key files with sync cleanup on process exit.
Also fixes a pre-existing tsc error in prefer-fs-util.js: `importKind`
is a TypeScript-ESTree extension missing from eslint's base AST types.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(fs): make rmCache ignore missing paths
All rmCache callers use it for cleanup, where a missing path is a
no-op, so bake force: true in instead of guarding call sites with
an existence check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(linters): exempt lib/instrumentation from prefer-fs-util
`lib/instrumentation/file-exporter.ts` landed on main while this branch
was open and imports `node:fs/promises` directly.
The exporter writes to a user-configured system path
(`RENOVATE_TRACING_FILE_EXPORTER_PATH`) outside `localDir`/`cacheDir`,
so the scoped helpers do not apply. `lib/instrumentation` is also
deliberately kept out of the module graph of instrumented libraries
(see the `⚠️` note in `lib/renovate.ts`), so it must not pull in
`lib/util/fs` and its config/logger dependencies. Exempt the directory,
as is already done for `lib/logger`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: shorten doc
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(platform): get all branch update dates in a more performant manner
When Renovate is working on a repository with many /potential/ branches,
as well as some actually created branches, it's possible for the
calculation for `commitHourlyLimit` to end up taking significant time.
This can lead to a less performant job generally, but can sometimes lead
to the Renovate job failing to complete, and then timing out when
executed by a job runner.
To handle this, we can instead use use `git for-each-ref` to look up
each Git ref (remotely) and request the commit date, which leads to a
much more performant lookup.
Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
* fixup! feat(platform): get all branch update dates in a more performant manner
Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
* fixup! Merge remote-tracking branch 'origin/HEAD' into wip/commit-hourly-slow
---------
Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
* ci(linter): add renovate/v8-ignore-reason rule
Flag coverage-ignore comments ("v8 ignore") that carry no justification.
Accepted forms: "-- reason" / "--- reason" (recommended) and the legacy
colon form "v8 ignore <kind>[ <count>]: reason", which is already
widespread in the codebase (29 sites). Bare "v8 ignore stop" markers are
exempt because the justification lives on the paired "start" marker. The
"v8 ignore next <N>" count form remains allowed; this rule only requires
a justification.
Registered as "warn" instead of "error": the repo currently has 119
reason-less v8 ignore comments, too many to fix mechanically in one PR,
so existing sites are left untouched and can be fixed incrementally.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ci(linter): promote renovate/v8-ignore-reason to error
Record the empirical verdict on the contested "v8 ignore next <N>" count
semantics in the rule doc: verified against vitest 4 with
ast-v8-to-istanbul that the count is not honored - the ignore hint always
exempts exactly the next AST node regardless of N. Counts are therefore
inert and stay allowed; the rule keeps requiring only a justification.
Promoting to error so all remaining reason-less coverage-ignore comments
must be fixed; the burndown follows in subsequent commits.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RxHFNsSworLcv23xXk9Hk
* refactor: add justifications to coverage-ignore comments (batch 1)
Give every reason-less "v8 ignore" comment outside the large platform
index files a site-specific justification explaining why the exempted
branch is untestable or defensive, as now required by the
renovate/v8-ignore-reason rule.
Also guard against a nullish comment.loc in the rule itself; the
merged-in stricter tools typechecking flagged the unguarded access.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RxHFNsSworLcv23xXk9Hk
* refactor: add justifications to coverage-ignore comments (batch 2)
Cover the five large platform index files (github, bitbucket,
bitbucket-server, codecommit, gitlab) with site-specific reasons on
every "v8 ignore" comment, completing the renovate/v8-ignore-reason
burndown. Two meaningless ignore hints inside commented-out CodeCommit
mergePr code are dropped instead of annotated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RxHFNsSworLcv23xXk9Hk
* Apply suggestions from code review
Co-authored-by: Michael Kriese <michael.kriese@gmx.de>
* Update lib/util/regex.ts
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Michael Kriese <michael.kriese@gmx.de>
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
* refactor(oxlint): enable func-style
Enable the built-in func-style rule as ["warn", "declaration"] so that
named functions are written as function declarations instead of arrow or
function expressions assigned to variables, matching the convention in
docs/development/best-practices.md (Functions section). Inline arrow
callbacks are unaffected; prefer-arrow-callback continues to cover the
callback direction.
Severity is "warn" instead of "error" as a fallback: a repo-wide run
found 196 pre-existing violations (70 in spec files, 126 in production
code), exceeding the 80-violation fix threshold for this change, so no
violations were fixed here. Follow-up work can convert the offenders
(largest clusters: lib/modules/versioning/ruby, lib/util/cache/package,
lib/modules/versioning/maven, lib/modules/versioning/gradle) and then
raise the rule to "error". Note oxlint 1.72 also supports an
allowTypeAnnotation option if typed consts should remain arrows.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ci(linter): enforce func-style at error
* refactor(func-style): convert const-arrow functions to declarations in with-cache spec and ruby versioning
* refactor(func-style): convert const-arrow functions to declarations in maven/gradle modules
* refactor(func-style): convert const-arrow functions to declarations in remaining versioning modules
* refactor(func-style): convert const-arrow functions to declarations in platform modules
Discriminated-union closures (ensureCommentRemoval byTopic/byContent
helpers in github, gitlab, bitbucket, bitbucket-server) needed the
narrowed field passed explicitly as a parameter, since function
declarations are hoisted and lose the outer if-narrowing that inline
arrow-function closures previously relied on.
* refactor(func-style): convert const-arrow functions to declarations across util/manager/datasource modules
One case (logger/renovate-logger.ts logOnceFn) is kept as an arrow
function with a documented oxlint-disable: it is stored on
this.logger.once[level] and relies on lexically-bound this, which a
hoisted function declaration would not preserve.
* refactor(func-style): convert remaining const-arrow test helpers to declarations
get-updated.spec.ts: hoisted expectPipCompilePackageAndLockFile to
module scope (it only closes over the module-level pipCompile mock)
and added a documented oxlint-disable for vitest/expect-expect on its
caller test, since oxlint's expect-expect check does not trace
assertions through an externally-defined helper function.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(config): make platformCommit global-only
* fix(git): reapply committer check for isBranchModified
Reapply #42975 (reverted in #43171): treat a different committer
(rebase/amend/Gerrit UI) as a branch modification.
* fix(git): ignore platformCommit committer from init-time list
Register noreply@${host} at GitHub initPlatform when platform commits
are active. isBranchModified only consumes the list — correct for
github.com, ghe.com, and self-hosted GHES.
Share isPlatformCommitEnabled() with GithubScm so commit path and
ignore-list registration use the same rule.
* refactor: stop threading platformCommit through commits
Decision is resolved once in GitHub initPlatform
(isPlatformCommitEnabled). Workers no longer pass the raw option on
CommitFilesConfig — it was unused after that change.
* refactor: simplify platformCommit / ignore-author wiring
Drop cached platformCommitEnabled flag — isPlatformCommitEnabled() is
a pure read of GlobalConfig + isGHApp. Trim redundant tests and
trace noise.
* Simplify env var setting
* revert: keep platformCommit packageRules-capable
Do not make platformCommit global-only. Restore CommitFilesConfig
threading and GithubScm reading commitConfig.platformCommit so rules
can still set platformCommit per package (see #44585 discussion).
* fix(github): always ignore platformCommit noreply committer
Do not gate noreply@host on isPlatformCommitEnabled (that needed
global platformCommit). Always register host noreply at init so
packageRules can still flip platformCommit per commit while
isBranchModified stays correct.
Remove isPlatformCommitEnabled; GithubScm again uses
commitConfig.platformCommit.
* Simplify some stuff
* Add one assertion
* Change some comments
* fix(gerrit): remove memCache to fix prNo coming as null when pr-created
* perf(gerrit): remove custom scm functions
* Fully remove login parameter from configureScm()
* Fetch refspecs in batches
* Remove unnecessary error handling
* Add tests
* Throw if branch init fails
* Simplify tests a little
* Reduce more the tests
* More test changes
* Reuse the existing test setup more
* Undo test import change
* Improve initializeBranchesFromRefspecs JSDoc
* The same for deleteBranchCreatedFromRefspec
* Reduce a log to trace level
* Abandon Code-Review -2 changes before initializing repository
* Simply initialize the branches after git.initRepo()
* Rework approach to be more generic
* Add TODO comment about commitAndPush()
* fix(gerrit): defer change creation to `createPr()`
Signed-off-by: Felipe Santos <felipe.santos@ericsson.com>
* Refactor hashtags handling
* Add prTitle to all missing commitAndPush()
* Fix type check
* Fix test issue
* Fix gerrit tests
* Add warning when prTitle is set in updatePr
* Remove now useless labels from commitAndPush
* Fix gerrit client coverage
* Fix gerrit scm coverage
* Fix rebase coverage
* Improve TODO about assignees
* Fix formatting
* Fix review comment
* Extract to a pushForReview function
* Fix after #41382
* Fix onboarding
* Cleanup code
* Fix ESLint
* Fix tests
* Restore gitRetry for git.merge
* Fix tests
* Simplify hashtags code
* Delete pendingChangeBranch in pushForReview()
* Fix test name
* Remove unnecessary test comments
* Improve test name again
* Fix another test name
* Delete local branch in Gerrit SCM
* Add test for deleting pending branch
* Add tests for pushForReview()
* Simplify/optimize commitAndPush
* Align jsdoc terminology on virtual branches
* Remove no longer relevant TODO
* Update virtal branch on push
* Simplify deleteVirtualBranch
* Fix test case name
* Improve another test name
* Restore test deleted by mistake
* Fix another small test inconsistency
* Fix coverage
* Fix Renovate not detecting modified commits
* Simplify Gerrit commitAndPush by leveraging git.commitFiles
* Fix some code style issues
* Revert "Simplify Gerrit commitAndPush by leveraging git.commitFiles"
This reverts commit d71edcc279.
* Fix another small code style issue
* Use isUndefined instead
* Fix one small test style issue
* Fix more code style issues
* Fix Gerrit mergeToLocal trying to fetch (and probably failing)
* Extract refs/remotes/origin to a function
* Rename virtualBranchRef to remoteBranchRef
* Fix remoteBranchRef tsc
* Consistently use currentRevision in Gerrit tests
* Remove unnecessary type assertion
* Instrument "fetch virtual branches"
* Remove pendingChangeBranches, avoid overriding deleteBranch and mergeToLocal
* Optimize virtual branches fetch (no longer requires update-ref), convert virtualBranches to Record, rename setVirtualBranch to registerVirtualBranch
* Combine registerVirtualBranch and updateVirtualBranch into a single setVirtualBranch, and update ref in virtual branch too
* Fix virtualBranches not being initialized
* Add some debug logs
* Simplify mergeToLocal for virtual branches
* Revert "Simplify mergeToLocal for virtual branches"
This reverts commit 375d57a474.
* Remove useless isVirtual const
* test(git): add toThrow message for virtualBranches fetch error
Satisfy vitest/require-to-throw-message so oxlint passes.
* Update lib/util/git/types.ts
Co-authored-by: RahulGautamSingh <rahultesnik@gmail.com>
* Address review feedback
---------
Signed-off-by: Felipe Santos <felipe.santos@ericsson.com>
Co-authored-by: RahulGautamSingh <rahultesnik@gmail.com>
* feat(git): validate length commit shas
* test: adapt test mocks
* chore: update import paths
* feat(git): make the validation more strict and support repositories using sha256
* test: add fakeSha function for deterministic git commit shas in tests
* fix(github): use base_tree in pushFiles to avoid org ruleset file-path restrictions (#42554)
pushFiles sent the entire repo tree to POST /git/trees without base_tree.
GitHub validates all file paths against org rulesets, rejecting the tree
when it contains protected files (e.g. semgrep.yaml) — even if Renovate
only changed unrelated paths.
Use base_tree (parent commit's tree) with only the changed files from
git diff-tree, so unchanged protected files are inherited without
validation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(git): add coverage for getCommitTreeSha and diffCommitTree
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(test): assert specific files instead of hardcoding diff count
The test fixture creates 2 known files between develop and
defaultBranch (master_file, file_to_delete), but CI produces 3
changed files — likely a .gitattributes from the CI environment.
Assert on the known files rather than the total count.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(git): handle renames, copies, and submodules in diffCommitTree
- Regex now matches multi-char status codes (R100, C100) and
tab-separated source/target paths for renames and copies.
- Renames emit a deletion for the source path + addition for target.
- Derive tree item type from mode: 160000→commit, 040000→tree,
otherwise blob.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(git): address review comments in diffCommitTree
Keep git utility types centralized and make diff status handling easier to follow.
Made-with: Cursor
* test(git): cover rename handling in diffCommitTree
Verify that renamed files are emitted as a deletion for the old path and an addition for the new path, matching the tree delta expected by GitHub.
Made-with: Cursor
* fix(git): validate parsed tree SHA
Fail fast when commit output cannot be parsed into a tree SHA so pushFiles does not propagate an invalid base_tree, and cover the failure path in tests.
Made-with: Cursor
* fix(git): enable rename detection and cover remaining diffCommitTree branches
Add -M to diff-tree so Git emits R status for renames, exercising the
existing case 'R' handler. Add focused tests for rename parsing,
submodule mode (160000), and tree mode (040000).
Made-with: Cursor
* fix(git): clarify copy vs rename comment in diffCommitTree
R uses both tab-separated paths (delete old + add new), while C falls
through to default where only the target path matters.
Made-with: Cursor
* refactor(git): remove dead listCommitTree and TreeItem
Nothing imports listCommitTree after the switch to diffCommitTree with
base_tree. Remove the function, its regex, its test, and the TreeItem
interface.
Made-with: Cursor
* refactor(git): tighten DiffTreeItem.type to GitObjectType union
Define GitObjectType = 'blob' | 'tree' | 'commit' and use it in
DiffTreeItem and treeTypeFromMode instead of plain string.
Made-with: Cursor
* fix(git): remove unreachable nullish coalescing in getCommitTreeSha
split()[0] on a non-empty string is always defined, so the ?? ''
fallback was unreachable and caused an uncovered branch in Codecov.
Made-with: Cursor
* fix(git): add v8 ignore for partial branch on throw template literal
v8 reports template literal interpolations inside throw as partial
branch coverage even when the line is tested. Use the established
v8 ignore pattern to suppress the false positive.
Made-with: Cursor
* fix(github): guard pushFiles against empty diff from diffCommitTree
GitHub's POST /git/trees rejects an empty tree array with 422
"Invalid tree info". While prepareCommit already prevents no-op
commits from reaching pushFiles, add a defensive early return so
the failure mode is a clear log message instead of a swallowed 422.
Made-with: Cursor
* docs(github): document hybrid git/REST commit strategy in pushFiles
Restore architectural knowledge from PRs #13824 and #14271 that was
lost when the old "for reasons unknown" comments were replaced. The
push-then-recreate pattern exists because custom refs avoid CI triggers,
REST-created commits get GitHub signing, and GraphQL lacks force-push
and file mode support.
Made-with: Cursor
* fix(git): wrap bare regex literals in regEx() in tests
Use the project's regEx() helper instead of bare /pattern/ in
toMatch and stringMatching calls, consistent with repo conventions.
Made-with: Cursor
* fix(git): revert regEx() in expect.stringMatching (only accepts bare RegExp)
expect.stringMatching() is a Vitest/Jest API that requires a native
RegExp, not the wrapped object from Renovate's regEx() helper.
Made-with: Cursor
* fix(git): drop line count from v8 ignore next
Vitest uses ast-v8-to-istanbul which is AST-aware: it ignores the
entire next AST node (here the whole throw statement), making the
line count unnecessary.
Made-with: Cursor
* refactor(git): replace magic mode strings with GitTreeMode enum
Introduce a documented GitTreeMode enum in types.ts and use it in
treeTypeFromMode, replacing raw '160000'/'040000' literals with named
members linked to the git data model documentation.
Made-with: Cursor
* fix(git): use const object instead of enum for GitTreeMode
Node's TypeScript strip-only mode does not support enum syntax.
Use `as const` object pattern consistent with the rest of the codebase.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(config/global): return default values for `GlobalConfig.get`
As noted in #39949, it can be awkward working with in `GlobalConfig.get`, where
we would previously need to specify the default value when calling `.get`,
which could lead to drift with the defaults for a given config option.
To reduce this risk, we can rely on a given `default` for a configuration
option, providing that as a default.
Because this now definitely returns a value that matches the config option, it
now allows us to remove `!` assertions.
* fixup! fix(config/global): return default values for `GlobalConfig.get`
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
* fixup! fix(config/global): return default values for `GlobalConfig.get`
* test(presets): remove impossible test
As it's now not possible to set a value as `undefined` (as
`GlobalConfig` will then use the default), this test no longer validates
anything.
* test(workers/repository): add mock
Now `platform` defaults to `github`, we need to mock out the call to
fetch config.
Co-authored-by: Claude Sonnet 4.6 <jamie.tanna+claude-code@mend.io>
* test: coverage
* test: coverage
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <jamie.tanna+claude-code@mend.io>
* fix(git): allow customEnvVariables to override GIT_SSH_COMMAND
* test: simplify env cleanup, rely on beforeEach reset
* refactor(git): use extraEnv for GIT_SSH_COMMAND default
Move GIT_SSH_COMMAND from forcedEnv to extraEnv (lowest priority)
so that both process.env and customEnvVariables override the default
naturally, without conditional logic.
Suggested-by: viceice
* central function for creating SimpleGit instance
* Updated tests.
* Set the LANG and LC_ALL environment variables for all Git operations instead of just initRepo.
* Moved GIT_SSH_COMMAND to `createSimpleGit` function.
* Removed unnecessary custom env vars.
* feat(update-date-cache): implement caching for branch update dates
* Try fix coverage
* Fix some test style inconsistencies
* Fix coverage
* Fix test
* Fix test testing the same path
* fix(gerrit): defer change creation to `createPr()`
Signed-off-by: Felipe Santos <felipe.santos@ericsson.com>
* Refactor hashtags handling
* Add prTitle to all missing commitAndPush()
* Fix gerrit tests
* Add warning when prTitle is set in updatePr
* Remove now useless labels from commitAndPush
* Fix gerrit client coverage
* Fix gerrit scm coverage
* Fix rebase coverage
* Improve TODO about assignees
* Fix formatting
* Fix review comment
* Extract to a pushForReview function
* Fix after #41382
* Fix onboarding
* Cleanup code
* Fix ESLint
* Fix tests
* Restore gitRetry for git.merge
* Fix tests
* Simplify hashtags code
* Delete pendingChangeBranch in pushForReview()
* Fix test name
* Remove unnecessary test comments
* Improve test name again
* Fix another test name
* Delete local branch in Gerrit SCM
* Add test for deleting pending branch
* Add tests for pushForReview()
* Fix test case name
* Improve another test name
* Fix another small test inconsistency
* Fix some code style issues
* Use isUndefined instead
* Fix one small test style issue
---------
Signed-off-by: Felipe Santos <felipe.santos@ericsson.com>
As noted in #41953, the introduction of `getBranchUpdateDate` did not
take into account the use of a repository cache.
When using a repository cache, the repo is not cloned until it is needed
(and various metadata is used from the cache instead).
However, when using a cache (with a repo not yet cloned) this results in
`getBranchUpdateDate` failing (and a debug log appearing).
We can instead handle this by performing a repo sync at this point.
Co-authored-by: Claude Sonnet 4.6 <jamie.tanna+claude-code@mend.io>
* fix(workers/repository): cleanup localDir before recursive retry after automerge
* add test for failed directory cleanup
* fix(git): don't pass extraCloneOpts to ls-remote when local clone exists
* test(util/git): add test coverage for `X_CLEAR_HOOKS`
Co-authored-by: Claude Opus 4.6 <jamie.tanna+claude-code@mend.io>
* fix(util/git): allow setting `config.hooksPath`
Since simple-git 3.35.2's update in #42588, the setting of
`config.hooksPath` is marked as "unsafe", and does not allow setting
this configuration.
This only affects users who specify the experimental self-hosted config
option, `RENOVATE_X_CLEAR_HOOKS`.
However, the primary user of this is Mend-hosted Renovate which, as
noted in #42628, resulted in all jobs failing to run.
We can instead conditionally set the `allowUnsafeHooksPath`
configuration if we're running with `RENOVATE_X_CLEAR_HOOKS`.
We need to refactor our test to make sure that we're re-initialising
`git` at the point the `RENOVATE_X_CLEAR_HOOKS` is set.
Co-authored-by: Claude Opus 4.6 <jamie.tanna+claude-code@mend.io>
---------
Co-authored-by: Claude Opus 4.6 <jamie.tanna+claude-code@mend.io>
* chore(logger): serialize `DateTime` to ISO strings
As an alternative to 2228a79015, we can
instead serialize any `DateTime` objects that are present in our logging
fields.
Co-authored-by: Claude Sonnet 4.5 <jamie.tanna+github-copilot@mend.io>
* Revert "fix(util/git): log date as string (#41360)"
This reverts commit 2228a79015.
* test(logger): clarify Luxon dates
As we'll follow-up with native dates in the future.
---------
Co-authored-by: Claude Sonnet 4.5 <jamie.tanna+github-copilot@mend.io>
feat(utilgit): log warning if file(s) contain hidden Unicode characters
With the rise of prompt injection, there is an increased number of
attacks occurring using hidden Unicode characters.
These hidden characters can also confuse other tools, so it's best for
Renovate to surface these to repository owners, as we read files that
may be problematic.
To avoid multiple reads of files, we can do this every time our
`readLocalFile` function is called, as we already have the file content.
An alternate option would be that in `workers/repository` we could check
the read package files, but given `extractAllPackageFiles` may be used
by a Manager, we can't inject the logic in, without Managers needing to
implement the logic themselves.
This should be performant, and not introduce too many false-positives,
but this will also depend on how (package) files are set up in
repositories.
The list of suspicious characters has been largely compiled by Claude
Sonnet 4.5.
Co-authored-by: Claude Sonnet 4.5 <jamie.tanna+github-copilot@mend.io>
* fix(git): instrument `SimpleGit` operations
Now we have the basis for instrumentation for SimpleGit, we can now
fully integrate it.
This creates a wrapper, `InstrumentedSimpleGit`, which has a simplified
interface for `SimpleGit` to allow us to focus on the important parts of
the interface for what we need, instead of trying to correctly extend /
implement SimpleGit's interface (and slightly complex types).
In each of our methods, we then make sure we instrument it with the
relevant Git subcommand being invoked.
Co-authored-by: gpt-4.1 <jamie.tanna+github-copilot@mend.io>
* test(instrumentation): remove unused path
Currently, SimpleGit's `getRemotes` function being passed a `verbose:
false` is the same as not providing a `verbose`.
This fixes the code coverage failures, as we do not call this.
---------
Co-authored-by: gpt-4.1 <jamie.tanna+github-copilot@mend.io>