Persist base branch for Paseo worktrees and gate merge action
This commit is contained in:
@@ -416,9 +416,12 @@ export default function HomeScreen() {
|
||||
]);
|
||||
|
||||
const baseBranchError = useMemo(() => {
|
||||
if (!isCreateWorktree || isNonGitDirectory || !baseBranch) {
|
||||
if (!isCreateWorktree || isNonGitDirectory) {
|
||||
return null;
|
||||
}
|
||||
if (!baseBranch) {
|
||||
return "Base branch is required";
|
||||
}
|
||||
const branches = repoInfo?.branches ?? [];
|
||||
if (branches.length === 0) {
|
||||
return null;
|
||||
@@ -442,6 +445,19 @@ export default function HomeScreen() {
|
||||
[setWorkingDirFromUser]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isCreateWorktree || isNonGitDirectory) {
|
||||
return;
|
||||
}
|
||||
if (baseBranch) {
|
||||
return;
|
||||
}
|
||||
const current = repoInfo?.currentBranch?.trim();
|
||||
if (current) {
|
||||
setBaseBranch(current);
|
||||
}
|
||||
}, [isCreateWorktree, isNonGitDirectory, baseBranch, repoInfo?.currentBranch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNonGitDirectory && worktreeMode !== "none") {
|
||||
setWorktreeMode("none");
|
||||
@@ -514,7 +530,7 @@ export default function HomeScreen() {
|
||||
...(modeId ? { modeId } : {}),
|
||||
...(trimmedModel ? { model: trimmedModel } : {}),
|
||||
};
|
||||
const effectiveBaseBranch = baseBranch.trim() || repoInfo?.currentBranch || undefined;
|
||||
const effectiveBaseBranch = baseBranch.trim();
|
||||
const effectiveWorktreeSlug =
|
||||
isCreateWorktree && !worktreeSlug ? createNameId() : worktreeSlug;
|
||||
if (isCreateWorktree && !worktreeSlug && effectiveWorktreeSlug) {
|
||||
|
||||
@@ -356,12 +356,13 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
|
||||
const [actionStatus, setActionStatus] = useState<string | null>(null);
|
||||
const { status, isLoading: isStatusLoading, isFetching: isStatusFetching, isError: isStatusError, error: statusError, refresh: refreshStatus } =
|
||||
useCheckoutStatusQuery({ serverId, agentId });
|
||||
const isGit = status?.isGit ?? false;
|
||||
const notGit = Boolean(status) && !status?.isGit && !status?.error;
|
||||
const gitStatus = status && status.isGit ? status : null;
|
||||
const isGit = Boolean(gitStatus);
|
||||
const notGit = status !== null && !status.isGit && !status.error;
|
||||
const statusErrorMessage =
|
||||
status?.error?.message ??
|
||||
(isStatusError && statusError instanceof Error ? statusError.message : null);
|
||||
const baseRef = status?.baseRef ?? undefined;
|
||||
const baseRef = gitStatus?.baseRef ?? undefined;
|
||||
const {
|
||||
files,
|
||||
payloadError: diffPayloadError,
|
||||
@@ -593,17 +594,22 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
|
||||
diffPayloadError?.message ??
|
||||
(isDiffError && diffError instanceof Error ? diffError.message : null);
|
||||
const prErrorMessage = prPayloadError?.message ?? null;
|
||||
const branchLabel = status?.currentBranch ?? (notGit ? "Not a git repository" : "Unknown");
|
||||
const branchLabel =
|
||||
gitStatus?.currentBranch ?? (notGit ? "Not a git repository" : "Unknown");
|
||||
const actionsDisabled = !isGit || Boolean(status?.error) || isStatusLoading;
|
||||
const hasUncommittedChanges = Boolean(status?.isDirty);
|
||||
const hasUncommittedChanges = Boolean(gitStatus?.isDirty);
|
||||
const showCommitAction =
|
||||
!isGit || hasUncommittedChanges || (diffMode === "uncommitted" && hasChanges);
|
||||
const showCreatePrAction = !isGit || !prStatus;
|
||||
const showMergeAction =
|
||||
diffMode === "base" && isGit && (gitStatus?.aheadBehind?.ahead ?? 0) > 0;
|
||||
const commitDisabled = actionsDisabled || commitMutation.isPending;
|
||||
const prDisabled = actionsDisabled || prMutation.isPending;
|
||||
const mergeDisabled = actionsDisabled || mergeMutation.isPending;
|
||||
const archiveDisabled =
|
||||
actionsDisabled || archiveMutation.isPending || !status?.isPaseoOwnedWorktree;
|
||||
actionsDisabled ||
|
||||
archiveMutation.isPending ||
|
||||
!gitStatus?.isPaseoOwnedWorktree;
|
||||
|
||||
let bodyContent: ReactElement;
|
||||
|
||||
@@ -745,18 +751,20 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
|
||||
)}
|
||||
</Pressable>
|
||||
) : null}
|
||||
<Pressable
|
||||
testID="changes-action-merge"
|
||||
style={[styles.actionButton, mergeDisabled && styles.actionButtonDisabled]}
|
||||
onPress={() => mergeMutation.mutate()}
|
||||
disabled={mergeDisabled}
|
||||
>
|
||||
{mergeMutation.isPending ? (
|
||||
<ActivityIndicator size="small" color={theme.colors.foreground} />
|
||||
) : (
|
||||
<Text style={styles.actionButtonText}>Merge to base</Text>
|
||||
)}
|
||||
</Pressable>
|
||||
{showMergeAction ? (
|
||||
<Pressable
|
||||
testID="changes-action-merge"
|
||||
style={[styles.actionButton, mergeDisabled && styles.actionButtonDisabled]}
|
||||
onPress={() => mergeMutation.mutate()}
|
||||
disabled={mergeDisabled}
|
||||
>
|
||||
{mergeMutation.isPending ? (
|
||||
<ActivityIndicator size="small" color={theme.colors.foreground} />
|
||||
) : (
|
||||
<Text style={styles.actionButtonText}>Merge to base</Text>
|
||||
)}
|
||||
</Pressable>
|
||||
) : null}
|
||||
<Pressable
|
||||
testID="changes-action-archive"
|
||||
style={[styles.actionButton, archiveDisabled && styles.actionButtonDisabled]}
|
||||
@@ -771,6 +779,11 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<Text style={styles.debugText} testID="changes-debug-state">
|
||||
mode={diffMode} files={files.length} isGit={String(isGit)} ahead=
|
||||
{gitStatus?.aheadBehind ? String(gitStatus.aheadBehind.ahead) : "null"}
|
||||
</Text>
|
||||
|
||||
{actionStatus ? <Text style={styles.actionStatusText}>{actionStatus}</Text> : null}
|
||||
{actionError ? <Text style={styles.actionErrorText}>{actionError}</Text> : null}
|
||||
{prStatus ? (
|
||||
@@ -866,6 +879,12 @@ const styles = StyleSheet.create((theme) => ({
|
||||
color: theme.colors.foreground,
|
||||
fontWeight: theme.fontWeight.medium,
|
||||
},
|
||||
debugText: {
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
paddingBottom: theme.spacing[1],
|
||||
fontSize: theme.fontSize.xs,
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
actionStatusText: {
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
paddingBottom: theme.spacing[1],
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useEffect } from "react";
|
||||
import { UnistylesRuntime } from "react-native-unistyles";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import { usePanelStore } from "@/stores/panel-store";
|
||||
import type { CheckoutStatusResponse } from "@server/shared/messages";
|
||||
|
||||
const CHECKOUT_STATUS_STALE_TIME = 15_000;
|
||||
@@ -22,6 +25,12 @@ export function useCheckoutStatusQuery({ serverId, agentId }: UseCheckoutStatusQ
|
||||
const isConnected = useSessionStore(
|
||||
(state) => state.sessions[serverId]?.connection.isConnected ?? false
|
||||
);
|
||||
const isMobile =
|
||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||
const mobileView = usePanelStore((state) => state.mobileView);
|
||||
const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
|
||||
const explorerTab = usePanelStore((state) => state.explorerTab);
|
||||
const isOpen = isMobile ? mobileView === "file-explorer" : desktopFileExplorerOpen;
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: checkoutStatusQueryKey(serverId, agentId),
|
||||
@@ -34,8 +43,18 @@ export function useCheckoutStatusQuery({ serverId, agentId }: UseCheckoutStatusQ
|
||||
enabled: !!client && isConnected && !!agentId,
|
||||
staleTime: CHECKOUT_STATUS_STALE_TIME,
|
||||
refetchInterval: 10_000,
|
||||
refetchIntervalInBackground: true,
|
||||
refetchOnMount: "always",
|
||||
});
|
||||
|
||||
// Revalidate when sidebar is open with "changes" tab active.
|
||||
useEffect(() => {
|
||||
if (!isOpen || explorerTab !== "changes" || !agentId) {
|
||||
return;
|
||||
}
|
||||
void query.refetch();
|
||||
}, [isOpen, explorerTab, agentId, query]);
|
||||
|
||||
return {
|
||||
status: query.data ?? null,
|
||||
isLoading: query.isLoading,
|
||||
|
||||
Reference in New Issue
Block a user