← Back to DevBytes

Git Merge Conflicts Resolution

What is a Git Merge Conflict?

A merge conflict occurs when Git cannot automatically reconcile differences between two commits during a merge operation. This typically happens when two branches have modified the same line (or adjacent lines) in a file, or when one branch deletes a file while another modifies it. Git pauses the merge and asks you, the developer, to manually resolve the discrepancies.

Behind the scenes, Git tries to combine the snapshots of your current branch (HEAD) and the branch you're merging (MERGE_HEAD). If the changes affect different parts of the same file, Git merges them cleanly. If they overlap, Git marks the conflicting area and leaves it for you to fix.

Why Understanding Merge Conflicts Matters

Conflicts are a normal part of collaborative development. Ignoring them or resolving them incorrectly can introduce bugs, break tests, or lose important work. Mastering conflict resolution lets you integrate features safely, maintain code quality, and keep your team's history coherent. It also reduces fear around merging, encouraging smaller, more frequent integrations.

Anatomy of a Conflict Marker

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

When a conflict arises, Git inserts markers into the affected file to show the conflicting regions. A typical conflict block looks like this:

<<<<<<< HEAD
def greet():
    print("Hello from main branch!")
=======
def greet():
    print("Hello from feature branch!")
>>>>>>> feature-branch

The markers are:

Everything between <<<<<<< and ======= is the version from your current branch (HEAD). The content between ======= and >>>>>>> is the version from the branch you are merging.

Step-by-Step Conflict Resolution

Step 1: Initiate the Merge and Detect Conflicts

Start a merge from your current branch (e.g., main) into a feature branch, or vice versa. If conflicts occur, Git will output a message and pause.

git checkout main
git merge feature-branch
# Auto-merging app.js
# CONFLICT (content): Merge conflict in app.js
# Automatic merge failed; fix conflicts and then commit the result.

Step 2: View the List of Conflicted Files

Use git status to see which files are in conflict. Files listed as both modified need resolution.

git status
# On branch main
# You have unmerged paths.
#   (fix conflicts and run "git commit")
#
# Unmerged paths:
#   (use "git add <file>..." to mark resolution)
#
#         both modified:   app.js
#
# no changes added to commit (use "git add" and/or "git commit -a")

Step 3: Open the Conflicted File

Open the file in your editor. You will see the conflict markers exactly as shown earlier. The goal is to replace the entire block (from <<<<<<< to >>>>>>>) with the final, correct code.

// Inside app.js
<<<<<<< HEAD
console.log("Hello from main branch!");
=======
console.log("Hello from feature branch!");
>>>>>>> feature-branch

Step 4: Choose a Resolution Strategy

You have several options:

Step 5: Edit the File to Resolve

For manual resolution, delete the markers and write the desired code. For example, you might decide to combine both log messages:

// Resolved version
console.log("Hello from main branch!");
console.log("Hello from feature branch!");

If you want to keep only the version from the feature branch, simply remove the HEAD block and the markers, leaving only the feature-branch line. There should be no leftover markers.

Step 6: Mark the File as Resolved

After saving the file, tell Git that the conflict is resolved by staging it with git add.

git add app.js

Run git status again to confirm the file is no longer listed under unmerged paths. It should appear in Changes to be committed.

Step 7: Complete the Merge Commit

Once all conflicted files are staged, finish the merge by committing. Git will supply a default merge commit message.

git commit
# (opens editor with merge message, then save and exit)

Alternatively, you can use git commit -m "Merge feature-branch: resolve conflicts in app.js".

Using Git Quick Commands to Accept One Side

If you want to keep one branch's version entirely for a particular file, you can use:

After running one of these, you still need to stage the file with git add.

# Keep the feature-branch version of the file
git checkout --theirs app.js
git add app.js

Note: --ours and --theirs are reversed during a rebase. In a rebase, --ours refers to the branch you are rebasing onto, while --theirs refers to the commits you are replaying. Always double-check which side is which during a rebase conflict.

Using a Merge Tool

Git integrates with various visual diff tools (like VSCode, Meld, Beyond Compare, or KDiff3). To launch a configured merge tool:

git mergetool

This opens a three-pane view (BASE, LOCAL, REMOTE) for each conflicted file, allowing you to pick changes visually. After saving, the tool automatically stages the file. You can also configure your preferred tool globally:

git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'

Practical Example: Resolving a Real Conflict

Let's simulate a conflict from scratch. We'll create a repository, make divergent changes in two branches, and resolve them.

# Setup
mkdir conflict-demo && cd conflict-demo
git init
echo "function calculateTotal(price, tax) {" > util.js
echo "  return price + tax;" >> util.js
echo "}" >> util.js
git add util.js && git commit -m "Initial implementation"

# Create feature branch and modify the same function
git checkout -b feature/add-discount
# Edit util.js to apply discount
sed -i 's/return price + tax;/return (price - discount) + tax;/' util.js
# Also add a 'discount' parameter
sed -i 's/function calculateTotal(price, tax)/function calculateTotal(price, tax, discount)/' util.js
git add util.js && git commit -m "Add discount parameter"

# Switch back to main and modify the function differently
git checkout main
# Edit util.js to add currency conversion
sed -i 's/return price + tax;/return (price + tax) * exchangeRate;/' util.js
sed -i 's/function calculateTotal(price, tax)/function calculateTotal(price, tax, exchangeRate)/' util.js
git add util.js && git commit -m "Add exchange rate support"

# Merge feature branch into main – CONFLICT!
git merge feature/add-discount

Now util.js contains conflict markers. Let's inspect it:

<<<<<<< HEAD
function calculateTotal(price, tax, exchangeRate) {
  return (price + tax) * exchangeRate;
=======
function calculateTotal(price, tax, discount) {
  return (price - discount) + tax;
>>>>>>> feature/add-discount

We need to combine both features: discount and exchange rate. We'll edit the file manually:

// Resolved version
function calculateTotal(price, tax, discount, exchangeRate) {
  return ((price - discount) + tax) * exchangeRate;
}

After saving, stage and commit:

git add util.js
git commit -m "Merge feature/add-discount: combine discount and exchange rate"

The merge is complete. Verify with git log --oneline and git status.

Best Practices for Avoiding and Handling Conflicts

Conclusion

Git merge conflicts are a natural part of collaborative software development. By understanding conflict markers, following a structured resolution workflow, and leveraging Git's built-in helpers and merge tools, you can turn a potentially frustrating situation into a controlled, routine process. The key is to stay calm, read the markers carefully, and always test the final result. With practice and the best practices outlined here, you'll be able to resolve conflicts quickly, maintain a clean history, and keep your team moving forward without fear of merges.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles