Files
renovate/lib/modules/manager/gomod/artifacts-extra.ts
Jamie Tanna 9ecbaa8581 chore(manager/gomod): clarify how artifact updates work with minimumReleaseAge (#45526)
When Go updates dependencies using i.e. `go get` or `go mod download`,
it uses Minimal Version Selection (MVS)[0], which takes the lowest
version available from the given packages.

In the case of an updated dependency, in practice this means that Go
will chose the version of an indirect (transitive) package dependency as
the version that the updated package points to in their `go.mod`, which
must be older than the updated package, as the updated package has only
just been updated.

This protects against cases in other ecosystems where an unpinned
transitive dependency can be pulled by an updated package, potentially
bypassing `minimumReleaseAge`.

However, I can imagine there may be some questions about this, so we can
preemptively add a note into the `Artifact update notice` when
`minimumReleaseAge` is set.

[0]: https://go.dev/ref/mod#minimal-version-selection
2026-08-28 11:39:19 +00:00

139 lines
3.8 KiB
TypeScript

import { diffLines } from 'diff';
import { markdownTable } from 'markdown-table';
import type { UpdateArtifactsConfig } from '../types.ts';
import { parseLine } from './line-parser.ts';
import type { ExtraDep } from './types.ts';
export function getExtraDeps(
goModBefore: string,
goModAfter: string,
excludeDeps: string[],
): ExtraDep[] {
const result: ExtraDep[] = [];
const diff = diffLines(goModBefore, goModAfter, {
newlineIsToken: true,
});
const addDeps: Record<string, string> = {};
const rmDeps: Record<string, string> = {};
for (const { added, removed, value } of diff) {
if (!added && !removed) {
continue;
}
const res = parseLine(value);
if (!res) {
continue;
}
const { depName, depType, currentValue } = res;
if (!depName || !currentValue) {
continue;
}
let expandedDepName = depName;
// NOTE: Right now the only special depType we care about is 'toolchain' because the table
// rendering prior to this change was ambiguous with regards to go version vs toolchain
// version updates.
if (depType === 'toolchain') {
expandedDepName = `${depName} (${depType})`;
}
if (added) {
addDeps[expandedDepName] = currentValue;
} else {
rmDeps[expandedDepName] = currentValue;
}
}
for (const [depName, currentValue] of Object.entries(rmDeps)) {
if (excludeDeps.includes(depName)) {
continue;
}
const newValue = addDeps[depName];
if (newValue) {
result.push({
depName,
currentValue,
newValue,
});
}
}
return result;
}
export function extraDepsTable(extraDeps: ExtraDep[]): string {
const tableLines: string[][] = [];
tableLines.push(['**Package**', '**Change**']);
for (const { depName, currentValue, newValue } of extraDeps) {
const depNameQuoted = `\`${depName}\``;
const versionChangeQuoted = `\`${currentValue}\` -> \`${newValue}\``;
tableLines.push([depNameQuoted, versionChangeQuoted]);
}
return markdownTable(tableLines, {
align: ['l', 'l'],
});
}
export function getExtraDepsNotice(
goModBefore: string | null,
goModAfter: string | null,
excludeDeps: string[],
{ minimumReleaseAge }: Pick<UpdateArtifactsConfig, 'minimumReleaseAge'>,
): string | null {
if (!goModBefore || !goModAfter) {
return null;
}
const extraDeps = getExtraDeps(goModBefore, goModAfter, excludeDeps);
if (extraDeps.length === 0) {
return null;
}
const noticeLines: string[] = [
'In order to perform the update(s) described in the table above, Renovate ran the `go get` command, which resulted in the following additional change(s):',
'\n',
];
const goUpdated = extraDeps.some(({ depName }) => depName === 'go');
const toolchainUpdated = extraDeps.some(
({ depName }) => depName === 'go (toolchain)',
);
const otherDepsCount =
extraDeps.length - (goUpdated ? 1 : 0) - (toolchainUpdated ? 1 : 0);
if (otherDepsCount === 1) {
noticeLines.push(`- ${otherDepsCount} additional dependency was updated`);
} else if (otherDepsCount > 1) {
noticeLines.push(
`- ${otherDepsCount} additional dependencies were updated`,
);
}
if (goUpdated) {
noticeLines.push(
'- The `go` directive was updated for compatibility reasons',
);
}
if (minimumReleaseAge) {
noticeLines.push('\n');
noticeLines.push(
`Due to Go's usage of [Minimal Version Selection (MVS)](https://go.dev/ref/mod#minimal-version-selection), these packages have been updated to the minimum version available, so will still abide by \`minimumReleaseAge=${minimumReleaseAge}\``,
);
}
noticeLines.push('\n');
noticeLines.push('Details:');
noticeLines.push('\n');
noticeLines.push(extraDepsTable(extraDeps));
return noticeLines.join('\n');
}