Skip to content
Git Branching & Merging — Complete Practical Guide

Git Branching & Merging — Complete Practical Guide

DodaTech Updated Jun 6, 2026 10 min read

Git branches let you work on features, fixes, or experiments in isolation without disrupting the main codebase — think of them as parallel universes where you can test changes safely before merging them back.

What You’ll Learn

By the end of this tutorial, you’ll create and switch branches, merge changes using fast-forward and three-way merges, resolve merge conflicts, use rebase for a clean history, and cherry-pick individual commits between branches.

Why Branching Matters

Branching is what makes Git powerful for teams. Without branches, everyone edits the same code simultaneously — chaos. With branches, multiple developers build features in parallel, and only reviewed, tested code reaches the main branch. DodaTech uses feature branching for every product: Doda Browser releases, DodaZIP updates, and Durga Antivirus Pro signature updates all go through branch-based workflows.

Security note: Understanding Git Branching helps build more secure applications — a core principle at DodaTech, where tools like Durga Antivirus Pro and Doda Browser rely on solid implementation practices.

Branching Learning Path

    flowchart LR
  A[Git Basics] --> B[Branching & Merging]
  B --> C[Remote Repositories]
  C --> D[Undoing Changes]
  D --> E[Advanced Git]
  style B fill:#3b82f6,stroke:#fff,color:#fff
  
Prerequisites: Familiarity with git init, git add, git commit, and git status. Review Git first if needed.

How Branching Works — The Parallel Universes Analogy

Imagine your codebase is a book. The main branch is the published edition. Creating a branch is like writing a draft chapter in a parallel notebook:

  • You can write freely in the draft without messing up the published book
  • Multiple writers can each have their own draft
  • When a draft is ready, you merge it into the published book
  • Sometimes two drafts change the same sentence — that’s a merge conflict
    gitGraph
  commit id:"A"
  commit id:"B"
  branch feature
  checkout feature
  commit id:"C"
  commit id:"D"
  checkout main
  merge feature
  commit id:"E"
  

Why create a branch? Because you don’t want half-finished work on main. If a colleague pulls main and finds broken code, they can’t work. Branches keep main clean and deployable at all times.

Creating & Switching Branches

# Create a branch (stays on current branch)
git branch feature-navbar

# Switch to it
git checkout feature-navbar

# Or do both at once (most common)
git checkout -b feature-navbar

# Modern alternative (Git 2.23+)
git switch -c feature-navbar   # create + switch
git switch main                # switch existing

Line-by-line explanation:

  • git branch feature-navbar — creates a new pointer at your current commit. You’re still on main
  • git checkout feature-navbar — moves your working directory to that branch
  • git checkout -b feature-navbar — creates AND switches in one command. You’ll use this 90% of the time
  • git switch — newer, more intuitive command. -c means “create”
Use git switch instead of git checkout for branch operations. Reserve git checkout for restoring files or older Git versions.

Listing Branches

git branch                 # local branches (* = current branch)
git branch -a              # all branches (including remote-tracking ones)
git branch -r              # remote branches only
git branch -v              # show last commit on each branch

Output:

* main
  feature-navbar
  remotes/origin/main

The * tells you which branch you’re currently on. Always check before making changes!

Merging — Bringing Changes Together

Merging takes changes from one branch and applies them to another. There are two types:

Fast-Forward Merge

When the target branch hasn’t diverged — main hasn’t moved since you branched off:

git checkout main
git merge feature-navbar
# Updating a1b2c3d..d4e5f6g
# Fast-forward

Analogy: You’re catching up to someone who ran ahead — just walk forward to where they are. No new junction created.

Three-Way Merge

When both branches have new commits — Git must combine two histories:

git checkout main
git merge feature-navbar
# Merge made by the 'ort' strategy.

Git creates a merge commit that has two parents — one from each branch.

    gitGraph
  commit id:"A"
  commit id:"B"
  branch feature
  checkout feature
  commit id:"C"
  checkout main
  commit id:"D"
  checkout feature
  commit id:"E"
  checkout main
  merge feature
  

