Git Workflow πŸ”€: Best Practices for Your Development Process

Scenario:

  • You have a main branch (main).
  • You create feature branches (e.g., feature-x) for development.
  • You want to keep your history clean and structured.

1️⃣ git merge β†’ Use When You Want to Keep Full History (Default Approach)

πŸ“Œ What It Does?

  • Combines feature branch commits into main, keeping all commit history.
  • Creates a new merge commit (Fast-forward if no diverging commits).
  • Easy to understand but can clutter history with many small commits.

πŸ“Œ When to Use?

βœ… If you want a full commit history, including every small commit.
βœ… When working in teams where visibility of each commit matters.

πŸ“Œ Example (Merging feature-x into main):

git checkout main
git pull origin main   # Ensure main is up to date
git merge feature-x
git push origin main

2️⃣ git rebase β†’ Use When You Want a Clean, Linear History

πŸ“Œ What It Does?

  • Moves (reapplies) all commits from the feature branch on top of the latest main.
  • No merge commit β†’ keeps history linear and clean.
  • Can cause conflicts if multiple people are rebasing.

πŸ“Œ When to Use?

βœ… When working solo and want a clean, linear history.
βœ… Before merging a feature branch to avoid unnecessary merge commits.
βœ… If you regularly update your feature branch with main.

πŸ“Œ Example (Rebasing feature-x onto main Before Merging):

git checkout feature-x
git pull origin main   # Ensure main is up to date
git rebase main        # Moves feature branch commits on top of the latest main

If there are conflicts, resolve them, then:

git rebase --continue

Then merge into main:

git checkout main
git merge feature-x   # Fast-forward merge (clean)
git push origin main

3️⃣ git squash β†’ Use When You Want a Single Commit for a Feature

πŸ“Œ What It Does?

  • Combines multiple commits in the feature branch into one commit before merging.
  • Keeps history very clean but loses commit granularity.

πŸ“Œ When to Use?

βœ… If a feature branch has many small commits (e.g., fix typo, refactor, debugging).
βœ… When you want one clean commit per feature.
βœ… If your team follows a “one commit per feature” policy.

πŸ“Œ Example (Squashing Commits Before Merging into main):

Step 1: Squash commits interactively

git checkout feature-x
git rebase -i main

Step 2: Mark commits to squash

You’ll see:

pick abc123 First commit
pick def456 Second commit
pick ghi789 Third commit

Change all but the first pick to squash (s), like this:

pick abc123 First commit
squash def456 Second commit
squash ghi789 Third commit

Save and exit.

Step 3: Push the squashed commit

git push origin feature-x --force  # Required after rewriting history

Step 4: Merge into main

git checkout main
git merge feature-x
git push origin main

Another git squash example. Suppose you have created a branch for adding a feature and you do 2 more commits one for refactoring and other for commenting on the feature. Now you have 3 commits in the feature-x branch.

Now you think better I would commit these 3 commit together and push it as one feature commit. Now what you do? follow the bel0w steps:

# this tells git you want to do some action upon the last 3 commits
git rebase -i HEAD~3

Now you can see 3 commits in your default editor prefixed: pick. Change the last 2 commit pick to squash and save the file. Now Another file popup in the editor to change the commit message for this one squashed commit. Ignore the messages start with # . Keep/modify the commit message which is not starting with # and save the file. This rewrites the history and save as one commit.

Now check the commit history:

git log --oneline

Checkout git HEAD in our post: https://railsdrop.com/2025/04/06/git-best-practices-git-head-useful-commands-commit-message/

Which One to Use in Your Development Process?

ScenarioUse merge?Use rebase?Use squash?
Merging a feature branchβœ… Yes (keeps history)βœ… Yes (cleaner history)βœ… Yes (one commit per feature)
Keeping history linear❌ Noβœ… Yesβœ… Yes
Keeping every commitβœ… Yesβœ… Yes❌ No (loses small commits)
Team collaborationβœ… Yes (safer)⚠️ Be careful (can rewrite history)βœ… Yes (if team agrees)
Avoiding merge commits❌ Noβœ… Yesβœ… Yes

Recommendation for Your Workflow πŸ”€

For daily work, keep your feature branch updated with main:

git checkout feature-x 
git pull origin main 
git rebase main

Before merging into main, If you want full history:

git merge feature-x

If you want a clean history:

git rebase main

If you want a single commit per feature:

