Welcome back, intrepid developer! In our previous chapters, we learned the magic of branching – how to create separate lines of development to work on features or fixes without disturbing the main codebase. We even touched upon merging, bringing those separate lines back together. But what happens when two brilliant minds (or even one mind working on two branches!) make conflicting changes to the exact same part of the same file?
That, my friend, is where merge conflicts come in. They might sound scary, but they’re a completely normal and expected part of collaborative development. This chapter is your trusty guide to understanding why conflicts happen, how Git tells you about them, and most importantly, how to confidently resolve them. By the end, you’ll not only fix conflicts but understand the underlying logic, turning a potential headache into a simple puzzle.
Ready to become a conflict resolution champion? Let’s dive in!
What Exactly is a Merge Conflict?
Imagine you and a colleague are both working on the same recipe file. You decide to change the amount of sugar from “1 cup” to “3/4 cup” for a healthier version. At the exact same time, your colleague, also trying to improve the recipe, changes the sugar amount to “1.5 cups” for extra sweetness. When you both try to submit your changes, Git, being a smart but not psychic tool, sees two different instructions for the same line. It doesn’t know which one to pick!
This exact scenario is a merge conflict. Git is designed to automatically merge changes when they don’t overlap. If you change line 5 and your colleague changes line 10 in the same file, Git can happily combine those. But when changes occur on the same lines or very close to each other, Git pauses and asks for your human intervention. It says, “Hey, I found a discrepancy here, and I need you to tell me which version is correct, or how to combine them.”
Conflicts are a sign of active development and collaboration, not a mistake! They are Git’s way of ensuring no code is accidentally overwritten and that the final merged version is exactly what the developers intend.
Setting the Stage: Creating Our First Conflict
To learn how to resolve conflicts, we first need to create one. This will give us a hands-on feel for the process. We’ll simulate a simple scenario with two “developers” (you, switching between branches).
First, let’s make sure we’re in a clean Git repository. If you’re following along from previous chapters, you might have one ready. If not, let’s quickly set one up:
Create a new directory and initialize Git:
mkdir git-conflict-demo cd git-conflict-demo git initWhat’s happening?
mkdir git-conflict-demo: Creates a new folder namedgit-conflict-demo.cd git-conflict-demo: Changes your current directory into this new folder.git init: Initializes an empty Git repository in this folder. This creates a hidden.gitdirectory where Git stores all its version control magic.
Create an initial file and commit it to our
mainbranch:echo "Hello, Git World!" > hello.txt echo "This is the initial version of our file." >> hello.txt git add hello.txt git commit -m "Initial commit: Add hello.txt"What’s happening?
echo "..." > hello.txt: Creates a new filehello.txtand puts the first line of text into it.echo "..." >> hello.txt: Appends the second line of text tohello.txt.git add hello.txt: Stageshello.txtfor the next commit.git commit -m "...": Records the changes in the staged file as a new commit with a descriptive message. Ourmainbranch now has its first commit.
Now, let’s simulate two parallel changes.
Create a new branch for “Feature A”:
git checkout -b feature-aWhat’s happening?
git checkout -b feature-a: Creates a new branch namedfeature-aand immediately switches your working directory to that branch. Any changes you make now will be onfeature-a.
Make a change on
feature-a:Open
hello.txtin your favorite text editor (like VS Code, Sublime Text, or even Notepad) and change the second line.Original
hello.txtonfeature-a:Hello, Git World! This is the initial version of our file.Change it to:
Hello, Git World! This is the awesome version for Feature A.Save the file.
Commit the change on
feature-a:git add hello.txt git commit -m "Feature A: Updated file content"What’s happening?
- We’ve now committed a change to
hello.txtspecifically on thefeature-abranch.
- We’ve now committed a change to
Switch back to
mainand make a conflicting change:git checkout mainWhat’s happening?
git checkout main: Switches your working directory back to themainbranch. Notice thathello.txtreverts to its original state before thefeature-achanges. This is the power of Git!
Now, open
hello.txtagain while on themainbranch.Original
hello.txtonmain:Hello, Git World! This is the initial version of our file.Change the same second line to:
Hello, Git World! This is the main branch's important update.Save the file.
Commit the change on
main:git add hello.txt git commit -m "Main: Important update for the file"What’s happening?
- We now have two distinct commit histories. Both
mainandfeature-ahave modified the same line ofhello.txtsince their common ancestor. This is the perfect recipe for a conflict!
- We now have two distinct commit histories. Both
The Moment of Collision: Encountering a Conflict
Now for the grand finale – let’s try to merge feature-a into main.
git merge feature-a
You should immediately see output similar to this:
Auto-merging hello.txt
CONFLICT (content): Merge conflict in hello.txt
Automatic merge failed; fix conflicts and then commit the result.
Whoa! Git explicitly tells us: CONFLICT (content): Merge conflict in hello.txt. This is Git’s way of saying, “Hold on, human! I need your help here.” The merge process has been paused, and it’s waiting for you to resolve the conflict.
Understanding Conflict Markers
When a conflict occurs, Git modifies the conflicted file (hello.txt in our case) by adding special markers to highlight the conflicting sections. Let’s open hello.txt again and see what Git has done:
Hello, Git World!
<<<<<<< HEAD
This is the main branch's important update.
=======
This is the awesome version for Feature A.
>>>>>>> feature-a
These are conflict markers, and understanding them is key to resolution:
<<<<<<< HEAD: This marks the beginning of the conflicting changes from your current branch (the one you are merging into), which ismainin this case.HEADalways refers to the tip of your current branch.=======: This is the separator. Everything above it (<<<<<<< HEADto=======) represents the changes from your current branch. Everything below it (=======to>>>>>>> feature-a) represents the incoming changes from the branch you are merging.>>>>>>> feature-a: This marks the end of the conflicting changes from the branch you are merging from (feature-a).
So, Git presents both versions of the conflicting code, neatly sectioned off, and leaves it to you to decide the final version.
Resolving the Conflict: Step-by-Step
Your task is to edit hello.txt, remove all conflict markers (<<<<<<<, =======, >>>>>>>), and leave only the code you want to keep.
Let’s say for this exercise, we want to combine both ideas: we want the “awesome version” but also acknowledge it’s an “important update.”
Edit the conflicted file:
Open
hello.txtagain. You’ll see the conflict markers.Hello, Git World! <<<<<<< HEAD This is the main branch's important update. ======= This is the awesome version for Feature A. >>>>>>> feature-aModify the file to the desired state. For example, let’s keep the “awesome version” and add a note about the “important update”:
Hello, Git World! This is the awesome version for Feature A, and it includes the main branch's important update.Crucially, ensure all
<<<<<<<,=======, and>>>>>>>lines are completely removed.Save the file.
Tell Git the conflict is resolved by staging the file:
After saving the merged file, you need to tell Git that you’ve finished resolving the conflict. You do this by staging the file, just like any other change.
First, let’s check the
git statusto see the conflict state:git statusYou’ll see something like:
On branch main You have unmerged paths. (fix conflicts and run "git commit") (use "git merge --abort" to abort the merge) Unmerged paths: (use "git add <file>..." to mark resolution) both modified: hello.txt no changes added to commit (use "git add" and/or "git commit -a")Git clearly states
Unmerged pathsand suggestsuse "git add <file>..." to mark resolution. Let’s do that:git add hello.txtNow, check
git statusagain:git statusOutput:
On branch main All conflicts fixed but you are still merging. (use "git commit" to conclude merge) Changes to be committed: modified: hello.txtGreat! Git now knows you’ve handled the conflict in
hello.txt.Commit the merge:
The final step is to commit the merge. Git will pre-populate a commit message for you, which is usually quite descriptive and includes the branches involved in the merge.
git commitThis will open your default text editor (like Vim, Nano, or your configured Git editor) with a pre-filled commit message:
Merge branch 'feature-a' # Conflicts: # hello.txt # # It looks like you may be committing a merge. # If this is not correct, please remove the file # .git/MERGE_MSG # and try again. # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch main # All conflicts fixed but you are still merging. # # Changes to be committed: # modified: hello.txt #You can accept this default message or modify it to add more context about how you resolved the conflict. For now, let’s just save and exit the editor (e.g., in Vim, type
:wqand press Enter).After committing, you’ll see output like:
[main <some_hash>] Merge branch 'feature-a'Congratulations! You have successfully resolved your first merge conflict!
You can verify the history with
git log --oneline --graph:git log --oneline --graphYou should see a clear merge commit, showing the two branches coming back together.
Mini-Challenge: Double the Trouble!
Let’s try another conflict, this time with a slightly different scenario.
Ensure you’re on
mainandfeature-ais merged.git checkout main(You should already be on
main).Create a new branch
feature-b:git checkout -b feature-bAdd a new line to
hello.txtonfeature-b: Openhello.txtand add a third line.Hello, Git World! This is the awesome version for Feature A, and it includes the main branch's important update. This line is for Feature B.Save and commit:
git add hello.txt git commit -m "Feature B: Added new line"Switch back to
mainand add a different new line at the same position:git checkout mainOpen
hello.txtand add a third line, but make it different fromfeature-b.Hello, Git World! This is the awesome version for Feature A, and it includes the main branch's important update. This line is for the main branch's new feature.Save and commit:
git add hello.txt git commit -m "Main: Added another new line"Attempt to merge
feature-bintomain:git merge feature-bChallenge: Resolve this conflict independently.
- Hint: Remember the conflict markers! Decide which version of the third line you want (or combine them) and remove the markers. Then
git addandgit commit. - What to observe/learn: You’ll see that conflicts can happen even with new lines if they try to occupy the same logical space (e.g., both branches added a “third line” to a file that only had two). The process of resolution remains the same.
- Hint: Remember the conflict markers! Decide which version of the third line you want (or combine them) and remove the markers. Then
Common Pitfalls & Troubleshooting
Merge conflicts are part of Git’s safety net, but it’s easy to stumble while learning to navigate them. Here are a few common pitfalls and how to troubleshoot them:
Forgetting to
git addafter resolving the file:- Pitfall: You meticulously edit the file, remove all conflict markers, save it, and then immediately try to
git commit. Git will complain, saying “no changes added to commit (use ‘git add’ and/or ‘git commit -a’)”. - Troubleshooting: Git needs you to explicitly tell it that the conflict is resolved and the file is ready. Always run
git add <conflicted-file>after editing and saving it.git statusis your best friend here, always showing you the current state of the merge.
- Pitfall: You meticulously edit the file, remove all conflict markers, save it, and then immediately try to
Accidentally discarding changes or accepting the wrong version:
- Pitfall: In the heat of resolution, you might accidentally delete a crucial line, or blindly accept
HEADwhen you really needed the incoming changes (or vice-versa). - Troubleshooting:
- During resolution: If you realize your mistake before committing the merge, you can simply re-edit the file. If you want to completely restart the conflict resolution for a specific file, you can use
git checkout --ours <file>to revert to your current branch’s version, orgit checkout --theirs <file>to revert to the incoming branch’s version. Be careful: these overwrite the file with one version, potentially losing combined work. - Aborting the entire merge: If you’re completely lost and want to start over, you can abort the entire merge operation:
git merge --abort. This will revert your repository to the state it was in before you started the merge, discarding any conflict resolutions you’ve made. - After committing: If you’ve already committed the merge and realize you made a mistake, you’ll need to use
git revert <merge-commit-hash>to undo the merge, or potentiallygit reset --hard <previous-commit-hash>(usegit reset --hardwith extreme caution, especially in shared repositories, as it rewrites history).
- During resolution: If you realize your mistake before committing the merge, you can simply re-edit the file. If you want to completely restart the conflict resolution for a specific file, you can use
- Pitfall: In the heat of resolution, you might accidentally delete a crucial line, or blindly accept
Confusion between
HEADand the incoming branch:- Pitfall: It’s easy to forget which side of the
=======belongs toHEAD(your current branch) and which belongs to the merging branch. - Troubleshooting: Always remember:
<<<<<<< HEADto=======is YOURS (the branch you’re currently on, which you’re merging into).=======to>>>>>>> <branch-name>is THEIRS (the branch you’re merging from).
- Take a moment to read the code in each section and understand its origin before making a decision.
- Pitfall: It’s easy to forget which side of the
Summary
Phew! You’ve just navigated the thrilling world of merge conflicts and emerged victorious. Let’s recap what you’ve learned:
- Merge conflicts occur when Git cannot automatically reconcile diverging changes to the same lines of code in different branches.
- Git pauses the merge process and marks the conflicted files with
<<<<<<< HEAD,=======, and>>>>>>> <branch-name>to show you the conflicting sections. - To resolve a conflict, you must manually edit the conflicted file(s), remove all conflict markers, and leave only the desired final code.
- After editing, you use
git add <file>to stage the resolved file, signaling to Git that you’ve fixed it. - Finally, you complete the merge with a
git commit, which records the resolution as a new merge commit in your history. git statusis your constant companion during conflict resolution, guiding you on the next steps.- If things go sideways,
git merge --abortis your escape hatch to restart the merge.
Resolving conflicts is a fundamental skill in collaborative development. The more you practice, the more intuitive it becomes. Don’t be afraid of them; embrace them as an opportunity to ensure your codebase stays clean and correct!
In our next chapter, we’ll dive deeper into another powerful Git reordering technique: Rebasing. Get ready to learn how to rewrite history (carefully!) for a cleaner, linear project history.
References
- Git Official Documentation: Basic Merge Conflicts
- GitHub Docs: Resolving a merge conflict on GitHub
- Atlassian Git Tutorial: Resolving a merge conflict
- GitLab Docs: Resolve a merge conflict
This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.