Merge with –no-ff

Forces a merge commit even when fast-forward is possible — useful for preserving branch history visually:

git merge --no-ff feature-navbar

Why use it? When you look at history later, you can see clearly: “here was a feature branch.” Without it, the commits look like they were all done on main in sequence.

Merge Conflicts

Conflicts happen when two branches modify the same lines of the same file:

git merge feature-navbar
# Auto-merging index.html
# CONFLICT (content): Merge conflict in index.html

Git marks the conflict in the file:

<<<<<<< HEAD
<title>My Site</title>
=======
<title>My Awesome Site</title>
>>>>>>> feature-navbar

Understanding the markers:

  • <<<<<<< HEAD — the start of the conflict, showing your version (current branch)
  • ======= — divides your version from the incoming version
  • >>>>>>> feature-navbar — end of conflict, showing their version

Resolution Steps

  1. Edit the file — keep your version, their version, or a mix of both
  2. Remove the conflict markers — delete <<<<<<<, =======, >>>>>>>
  3. Stage and commit:
git add index.html
git commit
# Git opens a pre-filled merge commit message; save and quit

Conflict Resolution Tools

# See both versions side by side
git mergetool             # opens configured merge tool (vimdiff, VS Code, etc.)

# Abort the merge entirely (restart from before)
git merge --abort

Rebase — Cleaner History

Rebase replays your commits on top of another branch, creating a linear history:

git checkout feature-navbar
git rebase main

Before rebase:

      D---E feature
     /
A---B---C main

After rebase:

              D'---E' feature
             /
A---B---C main

D' and E' are new commits with the same changes but new hashes. The history looks like you started working after C.

Why rebase instead of merge? If you merge main into your feature branch repeatedly, you get a tangled history with many merge commits. Rebase keeps it clean — linear and readable.

Never rebase commits that others have already pushed. Rebase rewrites history — it creates new commit hashes. If a teammate already pulled the old commits, their history diverges and Git gets confused. Only rebase local, unpushed branches.

Interactive Rebase — Squash, Reword, Reorder

Powerful for cleaning up commits before pushing:

git rebase -i HEAD~3       # last 3 commits
# Opens editor with:
# pick a1b2c3d Fix header
# pick d4e5f6g Add dark mode
# pick 7a8b9c0 Fix typo

Commands you can use:

  • pick — keep the commit as-is
  • squash — combine with the previous commit (and merge messages)
  • reword — change the commit message
  • edit — modify the commit content
  • drop — remove the commit entirely

Analogy: Interactive rebase is like editing a photo album — you can remove bad photos, combine similar ones, and rewrite captions before showing it to anyone.

Cherry-Pick — Selective Commits

Copy a specific commit from another branch without merging the whole branch:

git cherry-pick a1b2c3d   # applies that commit's changes here

# Multiple commits
git cherry-pick a1b2c3d d4e5f6g

# Stage changes without committing
git cherry-pick --no-commit a1b2c3d

When to use it: A bug fix was committed on feature-x but you need it on main now. Cherry-pick just that fix instead of merging the entire unfinished feature.

Branching Workflows

Feature Branch Workflow (Most Common)

git checkout -b feature-login    # create feature branch
# ... work, commit, work, commit ...
git checkout main
git pull                        # get latest main
git merge feature-login         # merge (or create PR on GitHub)
git branch -d feature-login     # delete local branch

Git Flow (Complex, for releases)

    flowchart LR
  main --> develop
  develop --> feature1
  develop --> feature2
  develop --> release
  release --> main
  main --> hotfix
  hotfix --> main
  hotfix --> develop
  

Git Flow uses develop as the integration branch, release branches for preparation, and hotfix branches for urgent production fixes. It’s heavy but structured — best for projects with scheduled releases.

Common Mistakes

1. Rebasing shared branches

Rebasing a branch others have pulled rewrites history. When they pull again, Git sees entirely different commits. The golden rule: only rebase before you push.

2. Rushing through conflict resolution

