Resolving Merge Conflicts: Strategies and Tools
AI generated
git
HEAD
Git · Merge Conflicts · Rebase · Version Control
Resolving Merge Conflicts: Strategies and Tools
Understanding conflict markers, using merge tools, avoiding chaos

Merge conflicts are part of everyday life for every Git team and scare off many developers unnecessarily. This article explains how to read conflict markers, when a graphical merge tool like VS Code, Meld or PhpStorm pays off, how git rerere resolves recurring conflicts automatically, and which strategies help teams reduce the number of conflicts in feature branches from the start.

14 min read Merge Conflicts git rerere Merge Tools

1. Why merge conflicts happen and when Git cannot auto-merge

Git resolves most merges without human intervention, because the three-way merge algorithm compares the changes of two branches against a shared ancestor commit and combines them line by line, as long as the changed regions do not overlap. A merge conflict only arises when both branches have changed the same line or the same code block differently and Git cannot make an unambiguous decision about which version is valid.

Besides overlapping text changes, structural conflicts also count: one branch deletes a file while the other keeps editing it; both branches rename the same file differently; or a file is created in the same location once as a directory and once as a regular file. Git reports such cases as conflicts too, even when not a single character in the text content collides. The cause then lies in the directory structure, not in the line content, which means git status needs to be read more carefully in these cases than for a classic content conflict.

2. Anatomy of conflict markers: reading <<<<<<<, =======, >>>>>>>

As soon as Git cannot resolve a conflict automatically, it writes both competing versions directly into the affected file and marks them with special character sequences. <<<<<<< HEAD marks the start of the current state on your own, checked-out branch. The separator ======= divides this section from the incoming change. >>>>>>> branch-name marks the end and names the branch or commit that brings in the change.

It is important to understand: these markers are valid file content, not comments. A PHP linter or compiler reports a syntax error on unresolved markers, because <<<<<<< HEAD is simply invalid code. Committing a file without removing all markers risks a broken build. A quick grep -rn '<<<<<<<' . before every commit after a merge reliably catches forgotten markers before they reach the CI pipeline.


<?php
// getDiscountedPrice() after a merge conflict: both branches changed the method
class PriceCalculator
{
    public function getDiscountedPrice(float $price, float $percentage): float
    {
<<<<<<< HEAD
        // Local change: round the discount to two decimal places
        $discount = round($price * ($percentage / 100), 2);
        return $price - $discount;
=======
        // Incoming change: enforce a minimum discount of 0 EUR
        $discount = $price * ($percentage / 100);
        return max(0.0, $price - $discount);
>>>>>>> feature/minimum-discount
    }
}

3. Resolving conflicts manually in a text editor, step by step

Anyone resolving conflicts manually opens the file Git marks as "both modified," visible via git status, and decides for each conflict block which code stays: their own version, the incoming version, a combination of both, or an entirely new, third solution. It is crucial to fully remove all three marker lines, not just the content between them. An overlooked leftover ======= leads to a syntax error that often only surfaces in the CI pipeline, not locally when saving.

After editing, git add <file> marks the file as resolved, regardless of whether changes were actually made. Git tracks this status per file. Only once git status no longer lists any files under "unmerged paths" can the merge be completed with git commit. For extensive conflicts, running git diff without arguments during an ongoing merge helps, because it automatically uses a diff mode that shows both conflict sides against the common ancestor, making the actual change clearer than the raw file content with markers.

4. Using a graphical merge tool: VS Code's 3-way merge editor

VS Code automatically detects conflict markers when opening a file and shows inline buttons above every conflict block: "Accept Current Change," "Accept Incoming Change," "Accept Both Changes" and "Compare Changes." For more complex cases, the built-in 3-way merge editor opens a three-column view with your own state on the left, the incoming change on the right, and the result in the middle, while the common ancestor is optionally shown as a fourth reference column.

The big advantage over pure text editing: syntax highlighting stays intact in all three columns, and changes can be adopted with a click instead of manual copy-paste, which avoids mistakes especially in deeply nested PHP classes or long merge conflicts in layout XML files. After resolving all blocks, VS Code shows a summary of open conflicts in the source control sidebar before the commit button even becomes clickable, an extra safety net against forgotten markers.

5. Meld and the PhpStorm merge dialog, configuring git mergetool

Meld is a standalone, cross-platform diff and merge tool using the same three-column logic as VS Code, but is usually invoked via git mergetool from the terminal instead of directly inside an editor. PhpStorm ships its own merge dialog that appears automatically on a conflict during git pull or git merge, and in addition to the pure three-way view shows a live preview of the resulting code with full PHP syntax checking, an advantage over pure text editors that cannot detect a syntactically invalid intermediate state.

For git mergetool to launch the desired program, it must be registered in the global or project-local .gitconfig. Without configuration, Git interactively asks which tool to use on every invocation, which is impractical in scripts or CI environments. After every resolved conflict, git mergetool creates a *.orig backup file by default, which can be disabled with mergetool.keepBackup = false.


