Advanced Git
Git Stash — Save Work in Progress
You're deep in a feature when a critical bug report comes in — you need to switch branches immediately, but your work isn't ready to commit. git stash is your escape hatch: it shelves your changes so you can switch contexts and come back later.
# Stash current uncommitted changes (tracked files) git stash # Stash with a descriptive message (highly recommended) git stash save "WIP: feature X — halfway through the auth logic" # Also stash untracked (new) files git stash -u # List all stashes git stash list # stash@{0}: WIP: feature X — halfway through the auth logic # stash@{1}: WIP on main: abc1234 fix: navbar color # Apply most recent stash (keeps it in the list) git stash apply # Apply AND remove from list (most common) git stash pop # Apply a specific stash by index git stash apply stash@{2} # Delete a specific stash git stash drop stash@{0} # Delete ALL stashes git stash clear
Interactive Rebase — Rewrite History
Interactive rebase lets you rewrite your commit history before pushing. This is how professionals keep their PRs clean: squash messy "WIP" commits into meaningful ones, fix typos in commit messages, and reorder commits for logical clarity.
--force-with-lease carefully.# Rebase the last 3 commits interactively git rebase -i HEAD~3 # This opens your configured editor with something like: # # pick a1b2c3d feat: add user model # pick d4e5f6g WIP: testing stuff # pick h7i8j9k fix: forgot to save # # Change 'pick' to: # squash (s) — merge into previous commit, combine messages # fixup (f) — merge into previous commit, discard this message # reword (r) — keep commit but edit the message # drop (d) — completely remove this commit # # Example: squash all 3 into one # pick a1b2c3d feat: add user model # fixup d4e5f6g WIP: testing stuff # fixup h7i8j9k fix: forgot to save # # Save and close editor. Git will combine the commits. # You'll be prompted to write the final commit message.
squash keeps the squashed commit's message and prompts you to combine them — great when both messages have useful info. fixup discards the squashed commit's message entirely and just uses the top commit's message — perfect for "fix typo", "WIP", and "oops" commits.Cherry-Pick — Apply Specific Commits
git cherry-pick copies a specific commit from anywhere in history and applies it to your current branch. It's like saying "I want exactly this change, and nothing else from that branch."
# Apply a single commit to current branch git cherry-pick a3f5b2c # Apply multiple commits git cherry-pick a3f5b2c d7e9f1a # Apply a range of commits (from..to, exclusive of first) git cherry-pick a3f5b2c..d7e9f1a # Cherry-pick without committing (stage only) git cherry-pick -n a3f5b2c # If a conflict arises during cherry-pick: # 1. Resolve the conflict in your editor # 2. Stage the resolved file git add conflicted-file.js # 3. Continue git cherry-pick --continue # Or abort entirely: git cherry-pick --abort
Common cherry-pick scenarios: A critical bug was fixed in a feature branch, but main needs it now without the unfinished feature. A hotfix needs to go to both main and a release/v2 branch. You accidentally committed to the wrong branch and want to move just that commit.
Resetting Commits — Three Modes
git reset moves HEAD (and the branch pointer) backwards in history. The three modes control what happens to your working directory and staging area after the reset. Choosing the wrong mode can mean lost work.
| Mode | Commits | Staging Area | Working Directory | Safety |
|---|---|---|---|---|
--soft |
Moved | Unchanged (staged) | Unchanged | ✅ Safe |
--mixed (default) |
Moved | Cleared (unstaged) | Unchanged | ⚡ Careful |
--hard |
Moved | Cleared | Cleared (lost!) | ❌ Destructive |
# --soft: Undo last commit, keep changes staged # Use when: you want to recommit with a better message or add more files git reset --soft HEAD~1 # --mixed: Undo last commit, keep files but unstage them # Use when: you want to review/edit before re-staging git reset HEAD~1 # (same as --mixed which is the default) # --hard: Undo last commit AND discard all changes # Use when: you want to completely throw away the last commit git reset --hard HEAD~1 # --hard to a specific commit hash git reset --hard a3f5b2c # Undo last 3 commits, keep code in working dir git reset --soft HEAD~3
git reset --hard discards all uncommitted changes with no trash bin, no undo. The only way to recover is git reflog (covered next). Before running any --hard reset, consider using --soft or --mixed first — or at least stash your changes as a safety net.Git Reflog — Your Safety Net
The reflog (reference log) records every movement of HEAD in your repository — even after resets, rebases, branch deletions, and other "destructive" operations. It's Git's hidden undo history and your safety net when things go wrong.
# View the reflog (last 90 days of HEAD movements) git reflog # Output example: # a3f5b2c HEAD@{0}: reset: moving to HEAD~1 # d7e9f1a HEAD@{1}: commit: feat: add login page # b2c4e6f HEAD@{2}: commit: feat: add user model # Recover a commit you "lost" with reset --hard # Find the commit hash in reflog, then: git checkout d7e9f1a # You're now in detached HEAD at that commit # Recover to a new branch from that point git switch -c recovery/lost-commit # Or just cherry-pick the recovered commit into your current branch git cherry-pick d7e9f1a # Recover a deleted branch git reflog | grep "feat/deleted-branch" git switch -c feat/deleted-branch d7e9f1a
git reflog the "oh no" command — it's saved countless disasters. Accidentally deleted a branch? Lost a rebase? git reflog will find it.Git Tags — Marking Releases
Tags mark specific points in history as important — typically software releases. Unlike branches, tags don't move when you add commits. They're permanent markers: "this commit is version 1.0.0".
# Annotated tag (recommended — stores tagger, date, message) git tag -a v1.0.0 -m "Release 1.0.0 — initial public release" # Lightweight tag (just a name pointing to a commit) git tag v1.0.0-beta # Tag a specific past commit git tag -a v0.9.0 a3f5b2c -m "Pre-release v0.9.0" # List all tags git tag git tag -l "v1.*" # filter pattern # Show tag details git show v1.0.0 # Push a specific tag to remote git push origin v1.0.0 # Push ALL tags to remote git push origin --tags # Delete a local tag git tag -d v1.0.0-beta # Delete a remote tag git push origin --delete v1.0.0-beta
Examples:
v2.0.0 = breaking API change. v1.3.0 = new feature added. v1.3.1 = bug fix. Start at v0.1.0 for pre-stable releases.Git Aliases & Config
Git aliases let you create shortcuts for commands you type constantly. A well-configured .gitconfig file can save hundreds of keystrokes per day. Aliases live in your global config at ~/.gitconfig.
# Add aliases via command line git config --global alias.st status git config --global alias.co checkout git config --global alias.br branch git config --global alias.lg "log --oneline --graph --decorate --all" git config --global alias.undo "reset --soft HEAD~1" git config --global alias.unstage "reset HEAD --" # Or edit ~/.gitconfig directly — [alias] section: # [alias] # st = status # co = checkout # br = branch -a # lg = log --oneline --graph --decorate --all # undo = reset --soft HEAD~1 # save = !git add -A && git commit -m 'SAVEPOINT' # wip = !git add -u && git commit -m 'WIP' # Other useful global settings git config --global core.editor "code --wait" # use VS Code git config --global push.default current # push to same-name branch git config --global pull.rebase true # rebase on pull git config --global init.defaultBranch main # default branch name
git stash do?git reset --hard HEAD~1 does what?git cherry-pick a3f5b2c applies:git reflog shows:1. Create a new repo or use an existing one:
mkdir adv-git-challenge && cd adv-git-challenge && git init2. Create commit A: add
index.html with git commit -m "feat: add index"3. Create commit B: edit index.html,
git commit -m "WIP: adjusting layout"4. Create commit C: edit again,
git commit -m "fix: typo"5. Run
git log --oneline — you should see all 3 commits6. Run
git rebase -i HEAD~3 — change B and C to fixup in the editor7. Save and close — B and C squash into A
8. Run
git log --oneline — now there's just one clean commit9. Tag it:
git tag -a v0.1.0 -m "Initial release"10. Verify:
git show v0.1.0
💡 Show hints
- If VS Code is your editor, the rebase file opens as a tab — edit, save, and close that tab
- Leave the top
pickline unchanged — only change the second and third lines tofixup - If you get stuck mid-rebase:
git rebase --abortto start over git log --oneline --graphgives a nice visual of the history after squashing