* fix: reject oversized checkout diffs * fix(diff): keep large previews from disconnecting clients The physical socket limit is an OOM backstop, not a frame-size contract. Bound structured diff construction incrementally and report oversized previews without closing the client's socket. * fix(app): preserve oversized diff state * fix(diff): stay within relay frame limit --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
172 lines
4.5 KiB
TypeScript
172 lines
4.5 KiB
TypeScript
import { useCallback, useEffect, useMemo } from "react";
|
|
import {
|
|
buildWorkspaceAttachmentScopeKey,
|
|
useWorkspaceAttachmentsStore,
|
|
} from "@/attachments/workspace-attachments-store";
|
|
import {
|
|
buildReviewDraftKey,
|
|
buildReviewDraftScopeKey,
|
|
useInlineReviewController,
|
|
useResolvedDiffMode,
|
|
useReviewAttachmentSnapshot,
|
|
useSetDiffModeOverride,
|
|
} from "@/review";
|
|
import { useCheckoutDiffQuery } from "@/git/use-diff-query";
|
|
import { useCheckoutStatusQuery } from "@/git/use-status-query";
|
|
|
|
interface UseWorkingDiffOptions {
|
|
serverId: string;
|
|
workspaceId?: string;
|
|
cwd: string;
|
|
ignoreWhitespace: boolean;
|
|
enabled: boolean;
|
|
queryScope?: string;
|
|
}
|
|
|
|
export function useWorkingDiff({
|
|
serverId,
|
|
workspaceId,
|
|
cwd,
|
|
ignoreWhitespace,
|
|
enabled,
|
|
queryScope,
|
|
}: UseWorkingDiffOptions) {
|
|
const {
|
|
status,
|
|
isLoading: isStatusLoading,
|
|
isError: isStatusError,
|
|
error: statusError,
|
|
} = useCheckoutStatusQuery({ serverId, cwd });
|
|
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 = gitStatus?.baseRef ?? undefined;
|
|
const hasUncommittedChanges = Boolean(gitStatus?.isDirty);
|
|
const currentBranchName =
|
|
gitStatus?.currentBranch && gitStatus.currentBranch !== "HEAD" ? gitStatus.currentBranch : null;
|
|
|
|
const reviewDraftScopeKey = useMemo(
|
|
() =>
|
|
buildReviewDraftScopeKey({
|
|
serverId,
|
|
workspaceId,
|
|
cwd,
|
|
baseRef,
|
|
ignoreWhitespace,
|
|
}),
|
|
[baseRef, cwd, ignoreWhitespace, serverId, workspaceId],
|
|
);
|
|
const diffMode = useResolvedDiffMode({
|
|
scopeKey: reviewDraftScopeKey,
|
|
hasUncommittedChanges,
|
|
});
|
|
const setDiffModeOverride = useSetDiffModeOverride();
|
|
const selectDiffMode = useCallback(
|
|
(mode: "uncommitted" | "base") => {
|
|
setDiffModeOverride({
|
|
scopeKey: reviewDraftScopeKey,
|
|
override: { serverId, cwd, mode, isDirtyAtSelection: hasUncommittedChanges },
|
|
});
|
|
},
|
|
[cwd, hasUncommittedChanges, reviewDraftScopeKey, serverId, setDiffModeOverride],
|
|
);
|
|
const selectUncommitted = useCallback(() => selectDiffMode("uncommitted"), [selectDiffMode]);
|
|
const selectBase = useCallback(() => selectDiffMode("base"), [selectDiffMode]);
|
|
|
|
const {
|
|
files,
|
|
payloadError: diffPayloadError,
|
|
diffTooLarge,
|
|
isLoading: isDiffLoading,
|
|
} = useCheckoutDiffQuery({
|
|
serverId,
|
|
cwd,
|
|
mode: diffMode,
|
|
baseRef,
|
|
ignoreWhitespace,
|
|
enabled: enabled && isGit,
|
|
queryScope,
|
|
});
|
|
const reviewDraftKey = useMemo(
|
|
() =>
|
|
buildReviewDraftKey({
|
|
serverId,
|
|
workspaceId,
|
|
cwd,
|
|
mode: diffMode,
|
|
baseRef,
|
|
ignoreWhitespace,
|
|
}),
|
|
[baseRef, cwd, diffMode, ignoreWhitespace, serverId, workspaceId],
|
|
);
|
|
const reviewActions = useInlineReviewController({ reviewDraftKey });
|
|
const reviewAttachment = useReviewAttachmentSnapshot({
|
|
key: reviewDraftKey,
|
|
diffFiles: files,
|
|
cwd,
|
|
mode: diffMode,
|
|
baseRef,
|
|
});
|
|
|
|
return {
|
|
status,
|
|
isStatusLoading,
|
|
isGit,
|
|
notGit,
|
|
statusErrorMessage,
|
|
baseRef,
|
|
currentBranchName,
|
|
diffMode,
|
|
selectUncommitted,
|
|
selectBase,
|
|
files,
|
|
diffPayloadError,
|
|
diffTooLarge,
|
|
isDiffLoading,
|
|
reviewActions,
|
|
reviewAttachment,
|
|
};
|
|
}
|
|
|
|
export function usePublishWorkingDiffAttachment({
|
|
serverId,
|
|
workspaceId,
|
|
cwd,
|
|
attachment,
|
|
enabled,
|
|
}: {
|
|
serverId: string;
|
|
workspaceId?: string;
|
|
cwd: string;
|
|
attachment: ReturnType<typeof useWorkingDiff>["reviewAttachment"];
|
|
enabled: boolean;
|
|
}) {
|
|
const scopeKey = useMemo(
|
|
() => buildWorkspaceAttachmentScopeKey({ serverId, workspaceId, cwd }),
|
|
[cwd, serverId, workspaceId],
|
|
);
|
|
const setWorkspaceAttachments = useWorkspaceAttachmentsStore(
|
|
(state) => state.setWorkspaceAttachments,
|
|
);
|
|
const clearWorkspaceAttachments = useWorkspaceAttachmentsStore(
|
|
(state) => state.clearWorkspaceAttachments,
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (!enabled) {
|
|
return;
|
|
}
|
|
const attachments = attachment ? [attachment] : [];
|
|
setWorkspaceAttachments({ scopeKey, attachments });
|
|
return () => {
|
|
const current = useWorkspaceAttachmentsStore.getState().attachmentsByScope[scopeKey];
|
|
if (current === attachments) {
|
|
clearWorkspaceAttachments({ scopeKey });
|
|
}
|
|
};
|
|
}, [attachment, clearWorkspaceAttachments, enabled, scopeKey, setWorkspaceAttachments]);
|
|
}
|