On the tenth day of Christmas, my true love gave to me… ten easy merges.
In the previous lesson, we learned about using remote branches with Git to share branches on remote repos, which makes collaborating with other very easy. Today, we’re going to to learn about merging different versions with Git, so that we can fix conflicts when more than one person changes the same part of a remote branch.
If you are following our song, “The Twelve Days of Git,” here are the verses so far:
- The 12 Days of Git: Learn Git over the Holidays
- The 12 Days of Git, Day 2: Tracking Files with Git
- The 12 Days of Git, Day 3: Viewing Git History
- The 12 Days of Git, Day 4: Double-checking Changes with Git
- The 12 Days of Git, Day 5: Sharing Changes Remotely with Git
- The 12 Days of Git, Day 6: Making a Copy of a Git Repository
- The 12 Days of Git, Day 7: Keeping Your Git Repository Up-To-Date with Others’ Changes
- The 12 Days of Git, Day 8: Using Git to Experiment Safely
- The 12 Days of Git, Day 9: Using Remote Branches with Git
At the end of the last lesson, we had pushed our father-christmas branch up to GitHub, and in that branch the
index.html file was different than the one in the master branch. Assume that we are happy with the work we’ve done in the new branch, and we want to include it in the main version of our project.
Merging two branches
First, make sure you have your master branch checked out (
git branch shows an asterisk beside the name master). If not, check out the master branch (
git checkout master). Now we’re going to merge those changes into master.
git merge father-christmas
Below is the output you should see. The commit hashes in the first line may be different
Updating 384888c..7f67565 Fast-forward index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
Here’s what each of those lines mean:
- The changeset with commit hash 7f67565 is being merged into the changeset with commit hash 384888c.
- The merge is able to take place simply by fast-forwarding the current branch from its current commit to the one commit of the one being merged.
- The merge affects
index.htmlin 4 lines. The two pluses (++) mean two lines were added; the two minuses (–) mean 4 lines were deleted.
- The last line is a summary of total number of files affected, the total number of lines added, and the total number of lines deleted.
If the merge had been more complicated, the second line may have listed recursion as the merge method used. If any additional files had been involved, each file would have been listed on its own line. Finally, if Git was not able to merge the two branches automatically, there would be additional lines with the details of where the two branches conflict.
Fixing merge conflicts
Let’s contrive to create a merge conflict so that we can then fix a conflict.
Start by creating a new branch called pere-noel, pushing it up to the remote repo, and switching to the new branch.
git branch pere-noel git push -u origin pere-noel git checkout pere-noel
Now, edit the index.html again, and change both occurrences of Father Christmas to Père Noël.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Père Noël Tracker: Bringing the North Pole to You</title> <link rel="stylesheet" type="text/css" href="main.css"> </head> <body> <h1>Père Noël Tracker</h1> <p>Bringing the North Pole to you since 2015</p> </body> </html>
Add your changing to the staging area, commit your changes to a new changeset, and then push those changes to the remote branch.
git add -A git commit -m'Change to Pere Noel' git push
You now have three (3) branches in your local repo, and remote copies of those same three branches in your remote repo on GitHub.
Normally, to encounter a merge conflict you would have to commit a change in your local repo, and someone else with access to your remote repo would have to commit and push a change that affected some of the same lines you changed. To simulate this situation without having to involve another person, we will take advantage of the GitHub feature that allows you to make edits to your remote repo through its GitHub web page.
On GitHub, select the index.html file and use the branch dropdown menu to switch to the pere-noel branch. Then, click the Pencil icon in the list of icons on the right side of the gray bar directly above the file listing.
Change Père to Papa on lines 5 and 9, enter a commit message and click the green button to commit your changes.
Now, back on your local machine, make sure you are still on the pere-noel branch. In the
index.html file, change those same two lines (5 and 9) so that they read Saint Nicholas instead of Père Noël. Add and commit those changes. Then, try to
git push. You will see the following message.
To https://github.com/vjwilson/santa-tracker.git ! [rejected] pere-noel -> pere-noel (fetch first) error: failed to push some refs to 'https://github.com/vjwilson/santa-tracker.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.
You can’t overwrite the new changes to that branch on GitHub with your local changes. This is one Git protects everyone from losing work. Instead, Git suggests what we need to do, so try
git pull. Now you will see this.
remote: Counting objects: 3, done. remote: Compressing objects: 100% (3/3), done. remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (3/3), done. From https://github.com/vjwilson/santa-tracker 3d60bca..9cf348c pere-noel -> origin/pere-noel Auto-merging index.html CONFLICT (content): Merge conflict in index.html Automatic merge failed; fix conflicts and then commit the result.
If you run
git status right now, you will see the following.
On branch pere-noel Your branch and 'origin/pere-noel' 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 ..." to mark resolution) both modified: index.html no changes added to commit (use "git add" and/or "git commit -a")
You have to edit the file with the conflict,
index.html, and commit the edit before Git will let you finish the merge. When you open that file, it will look like this.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <<<<<<< HEAD <title>Saint Nicholas Tracker: Bringing the North Pole to You</title> <link rel="stylesheet" type="text/css" href="main.css"> </head> <body> <h1>Saint Nicholas Tracker</h1> ======= <title>Papa Noël Tracker: Bringing the North Pole to You</title> <link rel="stylesheet" type="text/css" href="main.css"> </head> <body> <h1>Papa Noël Tracker</h1> >>>>>>> 9cf348c866da81caca26e9d74da481c342e96cb0 <p>Bringing the North Pole to you since 2015</p> </body> </html>
Notice that Git has automatically added lines to highlight what cannot be merged automatically. The lines from <<<<<<< HEAD to ======= are those that come from the current state of your local branch. The lines from ======= to >>>>>>> 9cf348c866da81caca26e9d74da481c342e96cb0 are those that come from the state of that branch in the remote repo.
To fix this particular conflict, let’s just delete the markers lines and every line from the remote EXCEPT its version of the <h1> line; delete the local version of the that line. You should save the file when it looks like this.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Saint Nicholas Tracker: Bringing the North Pole to You</title> <link rel="stylesheet" type="text/css" href="main.css"> </head> <body> <h1>Papa Noël Tracker</h1> <p>Bringing the North Pole to you since 2015</p> </body> </html>
git add -A now, and then
git status. Now the status message looks like this.
On branch pere-noel Your branch and 'origin/pere-noel' have diverged, and have 1 and 1 different commit each, respectively. (use "git pull" to merge the remote branch into yours) All conflicts fixed but you are still merging. (use "git commit" to conclude merge) Changes to be committed: modified: index.html
Finish the merge with an appropriate message, and push the merged result to the remote branch.
git commit -m'Compromise between Papa Noel and Saint Nicholas' git push
To see a visual representation of all this merging, type our comprehensive version of the log command:
git log --graph --decorate --pretty=oneline --abbrev-commit --all
* 1d990e1 (HEAD, origin/pere-noel, pere-noel) Compromise between Papa Noel and Saint Nicholas |\ | * 9cf348c Change to Papa * | 03176a1 Change to Saint Nicholas |/ * 3d60bca Change to Pere Noel * 7f67565 (origin/master, master, origin/father-christmas, father-christmas) Change Santa to Father Christmas * 384888c Change branding style of the website * 8bfbaa2 Add first files to the repository
In this lesson, we’ve covered the useful, and sometimes tricky, concept of merging versions with Git. Check back tomorrow, when we’ll learn about Stashing Changes with Git until You Need Them.
To see all the options available for the commands we’ve discussed today, check them out in the online Git reference: