mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
* Limit large git diff payloads in main process before IPC transfer Move the large diff rendering limit check to a shared module so that the main process can evaluate diff sizes before transferring them. If a diff exceeds the limits, its text content is dropped prior to IPC serialization, and only the limit metadata is sent. This prevents the application from freezing or crashing when loading massive diff files. * Gracefully handle and prune oversized files in diff viewer Prevent UI freezes and out-of-memory errors when viewing or editing extremely large diffs or files. Working-tree files above 10MB and git buffer overflows are treated as binary. Text diffs exceeding safe rendering limits have their contents pruned before IPC transport, and the UI is updated to show fallback states and disable invalid saves. * Document save action check for large diffs and fix test import Explain why saveContentAvailable is required for oversized diffs, as stripped text bodies before IPC prevent complete saves. Also update the large-diff-render-limit import in E2E tests to use the shared path.
197 lines
4.4 KiB
TypeScript
197 lines
4.4 KiB
TypeScript
export const MAX_RENDERED_DIFF_LINES_PER_SIDE = 120_000
|
|
export const MAX_RENDERED_DIFF_COMBINED_CHARACTERS = 6_000_000
|
|
|
|
export type LargeDiffRenderLimitReason = 'line-count' | 'character-count'
|
|
|
|
export type DiffLineCounts = {
|
|
original: number
|
|
modified: number
|
|
}
|
|
|
|
export type DiffLineCountMinimums = {
|
|
original: boolean
|
|
modified: boolean
|
|
}
|
|
|
|
export type LargeDiffRenderLimit =
|
|
| {
|
|
limited: false
|
|
lineCounts: DiffLineCounts
|
|
characterCount: number
|
|
}
|
|
| {
|
|
limited: true
|
|
reason: LargeDiffRenderLimitReason
|
|
lineCounts: DiffLineCounts | null
|
|
lineCountsAreMinimum?: DiffLineCountMinimums
|
|
characterCount: number
|
|
limits: {
|
|
maxLinesPerSide: number
|
|
maxCombinedCharacters: number
|
|
}
|
|
}
|
|
|
|
export function countLinesEmptyAsZero(content: string): number {
|
|
if (content.length === 0) {
|
|
return 0
|
|
}
|
|
|
|
let lineCount = 1
|
|
for (let index = 0; index < content.length; index += 1) {
|
|
if (content.charCodeAt(index) === 10) {
|
|
lineCount += 1
|
|
}
|
|
}
|
|
return lineCount
|
|
}
|
|
|
|
type BoundedLineCount = {
|
|
count: number
|
|
exceeded: boolean
|
|
}
|
|
|
|
export function countLinesEmptyAsZeroUpToLimit(
|
|
content: string,
|
|
maxLines: number
|
|
): BoundedLineCount {
|
|
if (content.length === 0) {
|
|
return { count: 0, exceeded: false }
|
|
}
|
|
|
|
let lineCount = 1
|
|
for (let index = 0; index < content.length; index += 1) {
|
|
if (content.charCodeAt(index) !== 10) {
|
|
continue
|
|
}
|
|
lineCount += 1
|
|
if (lineCount > maxLines) {
|
|
return { count: lineCount, exceeded: true }
|
|
}
|
|
}
|
|
return { count: lineCount, exceeded: false }
|
|
}
|
|
|
|
export function countLinesLikeSplit(content: string): number {
|
|
let lineCount = 1
|
|
for (let index = 0; index < content.length; index += 1) {
|
|
if (content.charCodeAt(index) === 10) {
|
|
lineCount += 1
|
|
}
|
|
}
|
|
return lineCount
|
|
}
|
|
|
|
type LargeDiffRenderLimitInput = {
|
|
originalContent: string
|
|
modifiedContent: string
|
|
}
|
|
|
|
type LargeDiffRenderLimitCountsInput = {
|
|
originalLineCount: number
|
|
modifiedLineCount: number
|
|
originalCharacterCount: number
|
|
modifiedCharacterCount: number
|
|
}
|
|
|
|
export function getLargeDiffRenderLimitFromCounts({
|
|
originalLineCount,
|
|
modifiedLineCount,
|
|
originalCharacterCount,
|
|
modifiedCharacterCount
|
|
}: LargeDiffRenderLimitCountsInput): LargeDiffRenderLimit {
|
|
const lineCounts = {
|
|
original: originalLineCount,
|
|
modified: modifiedLineCount
|
|
}
|
|
const characterCount = originalCharacterCount + modifiedCharacterCount
|
|
const limits = {
|
|
maxLinesPerSide: MAX_RENDERED_DIFF_LINES_PER_SIDE,
|
|
maxCombinedCharacters: MAX_RENDERED_DIFF_COMBINED_CHARACTERS
|
|
}
|
|
|
|
if (
|
|
lineCounts.original > MAX_RENDERED_DIFF_LINES_PER_SIDE ||
|
|
lineCounts.modified > MAX_RENDERED_DIFF_LINES_PER_SIDE
|
|
) {
|
|
return {
|
|
limited: true,
|
|
reason: 'line-count',
|
|
lineCounts,
|
|
characterCount,
|
|
limits
|
|
}
|
|
}
|
|
|
|
if (characterCount > MAX_RENDERED_DIFF_COMBINED_CHARACTERS) {
|
|
return {
|
|
limited: true,
|
|
reason: 'character-count',
|
|
lineCounts,
|
|
characterCount,
|
|
limits
|
|
}
|
|
}
|
|
|
|
return {
|
|
limited: false,
|
|
lineCounts,
|
|
characterCount
|
|
}
|
|
}
|
|
|
|
export function getLargeDiffRenderLimit({
|
|
originalContent,
|
|
modifiedContent
|
|
}: LargeDiffRenderLimitInput): LargeDiffRenderLimit {
|
|
const characterCount = originalContent.length + modifiedContent.length
|
|
const limits = {
|
|
maxLinesPerSide: MAX_RENDERED_DIFF_LINES_PER_SIDE,
|
|
maxCombinedCharacters: MAX_RENDERED_DIFF_COMBINED_CHARACTERS
|
|
}
|
|
|
|
if (characterCount > MAX_RENDERED_DIFF_COMBINED_CHARACTERS) {
|
|
return {
|
|
limited: true,
|
|
reason: 'character-count',
|
|
lineCounts: null,
|
|
characterCount,
|
|
limits
|
|
}
|
|
}
|
|
|
|
const originalLineCount = countLinesEmptyAsZeroUpToLimit(
|
|
originalContent,
|
|
MAX_RENDERED_DIFF_LINES_PER_SIDE
|
|
)
|
|
const modifiedLineCount = countLinesEmptyAsZeroUpToLimit(
|
|
modifiedContent,
|
|
MAX_RENDERED_DIFF_LINES_PER_SIDE
|
|
)
|
|
|
|
if (originalLineCount.exceeded || modifiedLineCount.exceeded) {
|
|
return {
|
|
limited: true,
|
|
reason: 'line-count',
|
|
lineCounts: {
|
|
original: originalLineCount.count,
|
|
modified: modifiedLineCount.count
|
|
},
|
|
lineCountsAreMinimum: {
|
|
original: originalLineCount.exceeded,
|
|
modified: modifiedLineCount.exceeded
|
|
},
|
|
characterCount,
|
|
limits
|
|
}
|
|
}
|
|
|
|
return {
|
|
limited: false,
|
|
lineCounts: {
|
|
original: originalLineCount.count,
|
|
modified: modifiedLineCount.count
|
|
},
|
|
characterCount
|
|
}
|
|
}
|