Skip to content

17.1 -- Git Conflicts

Git conflicts

Git conflicts arise when changes to a repo are committed from two or more different locations. This is usually a result of two developers collaborating on the same code, but it can also arise when a single person is comitting changes to a repo, for example, if they commit changes both locally on their laptop and remotely through the GitHub website.

What does a conflict look like?

Let's walk through an example using the repo https://github.com/hackers-test/hack-program. To keep things simple for now, we will make a conflict arise in the README file, caused by making a change to the same line both on my laptop and on GitHub.

First, I opened my browser to the repo linked to above and clicked on the pen icon on the README file to edit it directly online. On the third line of the README I then entered the text "Committed from GitHub" and committed the change.

Next, I opened my local clone of the repo in my terminal. Here I also made a change to the same README.md file, entering on the third line the text "Committed from local". I then committed and tried to push the change.

git add README.md
git commit -m "..."
git push
(base) 
To https://github.com/hackers-test/hack-program.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'https://github.com/hackers-test/hack-program.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

As you can see, we cannot push to the remote repo because it is currently one or more commits ahead of our current branch. This actually doesn't yet mean that there is a conflict, but it means that we need to check for a conflict by following the instructions, which tell us to perform a git pull.

git pull

Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.

This tells us that there is a conflict, and that is occurs in the README.md file, and tells us that we need to fix it. So how do we do that?

Resolving conflicts

When you made the git pull request above you accepted the changes from the remote main branch into your local main branch. Often people might create a separate branch in which to pull these changes to ensure that you do not mess up your main. Here we're taking a lazier approach. The message above told us which files contain conflicts, so let's take a look at that file. What we will find is a conflict represented by text that has been inserted into our code like the following:

<<<<<<< HEAD
Committed by Local.
=======
Committed by GitHub.
>>>>>>> af4e39e325cf414f268a0b75f41e1ff72a29322b

To understand this we first need to revisit some naming conventions from git. The term HEAD refers to the latest committed state of your current branch. Thus, anything in the section between <<<<<<< HEAD and ======= represents the code that was entered in our local repo. Alternatively, the code between ====== and >>>>>>> ... is code that was entered on the remote -- the code we pulled in from somewhere else. The name of that commit is much uglier, in this case af4e39e325cf414f268a0b75f41e1ff72a29322b. This is just a random hash value that is a unique signature of that specific commit. As you can see then, the format of a file that contains a conflict is to show both versions of the code separated by the ======= delimiter. Your job as the resolver is to select which version of the code you want to keep.

To do this you can simply open the file in your favorite text editor. You must remove the lines with arrows and the delimiter, and then leave just the code you want to keep. For this you have the option to select the code from one commit or the other; or, you can remove both of their commits and enter your own new solution. That's we will do here. I replaced this entire bloock between the arrowed commits with the line below showing that both users committed to this line:

Committed by Local & GitHub.

Finally, you can now push your changes and they are ensured not to conflict with any other changes on the remote, since you already pulled in and merged all of the changes that are present on the remote.

git add README.md
git commit -m "resolved conflict"
git push origin main

When there is NO conflict?

Although conflicts can arise, they are not inevitable. Two users can edit code at the same time and as long as they do not edit the exact same lines of code their commits can be merged without any conflicts at all. As an example, let's say you accepted the pull request from another user on GitHub and merged their commits into main. Then, like in the example above, you later try to push some new commits from your local branch. Again, git will tell you that you cannot push because the remote is ahead of you, and so you need to first do a git pull. When you do the git pull this time, because there are no conflicts, it will simply print a summary for you showing which files have been changed:

remote: Enumerating objects: 16, done.
remote: Counting objects: 100% (16/16), done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 12 (delta 6), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (12/12), 2.94 KiB | 601.00 KiB/s, done.
From github.com:eaton-lab/hack-the-planet
   df5c59a..c449dfd  master     -> origin/master
Merge made by the 'recursive' strategy.
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

Your files have been updated and no conflict characters (e.g., >>>>) were inserted, only the merged commits.

Other places conflicts arise

In our first example a conflict was identified when we tried to perform a git push from local, where git informed us that the remote already had merged commits that would cause a conflict. In other words, the other user beat us to the punch and made changes first. But what if it had been the other way around, and we pushed commits from local before the other user made their pull request. In that case both users will see a message on GitHub telling them that the new pull request contains conflicts and cannot be merged until they are resolved.

There is an option to resolve these conflicts using the interactive editor in GitHub, which is an easy option. But for more complex coding changes we'll use the terminal. Here the owner of the repo will pull in the changes from the pull request branch using git pull origin pull/{number}/head. To ensure we do not mess up our main branch I usually do this on a separate branch, which we first create using git checkout -b {any name}.

# checkout a new branch
git checkout -b hotfix

# merge pull request into this branch -- any conflict will arise
git pull origin pull/1/head

# edit to resolve conflicts
# ...

# test your code with these new changes
# ...

# swith to main and merge hotfix branch into main
git checkout main
git merge hotfix

# commit changes back to GitHub -- this will close the PR
# since the PR branch will become identical to main.
git add README.md
git commit -m "resolved conflict with PR #1"
git push origin main

This seems complicated

It is! It takes a long time to get comfortable with git to the level that you memorize the order in which to call all of these commands. Fortunately, there are hundreds of tutorials like this one around the web that you can search to help you whenever you have a question. Revisit them as often as you need, and remember, git is a time machine, you can always rewind to former committed state, so don't worry if you think you've totally broken your repo. Seek help if you get stuck, and you'll figure it out.