🌱 Introduction

Most devs say they know Git because they can push projects to github.

I’ve seen the opposite.

They know just ENOUGH git to break projects confidently.

A git add . here, a quick commit there… trusting AI agents and suddenly the project turns into something nobody wants to touch.

I’ve done that too, early on.

The difference is that you eventually get tired of fixing avoidable problems.

That’s when Git stops being a checklist of commands and starts becoming a way of working.

Most are trapped in so-called AI agents to clean their messes.

But their granny AI agent is confused too because their Git history looks like a clown show.

These 12 git commands I actually rely on when things get messy or when I want to keep things clean from the start.

Nothing fancy. Just practical stuff that saves time.

📘 Before we start (optional but important)

Git is a tool. it handles history. Clean Code handles everything inside that history. Developers don’t fail because of Git. They fail because the code itself becomes way too HARD to manage. If you fix that part early, Git becomes your oblige coworker.

👉 Clean Code Zero to One

use code: POWER40 40% OFF for one week.


Now put your seat belt on. Grab a black coffee and let's get serious 🚗

🎂 1) Rebasing

AI code reviewers and merge queues DO NOT babysit premature branches.

They expect discipline.

most so called git expert stack commits like this:

fix
fix again
testing
oops
final-final-v3

Then they open a PR (pull request) and hope it works.

Unfortunately it doesn’t. Machine has no emotion.

🍄‍🟫 What rebase actually is

Look at the image.

Bottom \= old commits
Top \= latest main
Dog \= your feature branch

Right now your branch is behind.

The dog is sitting somewhere in the middle.

But he wants the top.

So what does he do?

He cLimbs.

Step by step.

And then he sits on top of the latest main.

That’s rebase.

You don’t build sideways.
You don’t stay behind.

You move your work to the top.

Clean. Straight. Done.

🤔 You ask why?

Look, modern teams use AI merge queues, automated reviewers, async PR pipelines.

These systems are NOT your mother.

THey don’t review your failure.

AI pipelines don’t fix your alienated history.

They see this:

random commits
unclear changes
outdated branch

🙅‍♂️ And they reject it.

Not because you’re bad.

Because your git history is turmoil. 🧐🌡️

WitH rebase you fix everything before PR.

Now merge is simple.

🍨 Rebase workflow

This is what you actually do:

```
git switch feature-login
git fetch origin
git rebase origin/main

git add .
git rebase --continue

git push --force-with-lease
```

Enter fullscreen mode

Exit fullscreen mode

🏗️ Code demonstrations for beginners:

1) 🧗‍♂️ Switch to your feature branch \= git switch feature-login

You move into your own work area.

2) ⛅ Get latest updates from remote \= git fetch origin

You look at what changed on the server using fetch.

3) 🛟 Rebuild your work on top of latest main \= git rebase origin/main

This is the core moment.

Git takes your commits and replays them one by one on top of the newest main.

So instead of:

messy branch history
outdated base

You now get:

clean linear history
same work, new foundation

If conflicts happen, Git stops here.

4) 🚤 Fix conflicts (if any)

git add . git rebase --continue

Enter fullscreen mode

Exit fullscreen mode

This means:

you fix the files
you tell Git: “okay continue”

Git keeps replaying your commits until everything is applied.

5) Push updated history \= git push --force-with-lease

Important part:

You are not just pushing code.

You are rewriting history.

So Git says:

“Only push if nobody else changed this branch while you were working.”

That’s what --force-with-lease protects you from.

It checks if someone else updated the branch.

If yes, it stops you.

So you don’t destroy their work.

again, rebase changes history.

That’s why you don’t just --force push blindly.


🪦 2) Reset, Revert, Restore

Mistakes will happen.

That’s normal.

Panicking is optional.

Some developers break things… then freeze.

Others fix it in 10 seconds and move on.

The difference is knowing which command to use.

Git gives you three weapons to reverse damage:

git revert HEAD git reset --hard HEAD~1 git restore --staged app.js git restore app.js

Enter fullscreen mode

Exit fullscreen mode

[ code demonstration is below ]

1) 🦺 git revert \| safe undo

You don’t delete anything.

git revert HEAD \= Makes a new commit that cancels the last one.

History stays clean.

Nothing is hidden.

This is what serious teams want.

2) 🗑️ git reset \| delete it completely

This one is different.

It moves your branch back.

The commit is gone.

Like it never happened.

git reset --hard HEAD~1 \= Deletes the last commit and your changes.

Fast. Clean. But dangerous.

If someone already pulled that commit… you just created a big mess.

