diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 8692ad6155aec7029c97dd68c0f28eb37ee08416..da92af6b2318b2745e5685051a9f356711410304 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -309,6 +309,8 @@ See [shareable config presets](https://docs.renovatebot.com/config-presets) for The primary use case for this option is if you are following a pre-release tag of a certain dependency, e.g. `typescript` "insiders" build. When it's configured, Renovate bypasses its normal major/minor/patch logic and stable/unstable logic and simply raises a PR if the tag does not match your current version. +## git-submodules + ## gitLabAutomerge Please note that when this option is enabled it is possible that MRs with failing pipelines are getting merged. This is caused by a race condition in GitLab's Merge Request API - [read the corresponding issue](https://gitlab.com/gitlab-org/gitlab/issues/26293) for details. diff --git a/lib/config/definitions.ts b/lib/config/definitions.ts index b7268838480ca513c5943d5ab3887e1008705271..d3929a5aac5cd9f41426812c07043c4a0d6df875 100644 --- a/lib/config/definitions.ts +++ b/lib/config/definitions.ts @@ -555,6 +555,7 @@ const options: RenovateOptions[] = [ 'cargo', 'composer', 'docker', + 'git', 'hashicorp', 'hex', 'ivy', @@ -1688,6 +1689,18 @@ const options: RenovateOptions[] = [ mergeable: true, cli: false, }, + { + name: 'git-submodules', + description: 'Configuration object for git submodule files', + stage: 'package', + type: 'object', + default: { + versionScheme: 'git', + fileMatch: ['(^|/).gitmodules$'], + }, + mergeable: true, + cli: false, + }, { name: 'php', description: 'Configuration object for php', diff --git a/lib/datasource/git-submodules/index.ts b/lib/datasource/git-submodules/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..446313077924f8259f87602ddd8a8f6f2facff61 --- /dev/null +++ b/lib/datasource/git-submodules/index.ts @@ -0,0 +1,55 @@ +import Git from 'simple-git/promise'; +import { URL } from 'url'; + +import { ReleaseResult, PkgReleaseConfig, DigestConfig } from '../common'; +import { logger } from '../../logger'; + +export async function getPkgReleases({ + lookupName, + registryUrls, +}: PkgReleaseConfig): Promise<ReleaseResult | null> { + const cacheNamespace = 'datasource-git-submodules'; + const cacheKey = `${registryUrls[0]}-${registryUrls[1]}`; + const cachedResult = await renovateCache.get<ReleaseResult>( + cacheNamespace, + cacheKey + ); + // istanbul ignore if + if (cachedResult) { + return cachedResult; + } + + const git = Git(); + try { + const newHash = (await git.listRemote([ + '--refs', + registryUrls[0], + registryUrls[1], + ])) + .trim() + .split(/\t/)[0]; + + const sourceUrl = new URL(registryUrls[0]); + sourceUrl.username = ''; + + const result = { + sourceUrl: sourceUrl.href, + releases: [ + { + version: newHash, + }, + ], + }; + const cacheMinutes = 60; + await renovateCache.set(cacheNamespace, cacheKey, result, cacheMinutes); + return result; + } catch (err) { + logger.debug(`Error looking up tags in ${lookupName}`); + } + return null; +} + +export const getDigest = ( + config: DigestConfig, + newValue?: string +): Promise<string> => new Promise(resolve => resolve(newValue)); diff --git a/lib/datasource/index.ts b/lib/datasource/index.ts index 1f9604aad0a2e75461224d9109808ffede25e238..4079e1f2fd508a56d6eed51359f28a3ec9da88df 100644 --- a/lib/datasource/index.ts +++ b/lib/datasource/index.ts @@ -9,6 +9,7 @@ import * as hex from './hex'; import * as github from './github'; import * as gitlab from './gitlab'; import * as gitTags from './git-tags'; +import * as gitSubmodules from './git-submodules'; import * as go from './go'; import * as gradleVersion from './gradle-version'; import * as helm from './helm'; @@ -41,6 +42,7 @@ const datasources: Record<string, Datasource> = { github, gitlab, gitTags, + gitSubmodules, go, gradleVersion, maven, diff --git a/lib/manager/common.ts b/lib/manager/common.ts index 71040809bb6ee4ae1a15a57dd1cf75994f0d7425..dfea00da9f22401d6187c48754dd6154c4bf6028 100644 --- a/lib/manager/common.ts +++ b/lib/manager/common.ts @@ -145,6 +145,7 @@ export interface Upgrade<T = Record<string, any>> currentVersion?: string; depGroup?: string; downloadUrl?: string; + localDir?: string; name?: string; newDigest?: string; newFrom?: string; diff --git a/lib/manager/git-submodules/artifacts.ts b/lib/manager/git-submodules/artifacts.ts new file mode 100644 index 0000000000000000000000000000000000000000..81555ef443c66ef0cb85417453de55df5d3c4e7a --- /dev/null +++ b/lib/manager/git-submodules/artifacts.ts @@ -0,0 +1,17 @@ +import { UpdateArtifactsConfig, UpdateArtifactsResult } from '../common'; + +export default function updateArtifacts( + packageFileName: string, + updatedDeps: string[], + newPackageFileContent: string, + config: UpdateArtifactsConfig +): UpdateArtifactsResult[] | null { + return [ + { + file: { + name: updatedDeps[0], + contents: '', + }, + }, + ]; +} diff --git a/lib/manager/git-submodules/extract.ts b/lib/manager/git-submodules/extract.ts new file mode 100644 index 0000000000000000000000000000000000000000..83a780dea17cbafccac73cb7075c340085518461 --- /dev/null +++ b/lib/manager/git-submodules/extract.ts @@ -0,0 +1,87 @@ +import Git from 'simple-git/promise'; +import upath from 'upath'; +import URL from 'url'; + +import { ManagerConfig, PackageFile } from '../common'; + +async function getUrl( + git: Git.SimpleGit, + gitModulesPath: string, + submoduleName: string +): Promise<string> { + const path = (await Git().raw([ + 'config', + '--file', + gitModulesPath, + '--get', + `submodule.${submoduleName}.url`, + ])).trim(); + if (!path.startsWith('../')) { + return path; + } + const remoteUrl = (await git.raw([ + 'config', + '--get', + 'remote.origin.url', + ])).trim(); + return URL.resolve(`${remoteUrl}/`, path); +} + +async function getBranch( + gitModulesPath: string, + submoduleName: string +): Promise<string> { + return ( + (await Git().raw([ + 'config', + '--file', + gitModulesPath, + '--get', + `submodule.${submoduleName}.branch`, + ])) || 'master' + ).trim(); +} + +export default async function extractPackageFile( + content: string, + fileName: string, + config: ManagerConfig +): Promise<PackageFile | null> { + const git = Git(config.localDir); + const gitModulesPath = upath.join(config.localDir, fileName); + + const depNames = ( + (await git.raw([ + 'config', + '--file', + gitModulesPath, + '--get-regexp', + 'path', + ])) || '' + ) + .trim() + .split(/[\n\s]/) + .filter((_e: string, i: number) => i % 2); + + if (!depNames.length) { + return null; + } + + const deps = await Promise.all( + depNames.map(async depName => { + const currentValue = (await git.subModule(['status', depName])) + .trim() + .split(/[+\s]/)[0]; + const submoduleBranch = await getBranch(gitModulesPath, depName); + const subModuleUrl = await getUrl(git, gitModulesPath, depName); + return { + depName, + registryUrls: [subModuleUrl, submoduleBranch], + currentValue, + currentDigest: currentValue, + }; + }) + ); + + return { deps, datasource: 'gitSubmodules' }; +} diff --git a/lib/manager/git-submodules/index.ts b/lib/manager/git-submodules/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..6b14bdeab34dc9d9be1e9e614e5e845a5ed387bb --- /dev/null +++ b/lib/manager/git-submodules/index.ts @@ -0,0 +1,3 @@ +export { default as extractPackageFile } from './extract'; +export { default as updateDependency } from './update'; +export { default as updateArtifacts } from './artifacts'; diff --git a/lib/manager/git-submodules/update.ts b/lib/manager/git-submodules/update.ts new file mode 100644 index 0000000000000000000000000000000000000000..e33367dd6a44bc4ade56a4eec34b72e791bf831f --- /dev/null +++ b/lib/manager/git-submodules/update.ts @@ -0,0 +1,23 @@ +import Git from 'simple-git/promise'; + +import { Upgrade } from '../common'; + +export default async function updateDependency( + fileContent: string, + upgrade: Upgrade +): Promise<string | null> { + const git = Git(upgrade.localDir); + + try { + await git.raw([ + 'submodule', + 'update', + '--init', + '--remote', + upgrade.depName, + ]); + return fileContent; + } catch (err) { + return null; + } +} diff --git a/lib/manager/index.ts b/lib/manager/index.ts index ca8391611760d3d70a3296d8af60da3e0f2fb959..20fbb055664fd1b9dcfc4894c938378542824f75 100644 --- a/lib/manager/index.ts +++ b/lib/manager/index.ts @@ -21,6 +21,7 @@ const managerList = [ 'docker-compose', 'dockerfile', 'droneci', + 'git-submodules', 'github-actions', 'gitlabci', 'gitlabci-include', diff --git a/lib/platform/git/storage.ts b/lib/platform/git/storage.ts index cdb22fbaddec4b726f17824282e9f83bbf25d370..29b7ee7551eb79c200ebead15544b6ffe60d7e1f 100644 --- a/lib/platform/git/storage.ts +++ b/lib/platform/git/storage.ts @@ -76,6 +76,14 @@ function throwBaseBranchValidationError(branchName: string): never { throw error; } +async function isDirectory(dir: string): Promise<boolean> { + try { + return (await fs.stat(dir)).isDirectory(); + } catch (err) { + return false; + } +} + export class Storage { private _config: LocalConfig = {} as any; @@ -175,6 +183,7 @@ export class Storage { const submodules = await this.getSubmodules(); for (const submodule of submodules) { try { + logger.debug(`Cloning git submodule at ${submodule}`); await this._git.submoduleUpdate(['--init', '--', submodule]); } catch (err) { logger.warn(`Unable to initialise git submodule at ${submodule}`); @@ -470,6 +479,9 @@ export class Storage { // istanbul ignore if if (file.name === '|delete|') { deleted.push(file.contents); + } else if (await isDirectory(join(this._cwd!, file.name))) { + fileNames.push(file.name); + await this._git!.add(file.name); } else { fileNames.push(file.name); await fs.outputFile( diff --git a/lib/versioning/git/index.ts b/lib/versioning/git/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..b545fc8a4427c02f7602f2bd2d94caa9e9fff939 --- /dev/null +++ b/lib/versioning/git/index.ts @@ -0,0 +1,21 @@ +import * as generic from '../loose/generic'; +import { VersioningApi } from '../common'; + +const parse = (version: string): any => ({ release: [parseInt(version, 10)] }); + +const isCompatible = (version: string, range: string): boolean => true; + +const compare = (version1: string, version2: string): number => -1; + +const valueToVersion = (value: string): string => value; + +export const api: VersioningApi = { + ...generic.create({ + parse, + compare, + }), + isCompatible, + valueToVersion, +}; + +export default api; diff --git a/lib/workers/branch/get-updated.ts b/lib/workers/branch/get-updated.ts index 8946e5ead69cfb58d6c4233f6b5f421125b3463b..72f6784990bd51a196afe88c161865b896fcd56d 100644 --- a/lib/workers/branch/get-updated.ts +++ b/lib/workers/branch/get-updated.ts @@ -70,6 +70,12 @@ export async function getUpdatedPackageFiles( logger.debug('Updating packageFile content'); updatedFileContents[packageFile] = newContent; } + if ( + newContent === existingContent && + upgrade.datasource === 'gitSubmodules' + ) { + updatedFileContents[packageFile] = newContent; + } } } const updatedPackageFiles = Object.keys(updatedFileContents).map(name => ({ diff --git a/lib/workers/repository/process/lookup/index.ts b/lib/workers/repository/process/lookup/index.ts index 32c070a95c73291586a599764fe76a1ee2d2e87f..0f5775407ca4030d8d75786889aa2e4296d9b939 100644 --- a/lib/workers/repository/process/lookup/index.ts +++ b/lib/workers/repository/process/lookup/index.ts @@ -344,7 +344,7 @@ export async function lookupUpdates( } // Add digests if necessary if (supportsDigests(config)) { - if (config.currentDigest) { + if (config.currentDigest && config.datasource !== 'gitSubmodules') { if (!config.digestOneAndOnly || !res.updates.length) { // digest update res.updates.push({ @@ -361,6 +361,12 @@ export async function lookupUpdates( newValue: config.currentValue, }); } + } else if (config.datasource === 'gitSubmodules') { + const dependency = clone(await getPkgReleases(config)); + res.updates.push({ + updateType: 'digest', + newValue: dependency.releases[0].version, + }); } if (version.valueToVersion) { for (const update of res.updates || []) { diff --git a/renovate-schema.json b/renovate-schema.json index d59a344cab120d6c981bc9c5d4e4aaa10d8e067e..810042884ac68fa2705b91d37773ba74e152fb36 100644 --- a/renovate-schema.json +++ b/renovate-schema.json @@ -315,6 +315,7 @@ "cargo", "composer", "docker", + "git", "hashicorp", "hex", "ivy", @@ -1121,6 +1122,15 @@ }, "$ref": "#" }, + "git-submodules": { + "description": "Configuration object for git submodule files", + "type": "object", + "default": { + "versionScheme": "git", + "fileMatch": ["(^|/).gitmodules$"] + }, + "$ref": "#" + }, "php": { "description": "Configuration object for php", "type": "object", diff --git a/test/config/__snapshots__/validation.spec.ts.snap b/test/config/__snapshots__/validation.spec.ts.snap index 478ea19788835a07c67898f7184f618211aaf82e..783f7c42b88c23aea78497e05be98996e5a19379 100644 --- a/test/config/__snapshots__/validation.spec.ts.snap +++ b/test/config/__snapshots__/validation.spec.ts.snap @@ -87,7 +87,7 @@ Array [ "depName": "Configuration Error", "message": "packageRules: You have included an unsupported manager in a package rule. Your list: foo. - Supported managers are: (ansible, bazel, buildkite, bundler, cargo, circleci, composer, deps-edn, docker-compose, dockerfile, droneci, github-actions, gitlabci, gitlabci-include, gomod, gradle, gradle-wrapper, helm-requirements, homebrew, kubernetes, leiningen, maven, meteor, mix, npm, nuget, nvm, pip_requirements, pip_setup, pipenv, poetry, pub, sbt, swift, terraform, travis, ruby-version).", + Supported managers are: (ansible, bazel, buildkite, bundler, cargo, circleci, composer, deps-edn, docker-compose, dockerfile, droneci, git-submodules, github-actions, gitlabci, gitlabci-include, gomod, gradle, gradle-wrapper, helm-requirements, homebrew, kubernetes, leiningen, maven, meteor, mix, npm, nuget, nvm, pip_requirements, pip_setup, pipenv, poetry, pub, sbt, swift, terraform, travis, ruby-version).", }, ] `; diff --git a/test/datasource/git-submodules.spec.ts b/test/datasource/git-submodules.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..65d070c2d1993f26bd913cc3f6ab9bc77e239039 --- /dev/null +++ b/test/datasource/git-submodules.spec.ts @@ -0,0 +1,57 @@ +import _simpleGit from 'simple-git/promise'; +import { getPkgReleases, getDigest } from '../../lib/datasource/git-submodules'; + +jest.mock('simple-git/promise.js'); +const simpleGit: any = _simpleGit; + +const lookupName = 'https://github.com/example/example.git'; +const registryUrls = [lookupName, 'master']; + +describe('datasource/git-submoduless', () => { + beforeEach(() => global.renovateCache.rmAll()); + describe('getPkgReleases', () => { + it('returns null if response is wrong', async () => { + simpleGit.mockReturnValue({ + listRemote() { + return Promise.resolve(null); + }, + }); + const versions = await getPkgReleases({ lookupName, registryUrls }); + expect(versions).toEqual(null); + }); + it('returns null if remote call throws exception', async () => { + simpleGit.mockReturnValue({ + listRemote() { + throw new Error(); + }, + }); + const versions = await getPkgReleases({ lookupName, registryUrls }); + expect(versions).toEqual(null); + }); + it('returns versions filtered from tags', async () => { + simpleGit.mockReturnValue({ + listRemote() { + return Promise.resolve('commithash1\trefs/heads/master'); + }, + }); + + const versions = await getPkgReleases({ + lookupName, + registryUrls, + }); + const result = versions.releases.map(x => x.version).sort(); + expect(result).toEqual(['commithash1']); + }); + }); + describe('getDigest', () => { + it('returns null if passed null', async () => { + const digest = await getDigest({}, null); + expect(digest).toBeNull(); + }); + it('returns value if passed value', async () => { + const commitHash = 'commithash1'; + const digest = await getDigest({}, commitHash); + expect(digest).toEqual(commitHash); + }); + }); +}); diff --git a/test/manager/git-submodules/__snapshots__/artifact.spec.ts.snap b/test/manager/git-submodules/__snapshots__/artifact.spec.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..e1858ffeaf52ba3a4e7383e0f0b62f8863d68cd1 --- /dev/null +++ b/test/manager/git-submodules/__snapshots__/artifact.spec.ts.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`lib/manager/gitsubmodules/artifacts updateArtifacts() returns empty content 1`] = ` +Array [ + Object { + "file": Object { + "contents": "", + "name": "", + }, + }, +] +`; diff --git a/test/manager/git-submodules/__snapshots__/extract.spec.ts.snap b/test/manager/git-submodules/__snapshots__/extract.spec.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..208537bf1dea79b0be8c836728e7f1abce407d08 --- /dev/null +++ b/test/manager/git-submodules/__snapshots__/extract.spec.ts.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`lib/manager/gitsubmodules/extract extractPackageFile() default to master branch 1`] = ` +Array [ + Object { + "currentDigest": "4b825dc642cb6eb9a060e54bf8d69288fbee4904", + "currentValue": "4b825dc642cb6eb9a060e54bf8d69288fbee4904", + "depName": "PowerShell-Docs", + "registryUrls": Array [ + "git@github.com:PowerShell/PowerShell-Docs", + "master", + ], + }, +] +`; + +exports[`lib/manager/gitsubmodules/extract extractPackageFile() extract branch 1`] = ` +Array [ + Object { + "currentDigest": "4b825dc642cb6eb9a060e54bf8d69288fbee4904", + "currentValue": "4b825dc642cb6eb9a060e54bf8d69288fbee4904", + "depName": "PowerShell-Docs", + "registryUrls": Array [ + "git@github.com:PowerShell/PowerShell-Docs", + "staging", + ], + }, +] +`; + +exports[`lib/manager/gitsubmodules/extract extractPackageFile() extract relative URL 1`] = ` +Array [ + Object { + "currentDigest": "4b825dc642cb6eb9a060e54bf8d69288fbee4904", + "currentValue": "4b825dc642cb6eb9a060e54bf8d69288fbee4904", + "depName": "PowerShell-Docs", + "registryUrls": Array [ + "https://github.com/PowerShell/PowerShell-Docs", + "staging", + ], + }, +] +`; diff --git a/test/manager/git-submodules/_fixtures/.gitmodules.1 b/test/manager/git-submodules/_fixtures/.gitmodules.1 new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/test/manager/git-submodules/_fixtures/.gitmodules.2 b/test/manager/git-submodules/_fixtures/.gitmodules.2 new file mode 100644 index 0000000000000000000000000000000000000000..8ef96ca17da5b3ac2d90013bc8b912979f01921c --- /dev/null +++ b/test/manager/git-submodules/_fixtures/.gitmodules.2 @@ -0,0 +1,3 @@ +[submodule "PowerShell-Docs"] + path = PowerShell-Docs + url = git@github.com:PowerShell/PowerShell-Docs \ No newline at end of file diff --git a/test/manager/git-submodules/_fixtures/.gitmodules.3 b/test/manager/git-submodules/_fixtures/.gitmodules.3 new file mode 100644 index 0000000000000000000000000000000000000000..3af0ee1449e1c70c5fbc1e121083e96cc5a69552 --- /dev/null +++ b/test/manager/git-submodules/_fixtures/.gitmodules.3 @@ -0,0 +1,4 @@ +[submodule "PowerShell-Docs"] + path = PowerShell-Docs + url = git@github.com:PowerShell/PowerShell-Docs + branch = staging \ No newline at end of file diff --git a/test/manager/git-submodules/_fixtures/.gitmodules.4 b/test/manager/git-submodules/_fixtures/.gitmodules.4 new file mode 100644 index 0000000000000000000000000000000000000000..6ad8493e12e9680bc0a217079e5b7460a21138a7 --- /dev/null +++ b/test/manager/git-submodules/_fixtures/.gitmodules.4 @@ -0,0 +1,4 @@ +[submodule "PowerShell-Docs"] + path = PowerShell-Docs + url = ../../PowerShell/PowerShell-Docs + branch = staging diff --git a/test/manager/git-submodules/artifact.spec.ts b/test/manager/git-submodules/artifact.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..4c845d40ff56553748771379086d2019d14f89df --- /dev/null +++ b/test/manager/git-submodules/artifact.spec.ts @@ -0,0 +1,9 @@ +import updateArtifacts from '../../../lib/manager/git-submodules/artifacts'; + +describe('lib/manager/gitsubmodules/artifacts', () => { + describe('updateArtifacts()', () => { + it('returns empty content', () => { + expect(updateArtifacts('', [''], '', {})).toMatchSnapshot(); + }); + }); +}); diff --git a/test/manager/git-submodules/extract.spec.ts b/test/manager/git-submodules/extract.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..acc882241d7081f5af021f54195b361e1c010c9e --- /dev/null +++ b/test/manager/git-submodules/extract.spec.ts @@ -0,0 +1,46 @@ +import _simpleGit from 'simple-git/promise'; +import extractPackageFile from '../../../lib/manager/git-submodules/extract'; + +jest.mock('simple-git/promise.js'); +const simpleGit: any = _simpleGit; +const Git = jest.requireActual('simple-git/promise'); + +const localDir = `${__dirname}/_fixtures`; + +describe('lib/manager/gitsubmodules/extract', () => { + beforeAll(() => { + simpleGit.mockReturnValue({ + subModule() { + return Promise.resolve('4b825dc642cb6eb9a060e54bf8d69288fbee4904'); + }, + raw(options: string[]) { + if (options.includes('remote.origin.url')) { + return 'https://github.com/renovatebot/renovate.git'; + } + return Git().raw(options); + }, + }); + }); + describe('extractPackageFile()', () => { + it('handles empty gitmodules file', async () => { + expect( + await extractPackageFile('', '.gitmodules.1', { localDir }) + ).toBeNull(); + }); + it('default to master branch', async () => { + const res = await extractPackageFile('', '.gitmodules.2', { localDir }); + expect(res.deps).toMatchSnapshot(); + expect(res.deps).toHaveLength(1); + }); + it('extract branch', async () => { + const res = await extractPackageFile('', '.gitmodules.3', { localDir }); + expect(res.deps).toMatchSnapshot(); + expect(res.deps).toHaveLength(1); + }); + it('extract relative URL', async () => { + const res = await extractPackageFile('', '.gitmodules.4', { localDir }); + expect(res.deps).toMatchSnapshot(); + expect(res.deps).toHaveLength(1); + }); + }); +}); diff --git a/test/manager/git-submodules/update.spec.ts b/test/manager/git-submodules/update.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..60eee16fc0c13c309a281740b543317c1cb55a11 --- /dev/null +++ b/test/manager/git-submodules/update.spec.ts @@ -0,0 +1,31 @@ +import _simpleGit from 'simple-git/promise'; +import { dir } from 'tmp-promise'; + +import updateDependency from '../../../lib/manager/git-submodules/update'; + +jest.mock('simple-git/promise.js'); +const simpleGit: any = _simpleGit; + +describe('manager/git-submodules/update', () => { + describe('updateDependency', () => { + it('returns null on error', async () => { + simpleGit.mockReturnValue({ + raw() { + throw new Error(); + }, + }); + const update = await updateDependency('', {}); + expect(update).toBeNull(); + }); + it('returns content on update', async () => { + const tmpDir = await dir(); + simpleGit.mockReturnValue({ + raw() { + return Promise.resolve(); + }, + }); + const update = await updateDependency('', { localDir: tmpDir.path }); + expect(update).toEqual(''); + }); + }); +}); diff --git a/test/platform/git/storage.spec.ts b/test/platform/git/storage.spec.ts index d8ad8dde3b9eee63278fd22ae920fc2efaea32f0..018b73f13c64a0ee8f21409847bb40b17125b187 100644 --- a/test/platform/git/storage.spec.ts +++ b/test/platform/git/storage.spec.ts @@ -250,6 +250,19 @@ describe('platform/git/storage', () => { 'Update something' ); }); + it('updates git submodules', async () => { + const files = [ + { + name: '.', + contents: 'some content', + }, + ]; + await git.commitFilesToBranch( + 'renovate/something', + files, + 'Update something' + ); + }); }); describe('getCommitMessages()', () => { diff --git a/test/versioning/git.spec.ts b/test/versioning/git.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..21c4318b01ca4b452c311fd6992bafd5c280c6bf --- /dev/null +++ b/test/versioning/git.spec.ts @@ -0,0 +1,24 @@ +import git from '../../lib/versioning/git'; + +describe('git.', () => { + describe('isValid(version)', () => { + it('should return true', () => { + expect(git.isValid('a1')).toBeTruthy(); + }); + }); + describe('isCompatible(version)', () => { + it('should return true', () => { + expect(git.isCompatible('')).toBeTruthy(); + }); + }); + describe('isGreaterThan(version1, version2)', () => { + it('should return false', () => { + expect(git.isGreaterThan('', '')).toBeFalsy(); + }); + }); + describe('valueToVersion(version)', () => { + it('should return same as input', () => { + expect(git.valueToVersion('')).toEqual(''); + }); + }); +}); diff --git a/test/workers/branch/__snapshots__/get-updated.spec.js.snap b/test/workers/branch/__snapshots__/get-updated.spec.js.snap index 0b5271543dc9f668eef5634cb50ddeb30f6fe829..c61fc9a70a7f55ea73ab7fe3624755231028efd2 100644 --- a/test/workers/branch/__snapshots__/get-updated.spec.js.snap +++ b/test/workers/branch/__snapshots__/get-updated.spec.js.snap @@ -23,6 +23,20 @@ Object { } `; +exports[`workers/branch/get-updated getUpdatedPackageFiles() handles git submodules 1`] = ` +Object { + "artifactErrors": Array [], + "parentBranch": undefined, + "updatedArtifacts": Array [], + "updatedPackageFiles": Array [ + Object { + "contents": "existing content", + "name": "undefined", + }, + ], +} +`; + exports[`workers/branch/get-updated getUpdatedPackageFiles() handles lock file errors 1`] = ` Object { "artifactErrors": Array [ diff --git a/test/workers/branch/get-updated.spec.js b/test/workers/branch/get-updated.spec.js index eb7fccbf3a6dc7b3eb585f25837f32d3e7735139..953c711b0a1b5e292aeb3e3d20514e74f0c6c3e2 100644 --- a/test/workers/branch/get-updated.spec.js +++ b/test/workers/branch/get-updated.spec.js @@ -2,6 +2,8 @@ const composer = require('../../../lib/manager/composer'); /** @type any */ const npm = require('../../../lib/manager/npm'); +/** @type any */ +const gitSubmodules = require('../../../lib/manager/git-submodules'); const { getUpdatedPackageFiles, } = require('../../../lib/workers/branch/get-updated'); @@ -11,6 +13,7 @@ const { platform } = require('../../../lib/platform'); jest.mock('../../../lib/manager/composer'); jest.mock('../../../lib/manager/npm'); +jest.mock('../../../lib/manager/git-submodules'); describe('workers/branch/get-updated', () => { describe('getUpdatedPackageFiles()', () => { @@ -111,5 +114,14 @@ describe('workers/branch/get-updated', () => { const res = await getUpdatedPackageFiles(config); expect(res).toMatchSnapshot(); }); + it('handles git submodules', async () => { + config.upgrades.push({ + manager: 'git-submodules', + datasource: 'gitSubmodules', + }); + gitSubmodules.updateDependency.mockReturnValue('existing content'); + const res = await getUpdatedPackageFiles(config); + expect(res).toMatchSnapshot(); + }); }); }); diff --git a/test/workers/repository/extract/__snapshots__/index.spec.js.snap b/test/workers/repository/extract/__snapshots__/index.spec.js.snap index 1208fdc092d78f36d23ee0f3829654a4ff22b11d..ae5aa2627b22607a9a07bd238b9ee61cec0f1789 100644 --- a/test/workers/repository/extract/__snapshots__/index.spec.js.snap +++ b/test/workers/repository/extract/__snapshots__/index.spec.js.snap @@ -35,6 +35,9 @@ Object { "droneci": Array [ Object {}, ], + "git-submodules": Array [ + Object {}, + ], "github-actions": Array [ Object {}, ], diff --git a/test/workers/repository/process/lookup/__snapshots__/index.spec.js.snap b/test/workers/repository/process/lookup/__snapshots__/index.spec.js.snap index 8f74d40bb37a94bb78bedd916ad35624bce066c1..f719f0e95368b264bdd5cf0e20c4eedc22df806e 100644 --- a/test/workers/repository/process/lookup/__snapshots__/index.spec.js.snap +++ b/test/workers/repository/process/lookup/__snapshots__/index.spec.js.snap @@ -178,6 +178,22 @@ Object { } `; +exports[`workers/repository/process/lookup .lookupUpdates() handles git submodule update 1`] = ` +Object { + "skipReason": "unsupported-value", + "updates": Array [ + Object { + "fromVersion": undefined, + "newValue": "4b825dc642cb6eb9a060e54bf8d69288fbee4904", + "newVersion": "4b825dc642cb6eb9a060e54bf8d69288fbee4904", + "toVersion": undefined, + "updateType": "digest", + }, + ], + "warnings": Array [], +} +`; + exports[`workers/repository/process/lookup .lookupUpdates() handles github 404 1`] = `Array []`; exports[`workers/repository/process/lookup .lookupUpdates() handles packagist 1`] = `Array []`; diff --git a/test/workers/repository/process/lookup/index.spec.js b/test/workers/repository/process/lookup/index.spec.js index 4ea1b5b86e1c59188d32c44c59668380e5e19e1c..a215651fd1983de6393d9b0e126eccaa61c737c4 100644 --- a/test/workers/repository/process/lookup/index.spec.js +++ b/test/workers/repository/process/lookup/index.spec.js @@ -9,9 +9,12 @@ const vueJson = require('../../../../config/npm/_fixtures/vue.json'); const typescriptJson = require('../../../../config/npm/_fixtures/typescript.json'); /** @type any */ const docker = require('../../../../../lib/datasource/docker'); +/** @type any */ +const gitSubmodules = require('../../../../../lib/datasource/git-submodules'); const defaults = require('../../../../../lib/config/defaults'); jest.mock('../../../../../lib/datasource/docker'); +jest.mock('../../../../../lib/datasource/git-submodules'); qJson.latestVersion = '1.4.1'; @@ -1227,5 +1230,18 @@ describe('workers/repository/process/lookup', () => { const res = await lookup.lookupUpdates(config); expect(res).toMatchSnapshot(); }); + it('handles git submodule update', async () => { + config.datasource = 'gitSubmodules'; + config.versionScheme = 'git'; + gitSubmodules.getPkgReleases.mockReturnValueOnce({ + releases: [ + { + version: '4b825dc642cb6eb9a060e54bf8d69288fbee4904', + }, + ], + }); + const res = await lookup.lookupUpdates(config); + expect(res).toMatchSnapshot(); + }); }); });