authorgravatar for chaudhary.dev.talan@gmail.comDev Talan <chaudhary.dev.talan@gmail.com> 2026-08-15 17:56:27+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-15 12:26:27+00:00
log86fb47afd724ea28e96c4657a0e99b66af926b87
tree84a0340bd5f9daf2fae2cdcd016bdd8b52781640
parentd550b829bd169fa8973b17df940ef2fbf2e69f37
signature Signed by PGP key B5690EEEBB952194

fix(server): allow long-running git pushes (#6499)


3 files changed, 67 insertions(+), 25 deletions(-)

apps/server/src/vcs/GitVcsDriver.ts+1-1
......@@ -41,7 +41,7 @@ export interface ExecuteGitInput {
4141 readonly stdin?: string;
4242 readonly env?: NodeJS.ProcessEnv;
4343 readonly allowNonZeroExit?: boolean;
44 readonly timeoutMs?: number;
44 readonly timeoutMs?: number | null;
4545 readonly maxOutputBytes?: number;
4646 readonly appendTruncationMarker?: boolean;
4747 readonly progress?: ExecuteGitProgress;
apps/server/src/vcs/GitVcsDriverCore.test.ts+37
......@@ -1611,6 +1611,43 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
16111611 }),
16121612 );
16131613
1614 it.effect("allows pushes to run longer than the default command timeout", () =>
1615 Effect.gen(function* () {
1616 const delegate = yield* ChildProcessSpawner.ChildProcessSpawner;
1617 const pushStarted = yield* Deferred.make<void>();
1618 const delayedPushSpawner = ChildProcessSpawner.make((command) =>
1619 Effect.gen(function* () {
1620 if (ChildProcess.isStandardCommand(command) && command.args[0] === "push") {
1621 yield* Deferred.succeed(pushStarted, undefined);
1622 yield* Effect.sleep("31 seconds");
1623 }
1624 return yield* delegate.spawn(command);
1625 }),
1626 );
1627 const driver = yield* makeGitVcsDriverCore().pipe(
1628 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, delayedPushSpawner),
1629 Effect.provide(ServerConfigLayer),
1630 );
1631 const cwd = yield* makeTmpDir();
1632 const remote = yield* makeTmpDir("git-remote-");
1633 yield* initRepoWithCommit(cwd);
1634 yield* git(remote, ["init", "--bare"]);
1635 yield* git(cwd, ["remote", "add", "origin", remote]);
1636
1637 const pushing = yield* driver
1638 .pushCurrentBranch(cwd, null)
1639 .pipe(Effect.forkChild({ startImmediately: true }));
1640 yield* Deferred.await(pushStarted);
1641 yield* TestClock.adjust("31 seconds");
1642 const pushed = yield* Fiber.join(pushing);
1643
1644 assert.deepInclude(pushed, {
1645 status: "pushed",
1646 setUpstream: true,
1647 });
1648 }),
1649 );
1650
16141651 it.effect(
16151652 "pushes upstream branches to the remote branch name, not the upstream shorthand",
16161653 () =>
apps/server/src/vcs/GitVcsDriverCore.ts+29-24
......@@ -139,7 +139,7 @@ interface GitRefsSnapshot {
139139
140140interface ExecuteGitOptions {
141141 stdin?: string | undefined;
142 timeoutMs?: number | undefined;
142 timeoutMs?: number | null | undefined;
143143 allowNonZeroExit?: boolean | undefined;
144144 fallbackErrorDetail?: string | undefined;
145145 env?: NodeJS.ProcessEnv | undefined;
......@@ -712,7 +712,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
712712 ...input,
713713 args: [...input.args],
714714 } as const;
715 const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
715 const timeoutMs = input.timeoutMs === undefined ? DEFAULT_TIMEOUT_MS : input.timeoutMs;
716716 const maxOutputBytes = input.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
717717 const appendTruncationMarker = input.appendTruncationMarker ?? false;
718718
......@@ -813,8 +813,12 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
813813 } satisfies GitVcsDriver.ExecuteGitResult;
814814 });
815815
816 return yield* runGitCommand().pipe(
817 Effect.scoped,
816 const execution = runGitCommand().pipe(Effect.scoped);
817 if (timeoutMs === null) {
818 return yield* execution;
819 }
820
821 return yield* execution.pipe(
818822 Effect.timeoutOption(timeoutMs),
819823 Effect.flatMap((result) =>
820824 Option.match(result, {
......@@ -905,9 +909,9 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
905909 operation: string,
906910 cwd: string,
907911 args: readonly string[],
908 allowNonZeroExit = false,
912 options: ExecuteGitOptions = {},
909913 ): Effect.Effect<void, GitCommandError> =>
910 executeGit(operation, cwd, args, { allowNonZeroExit }).pipe(Effect.asVoid);
914 executeGit(operation, cwd, args, options).pipe(Effect.asVoid);
911915
912916 const runGitStdout = (
913917 operation: string,
......@@ -1905,12 +1909,12 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
19051909 const requestedRemoteName = options?.remoteName?.trim() || null;
19061910 if (requestedRemoteName) {
19071911 const publishBranch = yield* resolvePublishBranchName(cwd, branch);
1908 yield* runGit("GitVcsDriver.pushCurrentBranch.pushWithRequestedRemote", cwd, [
1909 "push",
1910 "-u",
1911 requestedRemoteName,
1912 `HEAD:refs/heads/${publishBranch}`,
1913 ]);
1912 yield* runGit(
1913 "GitVcsDriver.pushCurrentBranch.pushWithRequestedRemote",
1914 cwd,
1915 ["push", "-u", requestedRemoteName, `HEAD:refs/heads/${publishBranch}`],
1916 { timeoutMs: null },
1917 );
19141918 return {
19151919 status: "pushed" as const,
19161920 branch,
......@@ -1968,12 +1972,12 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
19681972 });
19691973 }
19701974 const publishBranch = yield* resolvePublishBranchName(cwd, branch);
1971 yield* runGit("GitVcsDriver.pushCurrentBranch.pushWithUpstream", cwd, [
1972 "push",
1973 "-u",
1974 publishRemoteName,
1975 `HEAD:refs/heads/${publishBranch}`,
1976 ]);
1975 yield* runGit(
1976 "GitVcsDriver.pushCurrentBranch.pushWithUpstream",
1977 cwd,
1978 ["push", "-u", publishRemoteName, `HEAD:refs/heads/${publishBranch}`],
1979 { timeoutMs: null },
1980 );
19771981 return {
19781982 status: "pushed" as const,
19791983 branch,
......@@ -1986,11 +1990,12 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
19861990 Effect.orElseSucceed(() => null),
19871991 );
19881992 if (currentUpstream) {
1989 yield* runGit("GitVcsDriver.pushCurrentBranch.pushUpstream", cwd, [
1990 "push",
1991 currentUpstream.remoteName,
1992 `HEAD:refs/heads/${currentUpstream.branchName}`,
1993 ]);
1993 yield* runGit(
1994 "GitVcsDriver.pushCurrentBranch.pushUpstream",
1995 cwd,
1996 ["push", currentUpstream.remoteName, `HEAD:refs/heads/${currentUpstream.branchName}`],
1997 { timeoutMs: null },
1998 );
19941999 return {
19952000 status: "pushed" as const,
19962001 branch,
......@@ -1999,7 +2004,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
19992004 };
20002005 }
20012006
2002 yield* runGit("GitVcsDriver.pushCurrentBranch.push", cwd, ["push"]);
2007 yield* runGit("GitVcsDriver.pushCurrentBranch.push", cwd, ["push"], { timeoutMs: null });
20032008 return {
20042009 status: "pushed" as const,
20052010 branch,