Use it when you’re alone on the branch.

3) 🗃️ git restore \| fix files only

This doesn’t touch history.

It just fixes your working files.

git restore --staged app.js \= Removes app.js from staging (undo git add). Your edits stay untouched.

git restore app.js \= Deletes your changes. File goes back to last commit.

🧠 Concretely:
revert → undo safely
reset → erase history
restore → fix files

Three different jobs.

Where people mess up?

They use reset when they should use revert.

Now history is broken.

Team is confused.

Time is wasted.

🦐 2026 RRR Best Practices

git switch and git restore replaced the overloaded confusing git checkout. This isn’t optional anymore.

Modern teams would love to see you using them.

Old messy git checkout command did everything:
Switch branches. Restore files. Confuse everyone.

Now it’s split into two:

git switch: move between branches
git restore: fix files / undo file changes

ITs much cleaner and accurate.

That’s why you will never see me use checkout in this article.

It’s outdated.

Just like a serious dev who refuses to shallow AI hypes.

I will show you EXACTLY how they work in the next section (#3).


🫰3) Switch \& Restore

```
git switch main
git switch -c feature-payment

git restore README.md
git restore --staged utils.js
```

Enter fullscreen mode

Exit fullscreen mode

👉 git switch main

with that, you leave your current branch and return to mission control.

Before:
You’re on: bugfix/login-issue
You’ve got random work-in-progress edits.

After running:

git switch main

Enter fullscreen mode

Exit fullscreen mode

You are now on the main branch.

Your files instantly match the main branch’s state.

✍️ NOTE: Uncommitted edits may follow, unless switching would create conflicts, in which case Git stops you to avoid damage. That's protection.

either way, you’ve returned to home base. You’re back to clean ground..

👉 git switch -c feature-payment

Result:
A new branch is created which is feature-payment
And you immediately enter that branch.

Now your payment feature lives in isolation.

No risk of breaking main.
No overlap with other features.

🪜 One branch per mission.


👉 git restore --staged utils.js

This is your emergency fix when you accidentally run:

git add .

and stage files you didn’t want to include.

Fix it:

git restore --staged utils.js

Enter fullscreen mode

Exit fullscreen mode

Result:

utils.js is unstaged which is removed from the commit list.

But your edits remain safely in your working directory.

which means, you fix the mistake without losing a single line.


🐾 4) --amend Commits [Fix Without Noise]

You forgot a file?
You wrote a typo?
You missed a small edit?

You --amend.

🧱 What “--amend” actually DOES?

Amend means:

“Take my LAST commit and update it as if the mistake NEVER existed.”

It does NOT create a new commit. It replaces the previous one.

But most commits like this:

“fix again”
“final fix”
“real final fix”

Now history looks like garbage.

No worries.. you can time travel and --amend.

Real example (this happens all the time)

You accidentally committed .env

Sensitive keys. Big mistake.

You don’t say:

“oops let me fix it in next commit”

No.

You clean it properly:

git rm --cached .env echo ".env" >> .gitignore git commit --amend --no-edit

Enter fullscreen mode

Exit fullscreen mode

Done.

That commit never had .env.

⏹️ You remove the sensitive file
and rewrite the last commit like it never existed.

Let me explain..

git rm --cached .env 👉 This removes .env from Git tracking (not your computer)

Then:

echo ".env" >> .gitignore 👉 So you don’t repeat the same mistake

Now your changes are staged.

THEN you run:

git commit --amend --no-edit

🫡 This rewrites the last commit
👉 Same message
🦵 But WITHOUT the .env file

Why --no-edit?

Because you don’t want to waste time rewriting the message.

You’re saying:

“Same commit message. Just fix the mistake.”

🤔 What if you WANT to edit?

Then DO NOT use --no-edit.

Just do:

git commit --amend

Now Git opens the editor.

You can fix the message, explain what changed and make it more professional

Now let me illustrate an everyday use case scenario:

git commit --amend git rebase -i HEAD~5 --amend → fix your last mistake instantly rebase -i → squash, reorder, clean commits like a surgeon

Enter fullscreen mode

Exit fullscreen mode

This is how you turn:

“fixed stuff lol”

into [for example]:

“Refactor auth flow + fix token expiry bug”

Now your history makes sense.

⚠️ WARNING (read twice):

Only --amend local commits.

Never --amend commits already pushed to a team branch.

Why?

Because you rewrite history.

Automated pipelines, merge queues and AI agents HATE rewritten history.

🥍 Push amended commits ONLY if you're 100% sure NO ONE else has pulled them.


