Resolving Complex Binary Conflicts in Git Jump to heading

When Git attempts to reconcile divergent histories, it relies on the 3-way merge algorithm to compute a unified diff. For text files, this enables granular conflict markers. For binary payloads, the diff engine cannot parse semantic boundaries, so it marks the file conflicted without writing any markers β€” the working tree simply contains one side’s blob. Pipeline execution halts. Manual editing is impossible. This page is a focused recipe within 3-Way Merge Fundamentals, part of the broader Conflict Resolution & Safe Merge Operations topic area.

The correct approach is deterministic: classify the binary type, choose the right resolution strategy, and automate it so the same conflict never requires human intervention twice.

SAFETY WARNING: Never open a binary file in a text editor during a merge conflict. Modifying raw bytes corrupts the file header. Use git checkout --ours / --theirs or a structured external tool β€” never raw editing.

When to Use This Approach Jump to heading

Apply this recipe when:

  • git status --porcelain reports a UU or AA code for a non-text file (.png, .psd, .wasm, .bin, .sketch, lockfiles, etc.)
  • git merge exits non-zero and the message names a binary file as unmerged
  • Your CI pipeline fails with β€œbinary file … needs merge” without producing usable conflict markers
  • A lockfile merge produces hash mismatches that break dependency installation β€” an alternative is to use squash and fixup strategies to avoid the conflicting commits ever landing together
  • A recurring binary conflict has been resolved manually more than once β€” at that point automation via .gitattributes is overdue

Binary Conflict Decision Flow Jump to heading

The diagram below maps the four key questions that determine which resolution path to take.

Binary conflict resolution decision flowA flowchart: start at "Binary conflict detected", ask "Is it a derived artifact?", then "Is it a lockfile?", then "Is it a design asset?", each leading to a labeled resolution action. The final default is "keep-ours / keep-theirs policy".Binary conflict detectedDerived artifact? (.o .class .wasm)yescheckout --theirsadd to .gitignorenoLockfile? (package-lock, Cargo.lock)yescheckout --theirsthen regeneratenoDesign asset? (.psd .sketch .fig)yesrestore authoritativeversion + Git LFSnoDefine keep-ours / keep-theirspolicy in .gitattributesIn all cases: validate with git hash-object before finalizing merge

Step-by-Step Recipe Jump to heading

Step 1 β€” Diagnose the conflict Jump to heading

Start with machine-readable status output. The UU code means both sides modified the same file:

# Show only unmerged files
git status --porcelain | grep '^UU'

Inspect the index stages. Git maintains three object versions for every conflicted file:

  • Stage 1 (:.1:): common ancestor
  • Stage 2 (:.2:): current branch
  • Stage 3 (:.3:): incoming branch
# List all unmerged paths with their stage numbers
git ls-files -u

Extract each blob to a temporary path for safe inspection:

# Replace "path/to/asset.psd" with the actual conflicted file path
git show :1:path/to/asset.psd > /tmp/base.bin
git show :2:path/to/asset.psd > /tmp/ours.bin
git show :3:path/to/asset.psd > /tmp/theirs.bin

# Verify actual content type β€” extensions frequently lie
file --mime-type /tmp/base.bin /tmp/ours.bin /tmp/theirs.bin

SAFETY WARNING: Use /tmp/ for inspection blobs. Files placed in the working tree can bypass .gitignore rules and be accidentally staged. Remove them immediately: rm -f /tmp/base.bin /tmp/ours.bin /tmp/theirs.bin.

Verification: git ls-files -u lists three rows for the conflicted path (stages 1, 2, 3). If only two stages appear, one side added the file fresh β€” resolve by accepting the relevant side.

Step 2 β€” Resolve compiled artifacts and build outputs Jump to heading

Compiled binaries (.o, .class, .wasm) are derived from source and should not live in version control. Resolve by accepting the version that aligns with the authoritative build pipeline:

# Accept the incoming branch's artifact
git checkout --theirs -- dist/app.wasm
git add dist/app.wasm
git commit -m "resolve: accept build artifact from incoming branch"

Or retain the current branch when it is authoritative:

# Accept the local version
git checkout --ours -- dist/app.wasm
git add dist/app.wasm

SAFETY WARNING: Never attempt byte-level editing of compiled binaries. Broken symbol tables and corrupt runtime loaders result. Add them to .gitignore to prevent the problem from recurring.

Verification: git show HEAD:dist/app.wasm | file --mime-type - should return the expected MIME type with no merge metadata.

Step 3 β€” Resolve serialized lockfiles Jump to heading

Lockfiles encode resolved dependency graphs with cryptographic hashes. Manual merging introduces supply-chain drift β€” any hand-edited version will have inconsistent hashes. Accept one side, then regenerate:

# Accept the incoming branch version as the base
git checkout --theirs -- package-lock.json

# Regenerate using the native resolver
npm install

