In the last post, I talked about how to create a Git repository and upload it to GitHub. In this post, I’m going to talk about how to resolve Git conflicts.
Setting Up Our Environment
First, we’re going to create a Git Repository for the user Doug. Since I already covered that in the last post, I’m bring to breeze through those steps below:
$ mkdir doug $ cd doug $ git init Initialized empty Git repository in /path/to/doug/.git/ $ touch README.md $ git add README.md $ git commit -m "First commit" [master (root-commit) d86a7e2] First commit 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 README.md
At this point, we have a repository created for the user Doug. Now I’m going to clone that repository for the user Andrew:
$ cd .. $ git clone doug andrew $ ls -l total 0 drwxr-xr-x 4 doug staff 136 Nov 28 16:59 andrew drwxr-xr-x 4 doug staff 136 Nov 28 16:47 doug $ cd andrew $ ls -l total 0 -rw-r--r-- 1 doug staff 0 Nov 28 16:59 README.md $ git remote -v origin /path/to/doug (fetch) origin /path/to/doug (push)
Note that Andrew’s remote of “origin” points to where the repo was cloned from. In this case, Doug’s directory. Having two copies of the same Git repository in adjacent directories is entirely acceptable, but not commonly done. It does help us for this example, though.
Finally, I’m going to return to Doug’s repo and set up a remote pointing to Andrew’s repo:
$ cd doug $ git remote add origin ../andrew/ $ git remote -v origin ../andrew/ (fetch) origin ../andrew/ (push) # # Since we manually added a remote, we also need to tell # Git that when we do a git pull, the default upstream of the # master branch in our repo is the master branch from the origin remote. # $ git branch --set-upstream-to=origin/master master Branch master set up to track remote branch master from origin. $ git pull -v From ../andrew = [up to date] master -> origin/master Already up-to-date.
Now we’re going to add content for both Doug and Andrew:
$ cd doug $ echo "foxes" > candids.txt $ git add candids.txt $ git commit -m "Added foxes" [master bbd8d94] Added foxes 1 file changed, 1 insertion(+) create mode 100644 candids.txt $ cd ../andrew/ $ git pull remote: Counting objects: 4, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From /path/to/doug d86a7e2..bbd8d94 master -> origin/master Updating d86a7e2..bbd8d94 Fast-forward candids.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 candids.txt $ echo "deer" > cervidae.txt $ git add cervidae.txt $ git commit -m "Added deer" [master 84ecfa2] Added deer 1 file changed, 1 insertion(+) create mode 100644 cervidae.txt
Note that before we before we make any commits to Andrew’s repository, we first do a git pull. This is generally good practice when working on an active repository, to make sure that you have the latest and greatest changes.
Finally, we’re going to go back to Doug’s directory and pull in Andrew’s changes:
$ cd ../doug/ $ git pull Updating bbd8d94..84ecfa2 Fast-forward cervidae.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 cervidae.txt
When Conflicts Happen
So far, this has worked pretty well. Both Doug and Andrew made changes to separate files and Git handled them with no difficulties. This is the way any good VCS (Version Control System) should work. But sometimes, conflicts arise. For example, what happens if both Doug and Andrew edit the same file? Let’s find out.
Getting back to the theme of animals, Doug is a fan of leopards. Andrew, on the other hand likes cheetahs. So we’re going to have both users create the same file called big-cats.txt and see what happens when they try to stomp on each others’ changes.
First, Doug creates leopards:
$ cd doug/ $ echo "leopards" > big-cats.txt $ git add big-cats.txt $ git commit -m "Leopards" [master 1a417b1] Leopards 1 file changed, 1 insertion(+) create mode 100644 big-cats.txt
Now, Andrew creates his cheetahs:
$ cd andrew/ $ echo "cheetahs" > big-cats.txt $ git add big-cats.txt $ git commit -m "Cheetahs" [master ccdc16f] Cheetahs 1 file changed, 1 insertion(+) create mode 100644 big-cats.txt
At this point, both Doug and Andrew have repositories that are exactly the same, except for the most recent commit (HEAD) in which the same file has been created, but with different contents. Everything will be fine, until either Doug or Andrew attempt to pull each others’ changes. In this case, Doug will pull Andrew’s changes:
$ cd doug/ $ git pull remote: Counting objects: 4, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From ../andrew 84ecfa2..ccdc16f master -> origin/master Auto-merging big-cats.txt CONFLICT (add/add): Merge conflict in big-cats.txt Automatic merge failed; fix conflicts and then commit the result.
What just happened? Git caught on that two separate changes affected the same file. At this point, Git did the equivalent of throwing up its hands and said “I have no idea what to do here, I need human intervention”. At this point, we are in a “half-commited” state and need to resolve the conflict so that we can finish up the commit and merge. Running “git status” will show the conflicted files as “unmerged”:
$ git st # On branch master # Your branch and 'origin/master' have diverged, # and have 1 and 1 different commit each, respectively. # (use "git pull" to merge the remote branch into yours) # # You have unmerged paths. # (fix conflicts and run "git commit") # # Unmerged paths: # (use "git add <file>..." to mark resolution) # # both added: big-cats.txt # no changes added to commit (use "git add" and/or "git commit -a")
How do we fix this? First, we need to look at the content of the file big-cats.txt which was listed as the culprit:
$ cat big-cats.txt <<<<<<< HEAD leopards ======= cheetahs >>>>>>> ccdc16f38db0d7200d4b1573c768fd8e362303fb
The notation here is pretty straightforward. The line that starts with “HEAD” shows the disputed lines in Doug’s commit. On the bottom half of the disputed section are the lines from commit ccdc16f, which was made by Andrew. At this point, a human has to edit the file and remove the commit IDs, the center separator (“=======”), and decide if he wants to keep Doug’s changes, Andrew’s changes, or both.
In this case, since it is a text file, we’ll opt to keep both changes, leaving the file looking like this:
$ cat big-cats.txt leopards cheetahs
If this were actual code, it is far more likely that only one person’s set of changes would survive.
Once the changes are made, we can add this file back into Git and finish the marge:
$ git add big-cats.txt $ git commit -m "Merged big cat changes" [master 96dac47] Merged big cat changes
Finally, Andrew would do a pull on his end to incorporate the changes:
$ git pull remote: Counting objects: 9, done. remote: Compressing objects: 100% (4/4), done. remote: Total 6 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (6/6), done. From /path/to/doug bbd8d94..96dac47 master -> origin/master Updating ccdc16f..96dac47 Fast-forward big-cats.txt | 1 + 1 file changed, 1 insertion(+) $ cat big-cats.txt leopards cheetahs
Assuming he made no further changes to that file, it would be a run of the mill git pull which would bring down Doug’s changes to the file and place them in Andrew’s copy of the code.
This was a long post, and again I apologize for that. But it was necessary to lay out both the conditions that can lead up to a merge conflict in Git, and how to resolve that contact. I hope the post was informative. Feel free to reach out to me with any further questions about Git, or leave a comment on this post.