@@ -32,6 +32,11 @@ import { decodeJsonResult } from "@t3tools/shared/schemaJson";
3232
3333const DEFAULT_TIMEOUT_MS = 30_000 ;
3434const DEFAULT_MAX_OUTPUT_BYTES = 1_000_000 ;
35+ const OUTPUT_TRUNCATED_MARKER = "\n\n[truncated]" ;
36+ const PREPARED_COMMIT_PATCH_MAX_OUTPUT_BYTES = 49_000 ;
37+ const RANGE_COMMIT_SUMMARY_MAX_OUTPUT_BYTES = 19_000 ;
38+ const RANGE_DIFF_SUMMARY_MAX_OUTPUT_BYTES = 19_000 ;
39+ const RANGE_DIFF_PATCH_MAX_OUTPUT_BYTES = 59_000 ;
3540const STATUS_UPSTREAM_REFRESH_INTERVAL = Duration . seconds ( 15 ) ;
3641const STATUS_UPSTREAM_REFRESH_TIMEOUT = Duration . seconds ( 5 ) ;
3742const STATUS_UPSTREAM_REFRESH_CACHE_CAPACITY = 2_048 ;
@@ -53,6 +58,8 @@ interface ExecuteGitOptions {
5358 timeoutMs ?: number | undefined ;
5459 allowNonZeroExit ?: boolean | undefined ;
5560 fallbackErrorMessage ?: string | undefined ;
61+ maxOutputBytes ?: number | undefined ;
62+ truncateOutputAtMaxBytes ?: boolean | undefined ;
5663 progress ?: ExecuteGitProgress | undefined ;
5764}
5865
@@ -439,12 +446,14 @@ const collectOutput = Effect.fn(function* <E>(
439446 input : Pick < ExecuteGitInput , "operation" | "cwd" | "args" > ,
440447 stream : Stream . Stream < Uint8Array , E > ,
441448 maxOutputBytes : number ,
449+ truncateOutputAtMaxBytes : boolean ,
442450 onLine : ( ( line : string ) => Effect . Effect < void , never > ) | undefined ,
443451) : Effect . fn . Return < string , GitCommandError > {
444452 const decoder = new TextDecoder ( ) ;
445453 let bytes = 0 ;
446454 let text = "" ;
447455 let lineBuffer = "" ;
456+ let truncated = false ;
448457
449458 const emitCompleteLines = ( flush : boolean ) =>
450459 Effect . gen ( function * ( ) {
@@ -469,27 +478,38 @@ const collectOutput = Effect.fn(function* <E>(
469478
470479 yield * Stream . runForEach ( stream , ( chunk ) =>
471480 Effect . gen ( function * ( ) {
472- bytes += chunk . byteLength ;
473- if ( bytes > maxOutputBytes ) {
481+ if ( truncateOutputAtMaxBytes && truncated ) {
482+ return ;
483+ }
484+ const nextBytes = bytes + chunk . byteLength ;
485+ if ( ! truncateOutputAtMaxBytes && nextBytes > maxOutputBytes ) {
474486 return yield * new GitCommandError ( {
475487 operation : input . operation ,
476488 command : quoteGitCommand ( input . args ) ,
477489 cwd : input . cwd ,
478490 detail : `${ quoteGitCommand ( input . args ) } output exceeded ${ maxOutputBytes } bytes and was truncated.` ,
479491 } ) ;
480492 }
481- const decoded = decoder . decode ( chunk , { stream : true } ) ;
493+
494+ const chunkToDecode =
495+ truncateOutputAtMaxBytes && nextBytes > maxOutputBytes
496+ ? chunk . subarray ( 0 , Math . max ( 0 , maxOutputBytes - bytes ) )
497+ : chunk ;
498+ bytes += chunkToDecode . byteLength ;
499+ truncated = truncateOutputAtMaxBytes && nextBytes > maxOutputBytes ;
500+
501+ const decoded = decoder . decode ( chunkToDecode , { stream : ! truncated } ) ;
482502 text += decoded ;
483503 lineBuffer += decoded ;
484504 yield * emitCompleteLines ( false ) ;
485505 } ) ,
486506 ) . pipe ( Effect . mapError ( toGitCommandError ( input , "output stream failed." ) ) ) ;
487507
488- const remainder = decoder . decode ( ) ;
508+ const remainder = truncated ? "" : decoder . decode ( ) ;
489509 text += remainder ;
490510 lineBuffer += remainder ;
491511 yield * emitCompleteLines ( true ) ;
492- return text ;
512+ return truncated ? ` ${ text } ${ OUTPUT_TRUNCATED_MARKER } ` : text ;
493513} ) ;
494514
495515export const makeGitCore = ( options ?: { executeOverride ?: GitCoreShape [ "execute" ] } ) =>
@@ -511,6 +531,7 @@ export const makeGitCore = (options?: { executeOverride?: GitCoreShape["execute"
511531 } as const ;
512532 const timeoutMs = input . timeoutMs ?? DEFAULT_TIMEOUT_MS ;
513533 const maxOutputBytes = input . maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES ;
534+ const truncateOutputAtMaxBytes = input . truncateOutputAtMaxBytes ?? false ;
514535
515536 const commandEffect = Effect . gen ( function * ( ) {
516537 const trace2Monitor = yield * createTrace2Monitor ( commandInput , input . progress ) . pipe (
@@ -537,12 +558,14 @@ export const makeGitCore = (options?: { executeOverride?: GitCoreShape["execute"
537558 commandInput ,
538559 child . stdout ,
539560 maxOutputBytes ,
561+ truncateOutputAtMaxBytes ,
540562 input . progress ?. onStdoutLine ,
541563 ) ,
542564 collectOutput (
543565 commandInput ,
544566 child . stderr ,
545567 maxOutputBytes ,
568+ truncateOutputAtMaxBytes ,
546569 input . progress ?. onStderrLine ,
547570 ) ,
548571 child . exitCode . pipe (
@@ -603,6 +626,10 @@ export const makeGitCore = (options?: { executeOverride?: GitCoreShape["execute"
603626 args,
604627 allowNonZeroExit : true ,
605628 ...( options . timeoutMs !== undefined ? { timeoutMs : options . timeoutMs } : { } ) ,
629+ ...( options . maxOutputBytes !== undefined ? { maxOutputBytes : options . maxOutputBytes } : { } ) ,
630+ ...( options . truncateOutputAtMaxBytes !== undefined
631+ ? { truncateOutputAtMaxBytes : options . truncateOutputAtMaxBytes }
632+ : { } ) ,
606633 ...( options . progress ? { progress : options . progress } : { } ) ,
607634 } ) . pipe (
608635 Effect . flatMap ( ( result ) => {
@@ -647,6 +674,14 @@ export const makeGitCore = (options?: { executeOverride?: GitCoreShape["execute"
647674 Effect . map ( ( result ) => result . stdout ) ,
648675 ) ;
649676
677+ const runGitStdoutWithOptions = (
678+ operation : string ,
679+ cwd : string ,
680+ args : readonly string [ ] ,
681+ options : ExecuteGitOptions = { } ,
682+ ) : Effect . Effect < string , GitCommandError > =>
683+ executeGit ( operation , cwd , args , options ) . pipe ( Effect . map ( ( result ) => result . stdout ) ) ;
684+
650685 const branchExists = ( cwd : string , branch : string ) : Effect . Effect < boolean , GitCommandError > =>
651686 executeGit (
652687 "GitCore.branchExists" ,
@@ -1162,12 +1197,15 @@ export const makeGitCore = (options?: { executeOverride?: GitCoreShape["execute"
11621197 return null ;
11631198 }
11641199
1165- const stagedPatch = yield * runGitStdout ( "GitCore.prepareCommitContext.stagedPatch" , cwd , [
1166- "diff" ,
1167- "--cached" ,
1168- "--patch" ,
1169- "--minimal" ,
1170- ] ) ;
1200+ const stagedPatch = yield * runGitStdoutWithOptions (
1201+ "GitCore.prepareCommitContext.stagedPatch" ,
1202+ cwd ,
1203+ [ "diff" , "--cached" , "--patch" , "--minimal" ] ,
1204+ {
1205+ maxOutputBytes : PREPARED_COMMIT_PATCH_MAX_OUTPUT_BYTES ,
1206+ truncateOutputAtMaxBytes : true ,
1207+ } ,
1208+ ) ;
11711209
11721210 return {
11731211 stagedSummary,
@@ -1363,14 +1401,33 @@ export const makeGitCore = (options?: { executeOverride?: GitCoreShape["execute"
13631401 const range = `${ baseBranch } ..HEAD` ;
13641402 const [ commitSummary , diffSummary , diffPatch ] = yield * Effect . all (
13651403 [
1366- runGitStdout ( "GitCore.readRangeContext.log" , cwd , [ "log" , "--oneline" , range ] ) ,
1367- runGitStdout ( "GitCore.readRangeContext.diffStat" , cwd , [ "diff" , "--stat" , range ] ) ,
1368- runGitStdout ( "GitCore.readRangeContext.diffPatch" , cwd , [
1369- "diff" ,
1370- "--patch" ,
1371- "--minimal" ,
1372- range ,
1373- ] ) ,
1404+ runGitStdoutWithOptions (
1405+ "GitCore.readRangeContext.log" ,
1406+ cwd ,
1407+ [ "log" , "--oneline" , range ] ,
1408+ {
1409+ maxOutputBytes : RANGE_COMMIT_SUMMARY_MAX_OUTPUT_BYTES ,
1410+ truncateOutputAtMaxBytes : true ,
1411+ } ,
1412+ ) ,
1413+ runGitStdoutWithOptions (
1414+ "GitCore.readRangeContext.diffStat" ,
1415+ cwd ,
1416+ [ "diff" , "--stat" , range ] ,
1417+ {
1418+ maxOutputBytes : RANGE_DIFF_SUMMARY_MAX_OUTPUT_BYTES ,
1419+ truncateOutputAtMaxBytes : true ,
1420+ } ,
1421+ ) ,
1422+ runGitStdoutWithOptions (
1423+ "GitCore.readRangeContext.diffPatch" ,
1424+ cwd ,
1425+ [ "diff" , "--patch" , "--minimal" , range ] ,
1426+ {
1427+ maxOutputBytes : RANGE_DIFF_PATCH_MAX_OUTPUT_BYTES ,
1428+ truncateOutputAtMaxBytes : true ,
1429+ } ,
1430+ ) ,
13741431 ] ,
13751432 { concurrency : "unbounded" } ,
13761433 ) ;
0 commit comments