Blindly keeping one side or the other without understanding both changes. You might delete important code. Always check the merged result — run tests — before committing.

3. Forgetting to switch before creating a branch

git branch new-feature creates it from your current branch. If you’re on an old branch, new-feature starts from there, not from main. Always verify with git branch first.

4. Leaving stale branches everywhere

Old branches clutter git branch -a output. Delete merged branches with git branch -d <branch>. Use git branch -D <branch> to force-delete unmerged branches you’re sure about.

5. Using git merge when rebase is cleaner

To keep your feature branch up to date with main, git rebase main creates a linear history without merge commits. Use rebase for local feature branches; use merge for integrating into shared branches.

6. Cherry-picking without understanding implications

Cherry-pick creates a new commit with a new hash — even if the change is identical. The same edit can appear multiple times in history, which gets confusing during later merges.

Practice Questions

1. What is the difference between a fast-forward merge and a three-way merge?

Answer: Fast-forward happens when main hasn’t diverged — Git just moves the pointer forward. Three-way merge happens when both branches have new commits, requiring a merge commit to join the histories.

2. How do you resolve a merge conflict?

Answer: Edit the file to remove conflict markers (<<<<<<<, =======, >>>>>>>), keep the correct version, then git add the file and git commit.

3. When should you use git rebase instead of git merge?

Answer: Use rebase to keep a linear history when updating a local feature branch with changes from main. Never rebase commits that others have already pulled.

4. What does git cherry-pick a1b2c3d do?

Answer: It takes the changes from commit a1b2c3d on another branch and applies them as new changes to your current branch.

Challenge

Create a repo with two branches that modify the same line of the same file. Merge them, resolve the conflict, then use git log --graph to see the resulting history. Now recreate the scenario and use rebase instead — observe how the history differs.

FAQ

What is the difference between merge and rebase?
Merge creates a commit that joins two histories, preserving the exact timeline. Rebase replays commits on top of another branch for a linear, cleaner history. Merge preserves context; rebase keeps history tidy.
How do I undo a merge?
If you haven’t pushed: git reset --hard ORIG_HEAD. If pushed: git revert -m 1 <merge-commit-hash>. The -m 1 tells Git which parent to keep (usually your current branch).
What is a fast-forward merge?
When the target branch hasn’t diverged since you branched off, Git simply moves the branch pointer forward — no merge commit needed.
How do I delete a remote branch?
git push origin --delete feature-navbar removes it from the remote. Then delete locally with git branch -d feature-navbar.
Can I rename a branch?
git branch -m old-name new-name for local. For remote, delete the old name and push the renamed one: git push origin --delete old-name && git push -u origin new-name.
What should I do when rebase conflicts happen?
Resolve conflicts the same way as merge conflicts. After resolving, git add <file> then git rebase --continue. Or git rebase --abort to cancel the entire rebase.

Try It Yourself

Simulate a real team workflow:

# 1. Create and switch to a feature branch
git checkout -b feature-footer

# 2. Make changes
echo "<footer>Copyright 2026</footer>" >> index.html
git add index.html
git commit -m "Add footer to index page"

# 3. Switch back to main and make a conflicting change
git checkout main
echo "<footer>All rights reserved</footer>" >> index.html
git add index.html
git commit -m "Update footer text on main"

# 4. Merge — experience a conflict
git merge feature-footer
# CONFLICT in index.html

# 5. Resolve — edit index.html to keep one footer, remove markers
git add index.html
git commit -m "Resolve footer merge conflict"

# 6. Clean up
git branch -d feature-footer
git log --oneline --graph

What’s Next

Now that you can branch and merge, connect to the world with remotes:

TopicDescription
https://tutorials.dodatech.com/devops/git/git-remote/Push to GitHub, pull requests, SSH keys
https://tutorials.dodatech.com/devops/git/git-undo/Fix mistakes with reset, revert, stash
https://tutorials.dodatech.com/devops/git/git-advanced/Tags, hooks, submodules, signing

Related topics to explore:

What’s Next

Congratulations on completing this Git Branching tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro