4. Branching and Merging
Branches are one of the most powerful features of Git. They let you create a version of the main code to work on new features, fix bugs, or simply experiment, without affecting the original code. Afterward, you can download the changes or include the new code in the "official" version.
Imagine your project is a tree:
- The main branch (usually called
mainormaster), which is the trunk of the tree. - The development branches (or new features), which are the branches that grow out of the trunk.
Each branch can evolve independently, and you can even create other branches from them. If a change is interesting and adds value, the new changes can be merged into the trunk.
This way you can:
- Work on new features without breaking the stable code.
- Multiple people can work in parallel on the same code base.
- Experiment with ideas without consequences. When you delete a branch, everything you did in it disappears without consequences.
To manage branches we'll use the following commands:
# View all branches
git branch
# View all branches (including remote ones)
git branch -a
# Create a new branch
git branch new-feature
# Create and switch to a branch
git branch -c my-branch
# Delete a branch
git branch -d branch-name
# Force delete a branch
git branch -D branch-name
# Rename a branch
git branch -m old-name new-name
git checkout
Switches between branches or restores files.
# Switch to an existing branch
git checkout main
# Create a branch and switch to it
git checkout -b new-branch
# Switch to a specific commit
git checkout abc1234
# Restore a file from the last commit
git checkout -- file.txt
git switch
A more modern and specific command for switching branches (since Git 2.23).
# Switch to a branch
git switch main
# Create and switch to a new branch
git switch -c new-feature
# Go back to the previous branch
git switch -
Recommendation: Use git switch to switch branches and git checkout for operations with files.
Branch workflow
# 1. Create a branch for your feature
git switch -c feature/login
# 2. Make changes
echo "Login component" > login.js
git add login.js
git commit -m "Add login component"
# 3. Make more commits as needed
echo "Tests" > login.test.js
git add login.test.js
git commit -m "Add tests for login"
# 4. Go back to main
git switch main
# 5. Merge your branch
git merge feature/login
git merge
Merges a branch into the current branch.
# Merge a branch
git merge branch-name
# Merge without fast-forward (creates a merge commit)
git merge --no-ff branch-name
# Abort a merge
git merge --abort
Types of merge:
Fast-forward: When there are no new commits on the base branch.
%%{init: {'theme':'base', 'themeVariables': { 'git0': '#ff6b6b', 'git1': '#4ecdc4'}}}%%
gitGraph
commit id: "A"
commit id: "B"
branch feature
commit id: "C"
commit id: "D"
checkout main
merge feature tag: "Fast-forward"
Three-way merge: When there are changes on both branches.
%%{init: {'theme':'base', 'themeVariables': { 'git0': '#ff6b6b', 'git1': '#4ecdc4', 'git2': '#95e1d3'}}}%%
gitGraph
commit id: "A"
commit id: "B"
branch develop
checkout develop
commit id: "C"
branch feature/new-feature
checkout feature/new-feature
commit id: "D"
commit id: "E"
checkout develop
commit id: "F"
merge feature/new-feature tag: "M1"
checkout main
commit id: "G"
merge develop tag: "M2"
Squash merge: Combines all the branch's commits into a single one.
%%{init: {'theme':'base', 'themeVariables': { 'git0': '#ff6b6b', 'git1': '#4ecdc4'}}}%%
gitGraph
commit id: "A"
commit id: "B"
branch feature
checkout feature
commit id: "C"
commit id: "D"
commit id: "E"
checkout main
commit id: "F"
commit id: "S (C+D+E)" type: HIGHLIGHT tag: "Squash"
Merge vs Rebase vs Fast-forward
There are different ways to integrate changes from one branch into another. Each one has its advantages and use cases.
Fast-forward merge
It's the simplest type of merge. It happens when the target branch has had no new commits since the branch you want to merge was created. Git simply moves the branch pointer forward.
- Doesn't create a merge commit
- The history stays linear
- It's as if you had never created a branch
# main is at commit B
# You create feature and make commits C and D
# main is still at B (no one has committed there)
git switch main
git merge feature # Automatic fast-forward
Result: main now points to D, the history is A → B → C → D
%%{init: {'theme':'base', 'themeVariables': { 'git0': '#ff6b6b', 'git1': '#4ecdc4'}}}%%
gitGraph
commit id: "A"
commit id: "B"
branch feature
commit id: "C"
commit id: "D"
checkout main
merge feature tag: "Fast-forward (main → D)"
Force or avoid fast-forward:
# Avoid fast-forward (always create a merge commit)
git merge --no-ff feature
# Force fast-forward (fails if not possible)
git merge --ff-only feature
Three-way merge (Traditional merge)
It happens when both branches have new commits. Git creates a new commit that has two parents: the last commit of each branch.
- Creates a merge commit (a commit with two parents)
- Preserves the entire history
- Clearly shows when branches were merged
- The history is not linear
When to use:
- When you want to keep the context that there was a branch
- On long-lived branches (develop, release)
- When you work in a team and want to see who merged what
Advantages:
- Complete and honest history
- Easy to revert (you only revert the merge commit)
- Doesn't rewrite history
Disadvantages:
- History can become complex with many branches
- Merge commits "pollute" the history
git switch main
git merge feature # Creates a merge commit if there are changes on main
%%{init: {'theme':'base', 'themeVariables': { 'git0': '#ff6b6b', 'git1': '#4ecdc4'}}}%%
gitGraph
commit id: "A"
commit id: "B"
branch feature
checkout feature
commit id: "C"
commit id: "D"
checkout main
commit id: "E"
commit id: "F"
merge feature tag: "M (merge commit)"
Squash merge
Combines all the commits of a branch into a single new commit before merging them. It's like taking all the branch's work and "compressing" it into a single commit.
- Creates a single commit with all the changes
- Loses the individual commit history of the branch
- The history on main stays clean and simple
- Doesn't keep the relationship with the original branch
When to use:
- Pull Requests with many small or development commits
- When the detailed branch history is not important
- You want to keep main with a clean history
- Each commit on main represents a complete feature
Advantages:
- Very clean and readable main history
- One commit = one complete feature
- Easy to revert (a single commit)
- Removes intermediate commits like "fix typo", "WIP", etc.
Disadvantages:
- You lose the detailed development history
- You can't cherry-pick individual commits
- Makes it harder to understand how the feature was developed
git switch main
git merge --squash feature
git commit -m "feat: add complete new feature"
%%{init: {'theme':'base', 'themeVariables': { 'git0': '#ff6b6b', 'git1': '#4ecdc4'}}}%%
gitGraph
commit id: "A"
commit id: "B"
branch feature
checkout feature
commit id: "C (WIP)"
commit id: "D (fix typo)"
commit id: "E (add tests)"
checkout main
commit id: "F"
commit id: "S (C+D+E squashed)" type: HIGHLIGHT tag: "Squash"
GitHub uses squash merge as the default option in many projects. It's very common in open source development.
Rebase
Rebase "relocates" your commits onto the tip of another branch. Instead of merging, it rewrites history as if you had started your work from the most recent commit of the target branch.
- Rewrites the history
- Creates new commits (with different hashes)
- The history stays linear
- There are no merge commits
# Before the rebase:
# main: A → B → E → F
# feature: A → B → C → D
git switch feature
git rebase main
# After the rebase:
# main: A → B → E → F
# feature: A → B → E → F → C' → D'
# (C' and D' are commits C and D "relocated")
When to use:
- Local feature branches before pushing
- You want a clean, linear history
- You update your branch with recent changes from main
Advantages:
- Linear history that's easy to read
- No merge commits
- Makes it easier to bisect and review changes
Disadvantages:
- Rewrites history (never rebase public commits)
- Can be confusing for beginners
- Conflicts are resolved commit by commit
NEVER rebase commits that are already in shared repositories (pushed to origin).
Practical comparison
Scenario: You have a feature branch with commits C and D. Meanwhile, main has commits E and F.
With merge:
git switch main
git merge feature
# Result: A → B → E → F → M (merge of C and D)
# ↘ C → D ↗
With rebase + merge:
git switch feature
git rebase main # Relocates C and D after F
git switch main
git merge feature # Fast-forward
# Result: A → B → E → F → C' → D'
Which one to use?
Use Fast-forward (letting it happen naturally):
- Small, quick changes
- You don't need to preserve the branch context
Use Merge (three-way):
- Important branches (develop → main, release → main)
- You want to preserve the historical context
- Collaborative work where the history matters
- You don't want to rewrite history
Use Squash merge:
- Pull Requests in open source projects
- Features with many intermediate development commits
- You want a clean history on main where each commit is a feature
- You don't need the detailed history of how it was developed
Use Rebase:
- Update your local branch with changes from main
- Before creating a PR (to have a clean history)
- Personal feature branches
- You want a linear history
Recommended workflow:
# While working on your feature
git switch feature
# Update with changes from main using rebase
git fetch origin
git rebase origin/main
# Resolve conflicts if any
git add file.txt
git rebase --continue
# When done, create a PR
# The maintainer will decide whether to merge or squash
Complete example:
# Strategy 1: Traditional merge
git switch main
git merge feature/login --no-ff -m "Merge: add login feature"
# Strategy 2: Rebase + Fast-forward
git switch feature/login
git rebase main # Update the branch
git switch main
git merge feature/login # Automatic fast-forward
# Strategy 3: Squash merge (combines everything into one commit)
git switch main
git merge --squash feature/login
git commit -m "feat: add complete login system"
# Strategy 4: Fast-forward only (fails if not possible)
git switch main
git merge --ff-only feature/login
Conflict resolution
A conflict occurs when two branches modify the same lines of code.
Example of a conflict:
# On branch main
echo "main version" > file.txt
git add file.txt
git commit -m "Update on main"
# Create and switch to a new branch from an earlier commit
git switch -c feature
echo "feature version" > file.txt
git add file.txt
git commit -m "Update on feature"
# Try to merge
git switch main
git merge feature
# Conflict!
Git will mark the conflict in the file:
<<<<<<< HEAD
main version
=======
feature version
>>>>>>> feature
Resolve the conflict:
- Open the file and decide which version to keep
- Remove the conflict markers (
<<<<<<<,=======,>>>>>>>) - Save the file
- Add it to staging:
git add file.txt - Complete the merge:
git commit
Tools to resolve conflicts:
# View files with conflicts
git status
# Use a visual merge tool
git mergetool
# Accept all changes from one branch
git checkout --theirs file.txt # The branch you're merging
git checkout --ours file.txt # The current branch
Branching strategies
Feature Branch
The simplest strategy: one branch per feature.
main
├── feature/login
├── feature/dashboard
└── bugfix/header-typo
# Create a feature branch
git switch -c feature/new-feature
# Develop
git add .
git commit -m "Implement feature"
# Merge when ready
git switch main
git merge feature/new-feature
Activity 1
- Create a branch called
feature/new-page. - On that branch, create a
page.htmlfile with basic HTML content. - Commit the changes.
- Go back to the
mainbranch. - Merge the
feature/new-pagebranch intomain. - Delete the
feature/new-pagebranch.
Activity 2
Conflict practice:
- On the
mainbranch, edit the first line ofREADME.md. - Commit.
- Create a
conflict-testbranch from an earlier commit. - Edit the same line of the README with different text.
- Commit.
- Try to merge
conflict-testintomain. - Resolve the conflict manually.
- Complete the merge.
This work is under a Attribution-NonCommercial-NoDerivatives 4.0 International license.
Desafíos de programación atemporales y multiparadigmáticos
Te encuentras ante un librillo de actividades, divididas en 2 niveles de dificultad. Te enfrentarás a los casos más comunes que te puedes encontrar en pruebas técnicas o aprender conceptos elementales de programación.
Buy the bookWill you buy me a coffee?
This is how I keep writing without ads or paywalls.
Sure, it's on me!
Comments
There are no comments yet.