fix: Parse merge-tree output from stdout instead of catch block
simple-git's .raw() resolves successfully even on exit code 1, returning stdout content. git merge-tree --write-tree outputs CONFLICT markers to stdout (not stderr), so the catch block never fired and conflicts were reported as clean merges.
This commit is contained in:
@@ -168,37 +168,31 @@ export class SimpleGitBranchManager implements BranchManager {
|
||||
async checkMergeability(repoPath: string, sourceBranch: string, targetBranch: string): Promise<MergeabilityResult> {
|
||||
const git = simpleGit(repoPath);
|
||||
|
||||
try {
|
||||
// git merge-tree --write-tree merges source INTO target virtually.
|
||||
// Exit 0 = clean merge, non-zero = conflicts.
|
||||
await git.raw(['merge-tree', '--write-tree', targetBranch, sourceBranch]);
|
||||
log.debug({ repoPath, sourceBranch, targetBranch }, 'merge-tree check: clean');
|
||||
return { mergeable: true };
|
||||
} catch (err) {
|
||||
const stderr = err instanceof Error ? err.message : String(err);
|
||||
// git merge-tree --write-tree outputs everything to stdout.
|
||||
// simple-git's .raw() resolves with stdout even on exit code 1 (conflicts),
|
||||
// so we parse the output text instead of relying on catch.
|
||||
const output = await git.raw(['merge-tree', '--write-tree', targetBranch, sourceBranch]);
|
||||
|
||||
// Parse conflict file names from "CONFLICT (content): Merge conflict in <path>"
|
||||
const conflictPattern = /CONFLICT \([^)]+\): (?:Merge conflict in|.* -> )(.+)/g;
|
||||
const conflicts: string[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = conflictPattern.exec(stderr)) !== null) {
|
||||
conflicts.push(match[1].trim());
|
||||
}
|
||||
|
||||
if (conflicts.length > 0) {
|
||||
log.debug({ repoPath, sourceBranch, targetBranch, conflicts }, 'merge-tree check: conflicts');
|
||||
return { mergeable: false, conflicts };
|
||||
}
|
||||
|
||||
// If we couldn't parse conflicts but the command failed, it's still a conflict
|
||||
// (could be add/add, rename conflicts, etc.)
|
||||
if (stderr.includes('CONFLICT')) {
|
||||
log.debug({ repoPath, sourceBranch, targetBranch }, 'merge-tree check: unparsed conflicts');
|
||||
return { mergeable: false, conflicts: ['(unable to parse conflict details)'] };
|
||||
}
|
||||
|
||||
// Genuine error (not a conflict)
|
||||
throw err;
|
||||
// Parse conflict file names from "CONFLICT (content): Merge conflict in <path>"
|
||||
const conflictPattern = /CONFLICT \([^)]+\): (?:Merge conflict in|.* -> )(.+)/g;
|
||||
const conflicts: string[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = conflictPattern.exec(output)) !== null) {
|
||||
conflicts.push(match[1].trim());
|
||||
}
|
||||
|
||||
if (conflicts.length > 0) {
|
||||
log.debug({ repoPath, sourceBranch, targetBranch, conflicts }, 'merge-tree check: conflicts');
|
||||
return { mergeable: false, conflicts };
|
||||
}
|
||||
|
||||
// Fallback: check for any CONFLICT text we couldn't parse specifically
|
||||
if (output.includes('CONFLICT')) {
|
||||
log.debug({ repoPath, sourceBranch, targetBranch }, 'merge-tree check: unparsed conflicts');
|
||||
return { mergeable: false, conflicts: ['(unable to parse conflict details)'] };
|
||||
}
|
||||
|
||||
log.debug({ repoPath, sourceBranch, targetBranch }, 'merge-tree check: clean');
|
||||
return { mergeable: true };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user