Change a worktree workspace base from Changes (#8642)

Change a worktree workspace's base from the Changes pane. The choice persists and diffs, counts, commits and Git actions follow it.

Supersedes #6872.

Co-authored-by: Tianyu Yang <yangtianyu.cl@bytedance.com>
This commit is contained in:
Mohamed Boudra
2026-11-05 22:30:14 +07:00
committed by GitHub
co-authored by Tianyu Yang
parent e9f897a66a
commit 3ed57e9dd7
41 changed files with 1192 additions and 46 deletions
+25
View File
@@ -1040,6 +1040,13 @@ export const WorkspacePinSetRequestSchema = z.object({
requestId: z.string(),
});
export const WorkspaceBaseBranchSetRequestSchema = z.object({
type: z.literal("workspace.base_branch.set.request"),
workspaceId: z.string(),
baseRef: z.string(),
requestId: z.string(),
});
export const WorkspaceLabelColorSchema = z.enum(WORKSPACE_LABEL_COLORS);
export const WorkspaceLabelDefinitionSchema = z.object({
name: z.string(),
@@ -2197,6 +2204,17 @@ export const WorkspacePinSetResponseSchema = z.object({
payload: WorkspacePinSetResponsePayloadSchema,
});
export const WorkspaceBaseBranchSetResponseSchema = z.object({
type: z.literal("workspace.base_branch.set.response"),
payload: z.object({
requestId: z.string(),
workspaceId: z.string(),
accepted: z.boolean(),
baseRef: z.string().nullable(),
error: z.string().nullable(),
}),
});
export const WorkspaceRecoveryStateSchema = z.discriminatedUnion("kind", [
z.object({
kind: z.literal("recoverable"),
@@ -2514,6 +2532,7 @@ export const BranchSuggestionsRequestSchema = z.object({
cwd: z.string(),
query: z.string().optional(),
limit: z.number().int().min(1).max(200).optional(),
refMode: z.literal("exact").optional(),
requestId: z.string(),
});
@@ -3328,6 +3347,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
ProjectRemoveRequestSchema,
WorkspaceTitleSetRequestSchema,
WorkspacePinSetRequestSchema,
WorkspaceBaseBranchSetRequestSchema,
WorkspaceLabelListRequestSchema,
WorkspaceLabelAssignmentSetRequestSchema,
WorkspaceLabelUpdateRequestSchema,
@@ -3712,6 +3732,8 @@ export const ServerInfoStatusPayloadSchema = z
directorySync: z.boolean().optional(),
// COMPAT(workspaceLabels): added in v0.5.0, remove after 2027-08-14.
workspaceLabels: z.boolean().optional(),
// COMPAT(workspaceBaseBranch): added after v0.11.2, remove gate after 2027-10-14.
workspaceBaseBranch: z.boolean().optional(),
// COMPAT(workspaceSetupRun): added in v0.8.0, remove gate after 2027-09-02.
workspaceSetupRun: z.boolean().optional(),
// COMPAT(workspaceTerminals): added in v0.8.0, remove gate after 2027-09-05.
@@ -5405,6 +5427,7 @@ const CheckoutStatusCommonSchema = z.object({
cwd: z.string(),
error: CheckoutErrorSchema.nullable(),
requestId: z.string(),
baseRefExact: z.string().nullable().optional(),
// The full ref currentBranch tracks, as git resolves `<branch>@{upstream}`:
// "refs/remotes/origin/main", "refs/remotes/upstream/main" on a fork, or a
// "refs/heads/..." ref for a branch tracking a local branch. Null when there is no
@@ -7203,6 +7226,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
ProjectRemoveResponseSchema,
WorkspaceTitleSetResponseSchema,
WorkspacePinSetResponseSchema,
WorkspaceBaseBranchSetResponseSchema,
WorkspaceRecoveryInspectResponseSchema,
WorkspaceRecoveryRestoreResponseSchema,
WaitForFinishResponseMessageSchema,
@@ -7564,6 +7588,7 @@ export type ProjectIconSetRequest = z.infer<typeof ProjectIconSetRequestSchema>;
export type ProjectRemoveRequest = z.infer<typeof ProjectRemoveRequestSchema>;
export type WorkspaceTitleSetRequest = z.infer<typeof WorkspaceTitleSetRequestSchema>;
export type WorkspacePinSetRequest = z.infer<typeof WorkspacePinSetRequestSchema>;
export type WorkspaceBaseBranchSetRequest = z.infer<typeof WorkspaceBaseBranchSetRequestSchema>;
export type WorkspaceRecoveryInspectRequest = z.infer<typeof WorkspaceRecoveryInspectRequestSchema>;
export type WorkspaceRecoveryRestoreRequest = z.infer<typeof WorkspaceRecoveryRestoreRequestSchema>;
export type SetAgentModeRequestMessage = z.infer<typeof SetAgentModeRequestMessageSchema>;
@@ -12,8 +12,65 @@ import {
WorkspaceSetupSnapshotSchema,
WorkspaceSetupProgressMessageSchema,
AgentTimelineEntryPayloadSchema,
CheckoutStatusResponseSchema,
BranchSuggestionsRequestSchema,
WorkspaceBaseBranchSetRequestSchema,
WorkspaceBaseBranchSetResponseSchema,
} from "./messages.js";
test("base selection RPCs are correlated while older checkout and branch messages remain valid", () => {
const payload = {
cwd: "/repo",
requestId: "status",
error: null,
isGit: true,
isPaseoOwnedWorktree: false,
repoRoot: "/repo",
currentBranch: "feature",
isDirty: false,
baseRef: "release",
aheadBehind: null,
aheadOfOrigin: null,
behindOfOrigin: null,
hasRemote: false,
remoteUrl: null,
};
expect(
CheckoutStatusResponseSchema.safeParse({ type: "checkout_status_response", payload }).success,
).toBe(true);
expect(
CheckoutStatusResponseSchema.parse({
type: "checkout_status_response",
payload: { ...payload, baseRefExact: "refs/remotes/upstream/release" },
}).payload.baseRefExact,
).toBe("refs/remotes/upstream/release");
expect(
BranchSuggestionsRequestSchema.safeParse({
type: "branch_suggestions_request",
cwd: "/repo",
requestId: "branches",
}).success,
).toBe(true);
const request = {
type: "workspace.base_branch.set.request",
workspaceId: "workspace",
baseRef: "refs/heads/release",
requestId: "base",
};
expect(WorkspaceBaseBranchSetRequestSchema.parse(request)).toEqual(request);
const response = {
type: "workspace.base_branch.set.response",
payload: {
workspaceId: "workspace",
baseRef: "refs/heads/release",
requestId: "base",
accepted: true,
error: null,
},
};
expect(WorkspaceBaseBranchSetResponseSchema.parse(response)).toEqual(response);
});
test("terminal listings accept older rows and retain new per-terminal directories", () => {
const response = {
type: "list_terminals_response",