Git Merging
Learn fast-forward merges, three-way merges, merge conflicts, and why teams sometimes use git merge --no-ff.
What Merging Does
Merging combines changes from one branch into another. It is how feature work eventually becomes part of main or another shared branch.
A common example is merging feature/login into main after the feature is complete and reviewed.
git switch main
git merge feature/login
Git tries to combine history automatically. When histories fit together cleanly, merging is easy. When the same lines changed in different ways, you may get a conflict that needs manual resolution.
Fast-Forward Merge
A fast-forward merge happens when the target branch has not moved since the feature branch was created. Git can simply move the branch pointer forward.
Imagine this sequence:
mainat commit A- create
feature/docs - add commits B and C on
feature/docs mainstill points at A
Merging can look like this:
git switch main
git merge feature/docs
Git may respond with a fast-forward update because there is no need to create a separate merge commit.
Why It Happens
The histories form a straight line. main can just “catch up” to the feature branch.
Benefits:
- simple history
- fewer merge commits
- easy to understand for small projects
Three-Way Merge
A three-way merge happens when both branches moved forward independently.
For example:
mainhas commit Dfeature/loginhas commits B and C- both branches share an earlier common ancestor A
Now Git must combine two lines of development. It creates a new merge commit that has two parents.
git switch main
git merge feature/login
This is called a three-way merge because Git compares:
- the common ancestor
- the current branch tip
- the branch being merged
When Teams Use --no-ff
By default, Git may fast-forward when possible. Some teams prefer to always keep a visible merge commit for feature branches.
Use:
git switch main
git merge --no-ff feature/login
This forces a merge commit even if a fast-forward would work.
Why Use --no-ff?
It can make history easier to read because the branch integration remains visible as a single merge event. That can help when reviewing past work or reverting an entire feature.
Tip: Teams that rely heavily on pull requests often like explicit merge commits because they preserve the story of how a feature entered the main branch.
What a Merge Conflict Is
A merge conflict happens when Git cannot decide how to combine changes automatically.
This usually occurs when:
- both branches changed the same lines
- one branch deleted a file the other edited
- file renames and edits overlap in confusing ways
Git will pause the merge and ask you to resolve the conflict manually.
Step-by-Step Conflict Resolution
Assume both branches edited the same line in app.conf.
Start the merge:
git switch main
git merge feature/config-update
Git may report a conflict. Check the status:
git status
Open the conflicted file. You may see markers like this:
<<<<<<< HEAD
port=8080
=======
port=9090
>>>>>>> feature/config-update
What the Markers Mean
<<<<<<< HEADshows the version from the current branch=======separates the two versions>>>>>>> feature/config-updateshows the incoming version
Resolve the File
Choose the correct final content, for example:
port=9090
Or combine both ideas if needed. Then stage the resolved file:
git add app.conf
Complete the merge:
git commit
Git usually opens your editor with a default merge message.
Verifying the Merge
After merging, inspect the history:
git log --oneline --graph --decorate -10
This helps you confirm whether Git created a fast-forward result or a merge commit.
You can also test the application after resolving conflicts:
npm test
Or if it is a shell-based project:
./run-checks.sh
Validation matters because a “clean” merge is not automatically a correct merge.
Abort a Problematic Merge
If you start a merge and realize you are not ready, you can often stop and return to the pre-merge state:
git merge --abort
This is helpful if you accidentally merged the wrong branch or want to review changes before trying again.
Note:
git merge --abortworks best before you make extra manual changes beyond the merge conflict state.
Merge vs Rebase at a High Level
Merging preserves the true branch structure. Rebasing rewrites commits onto a new base to create a straighter history. Both are useful, but merge is often the safer beginner choice because it does not rewrite existing commits.
What You Should Remember
Merging is how Git combines branches. A fast-forward merge simply moves a pointer. A three-way merge creates a merge commit when histories diverged. Conflicts happen when Git cannot safely decide between competing changes, and resolving them means editing the affected files, staging the results, and finishing the merge.
Use --no-ff when your team wants feature integrations to remain clearly visible in history.
Test Your Understanding
Let's see how well you understood the concepts! These exercises will help reinforce what you just learned.
Exercise 1: Recognizing a fast-forward
Scenario: main is still at commit A, while feature/docs moved from A to B to C and nothing new was added to main.
Question: What kind of merge should Git usually be able to perform when you merge feature/docs into main?
Exercise 1: Recognizing a fast-forward
Identify the merge type when the target branch has not moved since branching.
Exercise 2: Preserving feature history
Question: Why might a team choose git merge --no-ff feature/login even when a fast-forward merge is possible?
Exercise 2: Preserving feature history
Pick the reason teams force a merge commit with --no-ff.
Exercise 3: Finishing conflict resolution
Scenario: A merge stopped with conflict markers in app.conf. You edited the file so the final content is correct.
Question: According to the tutorial, what should you do next to complete the merge workflow?
Exercise 3: Finishing conflict resolution
Choose the next step after manually resolving merge conflict markers.