Skip to content
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
8 changes: 8 additions & 0 deletions apps/server/src/git/GitWorkflowService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export class GitWorkflowService extends Context.Service<
readonly remoteName: string;
readonly refName: string;
}) => Effect.Effect<boolean, GitCommandError>;
readonly resolvePrimaryRemoteName: (cwd: string) => Effect.Effect<string, GitCommandError>;
readonly resolveRemoteTrackingCommit: (input: {
readonly cwd: string;
readonly refName: string;
Expand Down Expand Up @@ -450,6 +451,13 @@ export const make = Effect.gen(function* () {
Effect.andThen(git.remoteBranchExists(input)),
);
}),
resolvePrimaryRemoteName: Effect.fn("GitWorkflowService.resolvePrimaryRemoteName")(
function (cwd) {
return ensureGitCommand("GitWorkflowService.resolvePrimaryRemoteName", cwd).pipe(
Effect.andThen(git.resolvePrimaryRemoteName(cwd)),
);
},
),
resolveRemoteTrackingCommit: Effect.fn("GitWorkflowService.resolveRemoteTrackingCommit")(
function (input) {
return ensureGitCommand("GitWorkflowService.resolveRemoteTrackingCommit", input.cwd).pipe(
Expand Down
10 changes: 10 additions & 0 deletions apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,12 @@ effectIt.effect(
upstream: "refs/remotes/origin/feature/restore",
base: "",
},
"pushed-sole-remote": {
tip: threadTip,
file: "thread.txt",
upstream: "refs/remotes/upstream/feature/restore",
base: "",
},
detached: { tip: baseTip, file: null, upstream: null, base: "main" },
"detached-no-remote": { tip: projectTip, file: "project.txt", upstream: null, base: "" },
} as const;
Expand All @@ -1151,7 +1157,11 @@ effectIt.effect(
yield* git(workspaceRoot, ["switch", "--detach"]);
expect(yield* git(workspaceRoot, ["branch", "--show-current"])).toBe("");
}
if (scenario === "pushed-sole-remote") {
yield* git(workspaceRoot, ["remote", "rename", "origin", "upstream"]);
}
if (scenario === "detached") {
yield* git(workspaceRoot, ["remote", "rename", "upstream", "origin"]);
yield* git(workspaceRoot, ["push", "origin", "--delete", "feature/restore"]);
}
if (scenario === "detached-no-remote") {
Expand Down
17 changes: 11 additions & 6 deletions apps/server/src/workspace/restoreMissingWorktree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const makeRestoreMissingWorktree = Effect.gen(function* () {

const resolveRecreateBase = Effect.fn("restoreMissingWorktree.resolveRecreateBase")(function* (
cwd: string,
remoteName: string | null,
) {
// Recovery can follow an external checkout, before the status cache expires.
yield* gitWorkflow.invalidateLocalStatus(cwd);
Expand All @@ -56,14 +57,15 @@ export const makeRestoreMissingWorktree = Effect.gen(function* () {
const refName = `refs/heads/${currentBranch}`;
return { refName, baseRefName: refName };
}
if (yield* gitWorkflow.hasCommit({ cwd, refName: "refs/remotes/origin/HEAD" })) {
const remoteHead = `refs/remotes/${remoteName}/HEAD`;
if (remoteName !== null && (yield* gitWorkflow.hasCommit({ cwd, refName: remoteHead }))) {
const { commitSha } = yield* gitWorkflow.resolveRemoteTrackingCommit({
cwd,
refName: "HEAD",
fallbackRemoteName: "origin",
fallbackRemoteName: remoteName,
});
// Start from the commit so the thread branch does not track the default branch.
return { refName: commitSha, baseRefName: "refs/remotes/origin/HEAD" };
return { refName: commitSha, baseRefName: remoteHead };
}
if (yield* gitWorkflow.hasCommit({ cwd, refName: "HEAD" })) {
return { refName: "HEAD", baseRefName: undefined };
Expand All @@ -83,9 +85,12 @@ export const makeRestoreMissingWorktree = Effect.gen(function* () {
yield* gitWorkflow.createWorktree({ cwd, refName: branch, path: worktreePath });
return;
}
const remoteName = yield* gitWorkflow
.resolvePrimaryRemoteName(cwd)
.pipe(Effect.orElseSucceed(() => null));
// Push keeps the tracking ref current, so recovery reads it without fetching.
const pushedRef = `refs/remotes/origin/${branch}`;
if (yield* gitWorkflow.hasCommit({ cwd, refName: pushedRef })) {
const pushedRef = remoteName === null ? null : `refs/remotes/${remoteName}/${branch}`;
if (pushedRef !== null && (yield* gitWorkflow.hasCommit({ cwd, refName: pushedRef }))) {
yield* gitWorkflow.createWorktree({
cwd,
refName: pushedRef,
Expand All @@ -94,7 +99,7 @@ export const makeRestoreMissingWorktree = Effect.gen(function* () {
});
return;
}
const base = yield* resolveRecreateBase(cwd);
const base = yield* resolveRecreateBase(cwd, remoteName);
if (base === null) {
return yield* new WorktreeRecreateNoBaseError({ worktreePath, branch });
}
Expand Down
Loading