Merge branch 'main' into dev

This commit is contained in:
Mohamed Boudra
2026-04-10 20:59:14 +07:00
23 changed files with 947 additions and 275 deletions
+94 -37
View File
@@ -29,6 +29,7 @@ import {
Archive,
ChevronDown,
Columns2,
Download,
GitBranch,
GitCommitHorizontal,
GitMerge,
@@ -81,6 +82,7 @@ import { openExternalUrl } from "@/utils/open-external-url";
import { GitActionsSplitButton } from "@/components/git-actions-split-button";
import { usePanelStore } from "@/stores/panel-store";
import { buildWorkspaceExplorerStateKey } from "@/hooks/use-file-explorer-actions";
import { useToast } from "@/contexts/toast-context";
import {
formatDiffContentText,
formatDiffGutterText,
@@ -607,12 +609,12 @@ type DiffFlatItem =
export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDiffPaneProps) {
const { theme } = useUnistyles();
const toast = useToast();
const isMobile = useIsCompactFormFactor();
const showDesktopWebScrollbar = Platform.OS === "web" && !isMobile;
const canUseSplitLayout = Platform.OS === "web" && !isMobile;
const router = useRouter();
const [diffModeOverride, setDiffModeOverride] = useState<"uncommitted" | "base" | null>(null);
const [actionError, setActionError] = useState<string | null>(null);
const [postShipArchiveSuggested, setPostShipArchiveSuggested] = useState(false);
const [shipDefault, setShipDefault] = useState<"merge" | "pr">("merge");
const { preferences: changesPreferences, updatePreferences: updateChangesPreferences } =
@@ -896,6 +898,9 @@ export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDi
const commitStatus = useCheckoutGitActionsStore((state) =>
state.getStatus({ serverId, cwd, actionId: "commit" }),
);
const pullStatus = useCheckoutGitActionsStore((state) =>
state.getStatus({ serverId, cwd, actionId: "pull" }),
);
const pushStatus = useCheckoutGitActionsStore((state) =>
state.getStatus({ serverId, cwd, actionId: "push" }),
);
@@ -913,83 +918,123 @@ export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDi
);
const runCommit = useCheckoutGitActionsStore((state) => state.commit);
const runPull = useCheckoutGitActionsStore((state) => state.pull);
const runPush = useCheckoutGitActionsStore((state) => state.push);
const runCreatePr = useCheckoutGitActionsStore((state) => state.createPr);
const runMergeBranch = useCheckoutGitActionsStore((state) => state.mergeBranch);
const runMergeFromBase = useCheckoutGitActionsStore((state) => state.mergeFromBase);
const runArchiveWorktree = useCheckoutGitActionsStore((state) => state.archiveWorktree);
const toastActionError = useCallback(
(error: unknown, fallback: string) => {
const message = error instanceof Error ? error.message : fallback;
toast.error(message);
},
[toast],
);
const toastActionSuccess = useCallback(
(message: string) => {
toast.show(message, { variant: "success" });
},
[toast],
);
const handleCommit = useCallback(() => {
setActionError(null);
void runCommit({ serverId, cwd }).catch((err) => {
const message = err instanceof Error ? err.message : "Failed to commit";
setActionError(message);
});
}, [runCommit, serverId, cwd]);
void runCommit({ serverId, cwd })
.then(() => {
toastActionSuccess("Committed");
})
.catch((err) => {
toastActionError(err, "Failed to commit");
});
}, [cwd, runCommit, serverId, toastActionError, toastActionSuccess]);
const handlePull = useCallback(() => {
void runPull({ serverId, cwd })
.then(() => {
toastActionSuccess("Pulled");
})
.catch((err) => {
toastActionError(err, "Failed to pull");
});
}, [cwd, runPull, serverId, toastActionError, toastActionSuccess]);
const handlePush = useCallback(() => {
setActionError(null);
void runPush({ serverId, cwd }).catch((err) => {
const message = err instanceof Error ? err.message : "Failed to push";
setActionError(message);
});
}, [runPush, serverId, cwd]);
void runPush({ serverId, cwd })
.then(() => {
toastActionSuccess("Pushed");
})
.catch((err) => {
toastActionError(err, "Failed to push");
});
}, [cwd, runPush, serverId, toastActionError, toastActionSuccess]);
const handleCreatePr = useCallback(() => {
void persistShipDefault("pr");
setActionError(null);
void runCreatePr({ serverId, cwd }).catch((err) => {
const message = err instanceof Error ? err.message : "Failed to create PR";
setActionError(message);
});
}, [persistShipDefault, runCreatePr, serverId, cwd]);
void runCreatePr({ serverId, cwd })
.then(() => {
toastActionSuccess("PR created");
})
.catch((err) => {
toastActionError(err, "Failed to create PR");
});
}, [cwd, persistShipDefault, runCreatePr, serverId, toastActionError, toastActionSuccess]);
const handleMergeBranch = useCallback(() => {
if (!baseRef) {
setActionError("Base ref unavailable");
toast.error("Base ref unavailable");
return;
}
void persistShipDefault("merge");
setActionError(null);
void runMergeBranch({ serverId, cwd, baseRef })
.then(() => {
setPostShipArchiveSuggested(true);
toastActionSuccess("Merged");
})
.catch((err) => {
const message = err instanceof Error ? err.message : "Failed to merge";
setActionError(message);
toastActionError(err, "Failed to merge");
});
}, [baseRef, persistShipDefault, runMergeBranch, serverId, cwd]);
}, [
baseRef,
cwd,
persistShipDefault,
runMergeBranch,
serverId,
toast,
toastActionError,
toastActionSuccess,
]);
const handleMergeFromBase = useCallback(() => {
if (!baseRef) {
setActionError("Base ref unavailable");
toast.error("Base ref unavailable");
return;
}
setActionError(null);
void runMergeFromBase({ serverId, cwd, baseRef }).catch((err) => {
const message = err instanceof Error ? err.message : "Failed to merge from base";
setActionError(message);
});
}, [baseRef, runMergeFromBase, serverId, cwd]);
void runMergeFromBase({ serverId, cwd, baseRef })
.then(() => {
toastActionSuccess("Updated");
})
.catch((err) => {
toastActionError(err, "Failed to merge from base");
});
}, [baseRef, cwd, runMergeFromBase, serverId, toast, toastActionError, toastActionSuccess]);
const handleArchiveWorktree = useCallback(() => {
const worktreePath = status?.cwd;
if (!worktreePath) {
setActionError("Worktree path unavailable");
toast.error("Worktree path unavailable");
return;
}
setActionError(null);
const targetWorkingDir = resolveNewAgentWorkingDir(cwd, status ?? null);
void runArchiveWorktree({ serverId, cwd, worktreePath })
.then(() => {
router.replace(buildNewAgentRoute(serverId, targetWorkingDir));
})
.catch((err) => {
const message = err instanceof Error ? err.message : "Failed to archive worktree";
setActionError(message);
toastActionError(err, "Failed to archive worktree");
});
}, [runArchiveWorktree, router, serverId, cwd, status]);
}, [cwd, router, runArchiveWorktree, serverId, status, toast, toastActionError]);
const renderFlatItem = useCallback(
({ item }: { item: DiffFlatItem }) => {
@@ -1041,6 +1086,7 @@ export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDi
: "Unknown";
const actionsDisabled = !isGit || Boolean(status?.error) || isStatusLoading;
const aheadCount = gitStatus?.aheadBehind?.ahead ?? 0;
const behindBaseCount = gitStatus?.aheadBehind?.behind ?? 0;
const aheadOfOrigin = gitStatus?.aheadOfOrigin ?? 0;
const behindOfOrigin = gitStatus?.behindOfOrigin ?? 0;
const baseRefLabel = useMemo(() => {
@@ -1066,6 +1112,7 @@ export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDi
(postShipArchiveSuggested || isMergedPullRequest);
const commitDisabled = actionsDisabled || commitStatus === "pending";
const pullDisabled = actionsDisabled || pullStatus === "pending";
const prDisabled = actionsDisabled || prCreateStatus === "pending";
const mergeDisabled = actionsDisabled || mergeStatus === "pending";
const mergeFromBaseDisabled = actionsDisabled || mergeFromBaseStatus === "pending";
@@ -1172,6 +1219,7 @@ export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDi
baseRefAvailable: Boolean(baseRef),
baseRefLabel,
aheadCount,
behindBaseCount,
aheadOfOrigin,
behindOfOrigin,
shouldPromoteArchive,
@@ -1183,6 +1231,12 @@ export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDi
icon: <GitCommitHorizontal size={16} color={theme.colors.foregroundMuted} />,
handler: handleCommit,
},
pull: {
disabled: pullDisabled,
status: pullStatus,
icon: <Download size={16} color={theme.colors.foregroundMuted} />,
handler: handlePull,
},
push: {
disabled: pushDisabled,
status: pushStatus,
@@ -1227,6 +1281,7 @@ export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDi
hasPullRequest,
prStatus?.url,
aheadCount,
behindBaseCount,
isPaseoOwnedWorktree,
isOnBaseBranch,
githubFeaturesEnabled,
@@ -1237,18 +1292,21 @@ export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDi
baseRefLabel,
shouldPromoteArchive,
commitDisabled,
pullDisabled,
pushDisabled,
prDisabled,
mergeDisabled,
mergeFromBaseDisabled,
archiveDisabled,
commitStatus,
pullStatus,
pushStatus,
prCreateStatus,
mergeStatus,
mergeFromBaseStatus,
archiveStatus,
handleCommit,
handlePull,
handlePush,
handleCreatePr,
handleMergeBranch,
@@ -1458,7 +1516,6 @@ export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDi
</View>
) : null}
{actionError ? <Text style={styles.actionErrorText}>{actionError}</Text> : null}
{prErrorMessage ? <Text style={styles.actionErrorText}>{prErrorMessage}</Text> : null}
<View style={styles.diffContainer}>