@@ -2,54 +2,68 @@ import { parse } from 'yaml';
2
2
import * as fs from 'fs-extra' ;
3
3
import * as os from 'os' ;
4
4
import * as path from 'path' ;
5
- import simpleGit from 'simple-git' ;
5
+ import simpleGit , { CheckRepoActions } from 'simple-git' ;
6
6
import { InitRepoOptions } from '../interfaces' ;
7
7
import { LogLevel } from '../enums' ;
8
8
import { log } from '../utils/log-util' ;
9
- import { ResetMode } from 'simple-git ' ;
9
+ import { Mutex } from 'async-mutex ' ;
10
10
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
+ }
12
44
13
45
/**
14
46
* Initializes the cloned repo trop will use to run backports.
15
47
*
16
48
* @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
18
50
*/
19
- export const initRepo = async ( { slug, accessToken } : InitRepoOptions ) => {
51
+ export const initRepo = async ( {
52
+ slug,
53
+ accessToken,
54
+ } : InitRepoOptions ) : Promise < { dir : string } > => {
20
55
log ( 'initRepo' , LogLevel . INFO , 'Setting up local repository' ) ;
21
56
22
57
await fs . mkdirp ( path . resolve ( baseDir , slug ) ) ;
23
58
const prefix = path . resolve ( baseDir , slug , 'job-' ) ;
24
59
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
-
31
60
const git = simpleGit ( dir ) ;
32
61
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
+ } ) ;
52
67
53
- await git . addConfig ( 'commit.gpgsign' , 'false' ) ;
54
68
return { dir } ;
55
69
} ;
0 commit comments