# Stage the freshly generated lockfile
git add package-lock.json
git commit -m "resolve: regenerate lockfile after dependency conflict"

Run npm audit (or cargo audit, go mod verify) after regeneration to confirm no new vulnerabilities were introduced.

SAFETY WARNING: Never commit a partially-merged lockfile. Hash inconsistencies cause CI/CD pipeline resolution failures that are hard to diagnose. Always regenerate before staging.

Verification: npm ci (or the equivalent for your package manager) must exit 0 on a fresh clone. If it fails, the lockfile is still inconsistent.

Step 4 β€” Resolve media and design assets Jump to heading

Design files require parallel editing controls at the workflow level, not at the Git diff level. Restore the authoritative version and adopt Git LFS for future tracking:

# Restore the authoritative version from the branch that owns this asset
git checkout design/v2-assets -- assets/hero.psd
git add assets/hero.psd
git commit -m "resolve: restore authoritative asset from design/v2-assets"

Document in the commit message which team or workflow produced the authoritative version. If Git LFS is not yet configured for this repository, this is the moment to add it β€” the interactive rebase workflows that follow will be cleaner once large binary blobs are stored outside the object database.

SAFETY WARNING: Standard Git delta compression performs poorly on large binary files. Configure Git LFS for design assets before the repository grows unwieldy. Once files are in LFS, their pointers are text and can be merged, but the underlying objects still require the same deterministic workflow above.

Verification: git lfs ls-files should list the resolved asset after the commit. If LFS was not set up beforehand, install it now and migrate: git lfs migrate import --include="*.psd".

Step 5 β€” Automate with .gitattributes and merge drivers Jump to heading

Recurring binary conflicts signal a missing repository policy. Define file patterns in .gitattributes:

# Treat as opaque blobs β€” no text diff, no merge attempt
*.bin  binary
*.png  binary

# Custom driver for design files β€” always keep the current branch version
*.psd  merge=keep-ours

Register the driver in the repository configuration. The driver receives three arguments substituted for %O (ancestor), %A (current branch β€” must contain the result on exit 0), and %B (incoming branch):

# "keep-ours" driver: leave %A unchanged and signal success
git config merge.keep-ours.name "Keep current-branch version"
git config merge.keep-ours.driver "true"

The true command always exits 0 without modifying %A, which is exactly the keep-ours semantic. For a keep-theirs policy, copy %B over %A:

# "keep-theirs" driver: overwrite %A with %B
git config merge.keep-theirs.name "Keep incoming-branch version"
git config merge.keep-theirs.driver "cp %B %A"

Commit .gitattributes to the repository root so all clones inherit the policy. Distribute driver registration via a team setup script β€” pair this with your local hook configuration onboarding step so new engineers get the drivers installed automatically.

SAFETY WARNING: Test driver scripts in an isolated repository before committing .gitattributes. A driver that exits non-zero for every file will halt every merge across the team.

Verification: Create a test conflict on a scratch branch. Run git merge β€” the driver should resolve it automatically and git status should be clean with no unmerged paths.

Step 6 β€” Validate integrity and finalize Jump to heading

Resolution does not guarantee integrity. Validate before finalizing.

Confirm no residual conflict markers remain in adjacent text files:

git diff --check

Verify the binary object hash matches the expected artifact from CI:

# Compare this hash against your artifact registry or the known-good CI output
git hash-object dist/app.wasm

Finalize the merge:

git merge --continue

SAFETY WARNING: Do not bypass CI validation gates when binary conflicts have occurred. Hash mismatches indicate incomplete resolution or toolchain drift. Enforce branch protection rules that require passing pipeline runs before merge β€” the pre-push validation rules pattern covers this enforcement layer.

Verification: git log --oneline -1 should show the merge commit with a clean message. git status must be empty.

Validation Checklist Jump to heading

Frequently Asked Questions Jump to heading

Why doesn’t Git write conflict markers into binary files? Jump to heading

Git’s diff engine operates on text lines. Binary blobs have no line boundaries, so there is no meaningful position at which to insert <<<<<<< / ======= / >>>>>>> markers. Git marks the file conflicted at the index level and leaves one side’s blob in the working tree unchanged.

Can I use git mergetool for binary conflicts? Jump to heading

Only if you configure a tool that understands the format β€” for example, a PSD-aware editor for Photoshop files. The default mergetool options for text (vimdiff, meld, etc.) will corrupt binary content. Use git checkout --ours / --theirs or a registered custom merge driver instead.

How do I prevent binary conflicts from recurring? Jump to heading

Commit a .gitattributes file that marks the pattern as binary and assigns a named merge driver. The driver encodes your resolution policy (keep-ours, keep-theirs, or regenerate) so every future merge on any clone resolves automatically without human intervention. For lockfiles specifically, combine the driver with a post-merge hook that re-runs the package manager’s install command.