1+ import { execFile } from "node:child_process" ;
12import { Config , SupportedParsers } from "./types" ;
3+ import { promisify } from "node:util" ;
4+
5+ const execFileAsync = promisify ( execFile ) ;
26
37/**
48 * Represents a parsed conflict from a file with `ours` and `theirs` versions.
@@ -10,6 +14,8 @@ export interface ParsedConflict<T = unknown> {
1014 ours : T ;
1115 /** Parsed content from the "theirs" side of the conflict. */
1216 theirs : T ;
17+ /** Parsed content from the "base" side of the conflict (optional). */
18+ base ?: T ;
1319 /** Format used to parse the content (`json`, `yaml`, `toml`, `xml`, or `custom`). */
1420 format : string ;
1521}
@@ -19,14 +25,14 @@ export interface ParsedConflict<T = unknown> {
1925 */
2026export interface ParseConflictOptions extends Pick < Config , "parsers" > {
2127 /**
22- * Optional filename hint to prioritize parser choice.
28+ * filename hint to prioritize parser choice as well as get base and ours from git .
2329 * Example:
2430 * - `config.yaml` → try `yaml` first.
2531 * - `data.toml` → try `toml` first.
2632 *
2733 * If extension is unknown, falls back to `parsers` or `"json"`.
2834 */
29- filename ? : string ;
35+ filename : string ;
3036}
3137
3238/**
@@ -48,7 +54,7 @@ export interface ParseConflictOptions extends Pick<Config, "parsers"> {
4854 */
4955export const parseConflictContent = async < T = unknown > (
5056 content : string ,
51- options : ParseConflictOptions = { } ,
57+ options : ParseConflictOptions ,
5258) : Promise < ParsedConflict < T > > => {
5359 const lines = content . split ( "\n" ) ;
5460 const oursLines : string [ ] = [ ] ;
@@ -87,8 +93,23 @@ export const parseConflictContent = async <T = unknown>(
8793 }
8894 }
8995
90- const oursRaw = oursLines . join ( "\n" ) ;
96+ let oursRaw = oursLines . join ( "\n" ) ;
9197 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+ }
92113
93114 if ( ! oursRaw || ! theirsRaw ) {
94115 throw new Error ( "Conflict parsing resulted in empty content." ) ;
@@ -98,11 +119,14 @@ export const parseConflictContent = async <T = unknown>(
98119 const parsers = normalizeParsers ( options ) ;
99120
100121 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+ ) ;
102125
103126 return {
104127 ours : oursParsed as T ,
105128 theirs : theirsParsed as T ,
129+ base : baseParsed ?. [ 0 ] as T | undefined ,
106130 format : typeof format === "string" ? format : format . name ,
107131 } ;
108132} ;
0 commit comments