Skip to content
This repository has been archived by the owner on Sep 28, 2024. It is now read-only.

Support default branch names other than main or master #17

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
15 changes: 15 additions & 0 deletions app/agent/activities/get-default-branch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ export type GetDefaultBranchActivity = (gitRepositoryId: bigint) => Promise<GitB
export const getDefaultBranch: CreateActivity<GetDefaultBranchActivity> =
({ db }) =>
async (gitRepositoryId) => {
const repo = await db.gitRepository.findFirstOrThrow({
where: {
gitRepositoryId,
},
select: { defaultBranch: true },
});
const branch = await db.gitBranch.findFirst({
where: {
gitRepositoryId,
name: repo.defaultBranch,
},
});
if (branch !== null) return branch;

// If the remote ref is invalid then fallback to main or master
const branches = await db.gitBranch.findMany({
where: {
gitRepositoryId,
Expand Down
13 changes: 9 additions & 4 deletions app/agent/git/git.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,25 @@ test.concurrent(
"getRemotes",
provideInjector(async ({ injector }) => {
const gitService = injector.resolve(Token.AgentGitService);
const remotesLinux = await gitService.getRemotes(
const linux = await gitService.getRemotes(
"[email protected]:torvalds/linux.git",
TESTS_PRIVATE_SSH_KEY,
);
const linuxMaster = remotesLinux.find((r) => r.name === "refs/heads/master");
const linuxMaster = linux.remotes.find((r) => r.name === "refs/heads/master");
expect(linuxMaster).toBeDefined();
expect(linux.defaultBranch).toEqual("refs/heads/master");

const remotes = await gitService.getRemotes(TESTS_REPO_URL, TESTS_PRIVATE_SSH_KEY);
const testRemote = remotes.find(
const tests = await gitService.getRemotes(TESTS_REPO_URL, TESTS_PRIVATE_SSH_KEY);
const testRemote = tests.remotes.find(
(r) =>
r.name === "refs/heads/git-service-test" &&
r.hash === "6aa1af1afb061b67d22e6bcd8a1d8d5bbec64987",
);
expect(testRemote).toBeDefined();
expect(tests.defaultBranch).toEqual("refs/heads/main");

const tests2 = await gitService.getRemotes(TESTS2_REPO_URL, TESTS_PRIVATE_SSH_KEY);
expect(tests.defaultBranch).toEqual("refs/heads/custom");
}),
);

Expand Down
31 changes: 22 additions & 9 deletions app/agent/git/git.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,25 @@ export class AgentGitService {

private parseLsRemoteOutput(output: string): {
remotes: GitRemoteInfo[];
defaultBranch: string;
errors: ParseError[];
} {
const remotes: GitRemoteInfo[] = [];
const errors: ParseError[] = [];
let defaultBranch = "";

output
.toString()
.split("\n")
.filter((line) => line.length > 0)
.forEach((line) => {
const words = line.split(/\s/);
if (words.length > 0 && words[0] === "ref:") {
if (words.length === 3 && words[2] === "HEAD") {
defaultBranch = words[1];
}
return;
}
const result = RemoteInfoTupleValidator.SafeParse(words);
if (result.success) {
const value = result.value;
Expand All @@ -79,7 +87,7 @@ export class AgentGitService {
}
});

return { remotes, errors };
return { remotes, defaultBranch, errors };
}

private async writeKey(pathToPrivateKey: string, key: string): Promise<void> {
Expand All @@ -98,11 +106,14 @@ export class AgentGitService {
* be a deploy key for a diffrerent repository. But GitHub must know
* that it exists, otherwise it will reject the connection.
*/
async getRemotes(repositoryUrl: string, privateKey: string): Promise<GitRemoteInfo[]> {
async getRemotes(
repositoryUrl: string,
privateKey: string,
): Promise<{ defaultBranch: string; remotes: GitRemoteInfo[] }> {
const pathToPrivateKey = `/tmp/${uuidv4()}.key`;
try {
await this.writeKey(pathToPrivateKey, privateKey);
const output = await execCmdWithOpts(["git", "ls-remote", repositoryUrl], {
const output = await execCmdWithOpts(["git", "ls-remote", "--symref", repositoryUrl], {
// TODO: allow the user to specify a known_hosts file.
env: { GIT_SSH_COMMAND: `ssh -i "${pathToPrivateKey}" -o StrictHostKeyChecking=no` },
});
Expand All @@ -112,7 +123,7 @@ export class AgentGitService {
`Failed to parse git ls-remote output:\n${error.message}\nOffending value: "${value}"`,
);
}
return result.remotes;
return { remotes: result.remotes, defaultBranch: result.defaultBranch };
} finally {
await fs.unlink(pathToPrivateKey).catch((err) => {
this.logger.warn(
Expand Down Expand Up @@ -161,10 +172,8 @@ export class AgentGitService {
},
});
let gitRepository = await getGitRepo(db);
const newRemotes = await this.getRemotes(
gitRepository.url,
gitRepository.sshKeyPair.privateKey,
).then((rs) => rs.filter((remote) => this.branchRefRegex.test(remote.name)));
const lsRemotes = await this.getRemotes(gitRepository.url, gitRepository.sshKeyPair.privateKey);
const newRemotes = lsRemotes.remotes.filter((remote) => this.branchRefRegex.test(remote.name));
return await db.$transaction(async (tdb) => {
await tdb.$executeRaw`SELECT id FROM "GitRepository" WHERE id = ${gitRepositoryId} FOR UPDATE`;
gitRepository = await getGitRepo(tdb);
Expand Down Expand Up @@ -223,10 +232,14 @@ export class AgentGitService {
}),
),
);
if (newGitBranches.length + updatedGitBranches.length > 0) {
if (
newGitBranches.length + updatedGitBranches.length > 0 ||
gitRepository.defaultBranch !== lsRemotes.defaultBranch
) {
await tdb.gitRepository.update({
where: { id: gitRepositoryId },
data: {
defaultBranch: lsRemotes.defaultBranch,
lastBranchUpdateAt: new Date(),
},
});
Expand Down
1 change: 1 addition & 0 deletions app/test-utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ W71TsDKa/ovAG6nXpmidAAAAF2hvY3VzLXRlc3RzQGV4YW1wbGUuY29tAQIDBAUG
-----END OPENSSH PRIVATE KEY-----
`;
export const TESTS_REPO_URL = "[email protected]:hocus-dev/tests.git";
export const TESTS2_REPO_URL = "[email protected]:hocus-dev/tests2.git";
export const HOCUS_REPO_URL = "[email protected]:hocus-dev/hocus.git";
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "GitRepository" ADD COLUMN "defaultBranch" TEXT NOT NULL DEFAULT '';
11 changes: 6 additions & 5 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,12 @@ model SshKeyPair {
}

model GitRepository {
id BigInt @id @default(autoincrement())
url String @unique
sshKeyPair SshKeyPair @relation(fields: [sshKeyPairId], references: [id])
sshKeyPairId BigInt
gitBranches GitBranch[]
id BigInt @id @default(autoincrement())
url String @unique
defaultBranch String @default("")
sshKeyPair SshKeyPair @relation(fields: [sshKeyPairId], references: [id])
sshKeyPairId BigInt
gitBranches GitBranch[]

lastBranchUpdateAt DateTime @default(now()) @db.Timestamptz(3)
createdAt DateTime @default(now()) @db.Timestamptz(3)
Expand Down