Skip to content

Commit

Permalink
feat(gomod): Notify extra packages updated by "go get" (#28938)
Browse files Browse the repository at this point in the history
Signed-off-by: Quentin BERTRAND <contact@quentinbertrand.fr>
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
Co-authored-by: Rhys Arkins <rhys@arkins.net>
Co-authored-by: Quentin BERTRAND <contact@quentinbertrand.fr>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Miles Budnek <miles.budnek@fortra.com>
Co-authored-by: Alex Kessock <akessock@gmail.com>
Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>
Co-authored-by: Florian Greinacher <florian.greinacher@siemens.com>
Co-authored-by: ddovile <71456312+ddovile@users.noreply.github.com>
Co-authored-by: Dovile Dikoviciute <Dovile.Dikoviciute@bentley.com>
Co-authored-by: lstoeferle <48953604+lstoeferle@users.noreply.github.com>
Co-authored-by: Stéphane Goetz <onigoetz@onigoetz.ch>
  • Loading branch information
13 people committed May 27, 2024
1 parent 1965526 commit cc5f68e
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 7 deletions.
138 changes: 138 additions & 0 deletions lib/modules/manager/gomod/artifacts-extra.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { codeBlock } from 'common-tags';
import {
extraDepsTable,
getExtraDeps,
getExtraDepsNotice,
} from './artifacts-extra';
import type { ExtraDep } from './types';

describe('modules/manager/gomod/artifacts-extra', () => {
const goModBefore = codeBlock`
go 1.22.0
require (
github.com/foo/foo v1.0.0
github.com/bar/bar v2.0.0
)
replace baz/baz => qux/qux
`;

const goModAfter = codeBlock`
go 1.22.2
// Note the order change
require (
github.com/bar/bar v2.2.2
github.com/foo/foo v1.1.1
)
replace baz/baz => quux/quux
`;

describe('getExtraDeps', () => {
it('detects extra dependencies', () => {
const excludeDeps = ['github.com/foo/foo'];

const res = getExtraDeps(goModBefore, goModAfter, excludeDeps);

expect(res).toEqual([
{
depName: 'go',
currentValue: '1.22.0',
newValue: '1.22.2',
},
{
depName: 'github.com/bar/bar',
currentValue: 'v2.0.0',
newValue: 'v2.2.2',
},
] satisfies ExtraDep[]);
});
});

describe('extraDepsTable', () => {
it('generates a table', () => {
const extraDeps: ExtraDep[] = [
{
depName: 'github.com/foo/foo',
currentValue: 'v1.0.0',
newValue: 'v1.1.1',
},
{
depName: 'github.com/bar/bar',
currentValue: 'v2.0.0',
newValue: 'v2.2.2',
},
];

const res = extraDepsTable(extraDeps);

expect(res).toEqual(
[
'| **Package** | **Change** |',
'| :------------------- | :------------------- |',
'| `github.com/foo/foo` | `v1.0.0` -> `v1.1.1` |',
'| `github.com/bar/bar` | `v2.0.0` -> `v2.2.2` |',
].join('\n'),
);
});
});

describe('getExtraDepsNotice', () => {
it('returns null when one of files is missing', () => {
expect(getExtraDepsNotice(null, goModAfter, [])).toBeNull();
expect(getExtraDepsNotice(goModBefore, null, [])).toBeNull();
});

it('returns null when all dependencies are excluded', () => {
const excludeDeps = ['go', 'github.com/foo/foo', 'github.com/bar/bar'];
const res = getExtraDepsNotice(goModBefore, goModAfter, excludeDeps);
expect(res).toBeNull();
});

it('returns a notice when there are extra dependencies', () => {
const excludeDeps = ['go', 'github.com/foo/foo'];

const res = getExtraDepsNotice(goModBefore, goModAfter, excludeDeps);

expect(res).toEqual(
[
'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):',
'',
'',
'- 1 additional dependency was updated',
'',
'',
'Details:',
'| **Package** | **Change** |',
'| :------------------- | :------------------- |',
'| `github.com/bar/bar` | `v2.0.0` -> `v2.2.2` |',
].join('\n'),
);
});

it('adds special notice for updated `go` version', () => {
const excludeDeps = ['github.com/foo/foo'];

const res = getExtraDepsNotice(goModBefore, goModAfter, excludeDeps);

expect(res).toEqual(
[
'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):',
'',
'',
'- 1 additional dependency was updated',
'- The `go` directive was updated for compatibility reasons',
'',
'',
'Details:',
'| **Package** | **Change** |',
'| :------------------- | :------------------- |',
'| `go` | `1.22.0` -> `1.22.2` |',
'| `github.com/bar/bar` | `v2.0.0` -> `v2.2.2` |',
].join('\n'),
);
});
});
});
112 changes: 112 additions & 0 deletions lib/modules/manager/gomod/artifacts-extra.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { diffLines } from 'diff';
import markdownTable from 'markdown-table';
import { parseLine } from './line-parser';
import type { ExtraDep } from './types';

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, currentValue } = res;
if (!depName || !currentValue) {
continue;
}

if (added) {
addDeps[depName] = currentValue;
} else {
rmDeps[depName] = 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[],
): 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 otherDepsCount = extraDeps.length - (goUpdated ? 1 : 0);

if (otherDepsCount > 0) {
noticeLines.push(`- ${otherDepsCount} additional dependency was updated`);
}

if (goUpdated) {
noticeLines.push(
'- The `go` directive was updated for compatibility reasons',
);
}