🏫 5) Git Log / Learn From History

Git doesn’t lie.

Your code can lie. Your memory can lie. Your teammates can lie.

nowadays devs zombie scroll through Git logs.

They don’t understand the POWER hidden in "history".

Good history \= easy debugging
Bad history \= endless suffering

⚖️ The 50/72 rule

Follow 50/72 Rule for efficient bug tracing AND debugging.

keep titles under 50 characters and wrap descriptions at 72.

write messages like "Add feature" instead of "Added feature".

and do not mix code refactors with new features in the same commit.

Bad example:

commit 1: "fixed stuff"
commit 2: "final update
commit 3: "changes lol.."

Good example:

commit 1: "fix(auth): resolve token expiry issue"

Now you know what happened without opening code.

Basic survival commands

git log --oneline
git log --oneline --graph --decorate
git log --oneline --author="Shahan"
git log --oneline -- path/to/file

What they really mean:

--oneline \= fast history scan + short, readable commits
--graph \= see branch chaos visually
--graph --decorate → visualize merges \& branches
--author \= see who broke what
-- path/to/file \= see only a single file’s history

This is how you stop guessing.

When things get serious you type
git log -p
git log -S "functionName"
git blame file.js
git reflog

Now you’re not reading history.

You’re hunting problems.

-p shows exact code changes
-S finds when a bug was introduced
git blame shows who touched each line
git reflog brings back “lost” commits


6) 🪝 Git Hooks to Automate Discipline

Junior devs hope code is fine.

Serious devs don’t hope anything.

They build systems that refuse bad code.

That’s what Git hooks are.

Hooks are simple:

They run automatically when you commit, push, or merge.

Without asking. Without reminders.

If something is wrong they stop you.

The idea is simple:

You try to commit.

Git says:

“Wait. Let me check that first.”

🔧 example of git hooks pre-commit (gatekeeper)

You create this file:

.git/hooks/pre-commit

Now you add rules:

```

!/usr/bin/env bash

echo "Running tests..."

npm test || {
echo "❌ Tests failed. Commit blocked."
exit 1
}
```

Enter fullscreen mode

Exit fullscreen mode

Then activate it:

chmod +x .git/hooks/pre-commit

Enter fullscreen mode

Exit fullscreen mode

What just happened?

You didn’t “add a tool.”

You added a guard at the door.

Now:

tests run automatically
failure \= commit rejected
broken code never enters history

Therefore, there's NO debate anymore.
No “I’ll fix it later” nap issue.

🪫2026 Git Hooks Reality

Nowadays most teams don’t hand-write hooks anymore.

They use tools:

they use Husky to manage hooks cleanly

or lint-staged to run checks only on changed files

Because raw hooks don’t scale well.

Tools make it consistent.

Important truth most tutorials skip

Local hooks are not secure.

Anyone can bypass them:

git commit --no-verify

And everything gets ignored.
So if you rely ONLY on local hooks you're mistaken.

So hooks alone are not enough.
Let me explain why..

Real system (what professionals use):

You combine two layers:

1. Local hooks

Fast feedback. Instant rejection.

2. CI/CD pipelines (GitHub Actions, etc.)

⚡Local \= speed
⚖️ Server \= enforcement

Local catches mistakes early.
Server enforces rules for everyone.

Together,nothing broken gets through.

You win.

🍋‍🟩🪞 7) Commit References

beginners copy full commit hashes like this:

a3f9c1d8b7e...

Long. Ugly. Useless in daily work.

You don’t need that noise.

🔧 Core Commands

git diff HEAD~2..HEAD git rev-parse --short HEAD git tag -a v2.0 -m "Release v2.0" git push origin v2.0

Enter fullscreen mode

Exit fullscreen mode

-- HEAD~2..HEAD \= compare recent changes

-- rev-parse --short HEAD \= quick reference (human-friendly ID)

-- tag -a \= mark a stable checkpoint (this matters)

-- push origin v2.0 \= share that checkpoint with the world

🧠 Understand this properly

HEAD \= where you are
HEAD~1 \= one step back
HEAD~2 \= two steps back

You’re not memorizing hashes anymore.

You’re jumping up/down in a timeline. its super productive.

🆔 Why Tags Actually Matter??

Tags are not decoration.

They are anchors.

When you say:

git tag -a v2.0 -m "Release v2.0"

Enter fullscreen mode

Exit fullscreen mode

You’re declaring:

“This version is stable. This is deployable.”

Now your CI/CD pipelines + Deployment systems and AI tools

All know exactly what to ship.

