A merge conflict is an event that occurs when Git is unable to automatically resolve differences in code between two commits. Despite following best practices, over the course of your career you will inevitably run into merge conflicts. Learning how to effectively resolve them is a crucial skill for any developer working with Git.
Understanding merge conflicts
Merge conflicts happen when two branches have been changed in conflicting ways. This usually occurs in collaborative environments where multiple developers are working on the same codebase. These conflicts arise when performing operations like git merge
, git rebase
, and git cherry-pick
, as Git doesn’t automatically know which version of the code to accept.
Common Operations Leading to Merge Conflicts:
Merging: When merging two branches, conflicts might occur if the same lines of code have been altered differently in each branch.
Rebasing: Similar to merging, rebasing can cause conflicts if the commits being rebased have conflicting changes with the new base.
Cherry-picking: Applying a commit from one branch to another with
git cherry-pick
can also lead to conflicts if that commit has conflicting changes with the current branch.
How to resolve Git merge conflicts
When you encounter a conflict, Git will pause the operation requiring a resolution. It will mark the file as conflicted and insert conflict markers into the file to visually show the conflicting changes.
Identifying conflicts
Git will notify you of a conflict during a merge, rebase, or cherry-pick operation.
Use
git status
to list all files with conflicts.
Conflict markers
Conflicted files will contain sections marked like this:
<<<<<<< HEAD// Code as it is on your local machine=======// Conflicting code from the upstream remote branch>>>>>>> feature/upstream-branch-name
- The
=======
line divides your changes (HEAD
) from the changes in the upstream branch (feature/upstream-branch-name
).
Resolving Conflicts
Manual Resolution: Open the conflicted file(s) in your code editor. Decide which version of changes to keep, or pick and choose from both versions, manually merging them together. After editing, remove the conflict markers.
Using an IDE: many IDEs support version control management including, merge conflict resolution. Each editor in the Jetbrains suite for example provides a specific merge conflict resolution view that highlights each conflict in your file across versions. You can then browse through these files line by line, accepting or rejecting changes by version.
After resolving the conflicts, you need to add the files to stage them for commit and then continue the operation that was interrupted by the conflict.
For a merge:
git add .git commit -m "Resolve merge conflicts between "
For a rebase:
git add .git rebase --continue
Best Practices for handling merge conflicts
Prevention is best: Keep your branches short-lived and merge them frequently to minimize conflicts. Always follow best pull request practices.
Understand the conflict: Take the time to understand why the conflict occurred. This understanding can prevent similar conflicts in the future.
Communicate: If the conflict involves changes made by others, discuss it with them. If possible it’s best to avoid making changes to the exact same lines of code that someone else is currently working on.
Use a consistent coding style: Many conflicts arise from formatting differences. Using tools like linters and formatters can prevent these types of conflicts.
Regularly fetch and merge: Stay updated with changes in your repository to avoid large, complex conflicts.
Example: Resolving a simple merge conflict
One common place merge conflicts arise is differences in dependency versions. Suppose you have a conflict in a config.json
file related to different configurations set in two different branches.
Original conflict markers in config.json
:
<<<<<<< HEAD{"name": "app","version": "1.0.0","dependencies": {"libraryA": "^2.0.0"}======={"name": "app","version": "1.0.1","dependencies": {"libraryA": "^2.1.0","libraryB": "^3.0.0"}>>>>>>> 03-19-chore_update_dependencies
Resolved config.json
:
The next step is to decide which changes to keep. If we want to keep the upgrade to version 1.0.1
and includeLibrary B
we’ll accept that change. In this case the resolved config.json
would look like this:
{"name": "app","version": "1.0.1","dependencies": {"libraryA": "^2.1.0","libraryB": "^3.0.0"}}
The the version version was updated and the dependencies were merged from both branches. Make sure to ensure the JSON format is valid after resolving conflicts, as incorrect syntax can lead to errors in your application.
By following these guidelines and practices, you can navigate and resolve merge conflicts in Git more effectively, maintaining a clean and efficient workflow within your projects.