Skip to content

Commit 22f3630

Browse files
Merge pull request #6548 from Shopify/fix-git-error-handling
Improve git error handling to prevent false bug reports in Observe
2 parents a43ed51 + c87dbbe commit 22f3630

File tree

2 files changed

+51
-56
lines changed

2 files changed

+51
-56
lines changed

packages/cli-kit/src/public/node/git.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ describe('initializeRepository()', () => {
252252

253253
// Then
254254
expect(simpleGit).toHaveBeenCalledOnce()
255-
expect(simpleGit).toHaveBeenCalledWith('/tmp/git-repo')
255+
expect(simpleGit).toHaveBeenCalledWith({baseDir: '/tmp/git-repo'})
256256

257257
expect(mockedInit).toHaveBeenCalledOnce()
258258
expect(mockedCheckout).toHaveBeenCalledOnce()
@@ -617,7 +617,7 @@ describe('removeGitRemote()', () => {
617617
await git.removeGitRemote(directory, remoteName)
618618

619619
// Then
620-
expect(simpleGit).toHaveBeenCalledWith(directory)
620+
expect(simpleGit).toHaveBeenCalledWith({baseDir: directory})
621621
expect(mockedGetRemotes).toHaveBeenCalled()
622622
expect(mockedRemoveRemote).toHaveBeenCalledWith(remoteName)
623623
})
@@ -635,7 +635,7 @@ describe('removeGitRemote()', () => {
635635
await git.removeGitRemote(directory, remoteName)
636636

637637
// Then
638-
expect(simpleGit).toHaveBeenCalledWith(directory)
638+
expect(simpleGit).toHaveBeenCalledWith({baseDir: directory})
639639
expect(mockedGetRemotes).toHaveBeenCalled()
640640
expect(mockedRemoveRemote).not.toHaveBeenCalled()
641641
})

packages/cli-kit/src/public/node/git.ts

Lines changed: 48 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,10 @@ export async function initializeGitRepository(directory: string, initialBranch =
2727
outputDebug(outputContent`Initializing git repository at ${outputToken.path(directory)}...`)
2828
await ensureGitIsPresentOrAbort()
2929
// We use init and checkout instead of `init --initial-branch` because the latter is only supported in git 2.28+
30-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
31-
// @ts-ignore
32-
const repo = git(directory)
33-
await repo.init()
34-
await repo.checkoutLocalBranch(initialBranch)
30+
await withGit({directory}, async (repo) => {
31+
await repo.init()
32+
await repo.checkoutLocalBranch(initialBranch)
33+
})
3534
}
3635

3736
/**
@@ -44,11 +43,7 @@ export async function initializeGitRepository(directory: string, initialBranch =
4443
* @returns Files ignored by the lockfile.
4544
*/
4645
export async function checkIfIgnoredInGitRepository(directory: string, files: string[]): Promise<string[]> {
47-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
48-
// @ts-ignore
49-
const repo = git(directory)
50-
const ignoredLockfile = await repo.checkIgnore(files)
51-
return ignoredLockfile
46+
return withGit({directory}, (repo) => repo.checkIgnore(files))
5247
}
5348

5449
export interface GitIgnoreTemplate {
@@ -199,11 +194,10 @@ export async function downloadGitRepository(cloneOptions: GitCloneOptions): Prom
199194
await git(simpleGitOptions).clone(repository!, destination, options)
200195

201196
if (latestTag) {
202-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
203-
// @ts-ignore
204-
const localGitRepository = git(destination)
205-
const latestTag = await getLocalLatestTag(localGitRepository, repoUrl)
206-
await localGitRepository.checkout(latestTag)
197+
await withGit({directory: destination}, async (localGitRepository) => {
198+
const latestTag = await getLocalLatestTag(localGitRepository, repoUrl)
199+
await localGitRepository.checkout(latestTag)
200+
})
207201
}
208202
} catch (err) {
209203
if (err instanceof Error) {
@@ -240,11 +234,7 @@ async function getLocalLatestTag(repository: SimpleGit, repoUrl: string): Promis
240234
* @returns The latest commit of the repository.
241235
*/
242236
export async function getLatestGitCommit(directory?: string): Promise<DefaultLogFields & ListLogLine> {
243-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
244-
// @ts-ignore
245-
const logs = await git({baseDir: directory}).log({
246-
maxCount: 1,
247-
})
237+
const logs = await withGit({directory}, (repo) => repo.log({maxCount: 1}))
248238
if (!logs.latest) {
249239
throw new AbortError(
250240
'Must have at least one commit to run command',
@@ -263,10 +253,7 @@ export async function getLatestGitCommit(directory?: string): Promise<DefaultLog
263253
* @returns A promise that resolves when the files are added to the index.
264254
*/
265255
export async function addAllToGitFromDirectory(directory?: string): Promise<void> {
266-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
267-
// @ts-ignore
268-
const simpleGit = git({baseDir: directory})
269-
await simpleGit.raw('add', '--all')
256+
await withGit({directory}, (repo) => repo.raw('add', '--all'))
270257
}
271258

272259
export interface CreateGitCommitOptions {
@@ -282,13 +269,8 @@ export interface CreateGitCommitOptions {
282269
* @returns The hash of the created commit.
283270
*/
284271
export async function createGitCommit(message: string, options?: CreateGitCommitOptions): Promise<string> {
285-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
286-
// @ts-ignore
287-
const simpleGit = git({baseDir: options?.directory})
288-
289272
const commitOptions = options?.author ? {'--author': options.author} : undefined
290-
const result = await simpleGit.commit(message, commitOptions)
291-
273+
const result = await withGit({directory: options?.directory}, (repo) => repo.commit(message, commitOptions))
292274
return result.commit
293275
}
294276

@@ -299,9 +281,7 @@ export async function createGitCommit(message: string, options?: CreateGitCommit
299281
* @returns The HEAD symbolic reference of the repository.
300282
*/
301283
export async function getHeadSymbolicRef(directory?: string): Promise<string> {
302-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
303-
// @ts-ignore
304-
const ref = await git({baseDir: directory}).raw('symbolic-ref', '-q', 'HEAD')
284+
const ref = await withGit({directory}, (repo) => repo.raw('symbolic-ref', '-q', 'HEAD'))
305285
if (!ref) {
306286
throw new AbortError(
307287
"Git HEAD can't be detached to run command",
@@ -354,9 +334,7 @@ export async function ensureInsideGitDirectory(directory?: string): Promise<void
354334
* @returns True if the directory is inside a .git directory tree.
355335
*/
356336
export async function insideGitDirectory(directory?: string): Promise<boolean> {
357-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
358-
// @ts-ignore
359-
return git({baseDir: directory}).checkIsRepo()
337+
return withGit({directory}, (repo) => repo.checkIsRepo())
360338
}
361339

362340
export class GitDirectoryNotCleanError extends AbortError {}
@@ -379,9 +357,7 @@ export async function ensureIsClean(directory?: string): Promise<void> {
379357
* @returns True is the .git directory is clean.
380358
*/
381359
export async function isClean(directory?: string): Promise<boolean> {
382-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
383-
// @ts-ignore
384-
return (await git({baseDir: directory}).status()).isClean()
360+
return (await withGit({directory}, (git: SimpleGit) => git.status())).isClean()
385361
}
386362

387363
/**
@@ -391,9 +367,7 @@ export async function isClean(directory?: string): Promise<boolean> {
391367
* @returns String with the latest tag or undefined if no tags are found.
392368
*/
393369
export async function getLatestTag(directory?: string): Promise<string | undefined> {
394-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
395-
// @ts-ignore
396-
const tags = await git({baseDir: directory}).tags()
370+
const tags = await withGit({directory}, (repo) => repo.tags())
397371
return tags.latest
398372
}
399373

@@ -408,18 +382,39 @@ export async function removeGitRemote(directory: string, remoteName = 'origin'):
408382
outputDebug(outputContent`Removing git remote ${remoteName} from ${outputToken.path(directory)}...`)
409383
await ensureGitIsPresentOrAbort()
410384

411-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
412-
// @ts-ignore
413-
const repo = git(directory)
385+
await withGit({directory}, async (repo) => {
386+
// Check if remote exists first
387+
const remotes = await repo.getRemotes()
388+
const remoteExists = remotes.some((remote: {name: string}) => remote.name === remoteName)
389+
390+
if (!remoteExists) {
391+
outputDebug(outputContent`Remote ${remoteName} does not exist, no action needed`)
392+
return
393+
}
414394

415-
// Check if remote exists first
416-
const remotes = await repo.getRemotes()
417-
const remoteExists = remotes.some((remote: {name: string}) => remote.name === remoteName)
395+
await repo.removeRemote(remoteName)
396+
})
397+
}
418398

419-
if (!remoteExists) {
420-
outputDebug(outputContent`Remote ${remoteName} does not exist, no action needed`)
421-
return
399+
async function withGit<T>(
400+
{
401+
directory,
402+
}: {
403+
directory?: string
404+
},
405+
callback: (git: SimpleGit) => Promise<T>,
406+
): Promise<T> {
407+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
408+
// @ts-ignore
409+
const repo = git({baseDir: directory})
410+
try {
411+
return await callback(repo)
412+
} catch (err) {
413+
if (err instanceof Error) {
414+
const abortError = new AbortError(err.message)
415+
abortError.stack = err.stack
416+
throw abortError
417+
}
418+
throw err
422419
}
423-
424-
await repo.removeRemote(remoteName)
425420
}

0 commit comments

Comments
 (0)