11import { CheckRepoActions , GitConfigScope , simpleGit , SimpleGitProgressEvent } from 'simple-git' ;
22import { mkdir } from 'node:fs/promises' ;
33import { env } from './env.js' ;
4+ import { dirname , resolve } from 'node:path' ;
5+ import { existsSync } from 'node:fs' ;
46
57type onProgressFn = ( event : SimpleGitProgressEvent ) => void ;
68
9+ /**
10+ * Creates a simple-git client that has it's working directory
11+ * set to the given path.
12+ */
13+ const createGitClientForPath = ( path : string , onProgress ?: onProgressFn , signal ?: AbortSignal ) => {
14+ if ( ! existsSync ( path ) ) {
15+ throw new Error ( `Path ${ path } does not exist` ) ;
16+ }
17+
18+ const parentPath = resolve ( dirname ( path ) ) ;
19+
20+ const git = simpleGit ( {
21+ progress : onProgress ,
22+ abort : signal ,
23+ } )
24+ . env ( {
25+ ...process . env ,
26+ /**
27+ * @note on some inside-baseball on why this is necessary: The specific
28+ * issue we saw was that a `git clone` would fail without throwing, and
29+ * then a subsequent `git config` command would run, but since the clone
30+ * failed, it wouldn't be running in a git directory. Git would then walk
31+ * up the directory tree until it either found a git directory (in the case
32+ * of the development env) or it would hit a GIT_DISCOVERY_ACROSS_FILESYSTEM
33+ * error when trying to cross a filesystem boundary (in the prod case).
34+ * GIT_CEILING_DIRECTORIES ensures that this walk will be limited to the
35+ * parent directory.
36+ */
37+ GIT_CEILING_DIRECTORIES : parentPath ,
38+ } )
39+ . cwd ( {
40+ path,
41+ } ) ;
42+
43+ return git ;
44+ }
45+
746export const cloneRepository = async (
847 {
948 cloneUrl,
1049 authHeader,
1150 path,
1251 onProgress,
52+ signal,
1353 } : {
1454 cloneUrl : string ,
1555 authHeader ?: string ,
1656 path : string ,
1757 onProgress ?: onProgressFn
58+ signal ?: AbortSignal
1859 }
1960) => {
2061 try {
2162 await mkdir ( path , { recursive : true } ) ;
2263
23- const git = simpleGit ( {
24- progress : onProgress ,
25- } ) . cwd ( {
26- path,
27- } )
64+ const git = createGitClientForPath ( path , onProgress , signal ) ;
2865
2966 const cloneArgs = [
3067 "--bare" ,
@@ -33,7 +70,11 @@ export const cloneRepository = async (
3370
3471 await git . clone ( cloneUrl , path , cloneArgs ) ;
3572
36- await unsetGitConfig ( path , [ "remote.origin.url" ] ) ;
73+ await unsetGitConfig ( {
74+ path,
75+ keys : [ "remote.origin.url" ] ,
76+ signal,
77+ } ) ;
3778 } catch ( error : unknown ) {
3879 const baseLog = `Failed to clone repository: ${ path } ` ;
3980
@@ -54,20 +95,17 @@ export const fetchRepository = async (
5495 authHeader,
5596 path,
5697 onProgress,
98+ signal,
5799 } : {
58100 cloneUrl : string ,
59101 authHeader ?: string ,
60102 path : string ,
61- onProgress ?: onProgressFn
103+ onProgress ?: onProgressFn ,
104+ signal ?: AbortSignal
62105 }
63106) => {
107+ const git = createGitClientForPath ( path , onProgress , signal ) ;
64108 try {
65- const git = simpleGit ( {
66- progress : onProgress ,
67- } ) . cwd ( {
68- path : path ,
69- } )
70-
71109 if ( authHeader ) {
72110 await git . addConfig ( "http.extraHeader" , authHeader ) ;
73111 }
@@ -90,12 +128,6 @@ export const fetchRepository = async (
90128 }
91129 } finally {
92130 if ( authHeader ) {
93- const git = simpleGit ( {
94- progress : onProgress ,
95- } ) . cwd ( {
96- path : path ,
97- } )
98-
99131 await git . raw ( [ "config" , "--unset" , "http.extraHeader" , authHeader ] ) ;
100132 }
101133 }
@@ -107,10 +139,19 @@ export const fetchRepository = async (
107139 * that do not exist yet. It will _not_ remove any existing keys that are not
108140 * present in gitConfig.
109141 */
110- export const upsertGitConfig = async ( path : string , gitConfig : Record < string , string > , onProgress ?: onProgressFn ) => {
111- const git = simpleGit ( {
112- progress : onProgress ,
113- } ) . cwd ( path ) ;
142+ export const upsertGitConfig = async (
143+ {
144+ path,
145+ gitConfig,
146+ onProgress,
147+ signal,
148+ } : {
149+ path : string ,
150+ gitConfig : Record < string , string > ,
151+ onProgress ?: onProgressFn ,
152+ signal ?: AbortSignal
153+ } ) => {
154+ const git = createGitClientForPath ( path , onProgress , signal ) ;
114155
115156 try {
116157 for ( const [ key , value ] of Object . entries ( gitConfig ) ) {
@@ -129,10 +170,19 @@ export const upsertGitConfig = async (path: string, gitConfig: Record<string, st
129170 * Unsets the specified keys in the git config for the repo at the given path.
130171 * If a key is not set, this is a no-op.
131172 */
132- export const unsetGitConfig = async ( path : string , keys : string [ ] , onProgress ?: onProgressFn ) => {
133- const git = simpleGit ( {
134- progress : onProgress ,
135- } ) . cwd ( path ) ;
173+ export const unsetGitConfig = async (
174+ {
175+ path,
176+ keys,
177+ onProgress,
178+ signal,
179+ } : {
180+ path : string ,
181+ keys : string [ ] ,
182+ onProgress ?: onProgressFn ,
183+ signal ?: AbortSignal
184+ } ) => {
185+ const git = createGitClientForPath ( path , onProgress , signal ) ;
136186
137187 try {
138188 const configList = await git . listConfig ( ) ;
@@ -155,10 +205,20 @@ export const unsetGitConfig = async (path: string, keys: string[], onProgress?:
155205/**
156206 * Returns true if `path` is the _root_ of a git repository.
157207 */
158- export const isPathAValidGitRepoRoot = async ( path : string , onProgress ?: onProgressFn ) => {
159- const git = simpleGit ( {
160- progress : onProgress ,
161- } ) . cwd ( path ) ;
208+ export const isPathAValidGitRepoRoot = async ( {
209+ path,
210+ onProgress,
211+ signal,
212+ } : {
213+ path : string ,
214+ onProgress ?: onProgressFn ,
215+ signal ?: AbortSignal
216+ } ) => {
217+ if ( ! existsSync ( path ) ) {
218+ return false ;
219+ }
220+
221+ const git = createGitClientForPath ( path , onProgress , signal ) ;
162222
163223 try {
164224 return git . checkIsRepo ( CheckRepoActions . IS_REPO_ROOT ) ;
@@ -184,7 +244,7 @@ export const isUrlAValidGitRepo = async (url: string) => {
184244}
185245
186246export const getOriginUrl = async ( path : string ) => {
187- const git = simpleGit ( ) . cwd ( path ) ;
247+ const git = createGitClientForPath ( path ) ;
188248
189249 try {
190250 const remotes = await git . getConfig ( 'remote.origin.url' , GitConfigScope . local ) ;
@@ -199,18 +259,13 @@ export const getOriginUrl = async (path: string) => {
199259}
200260
201261export const getBranches = async ( path : string ) => {
202- const git = simpleGit ( ) ;
203- const branches = await git . cwd ( {
204- path,
205- } ) . branch ( ) ;
206-
262+ const git = createGitClientForPath ( path ) ;
263+ const branches = await git . branch ( ) ;
207264 return branches . all ;
208265}
209266
210267export const getTags = async ( path : string ) => {
211- const git = simpleGit ( ) ;
212- const tags = await git . cwd ( {
213- path,
214- } ) . tags ( ) ;
268+ const git = createGitClientForPath ( path ) ;
269+ const tags = await git . tags ( ) ;
215270 return tags . all ;
216271}
0 commit comments