refactor(git): lazy sync git (#6984)

Co-authored-by: Jamie Magee <JamieMagee@users.noreply.github.com>
This commit is contained in:
Rhys Arkins 2020-08-17 13:31:53 +02:00 committed by GitHub
parent b45502cd28
commit 062045168a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 53 additions and 29 deletions

View file

@ -150,7 +150,7 @@ export async function initRepo({
const url = const url =
defaults.endpoint + defaults.endpoint +
`${encodeURIComponent(projectName)}/_git/${encodeURIComponent(repoName)}`; `${encodeURIComponent(projectName)}/_git/${encodeURIComponent(repoName)}`;
await git.initRepo({ git.initRepo({
...config, ...config,
localDir, localDir,
url, url,

View file

@ -179,7 +179,7 @@ export async function initRepo({
repository, repository,
}); });
await git.initRepo({ git.initRepo({
...config, ...config,
localDir, localDir,
url: gitUrl, url: gitUrl,

View file

@ -153,7 +153,7 @@ export async function initRepo({
repository, repository,
}); });
await git.initRepo({ git.initRepo({
...config, ...config,
localDir, localDir,
url, url,

View file

@ -306,7 +306,7 @@ const platform: Platform = {
gitEndpoint.auth = opts.token; gitEndpoint.auth = opts.token;
// Initialize Git storage // Initialize Git storage
await git.initRepo({ git.initRepo({
...config, ...config,
url: URL.format(gitEndpoint), url: URL.format(gitEndpoint),
gitAuthorName: global.gitAuthor?.name, gitAuthorName: global.gitAuthor?.name,

View file

@ -413,7 +413,7 @@ export async function initRepo({
); );
parsedEndpoint.pathname = config.repository + '.git'; parsedEndpoint.pathname = config.repository + '.git';
const url = URL.format(parsedEndpoint); const url = URL.format(parsedEndpoint);
await git.initRepo({ git.initRepo({
...config, ...config,
url, url,
gitAuthorName: global.gitAuthor?.name, gitAuthorName: global.gitAuthor?.name,

View file

@ -224,7 +224,7 @@ export async function initRepo({
repoUrl.auth = 'oauth2:' + opts.token; repoUrl.auth = 'oauth2:' + opts.token;
url = URL.format(repoUrl); url = URL.format(repoUrl);
} }
await git.initRepo({ git.initRepo({
...config, ...config,
url, url,
gitAuthorName: global.gitAuthor?.name, gitAuthorName: global.gitAuthor?.name,

View file

@ -60,7 +60,7 @@ describe('platform/git', () => {
const repo = Git(origin.path); const repo = Git(origin.path);
await repo.clone(base.path, '.', ['--bare']); await repo.clone(base.path, '.', ['--bare']);
tmpDir = await tmp.dir({ unsafeCleanup: true }); tmpDir = await tmp.dir({ unsafeCleanup: true });
await git.initRepo({ git.initRepo({
localDir: tmpDir.path, localDir: tmpDir.path,
url: origin.path, url: origin.path,
extraCloneOpts: { extraCloneOpts: {
@ -100,10 +100,11 @@ describe('platform/git', () => {
const repo = Git(base.path).silent(true); const repo = Git(base.path).silent(true);
await repo.submoduleAdd(base.path, 'submodule'); await repo.submoduleAdd(base.path, 'submodule');
await repo.commit('Add submodule'); await repo.commit('Add submodule');
await git.initRepo({ git.initRepo({
localDir: tmpDir.path, localDir: tmpDir.path,
url: base.path, url: base.path,
}); });
await git.syncGit();
expect(await fs.exists(tmpDir.path + '/.gitmodules')).toBeTruthy(); expect(await fs.exists(tmpDir.path + '/.gitmodules')).toBeTruthy();
expect(await git.getFileList()).toMatchSnapshot(); expect(await git.getFileList()).toMatchSnapshot();
await repo.reset(['--hard', 'HEAD^']); await repo.reset(['--hard', 'HEAD^']);
@ -129,6 +130,7 @@ describe('platform/git', () => {
}); });
describe('isBranchStale()', () => { describe('isBranchStale()', () => {
beforeEach(async () => { beforeEach(async () => {
await git.syncGit();
await git.setBranch('master'); await git.setBranch('master');
}); });
it('should return false if same SHA as master', async () => { it('should return false if same SHA as master', async () => {
@ -369,7 +371,7 @@ describe('platform/git', () => {
await git.setBranch('develop'); await git.setBranch('develop');
await git.initRepo({ git.initRepo({
localDir: tmpDir.path, localDir: tmpDir.path,
url: base.path, url: base.path,
}); });
@ -391,7 +393,7 @@ describe('platform/git', () => {
await repo.commit('past message2'); await repo.commit('past message2');
await repo.checkout('master'); await repo.checkout('master');
await git.initRepo({ git.initRepo({
localDir: tmpDir.path, localDir: tmpDir.path,
url: base.path, url: base.path,
}); });
@ -400,7 +402,7 @@ describe('platform/git', () => {
expect(await git.branchExists('renovate/test')).toBe(true); expect(await git.branchExists('renovate/test')).toBe(true);
const cid = await git.getBranchCommit('renovate/test'); const cid = await git.getBranchCommit('renovate/test');
await git.initRepo({ git.initRepo({
localDir: tmpDir.path, localDir: tmpDir.path,
url: base.path, url: base.path,
}); });
@ -429,10 +431,11 @@ describe('platform/git', () => {
'test', 'test',
]); ]);
await repo.commit('Add submodule'); await repo.commit('Add submodule');
await git.initRepo({ git.initRepo({
localDir: tmpDir.path, localDir: tmpDir.path,
url: base.path, url: base.path,
}); });
await git.syncGit();
expect(await fs.exists(tmpDir.path + '/.gitmodules')).toBeTruthy(); expect(await fs.exists(tmpDir.path + '/.gitmodules')).toBeTruthy();
await repo.reset(['--hard', 'HEAD^']); await repo.reset(['--hard', 'HEAD^']);
}); });

View file

@ -114,6 +114,13 @@ let git: SimpleGit | undefined;
let privateKeySet = false; let privateKeySet = false;
export function initRepo(args: StorageConfig): void {
config = { ...args } as any;
config.branchExists = {};
config.branchIsModified = {};
git = undefined;
}
async function resetToBranch(branchName: string): Promise<void> { async function resetToBranch(branchName: string): Promise<void> {
logger.debug(`resetToBranch(${branchName})`); logger.debug(`resetToBranch(${branchName})`);
await git.raw(['reset', '--hard']); await git.raw(['reset', '--hard']);
@ -143,14 +150,17 @@ async function cleanLocalBranches(): Promise<void> {
* By calling this function once the repo's branchPrefix is known, we can fetch all of Renovate's branches in one command. * By calling this function once the repo's branchPrefix is known, we can fetch all of Renovate's branches in one command.
*/ */
export async function setBranchPrefix(branchPrefix: string): Promise<void> { export async function setBranchPrefix(branchPrefix: string): Promise<void> {
logger.debug('Setting branchPrefix: ' + branchPrefix);
config.branchPrefix = branchPrefix; config.branchPrefix = branchPrefix;
const ref = `refs/heads/${branchPrefix}*:refs/remotes/origin/${branchPrefix}*`; // If the repo is already cloned then set branchPrefix now, otherwise it will be called again during syncGit()
try { if (git) {
await git.fetch(['origin', ref, '--depth=2', '--force']); logger.debug('Setting branchPrefix: ' + branchPrefix);
} catch (err) /* istanbul ignore next */ { const ref = `refs/heads/${branchPrefix}*:refs/remotes/origin/${branchPrefix}*`;
checkForPlatformFailure(err); try {
throw err; await git.fetch(['origin', ref, '--depth=2', '--force']);
} catch (err) /* istanbul ignore next */ {
checkForPlatformFailure(err);
throw err;
}
} }
} }
@ -258,20 +268,15 @@ export async function syncGit(): Promise<void> {
logger.debug({ err }, 'Error setting git author config'); logger.debug({ err }, 'Error setting git author config');
throw new Error(REPOSITORY_TEMPORARY_ERROR); throw new Error(REPOSITORY_TEMPORARY_ERROR);
} }
config.currentBranch = config.currentBranch || (await getDefaultBranch(git)); config.currentBranch = config.currentBranch || (await getDefaultBranch(git));
} if (config.branchPrefix) {
await setBranchPrefix(config.branchPrefix);
export async function initRepo(args: StorageConfig): Promise<void> { }
config = { ...args } as any;
config.branchExists = {};
config.branchIsModified = {};
git = undefined;
await syncGit();
} }
// istanbul ignore next // istanbul ignore next
export function getRepoStatus(): Promise<StatusResult> { export async function getRepoStatus(): Promise<StatusResult> {
await syncGit();
return git.status(); return git.status();
} }
@ -279,6 +284,7 @@ export async function createBranch(
branchName: string, branchName: string,
sha: string sha: string
): Promise<void> { ): Promise<void> {
await syncGit();
logger.debug(`createBranch(${branchName})`); logger.debug(`createBranch(${branchName})`);
await git.reset(ResetMode.HARD); await git.reset(ResetMode.HARD);
await git.raw(['clean', '-fd']); await git.raw(['clean', '-fd']);
@ -289,6 +295,7 @@ export async function createBranch(
} }
export async function branchExists(branchName: string): Promise<boolean> { export async function branchExists(branchName: string): Promise<boolean> {
await syncGit();
// First check cache // First check cache
if (config.branchExists[branchName] !== undefined) { if (config.branchExists[branchName] !== undefined) {
return config.branchExists[branchName]; return config.branchExists[branchName];
@ -315,6 +322,7 @@ export async function branchExists(branchName: string): Promise<boolean> {
// Return the commit SHA for a branch // Return the commit SHA for a branch
export async function getBranchCommit(branchName: string): Promise<string> { export async function getBranchCommit(branchName: string): Promise<string> {
await syncGit();
if (!(await branchExists(branchName))) { if (!(await branchExists(branchName))) {
throw Error( throw Error(
'Cannot fetch commit for branch that does not exist: ' + branchName 'Cannot fetch commit for branch that does not exist: ' + branchName
@ -325,6 +333,7 @@ export async function getBranchCommit(branchName: string): Promise<string> {
} }
export async function getCommitMessages(): Promise<string[]> { export async function getCommitMessages(): Promise<string[]> {
await syncGit();
logger.debug('getCommitMessages'); logger.debug('getCommitMessages');
const res = await git.log({ const res = await git.log({
n: 10, n: 10,
@ -334,6 +343,7 @@ export async function getCommitMessages(): Promise<string[]> {
} }
export async function setBranch(branchName: string): Promise<string> { export async function setBranch(branchName: string): Promise<string> {
await syncGit();
if (!(await branchExists(branchName))) { if (!(await branchExists(branchName))) {
throwBranchValidationError(branchName); throwBranchValidationError(branchName);
} }
@ -365,6 +375,7 @@ export async function setBranch(branchName: string): Promise<string> {
} }
export async function getFileList(): Promise<string[]> { export async function getFileList(): Promise<string[]> {
await syncGit();
const branch = config.currentBranch; const branch = config.currentBranch;
const submodules = await getSubmodules(); const submodules = await getSubmodules();
const files: string = await git.raw(['ls-tree', '-r', branch]); const files: string = await git.raw(['ls-tree', '-r', branch]);
@ -385,6 +396,7 @@ export async function getFileList(): Promise<string[]> {
export async function getAllRenovateBranches( export async function getAllRenovateBranches(
branchPrefix: string branchPrefix: string
): Promise<string[]> { ): Promise<string[]> {
await syncGit();
const branches = await git.branch(['--remotes', '--verbose']); const branches = await git.branch(['--remotes', '--verbose']);
return branches.all return branches.all
.map(localName) .map(localName)
@ -392,6 +404,7 @@ export async function getAllRenovateBranches(
} }
export async function isBranchStale(branchName: string): Promise<boolean> { export async function isBranchStale(branchName: string): Promise<boolean> {
await syncGit();
if (!(await branchExists(branchName))) { if (!(await branchExists(branchName))) {
throw Error( throw Error(
'Cannot check staleness for branch that does not exist: ' + branchName 'Cannot check staleness for branch that does not exist: ' + branchName
@ -407,6 +420,7 @@ export async function isBranchStale(branchName: string): Promise<boolean> {
} }
export async function isBranchModified(branchName: string): Promise<boolean> { export async function isBranchModified(branchName: string): Promise<boolean> {
await syncGit();
// First check cache // First check cache
if (config.branchIsModified[branchName] !== undefined) { if (config.branchIsModified[branchName] !== undefined) {
return config.branchIsModified[branchName]; return config.branchIsModified[branchName];
@ -438,6 +452,7 @@ export async function isBranchModified(branchName: string): Promise<boolean> {
} }
export async function deleteBranch(branchName: string): Promise<void> { export async function deleteBranch(branchName: string): Promise<void> {
await syncGit();
try { try {
await git.raw(['push', '--delete', 'origin', branchName]); await git.raw(['push', '--delete', 'origin', branchName]);
logger.debug({ branchName }, 'Deleted remote branch'); logger.debug({ branchName }, 'Deleted remote branch');
@ -457,6 +472,7 @@ export async function deleteBranch(branchName: string): Promise<void> {
} }
export async function mergeBranch(branchName: string): Promise<void> { export async function mergeBranch(branchName: string): Promise<void> {
await syncGit();
await git.reset(ResetMode.HARD); await git.reset(ResetMode.HARD);
await git.checkout(['-B', branchName, 'origin/' + branchName]); await git.checkout(['-B', branchName, 'origin/' + branchName]);
await git.checkout(config.currentBranch); await git.checkout(config.currentBranch);
@ -468,6 +484,7 @@ export async function mergeBranch(branchName: string): Promise<void> {
export async function getBranchLastCommitTime( export async function getBranchLastCommitTime(
branchName: string branchName: string
): Promise<Date> { ): Promise<Date> {
await syncGit();
try { try {
const time = await git.show(['-s', '--format=%ai', 'origin/' + branchName]); const time = await git.show(['-s', '--format=%ai', 'origin/' + branchName]);
return new Date(Date.parse(time)); return new Date(Date.parse(time));
@ -478,6 +495,7 @@ export async function getBranchLastCommitTime(
} }
export async function getBranchFiles(branchName: string): Promise<string[]> { export async function getBranchFiles(branchName: string): Promise<string[]> {
await syncGit();
try { try {
const diff = await git.diffSummary([branchName, config.currentBranch]); const diff = await git.diffSummary([branchName, config.currentBranch]);
return diff.files.map((file) => file.file); return diff.files.map((file) => file.file);
@ -491,6 +509,7 @@ export async function getFile(
filePath: string, filePath: string,
branchName?: string branchName?: string
): Promise<string | null> { ): Promise<string | null> {
await syncGit();
if (branchName) { if (branchName) {
const exists = await branchExists(branchName); const exists = await branchExists(branchName);
if (!exists) { if (!exists) {
@ -510,6 +529,7 @@ export async function getFile(
} }
export async function hasDiff(branchName: string): Promise<boolean> { export async function hasDiff(branchName: string): Promise<boolean> {
await syncGit();
try { try {
return (await git.diff(['HEAD', branchName])) !== ''; return (await git.diff(['HEAD', branchName])) !== '';
} catch (err) { } catch (err) {
@ -545,6 +565,7 @@ export async function commitFiles({
message, message,
force = false, force = false,
}: CommitFilesConfig): Promise<string | null> { }: CommitFilesConfig): Promise<string | null> {
await syncGit();
logger.debug(`Committing files to branch ${branchName}`); logger.debug(`Committing files to branch ${branchName}`);
if (!privateKeySet) { if (!privateKeySet) {
await writePrivateKey(config.localDir); await writePrivateKey(config.localDir);