🌳 Deep Git Workflows: Let's Learn from ZERO!

Git is not just git add, git commit, and git push. 😄 When multiple developers work on the same project, you need to know:

  • 🔀 How to combine code from different branches
  • 🧹 How to keep a clean commit history
  • ⚔️ How to fix conflicts when two developers change the same code
  • ⏪ How to undo mistakes safely
  • 🏷️ How to manage versions and releases
  • 🏢 How professional teams organize their branches

We'll learn everything with simple real-world examples, commands, and practical scenarios. Let's go! 🚀

1. 🧱 First, Git Basics

Imagine you're building an Instagram-like Social Feed app:

SocialFeed/ │ ├── frontend/ │ ├── App.tsx │ └── Feed.tsx │ ├── backend/ │ ├── main.py │ └── auth.py │ └── README.md 

Git helps you track changes made to these files. Some important terms:

Term Simple meaning
📁 Repository (repo) Your project tracked by Git
📸 Commit A saved snapshot of your changes
🌿 Branch An independent line of development
🔀 Merge Combining changes from branches
🔁 Rebase Replaying commits on top of another branch
⚔️ Conflict Git cannot automatically combine some changes
👉 HEAD Pointer to your current commit
☁️ Remote A copy of your repo hosted elsewhere, like GitHub

🌿 What is a branch?

Your main app works perfectly, and you want to build a login feature without disturbing it. Instead of changing main directly, you create a separate branch:

git branch feature/login # create the branch git switch feature/login # move to it 

Now you can work independently, and your main branch stays unaffected until you integrate your changes.

📌 Important: Branches are lightweight pointers to commits. They don't usually create a complete separate copy of your project.

2. 🔀 Git Merge vs Git Rebase

🎯 Very important for interviews and real projects!

Both bring changes from one branch into another, but they work differently.

🎬 Scenario

Your team has a main branch. You create feature/login. Both initially point to commit C. Then:

  • You make commit D in feature/login
  • Another developer makes commit E in main
 D (feature/login) / A -- B -- C -- E (main) 

You want the latest main changes in your feature branch. Two approaches:

🅰️ Git Merge

Merge combines two branches while preserving their existing history.

git switch feature/login git merge main 

What happens:

  1. Git checks the changes in your feature branch
  2. Git checks the changes in main
  3. Git combines them
  4. It creates a merge commit if a normal merge requires one
 D ---- M / / A -- B -- C -- E 
  • D = your login feature commit
  • E = another developer's main commit
  • M = the merge commit connecting both histories

📚 Analogy: Two people write separate book chapters. You combine both chapters and preserve who wrote what and when.

✅ Advantages

  • Preserves complete development history
  • Doesn't rewrite existing commits
  • Safe for branches shared with other developers
  • Shows when two lines of development were combined

❌ Disadvantages

  • Can create many merge commits
  • History can get complicated when many branches are merged often

🅱️ Git Rebase

Rebase takes your commits and replays them on top of another branch's latest commit.

git switch feature/login git rebase main 

What happens:

  1. Git identifies the commits unique to your feature branch
  2. It temporarily sets those commits aside
  3. It moves your feature branch to the latest main commit
  4. It replays your feature commits on top

Before rebase:

 D (feature/login) / A -- B -- C -- E (main) 

After rebase:

A -- B -- C -- E -- D' (feature/login) 

Notice D becomes D'. Git creates a new commit with the same changes but a different commit hash.

📚 Analogy: You wrote your chapter based on an old draft. Your colleague updates the main book. Instead of attaching your old chapter on the side, you reapply it on top of the updated book.

✅ Advantages

  • Cleaner, linear history
  • Avoids unnecessary merge commits
  • History is easier to read
  • Great before merging feature branches

❌ Disadvantages

  • Rewrites commit history
  • Can cause problems if others already use those commits
  • Conflicts may need resolving while replaying commits

⚖️ Merge vs Rebase: Comparison

Feature Merge Rebase
History Preserves history Rewrites commit history
Merge commit Often created Not created by rebase itself
Commit hashes Existing commits remain Replayed commits get new hashes
Shape Can be non-linear Usually linear
Shared branches Generally safe Risky if commits are shared
Main purpose Combine histories Reapply commits on a new base

🧠 Remember: Merge = preserve history. Rebase = cleaner, rewritten history.