noticeLines.push('\n');
noticeLines.push('Details:');
noticeLines.push(extraDepsTable(extraDeps));

return noticeLines.join('\n');
}
44 changes: 44 additions & 0 deletions lib/modules/manager/gomod/artifacts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { StatusResult } from '../../../util/git/types';
import * as _hostRules from '../../../util/host-rules';
import * as _datasource from '../../datasource';
import type { UpdateArtifactsConfig } from '../types';
import * as _artifactsExtra from './artifacts-extra';
import * as gomod from '.';

type FS = typeof import('../../../util/fs');
Expand All @@ -28,11 +29,13 @@ jest.mock('../../../util/fs', () => {
};
});
jest.mock('../../datasource', () => mockDeep());
jest.mock('./artifacts-extra', () => mockDeep());

process.env.CONTAINERBASE = 'true';

const datasource = mocked(_datasource);
const hostRules = mocked(_hostRules);
const artifactsExtra = mocked(_artifactsExtra);

const gomod1 = codeBlock`
module github.com/renovate-tests/gomod1
Expand Down Expand Up @@ -1866,6 +1869,47 @@ describe('modules/manager/gomod/artifacts', () => {
expect(execSnapshots).toMatchObject(expectedResult);
});

it('returns artifact notices', async () => {
artifactsExtra.getExtraDepsNotice.mockReturnValue('some extra notice');
GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
fs.readLocalFile.mockResolvedValueOnce('Current go.sum');
fs.readLocalFile.mockResolvedValueOnce(null); // vendor modules filename
fs.readLocalFile.mockResolvedValueOnce('someText\n\ngo 1.17\n\n');
mockExecAll();
git.getRepoStatus.mockResolvedValueOnce(
partial<StatusResult>({
modified: ['go.sum', 'main.go'],
}),
);
fs.readLocalFile
.mockResolvedValueOnce('New go.sum')
.mockResolvedValueOnce('New main.go')
.mockResolvedValueOnce('New go.mod');
datasource.getPkgReleases.mockResolvedValueOnce({
releases: [{ version: '1.17.0' }, { version: '1.14.0' }],
});
const res = await gomod.updateArtifacts({
packageFileName: 'go.mod',
updatedDeps: [
{ depName: 'github.com/google/go-github/v24', newVersion: 'v28.0.0' },
],
newPackageFileContent: gomod1,
config: {
updateType: 'major',
postUpdateOptions: ['gomodUpdateImportPaths'],
},
});

expect(res).toEqual([
{ file: { type: 'addition', path: 'go.sum', contents: 'New go.sum' } },
{ file: { type: 'addition', path: 'main.go', contents: 'New main.go' } },
{
file: { type: 'addition', path: 'go.mod', contents: 'New go.mod' },
notice: { file: 'go.mod', message: 'some extra notice' },
},
]);
});

it('config contains go version', async () => {
GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
fs.readLocalFile.mockResolvedValueOnce('Current go.sum');
Expand Down
24 changes: 21 additions & 3 deletions lib/modules/manager/gomod/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { logger } from '../../../logger';
import { coerceArray } from '../../../util/array';
import { exec } from '../../../util/exec';
import type { ExecOptions } from '../../../util/exec/types';
import { filterMap } from '../../../util/filter-map';
import {
ensureCacheDir,
isValidLocalPath,
Expand All @@ -24,6 +25,7 @@ import type {
UpdateArtifactsConfig,
UpdateArtifactsResult,
} from '../types';
import { getExtraDepsNotice } from './artifacts-extra';

const { major, valid } = semver;

Expand Down Expand Up @@ -364,14 +366,30 @@ export async function updateArtifacts({
.replace(regEx(/\/\/ renovate-replace /g), '')
.replace(regEx(/renovate-replace-bracket/g), ')');
if (finalGoModContent !== newGoModContent) {
logger.debug('Found updated go.mod after go.sum update');
res.push({
const artifactResult: UpdateArtifactsResult = {
file: {
type: 'addition',
path: goModFileName,
contents: finalGoModContent,
},
});
};

const updatedDepNames = filterMap(updatedDeps, (dep) => dep?.depName);
const extraDepsNotice = getExtraDepsNotice(
newGoModContent,
finalGoModContent,
updatedDepNames,
);

if (extraDepsNotice) {
artifactResult.notice = {
file: goModFileName,
message: extraDepsNotice,
};
}

logger.debug('Found updated go.mod after go.sum update');
res.push(artifactResult);
}
return res;
} catch (err) {
Expand Down
6 changes: 6 additions & 0 deletions lib/modules/manager/gomod/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,9 @@ export interface MultiLineParseResult {
reachedLine: number;
detectedDeps: PackageDependency[];
}

export interface ExtraDep {
depName: string;
currentValue: string;
newValue: string;
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@
"deepmerge": "4.3.1",
"dequal": "2.0.3",
"detect-indent": "6.1.0",
"diff": "5.2.0",
"editorconfig": "2.0.0",
"email-addresses": "5.0.0",
"emoji-regex": "10.3.0",
Expand Down Expand Up @@ -312,7 +313,6 @@
"callsite": "1.0.0",
"common-tags": "1.8.2",
"conventional-changelog-conventionalcommits": "7.0.2",
"diff": "5.2.0",
"emojibase-data": "15.3.0",
"eslint": "8.57.0",
"eslint-formatter-gha": "1.5.0",
Expand Down
Loading

0 comments on commit cc5f68e

Please sign in to comment.