Skip to content

Commit e6f8f65

Browse files
committed
Check that tracking branch exists when updating git settings
1 parent 2428ce0 commit e6f8f65

File tree

2 files changed

+96
-4
lines changed
  • apps/webapp/app

2 files changed

+96
-4
lines changed

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ import {
7575
import { GitBranchIcon } from "lucide-react";
7676
import { env } from "~/env.server";
7777
import { useEnvironment } from "~/hooks/useEnvironment";
78-
import { DateTime, DateTimeShort } from "~/components/primitives/DateTime";
78+
import { DateTime } from "~/components/primitives/DateTime";
79+
import { checkGitHubBranchExists } from "~/services/gitHub.server";
80+
import { tryCatch } from "@trigger.dev/core";
7981

8082
export const meta: MetaFunction = () => {
8183
return [
@@ -163,7 +165,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
163165
// Most installations will only have a couple of repos so loading them here should be fine.
164166
// However, there might be outlier organizations so it's best to expose the installation repos
165167
// via a resource endpoint and filter on user input.
166-
take: 100,
168+
take: 200,
167169
},
168170
},
169171
take: 20,
@@ -187,8 +189,8 @@ const ConnectGitHubRepoFormSchema = z.object({
187189

188190
const UpdateGitSettingsFormSchema = z.object({
189191
action: z.literal("update-git-settings"),
190-
productionBranch: z.string().optional(),
191-
stagingBranch: z.string().optional(),
192+
productionBranch: z.string().trim().optional(),
193+
stagingBranch: z.string().trim().optional(),
192194
previewDeploymentsEnabled: z
193195
.string()
194196
.optional()
@@ -333,12 +335,57 @@ export const action: ActionFunction = async ({ request, params }) => {
333335
where: {
334336
projectId: project.id,
335337
},
338+
include: {
339+
repository: {
340+
include: {
341+
installation: true,
342+
},
343+
},
344+
},
336345
});
337346

338347
if (!existingConnection) {
339348
return redirectBackWithErrorMessage(request, "No connected GitHub repository found");
340349
}
341350

351+
const [owner, repo] = existingConnection.repository.fullName.split("/");
352+
const installationId = Number(existingConnection.repository.installation.appInstallationId);
353+
354+
const existingBranchTracking = BranchTrackingConfigSchema.safeParse(
355+
existingConnection.branchTracking
356+
);
357+
358+
const [error, branchValidationsOrFail] = await tryCatch(
359+
Promise.all([
360+
productionBranch && existingBranchTracking.data?.production?.branch !== productionBranch
361+
? checkGitHubBranchExists(installationId, owner, repo, productionBranch)
362+
: Promise.resolve(true),
363+
stagingBranch && existingBranchTracking.data?.staging?.branch !== stagingBranch
364+
? checkGitHubBranchExists(installationId, owner, repo, stagingBranch)
365+
: Promise.resolve(true),
366+
])
367+
);
368+
369+
if (error) {
370+
return redirectBackWithErrorMessage(request, "Failed to validate tracking branches");
371+
}
372+
373+
const [productionBranchExists, stagingBranchExists] = branchValidationsOrFail;
374+
375+
if (productionBranch && !productionBranchExists) {
376+
return redirectBackWithErrorMessage(
377+
request,
378+
`Production tracking branch '${productionBranch}' does not exist in the repository`
379+
);
380+
}
381+
382+
if (stagingBranch && !stagingBranchExists) {
383+
return redirectBackWithErrorMessage(
384+
request,
385+
`Staging tracking branch '${stagingBranch}' does not exist in the repository`
386+
);
387+
}
388+
342389
await prisma.connectedGithubRepository.update({
343390
where: {
344391
projectId: project.id,

apps/webapp/app/services/gitHub.server.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { App, type Octokit } from "octokit";
22
import { env } from "../env.server";
33
import { prisma } from "~/db.server";
44
import { logger } from "./logger.server";
5+
import { tryCatch } from "@trigger.dev/core";
56

67
export const githubApp =
78
env.GITHUB_APP_ENABLED === "1"
@@ -133,3 +134,47 @@ async function fetchInstallationRepositories(octokit: Octokit, installationId: n
133134
defaultBranch: repo.default_branch,
134135
}));
135136
}
137+
138+
/**
139+
* Checks if a branch exists in a GitHub repository
140+
*/
141+
export async function checkGitHubBranchExists(
142+
installationId: number,
143+
owner: string,
144+
repo: string,
145+
branch: string
146+
): Promise<boolean> {
147+
if (!githubApp) {
148+
throw new Error("GitHub App is not enabled");
149+
}
150+
151+
if (!branch || branch.trim() === "") {
152+
return false;
153+
}
154+
155+
const octokit = await githubApp.getInstallationOctokit(installationId);
156+
const [error] = await tryCatch(
157+
octokit.rest.repos.getBranch({
158+
owner,
159+
repo,
160+
branch,
161+
})
162+
);
163+
164+
if (error && "status" in error && error.status === 404) {
165+
return false;
166+
}
167+
168+
if (error) {
169+
logger.error("Error checking GitHub branch", {
170+
installationId,
171+
owner,
172+
repo,
173+
branch,
174+
error: error.message,
175+
});
176+
throw error;
177+
}
178+
179+
return true;
180+
}

0 commit comments

Comments
 (0)