Use merge when:

  • You're combining changes from shared branches
  • You want to preserve the complete history
  • Your team prefers explicit merge records

Use rebase when:

  • You're updating your own feature branch with the latest main
  • You want a clean commit history
  • Your commits haven't been shared with others

🚨 Golden rule: Never casually rebase commits that other developers have already based their work on.

3. 🧹 Interactive Rebase

Suppose you made these commits while building login:

1. Created login component 2. Fixed login button 3. Fixed login button again 4. Added validation 5. Small CSS correction 

Showing this to your mentor? Messy! 😬 You may want to combine, reorder, edit, or remove commits. That's interactive rebase: it lets you edit your commit history.

git rebase -i HEAD~5 
  • git rebase → rebase operation
  • -i → interactive mode
  • HEAD~5 → the last 5 commits (including HEAD)

Git opens an editor with something like:

pick a123 Created login component pick b234 Fixed login button pick c345 Fixed login button again pick d456 Added validation pick e567 Small CSS correction 

You edit the instructions. Here are the important commands:

🅰️ pick: keep the commit as is

pick a123 Created login component 

🅱️ squash: combine with the previous commit (keep both messages)

pick a123 Created login component pick b234 Fixed login button squash c345 Fixed login button again 

Result could be: Created login component and fixed login button. Use it to turn many small commits into one meaningful commit.

🅲 fixup: combine, but discard the later message

pick a123 Created login component fixup b234 Fixed login button 
  • squash = combine changes and messages
  • fixup = combine changes, discard the later message

🅳 reword: change only the commit message

reword a123 login changes 

Old: login changes → New: Implemented user login API. The code stays the same.

🅴 edit: pause the rebase at that commit

edit a123 Created login component 

Then modify the code or amend:

git add . git commit --amend git rebase --continue 

🅵 drop: remove the commit

drop b234 Fixed login button 

⚠️ Dropping a commit also removes its changes from the resulting history unless they exist elsewhere.

🅶 Reorder commits

Just change the order of the lines:

# Before pick a123 Created login component pick b234 Added validation pick c345 Added styling # After pick c345 Added styling pick a123 Created login component pick b234 Added validation 

Git tries to replay them in the new order. Reordering can cause conflicts if later commits depend on earlier ones.

📋 Interactive rebase summary

Command Meaning
pick Keep commit
reword Change commit message
edit Pause and modify commit
squash Combine commits and messages
fixup Combine commits, discard later message
drop Remove commit

💡 Practical example: squash five small auth commits into one clean commit: Implemented authentication with login validation.

4. ⚔️ Resolving Git Merge Conflicts

🎯 Extremely important in real projects!

A merge conflict happens when Git cannot automatically decide how to combine changes from different branches. Git is good at combining changes, but sometimes it needs human help.

🎬 Real-life example

main has this in auth.py:

def login(): return "Login successful" 

Another developer changes that line in main:

def login(): return "Welcome back!" 

You change the same line in your feature branch:

def login(): return "User authenticated successfully" 

Now you run:

git merge main 

Both branches changed the same part of the same file. Git doesn't know your intention, so it reports a conflict. 💥

👀 How does a conflict look?

Git edits your file like this:

def login(): <<<<<<< HEAD return "User authenticated successfully" ======= return "Welcome back!" >>>>>>> main 
Marker Meaning
<<<<<<< HEAD Start of your current branch's version
======= Separates the two versions
>>>>>>> main End of the incoming branch's version
  • Above ======= → your current branch's change
  • Below ======= → the incoming branch's change

🛠️ How to resolve it

Say you want to keep your version. Manually delete the three marker lines and keep the final correct code:

def login(): return "User authenticated successfully" 

Then:

git add auth.py # tells Git you resolved this file git commit # completes the merge commit 

🔁 Resolving a conflict during a rebase

# 1. Open the conflicted file and fix the code # 2. Mark it as resolved git add auth.py # 3. Continue the rebase git rebase --continue 

Want to cancel?

git rebase --abort # cancel a rebase git merge --abort # cancel a merge 

⚠️ HEAD and incoming changes: during a normal merge, HEAD is your current branch and the incoming version comes from the branch you're merging. During a rebase, Git is replaying commits, so the terminology can be confusing. Don't blindly assume ours and theirs mean what you expect.