NOTE: ⚠️ Fix this weak idea

“Full hashes are sh$t”

No.

Blind usage is actual sh$t.

Full hashes are for finding exact location, scripting and debugging critical issues,

Short refs are for speed.

Know both.


8) 🧳Stash [Pause Work Without Panic]

Interruptions are coming.

Boss calls. Bug report drops. Production is on fire.

And you’re sitting there with half-written code…

Now what?

Commit garbage?
Lose your work?
Panic?

NO.. professionals don’t panic

They stash.

What stash actually is?

Stash is simple:

Save everything I’m doing right now… and bring me back to a clean state.

No commit. No history pollution.

Just pause.

EXAMPLE:

Let say you’re working on a feature.

Suddenly you recieved a message from colleage:

“URGENT: Fix login bug NOW.”

You don’t argue.

You run:

git stash push -m "WIP: profile fix"

Enter fullscreen mode

Exit fullscreen mode

Everything disappears.

Not deleted.

Just stored safely.

Your workspace becomes clean again.

Now switch safely

git switch main git pull

Enter fullscreen mode

Exit fullscreen mode

Now you fix the urgent issue without your half-work getting in the way.

See what you saved

git stash list

It’s like a small vault of paused work.

Nothing lost. Just parked.

Bring it back

When you’re ready:

git stash apply

Your work comes back exactly as it was.

Or:

git stash pop

Same thing, but removes it from stash after restoring.

Advanced move

git stash branch hotfix-auth stash@{0}

Enter fullscreen mode

Exit fullscreen mode

This does something smarter:

creates a new branch
restores your work there
keeps main branch clean

So you don’t mix emergency fixes with unfinished features.

No conflict with main work.

⚠️🛩️ Reality Check

Stash is NOT permanent storage.

It’s temporary.

Forget it… and it becomes a graveyard.🪦

So use it. Don’t hoard it.

When to use stash

Use it when:

you saves unfinished work safely and then switch tasks instantly

  • anytime create a branch from stash (stash branch) for experiment

In summary.. need to switch tasks mid-flow? Stash temporarily saves your changes.


9) Bisect 🐞

Manual bug hunting? WEAK MOVE.

Manual bug hunting is slow.

And slow means you suffer.

Bisect \= sniper rifle for bug hunting.

Think now: something breaks in production.

Login stops working.

No logs. No clear reason.

Only one truth:

It worked yesterday. It’s broken today.

And now you have 50+ commits in between.

Amateurs go to sleep.

Professionals starts hunting.

So what's the solution? you divide the problem.

you use bisect:

git bisect start # Begin the hunt git bisect bad # Current version is broken git bisect good v2.1 # Last known good version

Enter fullscreen mode

Exit fullscreen mode

How the process works

You test the code.

Then you answer only two things:

works \= git bisect good
broken \= git bisect bad

That’s it.

Git keeps cutting the search space in half.

Again. And again.

Why this is powerful?

50 commits becomes

\~6 checks.

its called the power of binary search.

🔥 Now Finish the Bug Hunting mission

When the bug is found:

git bisect reset

Enter fullscreen mode

Exit fullscreen mode

Back to normal.

Bug identified.

Target eliminated. ☠️

💡Tips:

You can automate the entire hunt:

git bisect run npm test

Enter fullscreen mode

Exit fullscreen mode

Now Git runs your tests automatically on each step.

No manual checking.

No human error.

You just sit back and watch it find the culprit like quillbot ;).

NOTE:

Bisect only works if:

you hAve a known good version
your tests actually work

If your tests are broken, bisect becomes useless.


10) 🧑‍🦯‍➡️ Don’t Merge Blindly

You’ve got a feature-cat branch.

It “works on your machine.”

Now you’re about to merge into main.

And you’re thinking:

“Should be fine.”

That sentence along has destroyed more codebases than bugs ever did.

Rule #1) Check What files are changing

git diff --name-only main..feature-cat

Enter fullscreen mode

Exit fullscreen mode

This is your first reality check.

It shows exactly what will be affected.

Rule #2) What actually changed

git diff --color-words main..feature-cat

Enter fullscreen mode

Exit fullscreen mode

Now you see the real story.

Not “files changed”.

But what inside those files changed.

This is where bad merges get exposed.

Two ways to compare branches:

git diff main..feature-cat git diff main...feature-cat

Enter fullscreen mode

Exit fullscreen mode

Two-dot vs Three-dot:

-- .. \= direct comparison (simple view)
-- ... \=true branch difference (REAL merge view)

Most people never learn this difference.

