Advanced — Cheat Sheet
Git · 6 topics. Download the PDF or the Instagram carousel and share it.
Interactive Rebase — Rewrite History Like a Pro
git rebase -i lets you reorder, squash, edit, split, or drop commits before merging. It is the tool for turning a messy development history into clean, reviewable commits.
- ✓git rebase -i HEAD~N opens the last N commits for editing — reorder, squash, fixup, drop, reword
- ✓fixup silently discards the commit message and merges changes into the previous commit
- ✓squash merges changes AND combines both commit messages (prompts you to edit the result)
- ✓Use edit to split one commit into multiple: reset HEAD~1, then stage/commit in pieces, rebase --continue
- ✓Never interactive rebase commits already pushed to a shared remote branch
# Open interactive rebase for last 5 commits git rebase -i HEAD~5 # The editor opens with: # pick a3f8c12 Add user registration # pick 9d2e441 WIP login # pick 7b2e441 fix typo # pick 3d9f2e0 fix login properly # pick 2c1a8b7 add tests for login # # Commands: # pick = use commit as-is # reword = use commit but edit message # edit = stop to amend the commit # squash = meld into previous commit (keep both messages) # fixup = meld into previous commit (discard this message) ↠most common # drop = remove the commit entirely # reorder= just rearrange the lines # Modified: # pick a3f8c12 Add user registration # reword 9d2e441 WIP login ↠fix the message # fixup 7b2e441 fix typo ↠squash into previous # squash 3d9f2e0 fix login properly ↠squash, keep message # pick 2c1a8b7 add tests for login
Cherry-pick — Apply Specific Commits
git cherry-pick applies the changes from one or more specific commits onto your current branch, creating new commits with the same diff but different hashes.
- ✓cherry-pick applies the diff of a specific commit onto your current branch as a new commit
- ✓Most common use: backporting a hotfix from main to a release/maintenance branch
- ✓Cherry-pick creates a new commit with a new hash — the original commit is unchanged
- ✓git cherry-pick --no-commit stages the changes without committing — useful for combining multiple cherry-picks
- ✓Overusing cherry-pick leads to duplicate commits and confusing history — prefer merge/rebase when possible
// Scenario: hotfix on main, need to backport to release branch // // main: A ── B ── C ── fix ── D // release/v2: A ── B ── C ────────────── // // We want "fix" on release/v2 WITHOUT D # Find the commit hash of the fix git log --oneline main # f2a1c9b hotfix: prevent null pointer in checkout # 3d9f2e0 Add new feature X # a3f8c12 Add feature Y # Switch to release branch and cherry-pick git switch release/v2 git cherry-pick f2a1c9b // After: // main: A ── B ── C ── fix ── D // release/v2: A ── B ── C ── fix' // (fix' has the same changes as fix but a new hash) # Cherry-pick a range of commits (oldest..newest) git cherry-pick a3f8c12..f2a1c9b # Cherry-pick without committing (stage only) git cherry-pick --no-commit f2a1c9b
Reflog — Recovering Lost Commits
The reflog is Git's safety net — it records every change to HEAD and branch pointers for 90 days. You can recover commits lost to reset --hard, dropped rebases, or accidental branch deletions.
- ✓git reflog records every HEAD movement for 90 days — it is Git's safety net
- ✓HEAD@{n} syntax references how many HEAD positions ago (HEAD@{0} = current, HEAD@{1} = previous)
- ✓Use git reflog to find the hash of a "lost" commit, then git reset --hard <hash> to recover
- ✓git fsck --lost-found finds dangling objects not reachable from any ref
- ✓Reflog is per-repository and local — it is NOT shared or pushed to remotes
git reflog
# a3f8c12 HEAD@{0}: commit: Add payment gateway
# 9d2e441 HEAD@{1}: rebase -i (finish): returning to refs/heads/main
# 7b2e441 HEAD@{2}: rebase -i (squash): Implement payment
# 3d9f2e0 HEAD@{3}: rebase -i (pick): Add auth
# 2c1a8b7 HEAD@{4}: reset: moving to HEAD~3
# f1a2b3c HEAD@{5}: commit: Messy WIP commit ↠"lost" commit
# e9d8c7b HEAD@{6}: checkout: moving from feature to main
# Reflog for a specific branch
git reflog show feature/payment
# Reflog shows both hash AND the operation that moved HEAD
# This is how you find a "lost" commit hashGit Hooks — Automate with Every Git Action
Git hooks are shell scripts that run automatically at key points in the Git workflow — before commits, before pushes, after merges. They enforce code quality, run tests, and prevent bad commits from ever entering the repository.
- ✓Hooks live in .git/hooks/ — any executable script (bash, node, python) triggered by Git events
- ✓pre-commit: runs before commit — ideal for linting staged files; non-zero exit rejects the commit
- ✓commit-msg: validates commit message format — receives the message file as $1
- ✓pre-push: runs before pushing — use for full test suites (too slow for pre-commit)
- ✓Use Husky (Node) or pre-commit framework (Python) to version and share hooks with your team
# Hooks live in .git/hooks/
ls .git/hooks/
# pre-commit.sample commit-msg.sample pre-push.sample
# Make a hook executable by removing .sample and adding a shebang
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/sh
# Run ESLint on staged JS files before every commit
staged=$(git diff --cached --name-only --diff-filter=ACM | grep '.js$')
if [ -n "$staged" ]; then
echo "$staged" | xargs npx eslint
if [ $? -ne 0 ]; then
echo "ESLint failed — commit rejected"
exit 1 # Non-zero exit rejects the commit
fi
fi
EOF
chmod +x .git/hooks/pre-commitSubmodules & Worktrees — Advanced Repo Management
Submodules embed one Git repository inside another at a pinned commit — for shared libraries or dependencies. Worktrees let you check out multiple branches simultaneously in separate directories from one repository.
- ✓A submodule is a pointer (commit hash) to another repo — git clone --recurse-submodules fetches both
- ✓Updating a submodule requires committing the updated pointer in the parent repo
- ✓Worktrees allow multiple branches checked out simultaneously in separate directories
- ✓All worktrees share one .git/ directory — no duplication of the object store
- ✓Prefer worktrees over stash when you need to switch context for more than a few minutes
# Add a submodule git submodule add https://github.com/org/shared-ui.git libs/shared-ui # .gitmodules is created/updated: # [submodule "libs/shared-ui"] # path = libs/shared-ui # url = https://github.com/org/shared-ui.git # Clone a repo WITH submodules (two ways) git clone --recurse-submodules https://github.com/org/project.git # Or in two steps: git clone https://github.com/org/project.git git submodule update --init --recursive # Update submodule to latest remote (then commit the pointer) cd libs/shared-ui git fetch && git checkout main && git pull cd ../.. git add libs/shared-ui git commit -m "chore: update shared-ui to latest main" # Check status of all submodules git submodule status
Git Internals Deep Dive — Objects, Packfiles, GC
Under the hood: how Git stores objects in loose and packed format, how the index works, how garbage collection reclaims space, and how remote protocols transfer data efficiently.
- ✓Loose objects: one zlib-compressed file per object. Packfiles: many objects in one binary, with delta compression
- ✓git gc packs loose objects into packfiles — reduces disk usage and speeds up network transfer
- ✓.git/index is a binary cache mapping file paths to blob hashes — git status reads this, not the disk
- ✓git push only sends objects the remote doesn't have — negotiated before transfer begins
- ✓git filter-repo (not git filter-branch) is the modern tool to permanently remove large files from history
# Loose objects live here (one file per object) ls .git/objects/ # 4a/ 9c/ f2/ pack/ info/ ls .git/objects/4a/ # 7f3b2c... (rest of SHA-1 hash) # Pack loose objects manually git gc git gc --aggressive # deeper compression, slower # Inspect a packfile ls .git/objects/pack/ # pack-abc123.idx ↠index for fast lookup # pack-abc123.pack ↠compressed objects git verify-pack -v .git/objects/pack/pack-abc123.idx # Outputs: hash, type, size, compressed-size, offset # Helps find large objects bloating the repo # Git auto-gc triggers around 6700 loose objects # Manual: git count-objects -v # count: 150 # size: 600 (KB, loose objects) # in-pack: 45000 (objects in packfiles)