🛡️ How to prevent conflicts

  1. Frequently pull or rebase the latest changes from main
  2. Communicate with teammates about which files you're modifying
  3. Avoid unnecessarily large changes in one branch
  4. Keep commits small and focused
  5. Resolve conflicts early, not at the end

💡 Remember: A conflict is not a Git error. It's Git asking you to make a decision.

5. 🗄️ Git Stash

You're working on login and have changed:

frontend/Login.tsx backend/auth.py 

Suddenly your mentor says: "Please switch to main and fix this production bug right now!" 😱 But your changes are unfinished, and you don't want to commit incomplete code.

📌 Git stash temporarily stores your uncommitted changes so you get a clean working directory. Think of it as putting unfinished work into a temporary drawer 🗄️.

A. Save your changes

git stash git switch main # now you can switch safely 

B. View your stashes

git stash list 
stash@{0}: WIP on feature/login stash@{1}: WIP on feature/profile 

(WIP = Work In Progress)

C. Restore your changes

git stash apply # restore the latest stash, KEEP it in the list git stash pop # restore the latest stash, REMOVE it from the list 

D. Stash with a message

git stash push -m "Login feature work" 

E. Delete stashes

git stash drop stash@{0} # delete one stash git stash clear # delete ALL stashes (careful!) 

F. Stash untracked files too

By default, git stash does not include untracked files (like a brand-new backend/new_auth_service.py). Use:

git stash -u 

📋 Stash summary

Command Purpose
git stash Temporarily save tracked changes
git stash -u Also save untracked files
git stash list View saved stashes
git stash apply Restore but keep the stash
git stash pop Restore and remove the stash
git stash drop Delete a particular stash
git stash clear Delete all stashes

⚠️ Stash is temporary storage, not a permanent backup. Don't use it as a replacement for meaningful commits.

6. 🍒 Git Cherry-Pick

You're working on the profile feature. Your teammate works on login and makes three commits:

Commit A: Created login page Commit B: Fixed login validation Commit C: Updated login styling 

You only need the validation fix (B). You don't want to merge the whole login branch because it has other unfinished changes.

📌 Cherry-pick takes a specific commit from another branch and applies its changes to your current branch. Like picking one item from a basket instead of taking the whole basket. 🧺

Step 1: find the commit ID

git log --oneline 
c345 Updated login styling b234 Fixed login validation a123 Created login page 

Step 2: switch to your branch

git switch feature/profile 

Step 3: cherry-pick

git cherry-pick b234 

Git applies the changes from b234 to your current branch, creating a new commit with the same changes but a different hash.

Conflict? Resolve it manually, then:

git add . git cherry-pick --continue # or cancel: git cherry-pick --abort 

🆚 Merge vs Cherry-pick

Merge Cherry-pick
Combines branch histories Applies selected commit changes
Usually brings multiple commits Can bring a specific commit
Useful for integrating branches Useful for reusing individual fixes

💡 Real-world use: a teammate fixes a critical auth bug in one branch. You cherry-pick just that fix into a release branch without merging their unfinished work.

7. ⏪ Git Revert vs Git Reset

🎯 Must know before working on shared repositories!

You accidentally committed bad code and want to undo it. Git offers two major approaches, and they work very differently.

🅰️ Git Revert

Revert creates a new commit that reverses the changes of an earlier commit. It does not delete the original commit.

Before: A -- B -- C -- D (D introduced a bug) git revert D After: A -- B -- C -- D -- E (E undoes D) 
  • D = the original incorrect change
  • E = the new commit that undoes D

The original history is preserved. This is especially useful on shared branches like main.

git revert <commit-id> 

🅱️ Git Reset

Reset moves your current branch pointer (HEAD) to an earlier commit. Depending on the mode, it also changes your staging area and working directory.

First, the three Git areas:

Area Simple meaning
📝 Working directory Where you edit your actual files
🎭 Staging area Where you prepare changes using git add
📦 Repository Where your commits are stored

1️⃣ Soft reset

git reset --soft HEAD~1 
  • Removes the latest commit from branch history
  • Keeps changes staged
  • Your code is preserved

Use when: you want to combine the last commit with other changes into a new commit.

2️⃣ Mixed reset (the default)

git reset --mixed HEAD~1 
  • Removes the latest commit
  • Unstages the changes
  • Keeps your code in the working directory

