Skip to content

Commit 65c107e

Browse files
authored
feat: use a git cache to speed up clones (#275)
* use a git cache to speed up clones * drop unused git config
1 parent af2c668 commit 65c107e

File tree

3 files changed

+58
-31
lines changed

3 files changed

+58
-31
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"prepare": "husky install"
1717
},
1818
"dependencies": {
19+
"async-mutex": "^0.5.0",
1920
"fs-extra": "^11.1.1",
2021
"global-agent": "^3.0.0",
2122
"node-fetch": "^2.6.7",

src/operations/init-repo.ts

+45-31
Original file line numberDiff line numberDiff line change
@@ -2,54 +2,68 @@ import { parse } from 'yaml';
22
import * as fs from 'fs-extra';
33
import * as os from 'os';
44
import * as path from 'path';
5-
import simpleGit from 'simple-git';
5+
import simpleGit, { CheckRepoActions } from 'simple-git';
66
import { InitRepoOptions } from '../interfaces';
77
import { LogLevel } from '../enums';
88
import { log } from '../utils/log-util';
9-
import { ResetMode } from 'simple-git';
9+
import { Mutex } from 'async-mutex';
1010

11-
const baseDir = path.resolve(os.tmpdir(), 'trop-working');
11+
const baseDir =
12+
process.env.WORKING_DIR ?? path.resolve(os.tmpdir(), 'trop-working');
13+
14+
function githubUrl({ slug, accessToken }: InitRepoOptions): string {
15+
return `https://x-access-token:${accessToken}@github.com/${slug}.git`;
16+
}
17+
18+
const repoMutex = new Map<string, Mutex>();
19+
function mutexForRepoCache(slug: string) {
20+
if (!repoMutex.has(slug)) repoMutex.set(slug, new Mutex());
21+
return repoMutex.get(slug)!;
22+
}
23+
24+
async function updateRepoCache({ slug, accessToken }: InitRepoOptions) {
25+
const cacheDir = path.resolve(baseDir, slug, 'git-cache');
26+
27+
await fs.mkdirp(cacheDir);
28+
const git = simpleGit(cacheDir);
29+
if (!(await git.checkIsRepo(CheckRepoActions.BARE))) {
30+
// The repo might be missing, or otherwise somehow corrupt. Re-clone it.
31+
log(
32+
'updateRepoCache',
33+
LogLevel.INFO,
34+
`${cacheDir} was not a git repo, cloning...`,
35+
);
36+
await fs.remove(cacheDir);
37+
await fs.mkdirp(cacheDir);
38+
await git.clone(githubUrl({ slug, accessToken }), '.', ['--bare']);
39+
}
40+
await git.fetch();
41+
42+
return cacheDir;
43+
}
1244

1345
/**
1446
* Initializes the cloned repo trop will use to run backports.
1547
*
1648
* @param {InitRepoOptions} options - repo and payload for repo initialization
17-
* @returns {Object} - an object containing the repo initialization directory
49+
* @returns {{dir: string}} - an object containing the repo initialization directory
1850
*/
19-
export const initRepo = async ({ slug, accessToken }: InitRepoOptions) => {
51+
export const initRepo = async ({
52+
slug,
53+
accessToken,
54+
}: InitRepoOptions): Promise<{ dir: string }> => {
2055
log('initRepo', LogLevel.INFO, 'Setting up local repository');
2156

2257
await fs.mkdirp(path.resolve(baseDir, slug));
2358
const prefix = path.resolve(baseDir, slug, 'job-');
2459
const dir = await fs.mkdtemp(prefix);
25-
26-
// Ensure that this directory is empty.
27-
await fs.mkdirp(dir);
28-
await fs.remove(dir);
29-
await fs.mkdirp(dir);
30-
3160
const git = simpleGit(dir);
3261

33-
await git.clone(
34-
`https://x-access-token:${accessToken}@github.com/${slug}.git`,
35-
'.',
36-
);
37-
38-
// Clean up just in case.
39-
await git.reset(ResetMode.HARD);
40-
const status = await git.status();
41-
42-
for (const file of status.not_added) {
43-
await fs.remove(path.resolve(dir, file));
44-
}
45-
46-
await git.pull();
47-
48-
const config = fs.readFileSync('./config.yml', 'utf8');
49-
const { tropEmail, tropName } = parse(config);
50-
await git.addConfig('user.email', tropEmail || '[email protected]');
51-
await git.addConfig('user.name', tropName || 'Trop Bot');
62+
// Concurrent access to the repo cache has the potential to mess things up.
63+
await mutexForRepoCache(slug).runExclusive(async () => {
64+
const cacheDir = await updateRepoCache({ slug, accessToken });
65+
await git.clone(cacheDir, '.');
66+
});
5267

53-
await git.addConfig('commit.gpgsign', 'false');
5468
return { dir };
5569
};

yarn.lock

+12
Original file line numberDiff line numberDiff line change
@@ -1704,6 +1704,13 @@ astral-regex@^2.0.0:
17041704
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
17051705
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
17061706

1707+
async-mutex@^0.5.0:
1708+
version "0.5.0"
1709+
resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.5.0.tgz#353c69a0b9e75250971a64ac203b0ebfddd75482"
1710+
integrity sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==
1711+
dependencies:
1712+
tslib "^2.4.0"
1713+
17071714
asynckit@^0.4.0:
17081715
version "0.4.0"
17091716
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -5602,6 +5609,11 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
56025609
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
56035610
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
56045611

5612+
tslib@^2.4.0:
5613+
version "2.6.2"
5614+
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
5615+
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
5616+
56055617
tsutils@^3.21.0:
56065618
version "3.21.0"
56075619
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"

0 commit comments

Comments
 (0)