Git is more than a version control system. Once you move beyond the basics, it becomes a tool for managing complex histories, recovering from mistakes, and collaborating with multiple developers safely. This guide focuses on intermediate and professional Git usage. It explains not only commands but also why and when to use them, illustrated with real scenarios and diagrams.
1. Understanding Git Internals
Before diving into advanced workflows, it is crucial to understand what Git actually tracks. Many people think Git stores changes, but in reality, Git stores snapshots of your project at each commit.
Key Concepts
- Commit: A snapshot of all files in your project. Each commit points to a parent commit, forming a chain.
- HEAD: A pointer to the current branch tip. Your working copy is based on HEAD.
- Branches: Lightweight pointers to commits. Moving a branch pointer does not alter commits.
- Reflog: A history of where HEAD has pointed. This is your ultimate safety net for recovering lost work.
Scenario
You accidentally commit a new feature to the wrong branch. Instead of losing your work or manually copying files, you can move the commit using cherry-pick.
| |
| |
Commit D is on feature. Cherry-pick moves it safely to main.
2. Cleaning Up History
2.1 Interactive Rebase
Imagine you made five small commits for a single feature. Reviewers find the history messy. Interactive rebase lets you clean it up.
| |
An editor opens showing:
| |
You can change pick to squash to combine commits or drop to remove trivial commits. After saving, the branch will have a clean, concise history.
Scenario: You made several tiny commits while experimenting. Instead of showing all of them to your team, you squash them into a single meaningful commit:
| |
This shows reviewers only the important changes.
| |
X combines D and E into one meaningful commit.
2.2 Commit Amend
If you forgot to add a file to your last commit, you can amend it without creating a new commit.
| |
This updates the previous commit to include the new changes. Be careful if you already pushed, because this rewrites history.
Scenario: You committed login.py but forgot auth.py. Instead of making another commit, you amend to keep history clean.
2.3 Reflog
Reflog tracks all movements of HEAD, even commits that seem lost.
| |
Output example:
| |
Recover lost work:
| |
Scenario: You ran git reset --hard HEAD~3 by mistake. Reflog lets you go back to where you were.
3. Branching and Collaboration
3.1 Feature Branch Workflow
Branches are used to isolate work. Each developer can work on a feature without affecting main.
| |
When ready, merge into main:
| |
| |
This keeps development organized and avoids conflicts.
3.2 Rebase vs Merge
If main advances while you work on a feature, you need to integrate changes.
Merge preserves the chronological history but adds a merge commit.
| |
Rebase creates a linear history by moving your commits on top of main.
| |
Rule: Never rebase public shared branches. Use rebase for private branches before merging.
4. Recovery and Debugging
4.1 Git Bisect
When a bug appears but you are not sure which commit introduced it, Git bisect helps find it efficiently.
| |
Git checks out the middle commit. You test and mark good or bad. Repeat until Git identifies the first bad commit.
Scenario: A regression appears in production. Bisect narrows down 500 commits to the single one that broke your code in only 9 steps.
4.2 Git Blame
Find out who last changed each line in a file:
| |
This shows the author and commit for every line. It is useful for understanding why a line exists or for debugging.
4.3 Recover Deleted Branch
| |
This restores a deleted branch. Git never truly deletes commits until garbage collection, so recovery is usually possible.
5. Stash and Worktrees
5.1 Git Stash
When you need to switch branches but your work is not ready to commit:
| |
Scenario: You are midway through a feature when a production bug needs a quick fix. Stash lets you save your work temporarily.
5.2 Git Worktree
Sometimes you need two branches checked out at the same time.
| |
You now have a separate folder with an independent checkout of main for hotfixes.
5.3 Sparse Checkout
If you work in a large monorepo:
| |
Only the frontend folder is checked out, saving disk space and time.
6. Cherry-Pick
Apply specific commits from another branch without merging the whole branch:
| |
Scenario: A hotfix commit exists on main. Cherry-pick allows you to bring just that commit into your feature branch.
7. Hooks and Automation
Git hooks allow scripts to run automatically:
.git/hooks/pre-commitcan run tests before a commit..git/hooks/pre-pushcan prevent pushing broken code.
Example:
| |
This stops commits if tests fail.
8. Force Push Safely
Avoid overwriting teammates’ work. Instead of --force:
| |
This ensures you only rewrite your own changes, not someone else’s.
9. Diff and Log Magic
| |
Scenario: During code review, you can use --word-diff to highlight exact changes in lines instead of entire lines.
10. Daily Professional Workflow
git fetch originto update local view of remote branches.git rebase origin/mainto keep your branch up to date.- Work on feature branch and commit regularly.
- Rebase interactively before opening a PR.
- Push with
--force-with-lease. - Open PR and wait for code review.
- Merge after review and cleanup if necessary.
Closing Notes
Honestly, Git clicked for me only after I broke something badly enough to need reflog. That is how most people actually learn it. You read the docs, nod along, and then one day you do a reset --hard on the wrong branch and suddenly every command in this guide becomes very real and very urgent. The fear of losing work is what finally makes you curious about how Git actually stores things under the hood.
The commands here are not a checklist to memorize. Think of them as tools you reach for when a specific situation demands them. You will not use git bisect every week, but the one time a subtle bug sneaks into production and you have no idea which of the last eighty commits introduced it, you will be very glad you know it exists. Same with worktrees — it sounds niche until you are in the middle of a long refactor and a critical bug drops in your lap and you just want a clean checkout without disturbing what you have already built.
A few things that have genuinely helped me over time:
- Write commit messages like you are explaining it to yourself six months from now. “Fix bug” is useless. “Fix null pointer in auth middleware when session token is missing” saves you a full context reload later.
- Rebase before you raise a PR, every single time. A clean linear history makes reviewing faster and
git logactually readable. Your teammates will notice, even if they never say it. - Do not be afraid of interactive rebase on your own branch. A lot of developers treat their local history as sacred. It is not. The point of
rebase -iis to shape your work into something coherent before it becomes someone else’s problem to review. --force-with-leaseis not optional if you rebase. Just make it muscle memory. The regular--forceis a footgun and there is no good reason to use it on a shared branch.- Treat reflog as your undo history. Git almost never deletes anything immediately. If something feels lost, check reflog before you start recreating work from scratch.
The real shift happens when you stop thinking of Git as a thing you do after writing code and start thinking of it as part of how you think about the work itself. Branching cheaply, committing often, cleaning up before sharing, these habits make you faster, not slower, because you stop being afraid of the codebase and start moving through it with confidence.