Use when: you want to modify some files before committing again.

3️⃣ Hard reset 🚨

git reset --hard HEAD~1 
  • Moves HEAD back one commit
  • Resets the staging area
  • Resets tracked files in your working directory to match the target commit

🚨 Destructive! You can permanently lose uncommitted work, and recovering discarded commits is not guaranteed.

📊 Soft vs Mixed vs Hard

Reset type Commit Staging area Working directory
Soft Removed from branch Changes kept Changes kept
Mixed Removed from branch Changes unstaged Changes kept
Hard Removed from branch Reset Reset

🧠 Easy memory trick:

  • Soft: keep everything staged
  • Mixed: keep the code, unstage it
  • Hard: discard tracked changes to match the target commit

⚔️ Revert vs Reset: the biggest difference

Feature Revert Reset
How it works Creates a reversing commit Moves the branch pointer
Original commit Preserved Removed from current branch history
History Preserved Rewritten
Shared main branch Usually safe ✅ Risky ⚠️
Best for Undoing shared changes Reorganizing local history

Which one should you use?

  • Pushed a faulty commit to the team's main? → git revert <commit-id>, because others may already have the original commit.
  • Made an accidental commit locally and haven't pushed? → git reset --soft HEAD~1 (or another mode).

🏆 Golden rule: shared/published history → prefer revert. Your own unpublished local history → reset can be useful.

📝 HEAD~1 means the first parent of the current commit. Merge commits have multiple parents, so reverting a merge commit needs extra care, usually git revert -m.

8. 🏷️ Git Tags: Tagging Releases

Your app evolves through milestones:

  • Version 1.0: basic feed
  • Version 1.1: login and authentication
  • Version 1.2: comments and likes
  • Version 2.0: major redesign

How do you mark these in Git? With tags.

📌 A Git tag is a label attached to a specific commit. It marks important points in history, especially software releases.

Commit A -- Commit B -- Commit C -- Commit D ↑ ↑ v1.0 v2.0 

A. Lightweight tag

git tag v1.0 

B. Annotated tag (preferred for releases)

git tag -a v1.0 -m "First stable release" 

It stores extra info: tag name, tagger's information, date, and message.

C. View tags

git tag 

D. Tag an older commit

git tag -a v1.0 <commit-id> -m "First stable release" 

E. Push tags to GitHub

Creating a tag locally doesn't send it to GitHub automatically.

git push origin v1.0 # push one tag git push origin --tags # push all tags 

F. Delete a tag

git tag -d v1.0 # delete locally git push origin --delete v1.0 # delete remotely 

🔢 Semantic Versioning (SemVer)

Versions like 1.2.3 follow MAJOR.MINOR.PATCH:

Part Meaning
1 MAJOR Breaking changes 💥
2 MINOR New compatible features ✨
3 PATCH Bug fixes 🩹
Version Meaning
1.0.0 First stable release
1.1.0 Added a new feature
1.1.1 Fixed a bug
2.0.0 Introduced breaking changes

🎯 Interview point: tags are labels for specific commits, whereas branches are movable pointers that normally advance as new commits are added.

9. 🏢 Git Branching Strategies

You work in a company with 10 developers: login, profile, notifications, feed, comments. If everyone changes main directly, chaos! 😵 Teams use branching strategies. Three popular ones:

  1. 🏛️ Git Flow
  2. 🐙 GitHub Flow
  3. 🚄 Trunk-Based Development

🏛️ A. Git Flow (structured)

A strategy with multiple long-lived and short-lived branches. Useful for projects with scheduled releases and separate development, testing, and production workflows. It has five branch types:

Branch Purpose
🟢 main Production-ready code. Only stable, released versions
🔵 develop Integration branch for upcoming features
🌿 feature/* One per feature (e.g. feature/login), created from develop and merged back into develop
📦 release/* Release preparation (e.g. release/1.0): final testing, bug fixes, version updates. Merged into main and usually back into develop
🚑 hotfix/* Critical production fixes (e.g. hotfix/login-security), created from main and merged into main and develop
main ●─────────────────────●─────────────●──────▶ \ ↗ (release) ↑ \ / │ hotfix develop ●──●──────●─────●──────────●──────●──────▶ \ ↗ ●──● feature/login 

(The diagram is conceptual. Real Git Flow merges create more detailed history.)

✅ Advantages

  • Clear separation between development and production
  • Suitable for planned releases
  • Supports hotfixes and release stabilization

❌ Disadvantages

  • More branches to manage
  • More complicated workflow
  • Can slow down teams that release frequently

🐙 B. GitHub Flow (simple and widely used)

Uses just:

  • main
  • Short-lived feature branches
  • Pull Requests (PRs)

How it works:

# Step 1: start from main git switch main git pull origin main # Step 2: create a feature branch git switch -c feature/login # Step 3: make changes and commit git add . git commit -m "Add login functionality" # Step 4: push your branch git push -u origin feature/login 

Step 5: create a Pull Request on GitHub. Teammates can review code, add comments, suggest improvements, request changes, or approve.

Step 6: merge the PR once it's approved and checks pass.

Step 7: delete the feature branch. 🧹

✅ Advantages

  • Simple workflow
  • Easy collaboration
  • Supports CI/CD
  • Less branch management

❌ Disadvantages

  • Requires good automated testing and code review
  • main must stay stable
  • Teams need to coordinate deployments and releases

🚄 C. Trunk-Based Development

The trunk is the main development line, usually main. Developers integrate small changes into main frequently instead of keeping long-lived feature branches. They may use very short-lived branches, or even commit directly to the trunk when the team's workflow permits.

git switch main git pull origin main git switch -c feature/login # Make small changes git add . git commit -m "Add login validation" git push -u origin feature/login 

The branch is reviewed and integrated quickly.

🚩 What are Feature Flags?

Your notification feature isn't ready for users, but you want its code in main. Keep it disabled with a flag:

ENABLE_NOTIFICATIONS = False if ENABLE_NOTIFICATIONS: show_notifications() 

When it's ready, you enable it. Real applications generally use proper configuration or feature-flag services instead of hardcoding flags.

✅ Advantages

  • Frequent integration
  • Fewer large, complicated merges
  • Faster feedback
  • Supports continuous delivery
  • Avoids long-lived branch problems

❌ Disadvantages

  • Requires strong automated testing
  • Developers must integrate small changes frequently
  • Incomplete features need careful isolation
  • Requires good CI/CD practices

📊 Compare all three

Feature Git Flow 🏛️ GitHub Flow 🐙 Trunk-Based 🚄
main branch Yes Yes Yes
develop branch Yes Usually no No
Feature branches Yes Yes Short-lived or none
Complexity High Low Low to moderate
Integration Around feature/release milestones Through PRs Very frequent
Best for Scheduled releases Web apps and teams using PRs Continuous delivery
  • Git Flow: more structured, multiple branches, scheduled releases
  • GitHub Flow: main + feature branches + Pull Requests
  • Trunk-Based: integrate small changes into main frequently

💡 For a typical small team building a web app, GitHub Flow is a straightforward starting point. Trunk-Based Development shines when you have strong automated testing and deployment practices.

10. 📖 Clean, Readable Git History as Documentation

One of the most underrated Git concepts! Imagine joining a company and opening its commit history:

1. changes 2. final changes 3. fixed 4. updated 5. test 6. again fixed 7. new changes 

Can you tell what was built? Probably not. 😵 Now compare:

1. Add user authentication API 2. Implement password hashing using Argon2 3. Add session expiration handling 4. Fix login validation for invalid credentials 5. Add cursor-based pagination to home feed 6. Implement post like and unlike endpoints 

Much better! ✨ That's why clean history matters.

✍️ What makes a good commit message?

git commit -m "Add user authentication API" 

A good message is short, meaningful, specific, and describes the change.

❌ Bad ✅ Good
changes Add user profile API
fixed Fix login validation error
updated code Add pagination to home feed
final Implement password hashing
test Add unit tests for auth service

⚛️ What is atomic committing?

Atomic = small and focused. One commit should ideally represent one logical change. Don't mash login API, profile API, a migration, UI styling, and a notification service into one giant commit. Instead:

Commit 1: Add login API Commit 2: Implement password hashing Commit 3: Add profile update endpoint Commit 4: Add profile validation tests 

This makes debugging and code review much easier.

🔍 Useful commands for reading history

git log # full history git log --oneline # compact history git log --oneline --graph --all # history as a graph git log --stat # which files changed per commit git log -p # actual changes per commit git show <commit-id> # inspect one commit 

🤔 Why is Git history "documentation"?

Someone asks: "Why did we change the authentication logic last month?" You search the history and find Fix session validation for expired access tokens, then inspect the commit to see exactly what changed. History helps answer:

  • What was changed?
  • When was it changed?
  • Why? (if the commit or PR explains the reason)
  • Which files were affected?
  • Which developer contributed it?

📝 Git history doesn't replace proper project documentation, but it's a valuable record of development decisions.

11. 🧵 A Real Team Workflow, Start to Finish

Your mentor says: "Implement the user profile update API." Here's a professional workflow:

Step 1: get the latest code

git switch main git pull origin main 

Step 2: create your feature branch

git switch -c feature/profile-update 

Step 3: develop your feature

backend/ api/ profile.py services/ profile_service.py schemas/ profile_schema.py 

Step 4: check your changes

git status # modified, staged and untracked files git diff # the actual changes 

Step 5: commit

git add . git commit -m "Add user profile update API" 

Step 6: update your branch if main has changed

git fetch origin git rebase origin/main 

git fetch downloads remote updates without integrating them. git rebase replays your local commits on top of the latest remote main. If there are conflicts, resolve them and run:

git add . git rebase --continue 

Step 7: push your feature branch

git push -u origin feature/profile-update 

If you rebased commits that were already pushed to your own feature branch, you may need:

git push --force-with-lease 

This is safer than --force because it checks that the remote branch hasn't moved unexpectedly. Still, coordinate with teammates before rewriting a shared branch.

Step 8: create a Pull Request

On GitHub: feature/profile-update → main. Your teammates review the changes.

Step 9: merge the Pull Request

After approval and successful checks, merge using your team's chosen merge method.

Step 10: tag the release

git tag -a v1.2.0 -m "Release version 1.2.0" git push origin v1.2.0 

🎉 You've followed a complete professional Git workflow!

12. 📋 Quick Revision: Important Git Commands

Requirement Command
Check current branch git branch --show-current
List branches git branch
Create branch git branch feature/name
Switch branch git switch branch-name
Create and switch git switch -c feature/name
Merge git merge main
Rebase git rebase main
Interactive rebase git rebase -i HEAD~5
Abort rebase git rebase --abort
Continue rebase git rebase --continue
View conflicts git status
Save changes temporarily git stash
Restore stash git stash pop
View stashes git stash list
Apply a specific commit git cherry-pick commit-id
Undo a commit safely git revert commit-id
Soft reset git reset --soft HEAD~1
Mixed reset git reset --mixed HEAD~1
Hard reset git reset --hard HEAD~1
View history git log --oneline --graph --all
Create release tag git tag -a v1.0 -m "Release"
Push a tag git push origin v1.0
Download remote updates git fetch origin
Push branch git push origin branch-name

13. 🎤 Interview Questions

Q1. What is the difference between Git merge and Git rebase?
Merge combines branches while preserving their existing history, often creating a merge commit. Rebase replays commits on top of another branch, creating new commit hashes and generally a linear history.

Q2. What is interactive rebase?
It lets us modify commit history. We can squash, reorder, edit, reword or drop commits.

Q3. What is a merge conflict?
It occurs when Git cannot automatically combine changes, usually because different branches modify overlapping parts of a file. We manually resolve the changes and mark the files as resolved.

Q4. What is Git stash?
It temporarily saves uncommitted changes and cleans the working directory so we can switch branches or work on something else.

Q5. What is cherry-pick?
It applies a specific commit from another branch to our current branch without merging the entire branch.

Q6. What is the difference between revert and reset?
Revert creates a new commit that reverses an earlier commit, preserving history. Reset moves the branch pointer to an earlier commit and can remove commits from the current branch history.

Q7. Explain soft, mixed and hard reset.

  • Soft: keeps changes staged
  • Mixed: keeps changes unstaged
  • Hard: discards tracked-file changes to match the target commit

Q8. What are Git tags?
Labels attached to specific commits, commonly used to mark software releases such as v1.0.0.

Q9. What is Git Flow?
A structured branching strategy using mainSource:https://dev.to/deepika_pusala/deep-git-workflows-rebasing-conflict-resolution-branching-strategies-explained-from-zero-15an