; ~/.gitconfig: register merge tools and set default behavior
[merge]
    tool = meld

[mergetool "meld"]
    cmd = meld "$LOCAL" "$BASE" "$REMOTE" --output "$MERGED"
    trustExitCode = true

[mergetool "phpstorm"]
    cmd = phpstorm merge "$LOCAL" "$REMOTE" "$BASE" "$MERGED"
    trustExitCode = true

[mergetool]
    keepBackup = false
    prompt = false

6. Conflicts in git merge, git rebase and git cherry-pick: what differs

With git merge, a conflict occurs exactly once, because Git combines the entire history of both branches into a single merge commit. All conflict blocks of a file appear bundled in one pass. With git rebase, by contrast, Git replays each commit of the rebased branch individually onto the new base commit, which means the same logical conflict can reoccur across several consecutive commits, especially unpleasant with long commit chains that overlap in changes to the same file.

git cherry-pick behaves like a mini-rebase of a single commit when it comes to conflicts: the chosen commit is applied against the current HEAD, and a conflict only affects that one commit's content, not the entire branch history. In all three cases, the resolution step stays identical, edit the file, git add, then continue, but the continuation command differs: git commit for merge, git rebase --continue for rebase, git cherry-pick --continue for cherry-pick.


# Start an interactive rebase that can cause conflicts across several commits
git rebase main

# Git stops at the first conflict and shows the affected commit
# CONFLICT (content): Merge conflict in src/Model/PriceCalculator.php

# Edit the file in an editor or merge tool, then mark it as resolved
git add src/Model/PriceCalculator.php

# Apply the next commit in the rebase sequence
git rebase --continue

# If the same conflict occurs again on another commit: repeat
git status
git add src/Model/PriceCalculator.php
git rebase --continue

7. Aborting safely: git merge --abort, git rebase --abort, git cherry-pick --abort

Not every started conflict has to be resolved to the end. Anyone who notices the wrong branch was merged, or that the resolution is too complex for the current moment, can abort cleanly at any time. git merge --abort resets the working area exactly to the state before the git merge command, as long as no manual commit exists yet. The same principle applies to git rebase --abort, which restores the original branch tip and discards all commits from the rebase sequence already applied successfully.

git cherry-pick --abort works identically for an ongoing cherry-pick, while git cherry-pick --quit ends the conflict state without resetting the working area, useful when partial changes have already been adopted manually. A common mistake is reaching for git reset --hard instead of --abort: reset does not know about the merge or rebase state and can leave inconsistent intermediate states behind, while the specific abort commands always restore the exact correct prior state.


# Adopt a single commit from a hotfix branch into main
git cherry-pick a1b2c3d

# Conflict in exactly this one commit
# error: could not apply a1b2c3d... Fix null pointer in checkout flow

# Resolve the conflict manually or with a merge tool, then continue
git add src/Controller/CheckoutController.php
git cherry-pick --continue

# If it turns out the commit does not fit after all: abort cleanly
git cherry-pick --abort

8. Preventing conflicts from the start: rebasing, small PRs, feature flags

The most effective conflict prevention happens long before the actual merge: anyone who brings their own feature branch up to date with main daily via git pull --rebase or git fetch && git rebase origin/main always works with a small, manageable diff instead of weeks of accumulated, overlapping changes. The longer a branch lives without being synced with main, the more exponentially likely and complex later conflicts become.

Small, focused pull requests additionally reduce the surface area for conflicts, because they change fewer files and fewer lines at once. A PR with 50 changed lines collides less often than one with 2,000. Feature flags solve the problem at a structural level: instead of developing a large feature branch separately from main for weeks, the new code is merged into main directly, but disabled behind a flag. That keeps the history linear and short, and conflicts arise against a constantly up-to-date main branch instead of an outdated divergence point.

9. git rerere: recording conflict resolutions and replaying them automatically

git rerere (reuse recorded resolution) remembers how a particular conflict was resolved once and applies the same resolution automatically as soon as the same conflict occurs again, typically when repeatedly rebasing a long-lived feature branch against main, where the same conflict spots pop up again on every rebase. The feature is enabled project-wide with git config rerere.enabled true, after which Git records every manually resolved conflict in the .git/rr-cache directory.

On a repeated, identical conflict, Git applies the recorded resolution automatically and marks the file with "Resolved by 'rerere'" in git status. A quick check with git diff is still worthwhile before committing the result. rerere is especially valuable for recurring conflicts in generated files like composer.lock or in translation files, where the same manual resolution would otherwise have to be redone by hand on every rebase.


# Enable rerere globally so resolutions are recorded permanently
git config --global rerere.enabled true
git config --global rerere.autoupdate true

# Start a rebase with a known, recurring conflict
git rebase main

# Resolve the conflict manually, once
git add composer.lock
git rebase --continue