git squash

Merge into main and push:

git checkout main 
git merge feature-x 
git push origin main

πŸ€” What’s the Problem with Merge Commits?

A merge commit happens when you run:

git merge feature-x

and Git creates an extra commit to record the merge.

Example merge commit:

commit abc123 (HEAD -> main)
Merge: def456 ghi789
Author: You <you@example.com>
Date:   Sat Mar 29 12:00:00 2025 +0000

    Merge branch 'feature-x' into 'main'

Why Do Some Developers Avoid Merge Commits?

1️⃣ Cluttered History

  • Merge commits pollute history when feature branches have multiple commits.
  • Running git log shows lots of merge commits, making it harder to track actual code changes.
  • Example of messy history with merge commits:
    * Merge branch 'feature-x' (Merge Commit)
    * Fix typo in error message
    * Add validation for user input
    * Implement user authentication
    * Merge branch 'feature-y' (Merge Commit)
    * Fix UI issue

2️⃣ Harder to Track Changes

  • git log gets filled with merge commits instead of meaningful commits.
  • git blame might point to a merge commit, making debugging harder.

3️⃣ Confusing Graph with Many Branches

  • Running git log --graph --oneline --all shows a complex branching history:
    * abc123 Merge branch ‘feature-x’
    |\
    | * ghi789 Fix authentication bug
    | * def456 Add new login method
    | 123456 Merge branch ‘feature-y’
    |/
  • If you rebase instead, the history stays linear.

πŸ› οΈ When Do Devs Avoid Merge Commits?

1️⃣ When They Want a Clean, Linear History

βœ… Use git rebase instead of git merge to avoid merge commits.
Example:

git checkout feature-x
git rebase main
git checkout main
git merge feature-x  # No merge commit (fast-forward merge)

πŸ”Ή This way, the commits from feature-x appear directly on top of main.

2️⃣ When They Squash Multiple Commits into One

βœ… Use git rebase -i main to combine multiple commits into one before merging.
Example:

git checkout feature-x
git rebase -i main  # Squash commits
git checkout main
git merge feature-x  # No unnecessary commits

πŸ”Ή This results in one clean commit per feature.

3️⃣ When Working on a Team with Many Developers

  • If multiple people merge their branches without rebasing, Git history gets full of merge commits.
  • Teams using trunk-based development or feature branch workflows often rebase instead of merging.

βœ… Solution β†’ Always rebase before merging:

git checkout feature-x
git pull --rebase origin main
git push origin feature-x

When Are Merge Commits Actually Useful?

❌ Merge commits are not always bad! Sometimes, they are necessary:

  1. Merging Long-Lived Feature Branches
    • If a branch exists for weeks or months, it has many commits.
    • A merge commit documents when the feature was merged.
  2. Merging Changes from a Release Branch
    • Example: Merging release-v1.0 into main should have a merge commit.
  3. Handling Complex Conflicts
    • When resolving big merge conflicts, a merge commit shows exactly when conflicts were fixed.

πŸ”‘ Final Answer: When to Avoid Merge Commits?

🚫 Avoid merge commits when you want a clean history
βœ… Use rebasing (git rebase) before merging to keep history linear.
βœ… Use squashing (git rebase -i) to avoid unnecessary commits in a feature branch.

πŸ”Ή If you’re working solo or in a small team, rebase before merging to keep history simple.
πŸ”Ή If you’re working in a large team with long-lived branches, merge commits may be useful for tracking.


πŸ”₯ Finally Best Practices for Your Development Process

Before merging, update your branch without a merge commit:

git checkout feature-x
git pull --rebase origin main

Squash unnecessary commits (optional):

git rebase -i main

Merge the branch (fast-forward, no merge commit):

git checkout main
git merge feature-x  # Fast-forward merge, no merge commit

This keeps your history clean and easy to read!

Unknown's avatar

Author: Abhilash

Hi, I’m Abhilash! A seasoned web developer with 15 years of experience specializing in Ruby and Ruby on Rails. Since 2010, I’ve built scalable, robust web applications and worked with frameworks like Angular, Sinatra, Laravel, Node.js, Vue and React. Passionate about clean, maintainable code and continuous learning, I share insights, tutorials, and experiences here. Let’s explore the ever-evolving world of web development together!

2 thoughts on “Git Workflow πŸ”€: Best Practices for Your Development Process”

Leave a comment