From 82a77ee06de268666bd10f996eb6140770be93db Mon Sep 17 00:00:00 2001 From: Derek Cormier Date: Fri, 20 Sep 2024 15:18:50 -0700 Subject: [PATCH] chore: shallow clone repositories --- src/domain/create-entry.spec.ts | 15 ++++++++------ src/domain/create-entry.ts | 2 +- src/domain/repository.spec.ts | 28 +++++++++++---------------- src/domain/repository.ts | 6 ++---- src/domain/ruleset-repository.spec.ts | 16 +++++++-------- src/domain/ruleset-repository.ts | 2 +- src/infrastructure/git.ts | 20 +++++++++++++------ 7 files changed, 46 insertions(+), 43 deletions(-) diff --git a/src/domain/create-entry.spec.ts b/src/domain/create-entry.spec.ts index 24d2bf2..52fa8e1 100644 --- a/src/domain/create-entry.spec.ts +++ b/src/domain/create-entry.spec.ts @@ -113,7 +113,8 @@ describe("createEntryFiles", () => { await createEntryService.createEntryFiles(rulesetRepo, bcrRepo, tag, "."); - expect(mockGitClient.checkout).toHaveBeenCalledWith( + expect(mockGitClient.shallowClone).toHaveBeenCalledWith( + rulesetRepo.url, rulesetRepo.diskPath, tag ); @@ -124,11 +125,13 @@ describe("createEntryFiles", () => { const tag = "v1.2.3"; const rulesetRepo = await RulesetRepository.create("repo", "owner", tag); - const bcrRepo = CANONICAL_BCR; + const bcrRepo = Repository.fromCanonicalName(CANONICAL_BCR.canonicalName); await createEntryService.createEntryFiles(rulesetRepo, bcrRepo, tag, "."); - expect(mockGitClient.checkout).toHaveBeenCalledWith( + expect(mockGitClient.shallowClone).toHaveBeenCalledTimes(2); + expect(mockGitClient.shallowClone).toHaveBeenCalledWith( + bcrRepo.url, bcrRepo.diskPath, "main" ); @@ -1256,8 +1259,8 @@ function mockRulesetFiles( patches?: { [path: string]: string }; } = {} ) { - mockGitClient.checkout.mockImplementation( - async (repoPath: string, ref?: string) => { + mockGitClient.shallowClone.mockImplementation( + async (url: string, diskPath: string, ref?: string) => { const moduleRoot = options?.moduleRoot || "."; if (options.extractedModuleContent) { mockedFileReads[EXTRACTED_MODULE_PATH] = options.extractedModuleContent; @@ -1268,7 +1271,7 @@ function mockRulesetFiles( }); } const templatesDir = path.join( - repoPath, + diskPath, RulesetRepository.BCR_TEMPLATE_DIR ); mockedFileReads[ diff --git a/src/domain/create-entry.ts b/src/domain/create-entry.ts index 25563b8..dc0f7a1 100644 --- a/src/domain/create-entry.ts +++ b/src/domain/create-entry.ts @@ -44,7 +44,7 @@ export class CreateEntryService { tag: string, moduleRoot: string ): Promise<{ moduleName: string }> { - await Promise.all([rulesetRepo.checkout(tag), bcrRepo.checkout("main")]); + await Promise.all([rulesetRepo.shallowCloneAndCheckout(tag), bcrRepo.shallowCloneAndCheckout("main")]); const version = RulesetRepository.getVersionFromTag(tag); diff --git a/src/domain/repository.spec.ts b/src/domain/repository.spec.ts index 24d9301..f4dabb3 100644 --- a/src/domain/repository.spec.ts +++ b/src/domain/repository.spec.ts @@ -28,32 +28,29 @@ describe("canonicalName", () => { describe("diskPath", () => { test("is a temp dir", async () => { const repository = new Repository("foo", "bar"); - await repository.checkout(); + await repository.shallowCloneAndCheckout(); expect(repository.diskPath.startsWith(os.tmpdir())).toEqual(true); }); test("is a unique path", async () => { const repositoryA = new Repository("foo", "bar"); - await repositoryA.checkout(); + await repositoryA.shallowCloneAndCheckout(); const repositoryB = new Repository("foo", "bar"); - await repositoryB.checkout(); + await repositoryB.shallowCloneAndCheckout(); expect(repositoryA.diskPath).not.toEqual(repositoryB.diskPath); }); }); -describe("checkout", () => { - test("clones and checks out the repository", async () => { +describe("shallowCloneAndCheckout", () => { + test("clones the repository at the specified branch ", async () => { const repository = new Repository("foo", "bar"); - await repository.checkout("main"); + await repository.shallowCloneAndCheckout("main"); const mockGitClient = mocked(GitClient).mock.instances[0]; - expect(mockGitClient.clone).toHaveBeenCalledWith( + expect(mockGitClient.shallowClone).toHaveBeenCalledWith( repository.url, - repository.diskPath - ); - expect(mockGitClient.checkout).toHaveBeenCalledWith( repository.diskPath, "main" ); @@ -61,13 +58,10 @@ describe("checkout", () => { test("clones and checks out the default branch when branch not specified", async () => { const repository = new Repository("foo", "bar"); - await repository.checkout(); + await repository.shallowCloneAndCheckout(); const mockGitClient = mocked(GitClient).mock.instances[0]; - expect(mockGitClient.clone).toHaveBeenCalledWith( + expect(mockGitClient.shallowClone).toHaveBeenCalledWith( repository.url, - repository.diskPath - ); - expect(mockGitClient.checkout).toHaveBeenCalledWith( repository.diskPath, undefined ); @@ -82,7 +76,7 @@ describe("isCheckedOut", () => { test("true when checked out", async () => { const repository = new Repository("foo", "bar"); - await repository.checkout(); + await repository.shallowCloneAndCheckout(); expect(repository.isCheckedOut()).toEqual(true); }); }); @@ -96,7 +90,7 @@ describe("equals", () => { test("true when one is checked out", async () => { const a = new Repository("foo", "bar"); - await a.checkout(); + await a.shallowCloneAndCheckout(); const b = new Repository("foo", "bar"); expect(a.equals(b)).toEqual(true); }); diff --git a/src/domain/repository.ts b/src/domain/repository.ts index 1b6c9f9..eae0eda 100644 --- a/src/domain/repository.ts +++ b/src/domain/repository.ts @@ -38,14 +38,12 @@ export class Repository { return this._diskPath; } - public async checkout(ref?: string): Promise { + public async shallowCloneAndCheckout(branchOrTag?: string): Promise { const gitClient = new GitClient(); if (!this.isCheckedOut()) { this._diskPath = path.join(os.tmpdir(), randomUUID(), this.name); - await gitClient.clone(this.url, this._diskPath); + await gitClient.shallowClone(this.url, this._diskPath, branchOrTag); } - - await gitClient.checkout(this._diskPath, ref); } public equals(other: Repository): boolean { diff --git a/src/domain/ruleset-repository.spec.ts b/src/domain/ruleset-repository.spec.ts index 371efa9..c56afe6 100644 --- a/src/domain/ruleset-repository.spec.ts +++ b/src/domain/ruleset-repository.spec.ts @@ -396,19 +396,19 @@ function mockRulesetFiles( ) { mocked(GitClient).mockImplementation(() => { return { - checkout: jest.fn(), - clone: jest.fn().mockImplementation(async (url, repoPath) => { + // checkout: jest.fn(), + shallowClone: jest.fn().mockImplementation(async (url, diskPath, branchOrTag) => { const templatesDir = path.join( - repoPath, + diskPath, RulesetRepository.BCR_TEMPLATE_DIR ); mocked(fs.existsSync).mockImplementation(((p: string) => { if ( options.fileExistsMocks && - path.relative(repoPath, p) in options.fileExistsMocks! + path.relative(diskPath, p) in options.fileExistsMocks! ) { - return options.fileExistsMocks[path.relative(repoPath, p)]; + return options.fileExistsMocks[path.relative(diskPath, p)]; } else if (p === path.join(templatesDir, "metadata.template.json")) { return !options.skipMetadataFile; } else if (p === path.join(templatesDir, "presubmit.yml")) { @@ -420,7 +420,7 @@ function mockRulesetFiles( path.join(templatesDir, `config.${options.configExt || "yml"}`) ) { return options.configExists || options.configContent !== undefined; - } else if (p === repoPath) { + } else if (p === diskPath) { return true; } return (jest.requireActual("node:fs") as any).existsSync(path); @@ -432,9 +432,9 @@ function mockRulesetFiles( ) => { if ( options.fileContentMocks && - path.relative(repoPath, p) in options.fileContentMocks! + path.relative(diskPath, p) in options.fileContentMocks! ) { - return options.fileContentMocks[path.relative(repoPath, p)]; + return options.fileContentMocks[path.relative(diskPath, p)]; } else if (p === path.join(templatesDir, "metadata.template.json")) { return fakeMetadataFile({ malformed: options.invalidMetadataFile, diff --git a/src/domain/ruleset-repository.ts b/src/domain/ruleset-repository.ts index 124386e..7df3e23 100644 --- a/src/domain/ruleset-repository.ts +++ b/src/domain/ruleset-repository.ts @@ -110,7 +110,7 @@ export class RulesetRepository extends Repository { verifyAtRef?: string ): Promise { const rulesetRepo = new RulesetRepository(name, owner); - await rulesetRepo.checkout(verifyAtRef); + await rulesetRepo.shallowCloneAndCheckout(verifyAtRef); rulesetRepo._config = loadConfiguration(rulesetRepo); diff --git a/src/infrastructure/git.ts b/src/infrastructure/git.ts index bb9732c..e502728 100644 --- a/src/infrastructure/git.ts +++ b/src/infrastructure/git.ts @@ -3,12 +3,20 @@ import { simpleGit } from "simple-git"; @Injectable() export class GitClient { - public async clone(url: string, repoPath: string): Promise { - await simpleGit().clone(url, repoPath); - } - - public async checkout(repoPath: string, ref?: string): Promise { - await simpleGit(repoPath).clean(["f", "f", "x", "d"]).checkout(ref); + public async shallowClone(url: string, diskPath: string, branchOrTag?: string): Promise { + await simpleGit().clone(url, diskPath, [ + ...(branchOrTag ? [ + // Check out a single commit on the tip of the branch or at a tag + // From the docs: "--branch can also take tags and detaches the HEAD at that commit in the resulting repository" + // https://git-scm.com/docs/git-clone#Documentation/git-clone.txt-code--branchcodeemltnamegtem + "--branch", + branchOrTag, + "--single-branch" + ] : [ + // Check out a single commit on the main branch + "--depth", "1" + ]) + ]); } public async setUserNameAndEmail(