@@ -14,6 +14,8 @@ export interface DiffLine {
1414export interface DiffHunk {
1515 /** Free text after the `@@ … @@` range, often the enclosing heading or function. */
1616 heading : string
17+ /** The file a hunk belongs to, set only when a git diff spans several files. */
18+ file ?: string
1719 lines : DiffLine [ ]
1820}
1921
@@ -29,7 +31,7 @@ export interface UnifiedDiff {
2931
3032/** Git metadata that precedes `---`/`+++` in `git diff` output and carries nothing to render. */
3133const GIT_HEADER =
32- / ^ (?: d i f f - - g i t | i n d e x | n e w f i l e m o d e | d e l e t e d f i l e m o d e | s i m i l a r i t y | r e n a m e | o l d m o d e | n e w m o d e | B i n a r y f i l e s ) /
34+ / ^ (?: i n d e x | n e w f i l e m o d e | d e l e t e d f i l e m o d e | s i m i l a r i t y | d i s s i m i l a r i t y | r e n a m e | c o p y | o l d m o d e | n e w m o d e | B i n a r y f i l e s ) /
3335const HUNK_HEADER = / ^ @ @ (?: - ( \d + ) (?: , \d + ) ? \+ ( \d + ) (?: , \d + ) ? @ @ ) ? \s ? ( .* ?) (?: \s * @ @ ) ? $ /
3436const SIM_SOURCE = / ^ s i m : ( f i l e | k n o w l e d g e ) \/ ( [ ^ / \s ] + ) (?: \/ ( [ ^ / \s ] + ) ) ? $ /
3537
@@ -72,28 +74,35 @@ export function parseUnifiedDiff(text: string): UnifiedDiff {
7274 let oldLine : number | undefined
7375 let newLine : number | undefined
7476
77+ let file : string | null = null
7578 for ( const [ index , row ] of rows . entries ( ) ) {
79+ if ( row . startsWith ( 'diff --git ' ) ) {
80+ hunk = null
81+ file = null
82+ continue
83+ }
7684 if ( ! hunk && ( row . startsWith ( '--- ' ) || row . startsWith ( '+++ ' ) ) ) {
7785 const target = row . slice ( 4 ) . trim ( )
7886 const parsed = parseSource ( target )
79- if ( row . startsWith ( '--- ' ) ) oldSource = parsed
80- else newSource = parsed
81- if ( ! parsed && target !== '/dev/null' && ( row . startsWith ( '+++ ' ) || ! path ) )
82- path = target . replace ( / ^ [ a b ] \/ / , '' ) . split ( '\t' ) [ 0 ]
87+ if ( row . startsWith ( '--- ' ) ) oldSource = parsed ?? oldSource
88+ else newSource = parsed ?? newSource
89+ if ( ! parsed && target !== '/dev/null' && ( row . startsWith ( '+++ ' ) || ! file ) )
90+ file = target . replace ( / ^ [ a b ] \/ / , '' ) . split ( '\t' ) [ 0 ]
91+ path ??= file
8392 continue
8493 }
8594 if ( ! hunk && GIT_HEADER . test ( row ) ) continue
8695 const header = HUNK_HEADER . exec ( row )
8796 if ( row . startsWith ( '@@' ) && header ) {
88- hunk = { heading : header [ 3 ] ?? '' , lines : [ ] }
97+ hunk = { heading : header [ 3 ] ?? '' , file : file ?? undefined , lines : [ ] }
8998 hunks . push ( hunk )
9099 oldLine = header [ 1 ] ? Number ( header [ 1 ] ) : undefined
91100 newLine = header [ 2 ] ? Number ( header [ 2 ] ) : undefined
92101 continue
93102 }
94103 if ( row . startsWith ( '\\' ) ) continue
95104 if ( ! hunk ) {
96- hunk = { heading : '' , lines : [ ] }
105+ hunk = { heading : '' , file : file ?? undefined , lines : [ ] }
97106 hunks . push ( hunk )
98107 }
99108 const marker = row [ 0 ]
@@ -114,5 +123,8 @@ export function parseUnifiedDiff(text: string): UnifiedDiff {
114123 throw new Error ( 'A diff needs at least one + or - line' )
115124 oldSource ??= newSource
116125 newSource ??= oldSource
126+ const files = new Set ( hunks . map ( ( entry ) => entry . file ) )
127+ if ( files . size <= 1 ) for ( const entry of hunks ) entry . file = undefined
128+ else path = `${ files . size } files`
117129 return { oldSource, newSource, path : oldSource ? null : path , hunks }
118130}
0 commit comments