If you've just read about the pendrive nightmare, you know the old way was chaos. But how does Git actually solve this in a real company environment?
This isn't a boring command reference. I'll walk you through Git the way you'll actually use it at work—from cloning the company repo to getting your code merged into production.
What makes this guide different:
- Structured around the actual workflow companies use
- Commands introduced when you need them (Just-in-Time Learning)
- Undo operations at every step (because mistakes happen)
- The "why" behind every command, not just the "what"
Let's get started.
Part 1: The Mental Model
What is Git?
Git is a distributed version control system that tracks changes in your code.
In plain English: Git is a time machine + collaboration manager for your code.
"Version Control System" means:
- It tracks every change you make to your code
- It remembers what your code looked like yesterday, last week, or last year
- It lets you go back to any previous version instantly
- It shows you exactly what changed between versions
"Distributed" means:
- Everyone on your team has a complete copy of the entire project history
- You don't need internet to work or save your progress
- If the server crashes, anyone can restore everything
- No single point of failure
Why Every Company Uses Git
Reason 1: Time Travel for Your Code
Remember Piyush with 23 "FINAL" folders? That never happens with Git.
# See all previous versions
git log --oneline
# Compare today with last week
git diff HEAD~7
# Go back to any version
git checkout abc123
Reason 2: Safe Collaboration
When two developers edit the same file, Git doesn't silently delete anyone's work. Instead:
- Developer A edits
checkout.jsand commits - Developer B edits
checkout.jsand tries to push - Git says: "Hold on! There are changes you haven't seen. Let me help you merge them."
- Both changes are preserved
Reason 3: Experiment Without Fear
Want to try a risky redesign? Create a branch, experiment freely. If it fails, delete the branch. If it works, merge it in. Main code stays safe either way.
Reason 4: Know Who Did What
git blame payment.js
# Output:
# abc123 (Piyush 2025-12-10) function processPayment() {
# def456 (Hitesh 2025-12-11) if (amount > 0) {
When a bug appears, you know exactly who to ask.
Reason 5: Work From Anywhere
Before the flight: git pull (get latest code)
On the flight: Work offline, commit progress
After landing: git push (sync with team)
No 100MB ZIP files. No airport WiFi struggles.
Part 2: Setup (Do This Once)
Configure Your Identity
Every commit you make will be stamped with this information:
# Set your name (appears in commits)
git config --global user.name "Your Name"
# Set your email (appears in commits)
git config --global user.email "your.email@company.com"
# Set default branch name to 'main'
git config --global init.defaultBranch main
# Verify your settings
git config --list
Why this matters: When something breaks, the team needs to know who wrote that code—not to blame, but to ask questions.
Part 3: The Company Workflow
This is how professional development teams work. Follow this flow for every feature you build.
Step 1: Clone the Main Repository
What it does: Downloads the complete project
(including all history) to your computer.
On GitHub, navigate to the main page of the repository.
Above the list of files, click Code.
To clone the repository using HTTPS, under "HTTPS",
click on copy icon.
To clone the repository using an SSH key, including
a certificate issued by your organization's
SSH certificate authority, click SSH, then click on copy icon.
To clone a repository using GitHub CLI, click GitHub CLI,
then click on copy icon.
Change the current working directory to the location
where you want the cloned repository.
Type git clone, and then paste the URL you copied earlier.
git clone https://github.com/USERNAME/REPOSITORY-NAME.git
Press Enter to create your local clone.
$ git clone https://github.com/USERNAME/REPOSITORY-NAME.git
> Cloning into `Spoon-Knife`...
> remote: Counting objects: 10, done.
> remote: Compressing objects: 100% (8/8), done.
> remove: Total 10 (delta 1), reused 10 (delta 1)
> Unpacking objects: 100% (10/10), done.
This creates:
project-name/
├── all project files
└── .git/ ← Complete history lives here
What happens:
- Creates a folder with the repository name
- Downloads all files and complete history
- Sets up a connection called "origin" pointing to the company repo.
- Checks out the default branch (usually
main)
UNDO: Cloned to wrong location?
Just delete the folder and clone again. Nothing on the server is affected.[!TIP]
SSH vs HTTPS: For repositories you'll push to frequently, use SSH:git clone git@github.com:company/repo-name.gitWhy? SSH uses key-based authentication, so you won't need to enter your password for every push. Set up SSH keys once, and Git remembers you.
Step 1.5: Understanding What You Just Cloned
Now that you have a real repository on your machine, let's understand how Git organizes your work.
Diagram: The Three Stages of Git
Think of it like working in an office:
Working Directory = Your desk with all your documents and tools (This is where you actively work on things — all files that exist on your computer.)
Staging Area = The stack of papers you put in the “ready to finalize” tray (You choose specific documents you want to prepare for submission or final processing.)
Repository = The archive cabinet where final approved documents are stored (This is the permanent record
Exploring the .git Folder
Look inside your cloned project. There's a hidden .git folder that contains everything:
# View the .git folder structure
ls -la .git/
# Output:
.git/
├── HEAD ← "You Are Here" marker
├── config ← Repository settings
├── refs/
│ ├── heads/ ← Local branch pointers
│ │ └── main ← Just a text file!
│ └── remotes/ ← Remote branch pointers
├── objects/ ← All your files and commits (compressed)
└── logs/ ← Reflog (your safety net)
Try this yourself:
# See what HEAD contains
cat .git/HEAD
# Output: ref: refs/heads/main
# See what the 'main' branch points to
cat .git/refs/heads/main
# Output: a1b2c3d4e5f6g7h8i9j0... (40-character commit hash)
[!NOTE]
A branch is just a 40-character text file. That's it. Branches are incredibly lightweight—creating one takes microseconds.
Diagram: Local Repository Structure
┌──────────────────────────────────────────────────┐
│ LOCAL REPOSITORY STRUCTURE │
└──────────────────────────────────────────────────┘
📁 my-project/
│
├── 📄 index.html ┐
├── 📄 style.css ├── Your working files (visible)
├── 📄 app.js ┘
│
└── 📁 .git/ ← HIDDEN FOLDER (all the magic)
│
├── HEAD ← "You are on: main"
│ └─→ refs/heads/main
│
├── refs/heads/
│ ├── main → abc123 ← Branch pointer (text file)
│ └── feature → def456 ← Another branch
│
├── objects/
│ ├── ab/c123... ← Commit objects
│ ├── de/f456... ← Tree objects (folders)
│ └── 12/3abc... ← Blob objects (file contents)
│
└── logs/
└── HEAD ← Reflog: everywhere HEAD has been
What is HEAD?
HEAD is a pointer that tells Git "where you currently are."
Normal State (Safe):
HEAD ──→ refs/heads/main ──→ Commit abc123
(branch name) (actual commit)
When you make a new commit, the branch moves forward automatically:
Before:
HEAD ──▶ main ──▶ C ──▶ B ──▶ A
After:
HEAD ──▶ main ──▶ D ──▶ C ──▶ B ──▶ A
Detached HEAD State (Dangerous):
When you checkout a specific commit instead of a branch:
git checkout abc123 # A specific commit, not a branch name
Now HEAD points directly to the commit, bypassing the branch:
HEAD ──→ Commit abc123
(no branch involved!)
Why Detached HEAD is Dangerous
The Problem: In detached HEAD, if you make new commits, they have no branch protecting them.
Before (on main):
HEAD → main → C (where A ← B ← C is the history)
You checkout commit B (detached):
main → C
↑
A ← B ← C
↑
HEAD (detached - pointing directly to B)
You make a new commit D while detached:
main → C
↑
A ← B ← C
↑
D ← HEAD
You switch back to main:
main → C ← HEAD
↑
A ← B ← C
↑
D (ORPHANED! No branch points to it)
Where is D? It has NO REFERENCE pointing to it!
The Rule: Git deletes objects with zero references pointing to them.
| Reference Type | Example | Protects Commits? |
|---|---|---|
| Branch name |
main, feature-x
|
✅ Yes |
| Tag | v1.0.0 |
✅ Yes |
| HEAD (on a branch) |
HEAD → main → commit |
✅ Yes |
| Detached HEAD |
HEAD → commit directly |
❌ Only while you're there! |
The moment you leave a detached commit, it becomes eligible for garbage collection.
The Chain of Custody: Why Old Commits Survive
You might wonder: "If main only points to the latest commit, why aren't old commits deleted?"
Answer: Commits point backward to their parents.
main ──→ C ──→ B ──→ A ──→ (initial)
│ │ │
parent parent parent
Git's garbage collector runs a reachability check:
- Start at all branch pointers and tags
- Follow parent links backward
- Mark everything reachable as "alive"
- Delete anything not reachable
Reachability Check:
┌─────────────────────────────────────────────────────────────────┐
│ main → C → B → A │
│ ✓ ✓ ✓ All reachable from main = PROTECTED │
│ │
│ Orphan commit D (from detached HEAD) │
│ ✗ No branch or tag points to it = WILL BE DELETED │
└─────────────────────────────────────────────────────────────────┘
The 30-Day Grace Period
Good news: orphaned commits aren't deleted immediately.
Git's reflog keeps a record of everywhere HEAD has been for 30-90 days (depending on configuration).
git reflog
# Output:
e4f5g6h HEAD@{0}: checkout: moving from detached to main
d123456 HEAD@{1}: commit: my orphan commit ← Still recoverable!
abc123 HEAD@{2}: checkout: moving from main to abc123
Recovery:
# Find the orphan commit hash in reflog
git reflog
# Create a branch to save it
git branch rescue-my-work d123456
# Now it's protected!
[!TIP]
Reflog entries expire after 30 days for unreachable commits and 90 days for reachable ones. Recover lost work promptly!
Quick Fix: Escaping Detached HEAD
If you see this warning:
You are in 'detached HEAD' state...
Option 1: Create a branch to keep your work:
git checkout -b my-new-branch
# Now you're safe—a branch protects your commits
Option 2: Go back to an existing branch:
git checkout main
# Warning: Any commits you made while detached may be lost!
Step 2: Create Your Feature Branch
The Golden Rule: Never work directly on main. Always create a feature branch.
# First, make sure you have the latest main
git checkout main
git pull origin main
# Create AND switch to a new branch
git checkout -b feature/user-authentication
# Or the modern way (Git 2.23+)
git switch -c feature/user-authentication
Why branches?
main: ──●──●──●──●─────────────────●── (stable, production-ready)
\ /
your-feature: ●──●──●──●──●──●──── (your work, isolated)
- Your experiments won't break the main code
- Multiple features can be developed simultaneously
- Easy to abandon failed experiments
- Clean history of what changed for each feature
Branch Naming Conventions:
| Prefix | Use For | Example |
|---|---|---|
feature/ |
New features | feature/dark-mode |
bugfix/ |
Bug fixes | bugfix/login-timeout |
hotfix/ |
Urgent production fixes | hotfix/security-patch |
UNDO: Created branch with wrong name?
git branch -m old-name new-name
Step 3: Work on Your Feature
This is where you spend most of your time. The cycle is: Edit → Stage → Commit → Repeat.
📁 Real-World Example: Building a Login Page
Let's walk through a complete example with actual files and terminal output.
Starting Point: You've created a feature branch and are ready to work.
# You're on your feature branch
git branch
# main
# * feature/login-page
Step A: Create a new file
Create login.html:
<!-- login.html -->
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Login to Your Account</h1>
<form>
<input type="email" placeholder="Email" />
<input type="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
</body>
</html>
Check what Git sees:
git status
# Output:
On branch feature/login-page
Untracked files:
(use "git add <file>..." to include in what will be committed)
login.html
nothing added to commit but untracked files present
Git sees the new file but isn't tracking it yet (red text if using colors).
Step B: Stage the file
git add login.html
git status
# Output:
On branch feature/login-page
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: login.html
The file is now in the staging area (green text), ready to be committed.
Step C: Commit the file
git commit -m "feat: add basic login page structure"
# Output:
[feature/login-page 3f2a1b9] feat: add basic login page structure
1 file changed, 14 insertions(+)
create mode 100644 login.html
Step D: Modify an existing file
Now add some CSS. Edit login.html:
<!-- login.html (modified) -->
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<link rel="stylesheet" href="login.css"> <!-- NEW LINE -->
</head>
<body>
<h1>Login to Your Account</h1>
<form>
<input type="email" placeholder="Email" />
<input type="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
</body>
</html>
And create login.css:
/* login.css */
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
padding-top: 100px;
}
button {
background: #007bff;
color: white;
padding: 10px 20px;
border: none;
cursor: pointer;
}
Check status:
git status
# Output:
On branch feature/login-page
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
modified: login.html
Untracked files:
login.css
Step E: See exactly what changed
git diff login.html
# Output:
diff --git a/login.html b/login.html
index 3f2a1b9..8c4d2e1 100644
--- a/login.html
+++ b/login.html
@@ -3,6 +3,7 @@
<head>
<title>Login</title>
+ <link rel="stylesheet" href="login.css">
</head>
Lines starting with + are additions, - are deletions.
Step F: Stage and commit both files
# Stage both files
git add login.html login.css
git status
# Output:
On branch feature/login-page
Changes to be committed:
modified: login.html
new file: login.css
# Commit
git commit -m "style: add CSS styling to login page"
# Output:
[feature/login-page 7d4e2f3] style: add CSS styling to login page
2 files changed, 18 insertions(+), 1 deletion(-)
create mode 100644 login.css
Step G: View your commit history
git log --oneline -3
# Output:
7d4e2f3 (HEAD -> feature/login-page) style: add CSS styling to login page
3f2a1b9 feat: add basic login page structure
9abc123 (origin/main, main) Previous commit on main
Diagram: Commit History Flow
Now let's look at each individual command in detail:
3.1 Check Status (Do This Constantly!)
git status
# Output shows:
# - Modified files (changed but not staged)
# - Staged files (ready to commit)
# - Untracked files (new files Git doesn't know about)
Pro tip: Compact status format
git status -s
# Output:
# M src/App.js # Modified, not staged
# M src/index.js # Modified and staged
# ?? src/NewFile.js # Untracked
# A src/AddedFile.js # New file, staged
Why use -s? When you're experienced, the full status output is verbose. -s gives you the same info in one line per file.
Make this a habit. Run git status before and after every operation. It's your dashboard.
3.2 Stage Your Changes
git add moves changes to the staging area—preparing them for commit.
Why staging exists: You might change 10 files but only want to commit 3 of them as one logical change. Staging lets you curate your commits, keeping each one focused on a single purpose.
# Stage specific file (when you want precise control)
git add src/login.js
# Stage multiple files
git add src/login.js src/auth.js
# Stage all changes in current directory
git add .
# Stage part of a file (interactive—powerful!)
git add -p src/login.js
Choosing the Right Staging Command:
| Command | What It Does | When to Use |
|---|---|---|
git add <file> |
Stages one specific file | When you want precise control over what's committed |
git add . |
Stages all changes in current directory and subdirectories | Quick staging when all changes belong together |
git add -A |
Stages all changes in entire repository | When you made changes in multiple directories |
git add -u |
Stages only already-tracked files | When you don't want to add new files |
git add -p |
Interactive staging (select parts of files) | When you made multiple unrelated changes in one file |
Why git add -p is powerful:
Imagine you fixed a bug AND added a new feature in the same file. With -p, you can commit them separately:
git add -p src/user.js
# Git shows each change and asks:
# y = stage this chunk
# n = don't stage this chunk
# s = split into smaller chunks
# q = quit
Real-world scenario: You're working on login and accidentally fix a typo in a comment. Stage the login code first, commit it, then stage and commit the typo fix separately. Clean history!
UNDO: Staged wrong file?
git reset src/wrong-file.js # or (Git 2.23+) git restore --staged src/wrong-file.jsWhy two commands?
git restoreis newer (Git 2.23) and clearer in intent. Both work the same, butrestoreexplicitly says "I'm restoring the staged state."
3.3 Review What You're About to Commit
# See unstaged changes
git diff
# See staged changes (what will be committed)
git diff --staged
# Output shows:
# - lines (deleted)
# + lines (added)
Always review before committing. This prevents "wait, did I change that?" moments.
3.4 Commit Your Changes
A commit is a permanent snapshot of your staged changes.
git commit -m "feat: add user login form validation"
Writing Good Commit Messages:
# ❌ BAD - tells nothing
git commit -m "fixed stuff"
git commit -m "update"
git commit -m "wip"
# ✅ GOOD - clear and searchable
git commit -m "fix: resolve login timeout on slow connections"
git commit -m "feat: add dark mode toggle to settings"
git commit -m "docs: update API authentication examples"
Conventional Commit Format:
| Type | When to Use |
|---|---|
feat: |
New feature |
fix: |
Bug fix |
docs: |
Documentation only |
style: |
Formatting (no code change) |
refactor: |
Code restructuring |
test: |
Adding tests |
chore: |
Maintenance tasks |
Why good messages matter: Six months later, you'll search through history to find when a bug was introduced. Would you rather search through "fixed stuff" or "fix: validate email format before API call"?
UNDO: Made a mistake in last commit?
# Fix the message git commit --amend -m "correct message here" # Add a forgotten file to last commit git add forgotten-file.js git commit --amend --no-edit[!WARNING]
Don't amend commits you've already pushed to a shared branch!
3.5 Stash: Temporary Work Storage
Scenario: You're mid-feature, but need to switch branches for an urgent bug fix. You can't commit half-done work.
# Save your work temporarily
git stash push -m "WIP: login form halfway done"
# Your working directory is now clean
git switch main
# ... fix the bug, commit, push ...
# Return to your feature
git switch feature/user-authentication
# Restore your work
git stash pop
# Your half-done work is back!
Stash Commands:
| Command | What It Does | When to Use |
|---|---|---|
git stash |
Stash all changes | Quick save when you need to switch immediately |
git stash push -m "desc" |
Stash with description | Recommended — you'll know what's in each stash |
git stash list |
Show all stashes | See what you've stashed before |
git stash pop |
Restore AND delete the stash | When you're sure you want to apply it |
git stash apply |
Restore but KEEP the stash | When you might need the same stash on multiple branches |
git stash drop |
Delete without restoring | When you no longer need a stash |
Why pop vs apply?
- Use
pop(90% of the time): You stashed, switched, fixed the bug, and now you're back. Pop and continue. - Use
apply(10% of the time): You have a utility change you want to apply to multiple branches. Apply keeps the stash for reuse.
UNDO: Popped wrong stash?
The stashed changes are now in your working directory. If you need to undo, just stash again or usegit checkout -- .to discard.
3.6 View History
# See commit history
git log
# Compact one-line format
git log --oneline
# Last 5 commits with graph
git log --oneline --graph -5
# History of specific file
git log -- src/login.js
UNDO: Need to discard ALL changes and start fresh?
git checkout -- . # Discard unstaged changes # or (Git 2.23+) git restore .
Step 4: Sync with Main (The Critical Step)
While you were working, your teammates pushed changes to main. Your branch is now outdated. Before pushing, you must sync.
Why this matters: If you try to merge outdated code, conflicts explode during the PR. The person who wrote the code (you) is best equipped to resolve conflicts, not the reviewer.
[!IMPORTANT]
The Golden Rule of Conflicts: You should never leave conflicts for the Senior Dev or Maintainer to solve. They don't know why you changed a specific line of code. If they try to fix your conflict, they might accidentally delete your new logic.
Two Approaches: Rebase vs Merge
The Train Track Analogy (from company-workflow.md):
| Approach | Visualization | History Result |
|---|---|---|
| Rebase | Pick up your train car and place it at the front of the train | Single straight line |
| Merge | Build a side track that loops out and joins back | Loop/diamond shapes ("bubbles") |
Why it matters: If 20 developers all use merge constantly, the history graph looks like a tangled bowl of spaghetti. Rebase keeps it clean.
| Rebase | Merge | |
|---|---|---|
| Command | git rebase main |
git merge main |
| History | Linear (straight line) | "Bubbles" (diamonds) |
| Result | Looks like you just wrote code on latest main | Preserves when parallel work happened |
| Team Preference | Modern teams prefer | Some teams require |
Approach A: Rebase (Preferred for Clean History)
# 1. Switch to main and get latest
git checkout main
git pull origin main
# 2. Switch back to your branch
git checkout feature/user-authentication
# 3. Rebase onto main
git rebase main
What rebase does:
BEFORE REBASE:
(main moved forward while you worked)
↓
main: A ── B ── C ── D
↑
└── E ── F ← your-branch (branched from B)
AFTER REBASE:
main: A ── B ── C ── D
↑
└── E' ── F' ← your-branch
Your commits E and F are "replayed" on top of D (latest main).
E' and F' are NEW commits with different hashes but same changes.
If conflicts occur during rebase:
# Git stops and shows conflicted files
git status
# 1. Open each conflicted file
# 2. Look for conflict markers:
<<<<<<< HEAD
code from main
=======
your code
>>>>>>> your-commit
# 3. Edit to keep what you want, remove markers
# 4. Stage the resolved file
git add resolved-file.js
# 5. Continue rebase
git rebase --continue
UNDO: Rebase going badly?
git rebase --abort # Cancels rebase, returns to before you started
Approach B: Merge (When Rebase is Banned)
Some teams forbid rebase because it rewrites history. In that case:
# While on your feature branch
git merge main
What merge does:
BEFORE MERGE:
(main moved forward while you worked)
↓
main: A ── B ── C ── D
↑
└── E ── F ← your-branch (branched from B)
AFTER MERGE (you run 'git merge main' on your branch):
main: A ── B ── C ── D ─────────────────┐
↑ ↓
└── E ── F ────────────── M ← your-branch
(merge commit)
M is a "merge commit" with TWO parents: F (your work) and D (main).
This creates a "diamond" or "bubble" in history.
Resolving merge conflicts: Same process as rebase—edit files, remove markers, git add, then git commit.
UNDO: Merge going badly?
git merge --abort # Cancels merge, returns to before you started
Step 5: Push to Remote
Your branch is synced with main and conflicts are resolved. Time to upload.
# First-time push (sets upstream tracking)
git push -u origin feature/user-authentication
# Subsequent pushes
git push
If you rebased after already pushing:
Rebase rewrites commit history. If you pushed before rebasing, you need to force push:
git push --force-with-lease origin feature/user-authentication
[!CAUTION]
--force-with-leaseis safer than--force. It fails if someone else pushed to your branch, preventing you from overwriting their work.[!CAUTION]
NEVER force push tomainor any shared branch! This rewrites history for everyone.UNDO: Pushed wrong code?
For pushed commits, use
git revert(safe—creates an "undo" commit):git revert abc123 # Creates new commit that undoes abc123 git pushDon't use
git reseton pushed commits—it rewrites history others depend on.
Step 6: Create Pull Request (PR)
On GitHub/GitLab, create a Pull Request to merge your branch into main.
A Good PR Includes:
- Clear title describing the change
- Description of what and why
- Screenshots for UI changes
- Links to related issues
What Happens Next:
- Teammates review your code
- They may request changes
- You make changes, commit, push (branch auto-updates PR)
- Once approved, PR is merged
Step 7: Squash and Merge
This is typically done by the approver (senior dev or team lead), not you.
What "Squash and Merge" does:
YOUR PR COMMITS:
E: "feat: add login form"
F: "fix: typo in label"
G: "style: adjust button padding"
H: "fix: forgot validation"
AFTER SQUASH:
S: "feat: add user authentication (#123)"
All your messy WIP commits become one clean commit on main.
Why squash: Main branch history stays clean and readable. Each commit represents one complete feature, not a stream of consciousness.
Step 8: Cleanup
After your PR is merged:
# Switch to main
git checkout main
# Get the merged changes
git pull origin main
# Delete local feature branch (it's merged, you don't need it)
git branch -d feature/user-authentication
# Delete remote feature branch (optional, GitHub can auto-delete)
git push origin --delete feature/user-authentication
Why -d vs -D?
| Flag | What It Does | When to Use |
|---|---|---|
-d (lowercase) |
Safe delete — only works if branch is fully merged | Normal cleanup after PR merge |
-D (uppercase) |
Force delete — deletes even if not merged | Abandoning a failed experiment |
Reasoning: Use -d by default. Git will refuse if you'd lose unmerged work. Only use -D when you intentionally want to discard unmerged commits.
Congratulations! You've completed the full company workflow. Repeat for every feature.
Part 4: Recovery and Safety Net
Mistakes happen. Git has your back.
The Reflog: Your Time Machine
The reflog tracks everywhere HEAD has been—even "deleted" commits.
git reflog
# Output:
e4f5g6h HEAD@{0}: reset: moving to HEAD~3
a1b2c3d HEAD@{1}: commit: my important work ← "deleted" commit is here!
9876543 HEAD@{2}: commit: previous work
Recover "lost" commits:
# Find the commit hash in reflog
git reflog
# Reset to that point
git reset --hard a1b2c3d
# Your work is back!
[!NOTE]
Reflog entries expire after 30 days for unreachable commits (like orphans) and 90 days for reachable commits. Recover lost work promptly!
The Complete Undo Cheatsheet
# ─────────────────────────────────────────────────────────────
# UNSTAGE A FILE (after git add, before commit)
# ─────────────────────────────────────────────────────────────
git reset <file>
# or (Git 2.23+)
git restore --staged <file>
# ─────────────────────────────────────────────────────────────
# DISCARD CHANGES IN A FILE (before staging)
# ─────────────────────────────────────────────────────────────
git checkout -- <file>
# or (Git 2.23+)
git restore <file>
# ─────────────────────────────────────────────────────────────
# UNDO LAST COMMIT (keep changes staged)
# ─────────────────────────────────────────────────────────────
git reset --soft HEAD~1
# ─────────────────────────────────────────────────────────────
# UNDO LAST COMMIT (keep changes unstaged)
# ─────────────────────────────────────────────────────────────
git reset HEAD~1
# ─────────────────────────────────────────────────────────────
# UNDO LAST COMMIT (DELETE changes completely) ⚠️
# ─────────────────────────────────────────────────────────────
git reset --hard HEAD~1
# ─────────────────────────────────────────────────────────────
# UNDO A PUSHED COMMIT (safe - creates "undo" commit)
# ─────────────────────────────────────────────────────────────
git revert <commit-hash>
# ─────────────────────────────────────────────────────────────
# RECOVER "LOST" COMMITS
# ─────────────────────────────────────────────────────────────
git reflog
git reset --hard <commit-from-reflog>
Understanding Reset Modes
Moves HEAD? Clears Staging? Clears Working Dir?
--soft ✓ ✗ ✗
--mixed (default) ✓ ✓ ✗
--hard ✓ ✓ ✓ ⚠️ DANGEROUS
Reset vs Revert: When to Use Which
| Scenario | Use | Why |
|---|---|---|
| Undo local commit (not pushed) | git reset |
Safe to rewrite local history |
| Undo pushed commit | git revert |
Creates undo commit, preserves history |
| Nuclear option (reset to remote) | git reset --hard origin/main |
Last resort |
Part 5: Common Scenarios
"I committed to the wrong branch!"
# You're on main but should be on feature branch
# You just committed here by mistake
# 1. Create the correct branch (with your commit)
git branch correct-branch
# 2. Reset main back to before your commit
git reset --hard HEAD~1
# 3. Switch to correct branch
git checkout correct-branch
# Your commit is now on the correct branch!
"I have uncommitted changes and need to switch branches!"
# Option 1: Stash (if you want to keep changes)
git stash push -m "WIP: description"
git checkout other-branch
# ... do work ...
git checkout original-branch
git stash pop
# Option 2: Commit (if changes are ready)
git add .
git commit -m "WIP: save progress"
git checkout other-branch
"I'm in detached HEAD state!"
This happens when you checkout a specific commit instead of a branch.
# You see: "You are in 'detached HEAD' state"
# If you want to keep working here, create a branch:
git checkout -b my-new-branch
# If you want to go back to a branch:
git checkout main
"My branch is way behind main and has tons of conflicts!"
# Option 1: Incremental rebase (less overwhelming)
git rebase main
# Resolve conflicts one commit at a time
# Option 2: Squash your commits first, then rebase
git rebase -i HEAD~10 # Squash your 10 commits into 1
git rebase main # Now only 1 commit to conflict-resolve
Part 6: Quick Reference Card
Daily Commands
git status # What's changed?
git add . # Stage all
git commit -m "message" # Save snapshot
git push # Upload to remote
git pull # Download and merge
git log --oneline -10 # Recent history
Branch Operations
git branch # List branches
git checkout -b new-branch # Create and switch
git checkout main # Switch to main
git merge feature-branch # Merge into current
git branch -d old-branch # Delete branch
Sync Operations
git fetch origin # Download without merging
git pull origin main # Download and merge
git rebase main # Replay commits on main
git push -u origin branch # First push with tracking
git push --force-with-lease # Force push (after rebase)
Undo Operations
git restore <file> # Discard unstaged changes
git restore --staged <file> # Unstage
git reset --soft HEAD~1 # Undo commit, keep staged
git reset HEAD~1 # Undo commit, unstage
git reset --hard HEAD~1 # Undo and delete ⚠️
git revert <commit> # Create undo commit
git reflog # Find lost commits
Stashing
git stash # Save temporarily
git stash pop # Restore and delete
git stash list # Show all stashes
What You've Learned
✅ The Mental Model: Git as time machine + collaboration manager
✅ The Three Stages: Working Directory → Staging → Repository
✅ The .git Folder: What HEAD, refs, and objects actually are
✅ The Company Workflow: Clone → Branch → Work → Sync → Push → PR → Merge → Cleanup
✅ Undo Operations: Reset, revert, restore, and reflog for every situation
✅ Best Practices: Conventional commits, branch naming, never force-push to main
Next Steps
- Practice the workflow on a real project
- Make mistakes intentionally and practice recovering
-
Use
git statusconstantly until it's second nature - Read error messages—Git's errors are actually helpful
Pro tip: Every expert developer was once confused by Git. The difference is they kept practicing.
You've got this! 🚀
Have questions? Found this helpful? Let me know in the comments below!






Top comments (0)