1
+ import { execFile } from "node:child_process" ;
1
2
import { Config , SupportedParsers } from "./types" ;
3
+ import { promisify } from "node:util" ;
4
+
5
+ const execFileAsync = promisify ( execFile ) ;
2
6
3
7
/**
4
8
* Represents a parsed conflict from a file with `ours` and `theirs` versions.
@@ -10,6 +14,8 @@ export interface ParsedConflict<T = unknown> {
10
14
ours : T ;
11
15
/** Parsed content from the "theirs" side of the conflict. */
12
16
theirs : T ;
17
+ /** Parsed content from the "base" side of the conflict (optional). */
18
+ base ?: T ;
13
19
/** Format used to parse the content (`json`, `yaml`, `toml`, `xml`, or `custom`). */
14
20
format : string ;
15
21
}
@@ -19,14 +25,14 @@ export interface ParsedConflict<T = unknown> {
19
25
*/
20
26
export interface ParseConflictOptions extends Pick < Config , "parsers" > {
21
27
/**
22
- * Optional filename hint to prioritize parser choice.
28
+ * filename hint to prioritize parser choice as well as get base and ours from git .
23
29
* Example:
24
30
* - `config.yaml` → try `yaml` first.
25
31
* - `data.toml` → try `toml` first.
26
32
*
27
33
* If extension is unknown, falls back to `parsers` or `"json"`.
28
34
*/
29
- filename ? : string ;
35
+ filename : string ;
30
36
}
31
37
32
38
/**
@@ -48,7 +54,7 @@ export interface ParseConflictOptions extends Pick<Config, "parsers"> {
48
54
*/
49
55
export const parseConflictContent = async < T = unknown > (
50
56
content : string ,
51
- options : ParseConflictOptions = { } ,
57
+ options : ParseConflictOptions ,
52
58
) : Promise < ParsedConflict < T > > => {
53
59
const lines = content . split ( "\n" ) ;
54
60
const oursLines : string [ ] = [ ] ;
@@ -87,8 +93,23 @@ export const parseConflictContent = async <T = unknown>(
87
93
}
88
94
}
89
95
90
- const oursRaw = oursLines . join ( "\n" ) ;
96
+ let oursRaw = oursLines . join ( "\n" ) ;
91
97
const theirsRaw = theirsLines . join ( "\n" ) ;
98
+ const baseRaw = await execFileAsync ( "git" , [ "show" , `:1:${ options . filename } ` ] , {
99
+ maxBuffer : 1024 * 1024 * 50 ,
100
+ } )
101
+ . then ( ( { stdout } ) => stdout )
102
+ . catch ( ( ) => null ) ;
103
+
104
+ // No conflict
105
+ if ( oursRaw === theirsRaw ) {
106
+ oursRaw =
107
+ ( await execFileAsync ( "git" , [ "show" , `HEAD:${ options . filename } ` ] , {
108
+ maxBuffer : 1024 * 1024 * 50 ,
109
+ } )
110
+ . then ( ( { stdout } ) => stdout )
111
+ . catch ( ( ) => null ) ) ?? oursRaw ;
112
+ }
92
113
93
114
if ( ! oursRaw || ! theirsRaw ) {
94
115
throw new Error ( "Conflict parsing resulted in empty content." ) ;
@@ -98,11 +119,14 @@ export const parseConflictContent = async <T = unknown>(
98
119
const parsers = normalizeParsers ( options ) ;
99
120
100
121
const [ oursParsed , format ] = await runParser ( oursRaw , parsers ) ;
101
- const [ theirsParsed ] = await runParser ( theirsRaw , [ format ] ) ;
122
+ const [ [ theirsParsed ] , baseParsed ] = await Promise . all (
123
+ ( baseRaw ? [ theirsRaw , baseRaw ] : [ theirsRaw ] ) . map ( raw => runParser ( raw , [ format ] ) ) ,
124
+ ) ;
102
125
103
126
return {
104
127
ours : oursParsed as T ,
105
128
theirs : theirsParsed as T ,
129
+ base : baseParsed ?. [ 0 ] as T | undefined ,
106
130
format : typeof format === "string" ? format : format . name ,
107
131
} ;
108
132
} ;
0 commit comments