Skip to content

Merge

functionanalyzeMerge
analyzeMerge(repo: Repo, sourceBranch: string, targetBranch: string): Promise<MergeAnalysis>

Returns: Promise<MergeAnalysis>

A cheap pre-merge check: do both branches exist, and is this a fast-forward? { canMerge, hasConflicts, conflictingFiles, fastForward }.

canMerge: false means one of the refs genuinely didn’t resolve (NotFoundError — a deleted branch, a typo). Any other failure — a corrupted object, a storage read error — propagates instead of being folded into canMerge: false, which would otherwise misreport “this branch doesn’t exist” for a problem that has nothing to do with either branch’s existence.

fastForwardMerge(repo: Repo, sourceBranch: string, targetBranch: string): Promise<{ success: true; commitSha: string } | null>

Returns: Promise<{ success: true; commitSha: string } | null>

Attempts a fast-forward directly against the bare repo — when source is a descendant of target, this just moves the target ref, no worktree, no new commit object. Returns null when the merge isn’t a fast-forward (diverged branches); callers fall back to a real three-way merge, which does need a worktree.

Serialize with a per-repo lock — the resolve → write-ref sequence isn’t atomic on object storage.

Going further: real three-way merges without a worktree

Section titled “Going further: real three-way merges without a worktree”

Neither analyzeMerge nor fastForwardMerge performs a real content merge for diverged branches — that’s intentionally out of scope here, since it needs either a worktree (isomorphic-git’s own git.merge) or an object-level merge implementation.

git-edge’s threeWayMerge is exactly that: a real three-way content merge that works directly on the object graph — no checkout, no worktree — specifically built to compose with this package for repos that live entirely in object storage with no durable disk to check out onto.

import { threeWayMerge, GitMergeConflictError } from 'git-edge';
try {
const { commitOid } = await threeWayMerge(repo, sourceBranch, targetBranch);
} catch (err) {
if (err instanceof GitMergeConflictError) {
console.log('conflicts in:', err.conflictingPaths);
}
}