Git Merge Conflict — --theirs Wiped a Security Header
A --theirs merge silently dropped a security header, exposing an API.
- Git merge conflicts happen when two branches edit the same lines — Git stops to ask for human input
- Conflict markers show your version (HEAD), the incoming version, and (with diff3) the common ancestor
- Three resolution methods: manual edit, three-way merge tool, or --ours/--theirs for whole files
- Using diff3 conflict style reduces resolution errors by ~40% by revealing the original base
- Most production incidents come from blind --theirs that silently discard critical environment configs
- Prevention: daily sync with main keeps conflicts small and rare
Imagine two chefs are both improving the same recipe book at the same time — one changes the pasta sauce on page 12, and the other also rewrites that exact same page. When they try to combine their books into one final version, nobody knows which sauce to keep. Git hits the same wall: two developers edited the same line of code, and Git genuinely cannot decide whose version wins. A merge conflict is just Git raising its hand and saying 'I need a human to sort this out.'
Every team that uses Git will eventually hit a merge conflict. It is not a sign something went wrong — it is a sign your team is working in parallel, which is exactly what Git is built for. The problem is that most developers treat conflicts like a fire alarm: panic, smash the keyboard, accept every incoming change, and hope for the best. That approach silently breaks production code and ruins trust in your codebase.
Merge conflicts exist because Git tracks changes at the line level. When two branches modify the same line (or adjacent lines) independently, Git cannot apply both changes automatically without risking data loss. So it stops, marks the battlefield inside the file, and waits for you. The conflict markers Git leaves behind are not cryptic error messages — they are a structured diff you can read and act on deliberately.
By the end of this article you will be able to: read and interpret conflict markers without guessing, manually resolve conflicts with confidence, use a three-way merge tool to handle complex conflicts visually, abort or retry a merge cleanly when things go sideways, and adopt the team habits that prevent unnecessary conflicts in the first place.
Why Git Merge Conflicts Happen — and When to Expect Them
Git merges work beautifully most of the time because the changes on two branches touch different files or different sections of the same file. Git's merge algorithm is smart enough to combine those non-overlapping edits automatically. Conflicts only surface when the algorithm genuinely cannot make a safe decision.
There are three common triggers. First, two developers edit the exact same line in the same file on different branches. Second, one developer edits a block of code while another deletes that entire file. Third, two developers rename the same file to different names. Each of these cases forces Git to stop and defer to a human.
The branch strategy your team uses matters enormously here. Long-lived feature branches that drift far from main are conflict factories — by the time you merge, dozens of lines may have diverged. Short-lived branches merged frequently are the antidote. Knowing this changes how you plan your work, not just how you fix conflicts after they happen.
Conflicts during a rebase feel slightly different from conflicts during a merge because a rebase replays commits one at a time, so you might resolve the same conceptual conflict multiple times. Understanding which operation triggered the conflict changes how you resolve it.
Reading Conflict Markers — Decoding What Git Actually Wrote in Your File
After a conflict, Git edits the file directly and inserts markers to show you both sides of the disagreement. Most developers scan these markers, pick a side, and move on. That works for trivial conflicts. For anything real, you need to read all three pieces of information Git gives you.
The structure is always the same. The <<<<<<< HEAD line opens the block and the content below it is what YOUR current branch has. The ======= line is the divider. Everything below the divider down to >>>>>>> feature/branch-name is what the incoming branch contributed. Between those three markers is the full picture of the disagreement.
What most guides skip is the base — the version that BOTH branches started from before they diverged. Git stores this internally. Knowing the base tells you whether both developers added new logic (in which case you likely need to keep both), or whether they both rewrote the same existing logic (in which case you need to pick the right one). Without the base, you are reading a debate without knowing what both parties agreed on before it started.
You can surface the base yourself with git diff --diff-filter=U or by configuring Git to use the diff3 conflict style, which embeds the ancestor version directly between the markers. This single setting prevents enormous amounts of bad conflict resolution.
git config --global merge.conflictStyle diff3 right now. Without the ancestor block in your conflict markers, you are always making decisions with incomplete information. This one setting has saved countless hours of debugging bad resolutions.merge.conflictStyle diff3 globally — it's a one-time config that pays forever.Resolving Conflicts Three Ways — Manual Edit, git mergetool, and Theirs/Ours
There is no single correct way to resolve a conflict — the right approach depends on how complex the conflict is and what the code is doing. You have three practical options and each has a place.
Manual editing works well for simple conflicts: open the file, delete the markers, keep the correct code, save. This forces you to read the code carefully, which is often exactly what you should do. It breaks down when conflicts span dozens of lines or when you cannot tell which version is correct without seeing both in context side-by-side.
A three-way merge tool (like VS Code, IntelliJ, or vimdiff) shows you the base, the current branch, and the incoming branch in three panes simultaneously, with a fourth result pane you build from clicking to accept each hunk. This is the right approach for complex conflicts involving multiple changed functions.
The --ours and --theirs flags are a power tool for a specific situation: when you know categorically that one entire side is correct. They are NOT a shortcut for laziness. Use --ours to keep the current branch's version of a file in full, and --theirs to take the incoming branch's full version. Misusing these flags is one of the most common ways teams silently discard real code changes.
After any resolution method, the workflow is identical: stage the file, verify the state, and commit.
git add filename stages the file regardless of whether conflict markers are still inside it. Git trusts you. If you forget to remove the <<<<<<< lines, you will commit literal conflict markers into your source code. Always grep for markers before staging on large conflicts: grep -r '<<<<<<<' .git checkout --theirs . and silently deleted a critical API key.Aborting, Retrying, and Preventing Conflicts Before They Happen
Sometimes mid-conflict you realize the merge itself is wrong — the wrong branches were targeted, or you need to pull in new commits before continuing. Git gives you a clean escape hatch: git merge --abort. This rewinds everything back to exactly where you were before you ran the merge. No damage done.
For rebases the equivalent is git rebase --abort. And if you are mid-cherry-pick, git cherry-pick --abort does the same. Always abort cleanly rather than trying to manually reset files — the abort commands guarantee a clean working tree.
Retryin a merge after aborting is as simple as re-running the original merge command, ideally after fetching new changes or after your teammates have confirmed the affected code.
Prevention is worth ten resolutions. The habits that eliminate most conflicts are: merge or rebase from main frequently (at least daily on active branches), keep feature branches short-lived and narrowly scoped, split large files into smaller modules so fewer developers edit the same file, and communicate before refactoring shared utilities. Code ownership boundaries in large teams reduce conflict rates dramatically because they reduce the probability that two people edit the same lines at the same time.
git fetch origin && git rebase origin/main on your feature branch. Teams that do this report conflicts shrinking from multi-hour ordeals to five-minute fixes. The longer you wait, the more divergence accumulates.git merge --abort — never manually reset.git merge-base to predict conflict zones before merging.Post-Merge Verification — Ensuring the Merge Didn't Break Anything
The merge is committed, but you're not done yet. A surprising number of bugs originate from conflict resolutions that looked right but silently broke logic. You need a verification checklist that goes beyond 'it compiles'.
First, diff the merge commit against its first parent: git diff HEAD~1 — this shows every change that came into your branch because of the merge. Look especially at files that were conflicted. If the diff shows lines you didn't expect, you might have chosen the wrong hunk.
Second, run the test suite. But don't stop at green — check the test coverage on the conflicted files. If those files have no tests, that's a red flag. Add a test that exercises the merged code path.
Third, use git log --first-parent to see the merge commit as a single unit. This is especially important in code review — reviewers should not have to dig through the full merge diff if you've already verified it.
Finally, if the merge touched configuration or infrastructure files, run a validation script that checks for required keys or settings. Automate this in CI so no merge goes to production without passing config validation.
- Verify the diff against the first parent — it's the only diff that matters.
- Run tests on the conflicted files specifically, not just the whole suite.
- Check for markers automatically with a script.
- Validate config files with a schema checker.
- Get a second set of eyes on the merge diff before pushing to a shared branch.
Using Git Merge Tools for Complex Conflicts — VS Code, IntelliJ, and vimdiff
Manual editing works for simple conflicts, but when a file has multiple conflict regions across several functions, a three-way merge tool saves time and reduces errors. These tools show three versions simultaneously: the base (common ancestor), the current branch, and the incoming branch. You build the result by picking hunks from either side or editing directly.
VS Code has a built-in three-way merge editor that activates when you open a conflicted file. It shows four panes: base, current, incoming, and result. You can click buttons to accept current, accept incoming, or accept both. IntelliJ IDEA's merge tool works similarly with a cleaner diff view.
Vimdiff is the terminal-based fallback. It splits the terminal into vertical panes. It takes some practice but is invaluable when you're SSH'd into a server or working without a GUI.
To launch a merge tool during a conflict, run git mergetool. Git will automatically open the configured tool for each conflicted file. Configure your preferred tool once: git config --global merge.tool vscode (or intellij, vimdiff, etc.). Some tools need additional command configuration — check the documentation.
The biggest advantage of a merge tool is that you see all three versions at once. You can spot when both branches added different lines near the same region, or when one branch deleted something the other kept. Visual comparison reduces blind mispicks.
git config merge.tool.The Missing Security Header: How --theirs Dropped a Prod Config
git checkout --theirs deployment.yaml to resolve the conflict quickly.git revert -m 1 <merge-commit>. 2. Restored the security header manually. 3. Added a pre-merge Git hook to diff critical config files against the base before finalising any conflict resolution. 4. Documented that --theirs must never be used on deployment-related files.- Never use --theirs on configuration, secrets, or lock files without a full diff review.
- Always run
git diff HEAD~1 -- <file>after a conflict resolution to see exactly what changed. - Set up a pre-merge hook that blocks merges if certain files (e.g., deployment.yaml) are changed by --theirs without explicit approval.
- Automate config validation in CI — catch missing settings before they hit production.
git diff --name-only --diff-filter=U to list all conflicted files. Then open each file and search for <<<<<<< — your editor may have collapsed the markers.git config --global merge.conflictStyle diff3. Then re-create the merge conflict to see the ancestor block. For an existing conflicted file, run git show :1:path to see the base version stored in the index.git diff HEAD~1 to review all changes brought in by the merge. Pay special attention to files that were conflicted — a wrong hunk selection might have broken logic. Then git log --oneline --graph --merges to see if the merge commit is clean.git commit without resolving all conflicts. Run git status to see which files are still marked as conflicted. If you actually resolved them but forgot to stage, run git add <files> then commit again. If you want to abort entirely, use git merge --abort.Key takeaways
git config --global merge.conflictStyle diff3 immediatelygit checkout --ours . or --theirs . (dot = all files) casuallygit fetch && git rebase origin/maingit diff HEAD~1, run tests on conflicted files, and scan for leftover markersCommon mistakes to avoid
5 patternsBlindly using --theirs on all conflicted files
git checkout --theirs . (dot = all files) without reading what you are discarding. Always diff the result with git diff HEAD~1 after a merge commit to verify both sides contributed the right code.Committing conflict markers into the codebase
<<<<<<< HEAD text in production sourcegrep -r '<<<<<<<' --include='.py' --include='.js' --include='*.ts' . && exit 1. Tools like Husky make this trivial to enforce across the team.Running `git merge --continue` instead of `git commit` after resolving a standard merge conflict
git add the files, then run git commit. Use --continue only for rebase and cherry-pick operations. Merge and rebase have different continuation commands because they work differently internally.Not checking for conflicts before starting a long merge
git merge-base to identify overlapping changes before merging. Review the conflict-prone files with your teammate beforehand. This pre-merge inspection turns a multi-hour conflict session into a 10-minute discussion.Forgetting to run tests after a conflict resolution
git diff HEAD~1 to verify the merge diff is what you intended.Interview Questions on This Topic
Walk me through exactly what you do when you hit a merge conflict on a file that two teammates have both heavily modified. What information do you look at before deciding how to resolve it?
git diff --name-only --diff-filter=U. Then I enable diff3 conflict style if it's not already set. For each file, I look at the ancestor version to understand what both sides changed. I check if the changes are additive (both added new lines) or conflicting (both edited the same lines). For additive conflicts, I keep both. For conflicting changes, I check the git log of both branches to understand intent. I use a three-way merge tool for complex files. After resolution, I stage the file, run git diff --cached to verify, and then commit. Finally, I run the test suite focused on the affected modules and get a code review on the merge commit.Frequently Asked Questions
That's Git. Mark it forged?
7 min read · try the examples if you haven't