The following is a guest blog post written by Carlos Schults.
Git’s branching and merging capabilities are powerful, but they can be confusing for beginners. Today, we are contributing to your understanding of Git by answering the title question: how do you merge a branch to master?
We could jump right into the explanation of how to merge in Git, but first, let’s take a step back and talk about some fundamentals. We’ll explain how branches work under the hood, which will make you really appreciate how different Git is from most other VCS tools. Then we’ll get back to covering the many ways to merge in Git.
Let’s dig in.
Git Merge Branch to Master Fundamentals: Starting From the Beginning
Without further ado, let’s start covering the fundamentals of Git branching and merging.
How Do Branches Work in Git?
A Git repo contains both objects and references. The main objects in a Git repo are commits, which are snapshots of a point in the project’s history. Commits point to their predecessors (called parents), and from the relationships between commits, we obtain revision history. Objects in a Git repository are immutable. For instance, you can’t change anything in a commit, nor can you change its position in history, despite there being commands that appear to do so. References, on the other hand, are labels that you attach to a commit, and unlike objects, references are movable.
The main type of reference in Git is a branch. When you create a branch from a point in history, Git creates a new reference pointing to that commit. Another crucial reference in Git is called HEAD. HEAD indicates the current point in history, and it’s used by commands like git log to define where to start a task from.
In the figure below, we see a repository with three commits. HEAD points to the master branch, which is the only available branch, for now.
Now let’s say we create a branch called example, but don’t switch to it. This is what our repo looks like now:
A reference, in practice, is nothing more than a plain text file containing the hash of the commit it points to. Creating such a file is trivial, and thus, creating branches in Git is very fast.
What Is Git Merge?
Branching is a way to create a divergence in a repository. A branch can be a separate line of development that you create, allowing you to test or experiment in a safe way. But, eventually, most branches need to converge back to where they started.
That’s what the git merge command is for. It allows you to integrate all of the changes (i.e., commits) made in another branch into your current branch, from the point at which the branches diverged.
How Do I Merge Branches?
As already mentioned, to merge branches, you can use the git merge command—but this isn’t the only way to incorporate changes from another development line, as you’ll soon see.
To use the merge command in its most common form, you have to pass the source branch—in our case, example—to the command as a parameter. That means you must be in the branch that will receive the changes. So, in the scenario in the figure, let’s say you’re not in the branch master, and you want to merge example to master. You’d only have to do this:
git checkout master # changing to the master branch
git merge example
In our scenario, example points to the same commit as master, which means there are no changes to integrate , and Git simply responds with “Already up to date.”
Types of Merges
But what if there were changes to integrate? How would Git go about merging them? Let’s cover that, by learning about the different types of merging that Git provides.
The simplest type of merge in Git is called fast-forward. Git performs this type of merge when only the source branch contains new changes since a divergence. Let’s continue the previous scenario. Suppose that after creating the example branch, we add a couple of commits to that branch. The repository now looks like this:
Then, suppose we go back to master and try to merge from example with these commands:
git switch master
git merge example
Git will complete the merge and display a message saying that a “fast-forward” was performed. This means that, before the merge, the master branch was behind the example branch. It didn’t have divergent commits of its own. So, the merge here is simply a matter of having master catch up to the source branch. Here’s a visual representation of the repository after the merge:
In practice, what happens in this merge is that Git updates the content of the text file for the branch master, replacing its content with the fifth commit’s hash. This is trivial to do, making this type of merge very efficient.
But what if the two lines of development had new changes? To simulate that scenario, we can use the reset command to go back to the third commit, and then add one or more commits from that point.
git reset --hard HEAD~2
git add .
git commit -m "Add readme file"
Now both sides have diverged, and the repository looks like this:
The command to execute the merge is the same as before. But in this case, Git prompts us to provide a commit message explaining why the merge is necessary. After you write and save the message using your choice of text editor, Git can complete the merge if there are no merge conflicts. Here’s what the repository looks like now:
What happened? Since the two branches contained exclusive changes, a fast-forward wasn’t possible, so Git performed a three-way merge. It’s named "three-way merge" because, in order to figure out the merge result (e.g., which versions of the same file to pick when there are differences in each branch), Git has to look in three places: the current branch you’re in, the branch you’re trying to merge from, and the point at which they diverged.
As you can see in the figure above, the three-way merge results in an extra commit that has two parents instead of one. The master reference now points to this new commit, which is called a merge commit, and is needed to indicate the merge from diverging histories.
How Do I Merge a Branch into Master? A Few Bonus Tips
You’ve already seen that merging to a branch called master is just a matter of running git checkout master followed by git merge <other-branch-name>. Those are the basics, at least. How and when to do it will depend on the branch strategy your team adopts.
Before we wrap up though, here are some additional tips on merging that might come in handy.
1. Squashing Commits
Imagine you want to integrate the changes from one branch with another branch, but you can’t or don’t want to merge the original commits as they are. Instead, you want to incorporate all of the changes into a single commit with a message that you choose. To do that, you just have to use the –squash option when merging:
git checkout branch
git merge --squash <branch-name>
After running these commands, if there are no conflicts, Git incorporates the changes from the other branch and stages them but stops right before committing them. This way, you can unstage some of the changes, add and stage some changes of your own, and then commit them when you’re ready.
After committing, you might want to delete the original branch. When you try to do it using git branch -d <branch-name>, Git will show you a message like this:
error: The branch '<branch-name>' is not fully merged.
If you are sure you want to delete it, run 'git branch -D <branch-name>'.
Git thinks you don’t have the changes from the other branch, but you do. You just have a commit with a hash that differs from those of the commits on the source branch, and that’s the information Git uses to determine whether a branch is fully merged. Since you know better, you can delete the branch by replacing the “-d” with a “-D” (with a capital D), just like Git itself advises.
2. Using the “No-Commit” Option
Using the –squash option gives you more control when merging. However, it results in a regular commit. Git even thinks you haven’t incorporated the changes from the other branch. If you want to stop right before committing, in order to tweak some things, but you still want a merge commit to result from this, you have to use the no-commit option, as in git merge –no-commit <branch-name>.
If everything works fine, Git says:
Automatic merge went well; stopped before committing as requested.
Now you can stage or unstage changes, and then commit when you’re ready. The difference is that, unlike –no-squash, using –no-commit results in a merge commit.
3. Overriding Local Changes When Merging
If you have local changes that might be overridden by a merge, Git doesn’t allow the merge to finish and displays a message like this:
error: Your local changes to the following files would be overwritten by merge:
Please commit your changes or stash them before you merge.
This is a safety mechanism provided by Git to prevent you from losing work. But what if you do want to override the local changes? In that case, run git reset –hard before merging. Beware: by doing so, your changes will be gone for good.
Branching Is a Tool: Don’t Abuse It
One of Git’s main selling points is its unique approach to branching. Beginners often struggle with it though, due to a lack of knowledge about Git’s internals. In this post, we addressed that by explaining the fundamentals of how branching and merging work in Git.
What we didn’t address is whether you should make heavy use of branching. Often teams get too excited about how cheap branching is in Git and adopt branching strategies that are complex, error-prone, and that can cause integration delays.
Remember: branching is a tool. Like any other tool, it can be abused. Use it wisely though and you’ll be fine.
Thanks for reading and until next time!
Carlos is a .NET software developer with experience in both desktop and web development, and he’s now trying his hand at mobile. He has a passion for writing clean and concise code, and he’s interested in practices that help you improve app health, such as code review, automated testing, and continuous build.
Stay up to date
We'll never share your email address and you can opt out at any time, we promise.