# On the next rebase with the same conflict: rerere applies the resolution automatically
git rebase main
# Resolved 'composer.lock' using previous resolution.

# Inspect recorded resolutions
git rerere diff

The overview below summarizes which habits tend to cause conflicts and which practices have proven themselves in Git teams instead.

Area Conflict-prone practice Conflict-reducing practice Tool / command
Branch lifespan Weeks-long feature branch without syncing Daily rebase onto main git pull --rebase
PR size 2,000+ lines in one PR Focused PRs under 400 lines Smaller, more frequent commits
Conflict resolution Resolving blindly without context Reusing a recorded resolution git config rerere.enabled true
Divergence strategy Long branch running parallel to main Feature flags directly in main Feature flag system
Uncertainty during a conflict git reset --hard as a workaround Using the correct abort command git merge/rebase --abort

In practice these habits reinforce each other: short, frequently synced branches produce smaller conflicts that resolve faster, and every conflict resolved once is thanks to rerere never handled manually again. Teams that consistently apply all five points spend, in practice, only a fraction of the time on conflict resolution compared to teams with long-lived, unsynced branches.

Mironsoft

Git workflows, code reviews and merge strategies for Magento teams

Get merge conflicts under professional control?

We analyze your Git workflow, set up merge tools and rerere configuration for the whole team, and establish branch strategies that reduce conflicts in Magento and Hyva projects from the start.

Git Workflow Audit

Analysis of branch strategy, PR size and conflict frequency

Merge Strategy Consulting

Introducing feature flags, trunk-based development and rebase policies

Team Onboarding & Training

Establishing merge tools, git rerere and safe aborting across the team

10. Summary

Merge conflicts are not a sign of faulty work, they are a normal consequence of parallel development that can be resolved quickly and safely with the right tools and habits. Anyone who has internalized the meaning of the conflict markers <<<<<<<, ======= and >>>>>>> can confidently decide which code stays, both in a text editor and in graphical merge tools like VS Code, Meld or PhpStorm. The difference between conflicts in git merge, git rebase and git cherry-pick determines how often a conflict can reoccur during an operation and which command continues or safely aborts it.

In the long run, prevention pays off more than repair: rebasing onto main frequently, small pull requests and feature flags structurally reduce the number and complexity of conflicts. Where conflicts still repeat, for example in generated files like composer.lock, git rerere automatically applies the resolution worked out once, saving teams noticeable time on every further rebase.

Resolving Merge Conflicts, the essentials at a glance

Reading conflict markers

<<<<<<< HEAD is your own state, ======= separates, >>>>>>> marks the incoming change. All three lines must be removed.

Using merge tools

VS Code, Meld and PhpStorm offer 3-way views. Configure git mergetool in your .gitconfig.

Aborting safely

git merge/rebase/cherry-pick --abort always restores the correct prior state, never use reset --hard.

Preventing & automating

Frequent rebasing, small PRs, feature flags and git rerere reduce conflicts structurally.

11. FAQ: Resolving Merge Conflicts

1What do the conflict markers <<<<<<<, ======= and >>>>>>> mean in a file?
<<<<<<< HEAD marks your own state, ======= separates it from the incoming code, >>>>>>> branch-name marks the end and the source. All three lines must be removed before committing.
2How do I resolve a merge conflict manually in a text editor?
Open the file, decide for each block, remove all marker lines, mark it resolved with git add and complete with git commit once git status shows no unmerged paths.
3Which merge tool works best for Magento developers?
VS Code for fast, editor-native resolution with inline buttons. PhpStorm with live PHP syntax checking. Meld as a lean, standalone terminal tool.
4How do I configure git mergetool for VS Code, Meld or PhpStorm?
In .gitconfig set the default tool under [merge] and define the invocation command with $LOCAL, $BASE, $REMOTE, $MERGED in the matching [mergetool "name"] block.
5What is the difference between conflicts in git merge and git rebase?
With merge a conflict occurs once for all affected files. With rebase each commit is applied individually, the same conflict can occur repeatedly in sequence.
6How do I safely abort a merge or rebase?
git merge --abort or git rebase --abort reset the working area exactly. For cherry-picks use git cherry-pick --abort. Never use reset --hard as a substitute.
7What does git rerere do and how do I enable it?
Saves conflicts resolved once and automatically applies the same resolution when it recurs. Enable with git config --global rerere.enabled true.
8How do I prevent merge conflicts from the start?
Sync daily via git pull --rebase with main, keep PRs small and replace long-running branches with feature flags directly in main.
9Why does git reset --hard cause problems during an ongoing merge?
Reset does not know about the merge or rebase state and can leave inconsistent intermediate states. The --abort commands are built exactly for this state.
10Does git cherry-pick resolve conflicts differently than a normal merge?
Yes, cherry-pick applies only the content of a single commit, a conflict affects only that commit. Continue with --continue, abort with --abort.