Merging Simplified
Let's merge some branches!
Common Merging Confusion
A simple fast-forward merge is easy to grasp. It looks something like this:
(C3)
/ ^ HEAD,ISS:1
(C1) -> (C2)
^MASTER
We merge our commits from ISS:1 branch into Master.
$ git stash/commit #Let's clean up changes on ISS:1 before merging
$ git checkout master
$ git merge ISS:1
// master was fast-forwarded one commit
Our git tree now looks like this:
(C1) -> (C2) -> (C3)
^ HEAD, MASTER, ISS:1
But what about when the master branch has diverged from the commit you started ISS:1 from?
$ git remote add origin https://github.com/repo_name
$ git fetch origin master # Fetch the master branch in the origin repo
$ git checkout master
$ git merge origin/master # Merge remote master commits into local master
// Fast-forwarded master by 2 commits
Now our tree looks like this:
(C3)
/ ^ HEAD,ISS:1
(C1) -> (C2)
\
(C4) -> (C5)
^ MASTER
This can be a confusing situation & there are three ways to merge ISS:1 into Master.
Create a merge commit - Probably the most common scenario
Create a squash commit
Rebase ISS:1 commits on top of Master
Merge Commits
Most commits have one parent. Merge commits have two parents thus tailoring all the commits from one branch into another. This is the default behavior of the merge command when a branch has diverged from the other. Let's go back to our example:
(C3)
/ ^ ISS:1
(C1) -> (C2) -> (C4) -> (C5)
^ Master
No Merge Conflicts
This is the workflow when Git can do the merge without the need for human intervention.
$ git stash/commit # Finish changes on ISS:1
$ git checkout master
$ git merge ISS:1
// Merge commit created on Master
Our tree after the merge looks like this:
--------> (C3) ----
/ ^ISS:1 \
(C1) -> (C2) -> (C4) -> (C5) ------ > (C6)
^ Master
Success! C6 now combines the changes of ISS:1 & Master. It is the resulting merge commit since it has two parents (C3 & C5).
Merge Conflicts
Often times, merge conflicts can arise if master & ISS:1 have differing changes in identical regions of a file. Human intervention is needed in this case. Git leaves the working directory in a limbo state awaiting you to resolve the commit. Let's go back to our example:
(C3)
/ ^ ISS:1
(C1) -> (C2) -> (C4) -> (C5)
^ Master
$ git stash/commit # Finish changes on ISS:1
$ git checkout master
$ git merge ISS:1
// Error! Conflicts were detected. Resolve changes and commit to continue...
We don't generally want to commit anything onto our Master branch or play around with things until we are exactly sure of what we are doing. Thus, we importantly resolve the commits on our ISS:1 branch as opposed to the Master branch.
Let's change back to the ISS:1 branch.
$ git checkout ISS:1
$ vim index.js # Fix the conflicts
$ git add *; git commit -m 'Fixed conflicts'
// Fixed Conflicts is a merge commit
Our git tree looks like this now!
(C3) ------------- > (C6)
/ / ^ISS:1
(C1) -> (C2) -> (C4) -> (C5)
^ Master
But our goal was to merge master but it is still behind ISS:1. We want to move the Master pointer to C6. We can now do this since C6 is a child of C5. Merging ISS:1 into Master is now a simple affair.
$ git checkout master
$ git merge ISS:1
// Fast forward Master by one commit
(C3) ------------- > (C6)
/ / ^ISS:1, Master
(C1) -> (C2) -> (C4) -> (C5)
Pull Requests
PRs are a Github feature that let other developers review the changes from a feature before it goes into the production branch. PRs are also a good way to enforce a consistent, controlled access point for changes to the production branch. For example, only a select few members of the organization may have permission to merge branches into the production branch.
Cannot Merge Automatically
When you create a PR that will result in merge conflicts, the PR will say 'Cannot Merge Automatically'. To resolve this, we let local/master be even with origin/master and then merge master into ISS:1 creating the merge commit on ISS:1.
(C3)
/ ^ ISS:1
(C1) -> (C2) -> (C4) -> (C5)
^ master ^ origin/master
$ git fetch origin master
$ git checkout master
$ git merge origin/master
Out git tree now looks like this:
(C3)
/ ^ ISS:1
(C1) -> (C2) -> (C4) -> (C5)
^ master, origin/master
Let's merge master into ISS:1
$ git checkout ISS:1
$ git stash/commit # Clean your changes
$ git merge master # Merging master into ISS:1
// Merge Conflicts!!!
$ vim index.js # Resolve conflicts
$ git add *; git commit -m 'Resolved Conflicts'
$ git push origin ISS:1 # Pushed changes to origin
Our local repo now looks like this:
(C3) ------------- > (C6)
/ / ^ISS:1
(C1) -> (C2) -> (C4) -> (C5)
^ master, origin/master
If we use the PR button to merge, Github will make a new commit on our master branch. This is because PRs are configured to make an explicit merge commit as opposed to doing a simple fast-forward even when this is possible (as is the case in this situation).
After clicking merge button on the PR:
(C3) ------------- > (C6) -------------
/ / ^ISS:1 \
(C1) -> (C2) -> (C4) -> (C5)--------------------------- > (C7)
^ master ^ origin/master
This was a little confusing to me as C6 & C7 would represent identical snapshots of the repository.
Last updated
Was this helpful?