And that’s exactly why they merge wrong.

Before merge, you don’t ask:

“Will this work?”

You ask:

“What exactly am I about to change?”

To sum up, know the battlefield before the fight.

Compare first, merge second.


🫳 11) Cherry-Pick (pick ONLY What You Need)

Sometimes you only need ONE commit from another branch.

You Merging entire branches for one fix?

That’s how you import bugs you NEVER asked for.

lets cherry-pick..

Real Case scenario:

Your feature-login branch has 10 commits:

  • LoginUI.tsx redesign ❌
  • authStyles.css change UI ❌
  • useAuth.ts refactor ❌
  • ✅ Fix: token refresh bug (authService.ts)

Production is breaking.

Users getting logged out every 5 minutes.

You don’t need UI changes.
You don't need refactor.

BUT you need that ONE fix: token refresh bug in authService.ts

💆 Step 1: Find the exact commit

git log --oneline feature-login

Enter fullscreen mode

Exit fullscreen mode

You see:

a1b2c3d fix: refresh token expiry bug in authService f4e5d6c refactor: auth hooks 9a8b7c6 style: login UI update

Enter fullscreen mode

Exit fullscreen mode

You take the one that matters: 👉 a1b2c3d

🩺 Step 2: Surgical extraction

git checkout main git pull git cherry-pick a1b2c3d

Enter fullscreen mode

Exit fullscreen mode

Now only this change is applied:

authService.ts → FIXED Everything else → untouched

Enter fullscreen mode

Exit fullscreen mode

No UI risk. No side effects.

🔨 Step 3: Clean it like a pro

git cherry-pick -e a1b2c3d

Enter fullscreen mode

Exit fullscreen mode

Update message:

fix(auth): resolve token refresh logout issue (hotfix)

Now your history makes sense to your team + your CI/CD pipeline

🙎‍♂️What just happened after that 3 steps workflow??

-- Bug reported in production
-- You located the fix commit
-- You shipped ONLY the fix
-- You avoided merging unstable work

This is how senior SWE teams move fast without breaking things.

But hey.. this is also where juniors get destroyed

Cherry-pick can bite you if you have:

-- Same fix exists in two branches later
-- Merge conflicts when syncing branches
-- Duplicate commits + confusing history

You didn’t remove the problem.

You copied the solution.

Big difference.

🧠 So when cherry-pick is the RIGHT move?

Use cherry-pick when:

  • 🚨 Production hotfix
  • 🎯 One isolated commit
  • ⛔ Full merge is risky

Avoid when:

  • 🌆 You need long-term branch sync
  • ♾️ You’re patching randomly without plan

🧱 12) Git Add

If you think git mistakes happen during commits.

You're wrong.

They happen before that.

At staging.

You worked on:

  • login.js
  • profile.js
  • utils.js

Only login.js is ready.

But only amateurs do this:

git add .

Enter fullscreen mode

Exit fullscreen mode

And suddenly:

/ half-finished code enters history
/ unrelated changes get bundled
/ reviews become useless
/ AI gets confused
/ debugging becomes harder later

That’s how conflicts starts.

🧑‍⚕️Professional workflow

You don’t git add . everything.

You choose what deserves to exist in history.

git add -p login.js

Enter fullscreen mode

Exit fullscreen mode

👉 Stage only specific parts of a file
👉 Build clean, intentional commits

🔍 Preview before committing

git add -n .

Enter fullscreen mode

Exit fullscreen mode

👉 Shows what would be staged

⚠️ Advanced control (use carefully)

git add --force .env

Enter fullscreen mode

Exit fullscreen mode

👉 Used only when you intentionally need ignored files
👉 Dangerous if you don’t understand why

Staging is not “pre-commit.”

It is decision-making.

You are deciding:

“What deserves to become history?”

✍️ NOTE:

Amateurs commit everything and fix later. Professionals stage with intention and never create havoc in the first place.


Conclusion 🏁

Git is not complicated.

Most of the commands above aren’t even complicated.

They just force you to slow down at the right moments.

It becomes messy when you stop paying attention.

After a while, you stop thinking in commands and start thinking in states:

what changed, what matters, and what should actually be saved.

That’s basically it.

Hope you find this article useful. let me know in the comments section what you have learned so far. Its great to hear. Cheers 🥂


SPONSOR:
Powered by Bright Data.

Git take care of your code.
Bright Data take care of your data:
proxies, scraping, data pipelines, all handled.

Just clean access to data pipelines that actually work when you need them for you apps or websites.

You build. It runs on your behalf.