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 {...@@ -41,7 +41,7 @@ export interface ExecuteGitInput {
41 readonly stdin?: string;41 readonly stdin?: string;
42 readonly env?: NodeJS.ProcessEnv;42 readonly env?: NodeJS.ProcessEnv;
43 readonly allowNonZeroExit?: boolean;43 readonly allowNonZeroExit?: boolean;
44 readonly timeoutMs?: number;44 readonly timeoutMs?: number | null;
45 readonly maxOutputBytes?: number;45 readonly maxOutputBytes?: number;
46 readonly appendTruncationMarker?: boolean;46 readonly appendTruncationMarker?: boolean;
47 readonly progress?: ExecuteGitProgress;47 readonly progress?: ExecuteGitProgress;
apps/server/src/vcs/GitVcsDriverCore.test.ts+37
...@@ -1611,6 +1611,43 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {...@@ -1611,6 +1611,43 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
1611 }),1611 }),
1612 );1612 );
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
1614 it.effect(1651 it.effect(
1615 "pushes upstream branches to the remote branch name, not the upstream shorthand",1652 "pushes upstream branches to the remote branch name, not the upstream shorthand",
1616 () =>1653 () =>
apps/server/src/vcs/GitVcsDriverCore.ts+29-24
...@@ -139,7 +139,7 @@ interface GitRefsSnapshot {...@@ -139,7 +139,7 @@ interface GitRefsSnapshot {
139139
140interface ExecuteGitOptions {140interface ExecuteGitOptions {
141 stdin?: string | undefined;141 stdin?: string | undefined;
142 timeoutMs?: number | undefined;142 timeoutMs?: number | null | undefined;
143 allowNonZeroExit?: boolean | undefined;143 allowNonZeroExit?: boolean | undefined;
144 fallbackErrorDetail?: string | undefined;144 fallbackErrorDetail?: string | undefined;
145 env?: NodeJS.ProcessEnv | undefined;145 env?: NodeJS.ProcessEnv | undefined;
...@@ -712,7 +712,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*...@@ -712,7 +712,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
712 ...input,712 ...input,
713 args: [...input.args],713 args: [...input.args],
714 } as const;714 } as const;
715 const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;715 const timeoutMs = input.timeoutMs === undefined ? DEFAULT_TIMEOUT_MS : input.timeoutMs;
716 const maxOutputBytes = input.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;716 const maxOutputBytes = input.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
717 const appendTruncationMarker = input.appendTruncationMarker ?? false;717 const appendTruncationMarker = input.appendTruncationMarker ?? false;
718718
...@@ -813,8 +813,12 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*...@@ -813,8 +813,12 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
813 } satisfies GitVcsDriver.ExecuteGitResult;813 } satisfies GitVcsDriver.ExecuteGitResult;
814 });814 });
815815
816 return yield* runGitCommand().pipe(816 const execution = runGitCommand().pipe(Effect.scoped);
817 Effect.scoped,817 if (timeoutMs === null) {
818 return yield* execution;
819 }
820
821 return yield* execution.pipe(
818 Effect.timeoutOption(timeoutMs),822 Effect.timeoutOption(timeoutMs),
819 Effect.flatMap((result) =>823 Effect.flatMap((result) =>
820 Option.match(result, {824 Option.match(result, {
...@@ -905,9 +909,9 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*...@@ -905,9 +909,9 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
905 operation: string,909 operation: string,
906 cwd: string,910 cwd: string,
907 args: readonly string[],911 args: readonly string[],
908 allowNonZeroExit = false,912 options: ExecuteGitOptions = {},
909 ): Effect.Effect<void, GitCommandError> =>913 ): Effect.Effect<void, GitCommandError> =>
910 executeGit(operation, cwd, args, { allowNonZeroExit }).pipe(Effect.asVoid);914 executeGit(operation, cwd, args, options).pipe(Effect.asVoid);
911915
912 const runGitStdout = (916 const runGitStdout = (
913 operation: string,917 operation: string,
...@@ -1905,12 +1909,12 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*...@@ -1905,12 +1909,12 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
1905 const requestedRemoteName = options?.remoteName?.trim() || null;1909 const requestedRemoteName = options?.remoteName?.trim() || null;
1906 if (requestedRemoteName) {1910 if (requestedRemoteName) {
1907 const publishBranch = yield* resolvePublishBranchName(cwd, branch);1911 const publishBranch = yield* resolvePublishBranchName(cwd, branch);
1908 yield* runGit("GitVcsDriver.pushCurrentBranch.pushWithRequestedRemote", cwd, [1912 yield* runGit(
1909 "push",1913 "GitVcsDriver.pushCurrentBranch.pushWithRequestedRemote",
1910 "-u",1914 cwd,
1911 requestedRemoteName,1915 ["push", "-u", requestedRemoteName, `HEAD:refs/heads/${publishBranch}`],
1912 `HEAD:refs/heads/${publishBranch}`,1916 { timeoutMs: null },
1913 ]);1917 );
1914 return {1918 return {
1915 status: "pushed" as const,1919 status: "pushed" as const,
1916 branch,1920 branch,
...@@ -1968,12 +1972,12 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*...@@ -1968,12 +1972,12 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
1968 });1972 });
1969 }1973 }
1970 const publishBranch = yield* resolvePublishBranchName(cwd, branch);1974 const publishBranch = yield* resolvePublishBranchName(cwd, branch);
1971 yield* runGit("GitVcsDriver.pushCurrentBranch.pushWithUpstream", cwd, [1975 yield* runGit(
1972 "push",1976 "GitVcsDriver.pushCurrentBranch.pushWithUpstream",
1973 "-u",1977 cwd,
1974 publishRemoteName,1978 ["push", "-u", publishRemoteName, `HEAD:refs/heads/${publishBranch}`],
1975 `HEAD:refs/heads/${publishBranch}`,1979 { timeoutMs: null },
1976 ]);1980 );
1977 return {1981 return {
1978 status: "pushed" as const,1982 status: "pushed" as const,
1979 branch,1983 branch,
...@@ -1986,11 +1990,12 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*...@@ -1986,11 +1990,12 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
1986 Effect.orElseSucceed(() => null),1990 Effect.orElseSucceed(() => null),
1987 );1991 );
1988 if (currentUpstream) {1992 if (currentUpstream) {
1989 yield* runGit("GitVcsDriver.pushCurrentBranch.pushUpstream", cwd, [1993 yield* runGit(
1990 "push",1994 "GitVcsDriver.pushCurrentBranch.pushUpstream",
1991 currentUpstream.remoteName,1995 cwd,
1992 `HEAD:refs/heads/${currentUpstream.branchName}`,1996 ["push", currentUpstream.remoteName, `HEAD:refs/heads/${currentUpstream.branchName}`],
1993 ]);1997 { timeoutMs: null },
1998 );
1994 return {1999 return {
1995 status: "pushed" as const,2000 status: "pushed" as const,
1996 branch,2001 branch,
...@@ -1999,7 +2004,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*...@@ -1999,7 +2004,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
1999 };2004 };
2000 }2005 }
20012006
2002 yield* runGit("GitVcsDriver.pushCurrentBranch.push", cwd, ["push"]);2007 yield* runGit("GitVcsDriver.pushCurrentBranch.push", cwd, ["push"], { timeoutMs: null });
2003 return {2008 return {
2004 status: "pushed" as const,2009 status: "pushed" as const,
2005 branch,2010 branch,