Advanced Git Patterns
Quick Reference
| Task | Command |
|---|---|
| Rebase onto main | git rebase main |
| Interactive rebase (squash) | git rebase -i HEAD~N |
| Cherry-pick a commit | git cherry-pick <sha> |
| Find bug introduction | git bisect start |
| Stash with name | git stash push -m "description" |
| Undo last commit (keep changes) | git reset --soft HEAD~1 |
| See what changed in a file | git log -p -- path/to/file |
| Find who changed a line | git blame path/to/file |
Rebase Workflows
Rebase feature branch onto main
git checkout feature-branch
git fetch origin
git rebase origin/main
# Fix conflicts if any, then:
git push --force-with-lease # safer than --force
Use --force-with-lease instead of --force -- it refuses to push if someone else has pushed to the branch since your last fetch.
Interactive Rebase (clean up commits)
git rebase -i HEAD~5 # last 5 commits
In the editor:
pick abc1234 feat: add user model
squash def5678 fix typo in user model
squash ghi9012 more fixes
pick jkl3456 feat: add user routes
fixup mno7890 cleanup
| Command | Effect |
|---|---|
pick |
Keep commit as-is |
squash |
Merge into previous, combine messages |
fixup |
Merge into previous, discard message |
reword |
Keep commit, edit message |
drop |
Remove commit entirely |
When to Rebase vs Merge
| Rebase | Merge |
|---|---|
| Your feature branch behind main | Integrating shared branches |
| Cleaning up local commits before PR | Preserving branch history matters |
| Linear history preferred | Multiple people on same branch |
| Your own branches only | Never on shared/public branches |
Conflict Resolution
Workflow
# During rebase or merge, conflicts show as:
<<<<<<< HEAD
current changes
=======
incoming changes
>>>>>>> branch-name
# After resolving each file:
git add resolved-file.py
git rebase --continue # or git merge --continue
Strategy
- Read both sides -- understand what each change is trying to do
- Check the base --
git diff HEAD...branchto see the original intent - Keep both when changes are in different logical sections
- Choose one when changes are mutually exclusive
- Rewrite when neither side is correct after the merge
- Run tests after resolving before continuing
Abort if stuck
git rebase --abort # undo the entire rebase
git merge --abort # undo the entire merge
Cherry-Pick
Copy a specific commit to current branch:
git cherry-pick abc1234
# Cherry-pick without committing (stage changes only)
git cherry-pick --no-commit abc1234
# Cherry-pick a range
git cherry-pick abc1234..def5678
Use cases: hotfix from main to release branch, backport a fix.
Git Bisect (find bug introduction)
git bisect start
git bisect bad # current commit is broken
git bisect good v1.0.0 # this tag/commit was working
# Git checks out a middle commit. Test it, then:
git bisect good # this commit is fine
# or
git bisect bad # this commit has the bug
# Repeat until git identifies the first bad commit
git bisect reset # return to original branch
Automate with a test script:
git bisect start HEAD v1.0.0
git bisect run pytest tests/test_broken.py
Stash Management
# Stash with descriptive name
git stash push -m "WIP: auth refactor"
# List stashes
git stash list
# Apply most recent (keep in stash)
git stash apply
# Apply and remove from stash
git stash pop
# Apply specific stash
git stash apply stash@{2}
# Stash specific files only
git stash push -m "partial" -- file1.py file2.py
# Create branch from stash
git stash branch new-branch stash@{0}
Undo Operations
| Want to Undo | Command | Destructive? |
|---|---|---|
| Last commit (keep changes staged) | git reset --soft HEAD~1 |
No |
| Last commit (keep changes unstaged) | git reset HEAD~1 |
No |
| Last commit (discard changes) | git reset --hard HEAD~1 |
YES |
| Staged file | git restore --staged file |
No |
| Unstaged changes to a file | git restore file |
YES |
| A pushed commit | git revert <sha> |
No (creates new commit) |
| A bad rebase | git reflog then git reset --hard <sha> |
Recoverable |
Always prefer git revert over git reset for pushed commits. Revert creates a new commit that undoes the change, preserving history.
Reflog (emergency recovery)
# See recent HEAD movements
git reflog
# Output:
# abc1234 HEAD@{0}: rebase: finish
# def5678 HEAD@{1}: rebase: start
# ghi9012 HEAD@{2}: commit: my important work
# Recover lost commit
git reset --hard ghi9012
The reflog keeps ~90 days of history. If you lost something, it's probably here.
History Exploration
# Commits that changed a specific file
git log --oneline -- path/to/file
# Search commit messages
git log --grep="fix login"
# Search code changes (pickaxe)
git log -S "functionName" --oneline
# Who last changed each line
git blame path/to/file
# Show blame ignoring whitespace and moves
git blame -w -M path/to/file
# Diff between branches
git diff main...feature-branch
Anti-Patterns
| Don't | Do Instead |
|---|---|
git push --force on shared branches |
git push --force-with-lease on your own branches only |
| Rebase public/shared branches | Only rebase your own feature branches |
| Resolve conflicts by accepting "ours" blindly | Read both sides and merge intentionally |
| Stash and forget | Name stashes, clean up regularly with git stash drop |
git reset --hard without checking reflog |
Use --soft first, escalate to --hard only when sure |
| Giant squash of entire branch | Squash related commits, keep logical units separate |