Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: shallow clone repositories #167

Merged
merged 1 commit into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
chore: shallow clone repositories
  • Loading branch information
kormide committed Sep 20, 2024
commit b4f5f02b33cdb0c62dab38e376078c1757a84856
15 changes: 9 additions & 6 deletions src/domain/create-entry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
Expand All @@ -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"
);
Expand Down Expand Up @@ -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;
Expand All @@ -1268,7 +1271,7 @@ function mockRulesetFiles(
});
}
const templatesDir = path.join(
repoPath,
diskPath,
RulesetRepository.BCR_TEMPLATE_DIR
);
mockedFileReads[
Expand Down
2 changes: 1 addition & 1 deletion src/domain/create-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
28 changes: 11 additions & 17 deletions src/domain/repository.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,46 +28,40 @@ 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"
);
});

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
);
Expand All @@ -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);
});
});
Expand All @@ -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);
});
Expand Down
6 changes: 2 additions & 4 deletions src/domain/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,12 @@ export class Repository {
return this._diskPath;
}

public async checkout(ref?: string): Promise<void> {
public async shallowCloneAndCheckout(branchOrTag?: string): Promise<void> {
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 {
Expand Down
16 changes: 8 additions & 8 deletions src/domain/ruleset-repository.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")) {
Expand All @@ -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);
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/domain/ruleset-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export class RulesetRepository extends Repository {
verifyAtRef?: string
): Promise<RulesetRepository> {
const rulesetRepo = new RulesetRepository(name, owner);
await rulesetRepo.checkout(verifyAtRef);
await rulesetRepo.shallowCloneAndCheckout(verifyAtRef);

rulesetRepo._config = loadConfiguration(rulesetRepo);

Expand Down
20 changes: 14 additions & 6 deletions src/infrastructure/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@ import { simpleGit } from "simple-git";

@Injectable()
export class GitClient {
public async clone(url: string, repoPath: string): Promise<void> {
await simpleGit().clone(url, repoPath);
}

public async checkout(repoPath: string, ref?: string): Promise<void> {
await simpleGit(repoPath).clean(["f", "f", "x", "d"]).checkout(ref);
public async shallowClone(url: string, diskPath: string, branchOrTag?: string): Promise<void> {
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(
Expand Down