This commit is contained in:
Jamie Tanna 2025-01-09 12:10:09 +01:00 committed by GitHub
commit f0c725909d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 177 additions and 28 deletions

View file

@ -3170,7 +3170,6 @@ Managers which do not support replacement:
- `bazel` - `bazel`
- `git-submodules` - `git-submodules`
- `gomod`
- `gradle` - `gradle`
- `homebrew` - `homebrew`
- `maven` - `maven`

View file

@ -4,7 +4,7 @@ import { join } from 'upath';
import { envMock, mockExecAll } from '../../../../test/exec-util'; import { envMock, mockExecAll } from '../../../../test/exec-util';
import { env, fs, git, mocked, partial } from '../../../../test/util'; import { env, fs, git, mocked, partial } from '../../../../test/util';
import { GlobalConfig } from '../../../config/global'; import { GlobalConfig } from '../../../config/global';
import type { RepoGlobalConfig } from '../../../config/types'; import type { RepoGlobalConfig, UpdateType } from '../../../config/types';
import * as docker from '../../../util/exec/docker'; import * as docker from '../../../util/exec/docker';
import type { StatusResult } from '../../../util/git/types'; import type { StatusResult } from '../../../util/git/types';
import * as _hostRules from '../../../util/host-rules'; import * as _hostRules from '../../../util/host-rules';
@ -1547,6 +1547,67 @@ describe('modules/manager/gomod/artifacts', () => {
]); ]);
}); });
it('updates import paths with gomodUpdateImportPaths when performing a replacement', async () => {
fs.readLocalFile.mockResolvedValueOnce('Current go.sum');
fs.readLocalFile.mockResolvedValueOnce(null); // vendor modules filename
const execSnapshots = 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');
expect(
await gomod.updateArtifacts({
packageFileName: 'go.mod',
updatedDeps: [
{
depName: 'github.com/google/go-github/v24',
newVersion: 'v28.0.0',
updateType: 'replacement' as UpdateType,
newName: 'github.com/forkzilla/go-github/v28',
},
],
newPackageFileContent: gomod1,
config: {
...config,
updateType: 'major',
postUpdateOptions: ['gomodUpdateImportPaths'],
},
}),
).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' } },
]);
expect(execSnapshots).toMatchObject([
{
cmd: 'go get -d -t ./...',
options: { cwd: '/tmp/github/some/repo' },
},
{
cmd: 'go install github.com/marwan-at-work/mod/cmd/mod@latest',
options: { cwd: '/tmp/github/some/repo' },
},
{
cmd: 'mod upgrade --mod-name=github.com/forkzilla/go-github/v28 -t=28',
options: { cwd: '/tmp/github/some/repo' },
},
{
cmd: 'go mod tidy',
options: { cwd: '/tmp/github/some/repo' },
},
{
cmd: 'go mod tidy',
options: { cwd: '/tmp/github/some/repo' },
},
]);
});
it('updates correct import paths with gomodUpdateImportPaths and multiple dependencies', async () => { it('updates correct import paths with gomodUpdateImportPaths and multiple dependencies', async () => {
fs.readLocalFile.mockResolvedValueOnce('Current go.sum'); fs.readLocalFile.mockResolvedValueOnce('Current go.sum');
fs.readLocalFile.mockResolvedValueOnce(null); // vendor modules filename fs.readLocalFile.mockResolvedValueOnce(null); // vendor modules filename

View file

@ -21,7 +21,7 @@ import { getGitEnvironmentVariables } from '../../../util/git/auth';
import { regEx } from '../../../util/regex'; import { regEx } from '../../../util/regex';
import { isValid } from '../../versioning/semver'; import { isValid } from '../../versioning/semver';
import type { import type {
PackageDependency, Upgrade,
UpdateArtifact, UpdateArtifact,
UpdateArtifactsConfig, UpdateArtifactsConfig,
UpdateArtifactsResult, UpdateArtifactsResult,
@ -31,7 +31,7 @@ import { getExtraDepsNotice } from './artifacts-extra';
const { major, valid } = semver; const { major, valid } = semver;
function getUpdateImportPathCmds( function getUpdateImportPathCmds(
updatedDeps: PackageDependency[], updatedDeps: Upgrade[],
{ constraints }: UpdateArtifactsConfig, { constraints }: UpdateArtifactsConfig,
): string[] { ): string[] {
// Check if we fail to parse any major versions and log that they're skipped // Check if we fail to parse any major versions and log that they're skipped
@ -52,8 +52,8 @@ function getUpdateImportPathCmds(
({ newVersion }) => ({ newVersion }) =>
valid(newVersion) && !newVersion!.endsWith('+incompatible'), valid(newVersion) && !newVersion!.endsWith('+incompatible'),
) )
.map(({ depName, newVersion }) => ({ .map(({ depName, newVersion, newName }) => ({
depName: depName!, depName: newName || depName!,
newMajor: major(newVersion!), newMajor: major(newVersion!),
})) }))
// Skip path updates going from v0 to v1 // Skip path updates going from v0 to v1
@ -247,10 +247,10 @@ export async function updateArtifacts({
logger.trace({ cmd, args }, 'go get command included'); logger.trace({ cmd, args }, 'go get command included');
execCommands.push(`${cmd} ${args}`); execCommands.push(`${cmd} ${args}`);
// Update import paths on major updates // Update import paths on major updates, or when performing replacements
const isImportPathUpdateRequired = const isImportPathUpdateRequired =
config.postUpdateOptions?.includes('gomodUpdateImportPaths') && config.postUpdateOptions?.includes('gomodUpdateImportPaths') &&
config.updateType === 'major'; (config.updateType === 'major' || config.updateType == 'replacement');
if (isImportPathUpdateRequired) { if (isImportPathUpdateRequired) {
const updateImportCmds = getUpdateImportPathCmds(updatedDeps, config); const updateImportCmds = getUpdateImportPathCmds(updatedDeps, config);

View file

@ -384,14 +384,6 @@ describe('modules/manager/gomod/update', () => {
expect(res).toContain('k8s.io/client-go/v2 => k8s.io/client-go v2.2.2'); expect(res).toContain('k8s.io/client-go/v2 => k8s.io/client-go v2.2.2');
}); });
it('should return null for replacement', () => {
const res = updateDependency({
fileContent: '',
upgrade: { updateType: 'replacement' },
});
expect(res).toBeNull();
});
it('should perform indirect upgrades when top-level', () => { it('should perform indirect upgrades when top-level', () => {
const upgrade = { const upgrade = {
depName: 'github.com/davecgh/go-spew', depName: 'github.com/davecgh/go-spew',
@ -415,5 +407,84 @@ describe('modules/manager/gomod/update', () => {
expect(res).not.toEqual(gomod2); expect(res).not.toEqual(gomod2);
expect(res).toContain(`${upgrade.newValue} // indirect`); expect(res).toContain(`${upgrade.newValue} // indirect`);
}); });
it('should perform package replacements', () => {
const upgrade = {
depName: 'github.com/aws/aws-sdk-go',
managerData: { lineNumber: 3 },
newValue: 'v1.27.1',
depType: 'require',
updateType: 'replacement' as UpdateType,
newName: 'github.com/aws/aws-sdk-go-v2',
};
const res = updateDependency({ fileContent: gomod1, upgrade });
expect(res).not.toEqual(gomod1);
expect(res).toContain(`github.com/aws/aws-sdk-go-v2 v1.27.1`);
});
it('should perform package replacements for indirect packages', () => {
const upgrade = {
depName: 'github.com/davecgh/go-spew',
managerData: { lineNumber: 4 },
newValue: 'v1.1.1',
depType: 'indirect',
updateType: 'replacement' as UpdateType,
newName: 'github.com/spew/go-spew',
};
const res = updateDependency({ fileContent: gomod1, upgrade });
expect(res).not.toEqual(gomod1);
expect(res).toContain(
`${upgrade.newName} ${upgrade.newValue} // indirect`,
);
});
it('should perform package replacements for a digest version', () => {
const upgrade = {
depName: 'golang.org/x/net',
managerData: { lineNumber: 54, multiLine: true },
currentDigest: 'c39426892332',
newDigest: 'foo',
depType: 'require',
updateType: 'replacement' as UpdateType,
newName: 'golang.org/foo-x-bar/net',
};
const res = updateDependency({ fileContent: gomod2, upgrade });
expect(res).not.toEqual(gomod2);
expect(res).toContain(`golang.org/foo-x-bar/net foo`);
});
it('should perform package replacements for a major version', () => {
const upgrade = {
depName: 'github.com/stretchr/testify',
managerData: { lineNumber: 48, multiLine: true },
newValue: 'v2.0.0',
newMajor: 2,
depType: 'require',
updateType: 'replacement' as UpdateType,
newName: 'github.com/testify-core/testify/v2',
};
const res = updateDependency({ fileContent: gomod2, upgrade });
expect(res).not.toEqual(gomod2);
expect(res).toContain(`github.com/testify-core/testify/v2 v2.0.0`);
});
it('should perform package replacements for a replace directive', () => {
const upgrade = {
depName: 'github.com/pravesht/gocql',
managerData: { lineNumber: 11 },
newValue: 'v0.0.1',
depType: 'replace',
updateType: 'replacement' as UpdateType,
newName: 'github.com/pravesht/gocql-new',
};
const res = updateDependency({ fileContent: gomod1, upgrade });
expect(res).not.toEqual(gomod1);
expect(res).toContain(`github.com/pravesht/gocql-new v0.0.1`);
});
}); });
}); });

View file

@ -17,12 +17,8 @@ export function updateDependency({
}: UpdateDependencyConfig): string | null { }: UpdateDependencyConfig): string | null {
try { try {
logger.debug(`gomod.updateDependency: ${upgrade.newValue}`); logger.debug(`gomod.updateDependency: ${upgrade.newValue}`);
const { depType, updateType } = upgrade; const { depType } = upgrade;
const currentName = upgrade.depName; const currentName = upgrade.depName;
if (updateType === 'replacement') {
logger.warn('gomod manager does not support replacement updates yet');
return null;
}
// istanbul ignore if: should never happen // istanbul ignore if: should never happen
if (!currentName || !upgrade.managerData) { if (!currentName || !upgrade.managerData) {
return null; return null;
@ -56,19 +52,21 @@ export function updateDependency({
if (depType === 'replace') { if (depType === 'replace') {
if (upgrade.managerData.multiLine) { if (upgrade.managerData.multiLine) {
updateLineExp = regEx( updateLineExp = regEx(
/^(?<depPart>\s+[^\s]+[\s]+[=][>]+\s+)(?<divider>[^\s]+\s+)[^\s]+/, /^(?<depPart>\s+[^\s]+[\s]+[=][>]+\s+)(?<depName>[^\s]+)(?<divider>\s+)[^\s]+/,
); );
} else { } else {
updateLineExp = regEx( updateLineExp = regEx(
/^(?<depPart>replace\s+[^\s]+[\s]+[=][>]+\s+)(?<divider>[^\s]+\s+)[^\s]+/, /^(?<depPart>replace\s+[^\s]+[\s]+[=][>]+\s+)(?<depName>[^\s]+)(?<divider>\s+)[^\s]+/,
); );
} }
} else if (depType === 'require' || depType === 'indirect') { } else if (depType === 'require' || depType === 'indirect') {
if (upgrade.managerData.multiLine) { if (upgrade.managerData.multiLine) {
updateLineExp = regEx(/^(?<depPart>\s+[^\s]+)(?<divider>\s+)[^\s]+/); updateLineExp = regEx(
/^(?<depPart>\s+)(?<depName>[^\s]+)(?<divider>\s+)[^\s]+/,
);
} else { } else {
updateLineExp = regEx( updateLineExp = regEx(
/^(?<depPart>require\s+[^\s]+)(?<divider>\s+)[^\s]+/, /^(?<depPart>require\s+)(?<depName>[^\s]+)(?<divider>\s+)[^\s]+/,
); );
} }
} }
@ -77,7 +75,27 @@ export function updateDependency({
return null; return null;
} }
let newLine: string; let newLine: string;
if (upgrade.updateType === 'digest') { let quote = '';
if (updateLineExp) {
const groups = lineToChange.match(updateLineExp)?.groups;
// istanbul ignore if: should never happen
if (!groups) {
return fileContent;
}
if (`${groups.depName}`.startsWith('"')) {
quote = '"';
}
}
// newName will be available for replacement
const newName = upgrade.newName ?? currentName;
if (
upgrade.updateType === 'digest' ||
(upgrade.updateType === 'replacement' && upgrade.newDigest)
) {
const newDigestRightSized = upgrade.newDigest!.substring( const newDigestRightSized = upgrade.newDigest!.substring(
0, 0,
upgrade.currentDigest!.length, upgrade.currentDigest!.length,
@ -92,13 +110,13 @@ export function updateDependency({
newLine = lineToChange.replace( newLine = lineToChange.replace(
// TODO: can be undefined? (#22198) // TODO: can be undefined? (#22198)
updateLineExp!, updateLineExp!,
`$<depPart>$<divider>${newDigestRightSized}`, `$<depPart>${quote}${newName}${quote}$<divider>${newDigestRightSized}`,
); );
} else { } else {
newLine = lineToChange.replace( newLine = lineToChange.replace(
// TODO: can be undefined? (#22198) // TODO: can be undefined? (#22198)
updateLineExp!, updateLineExp!,
`$<depPart>$<divider>${upgrade.newValue}`, `$<depPart>${quote}${newName}${quote}$<divider>${upgrade.newValue}`,
); );
} }
if (upgrade.updateType === 'major') { if (upgrade.updateType === 'major') {