Introduction
# Git Workflows
Advanced git operations for real-world development. Covers interactive rebase, bisect, worktree, reflog recovery, subtrees, submodules, sparse checkout, conflict resolution, and monorepo patterns.
## When to Use
- Cleaning up commit history before merging (interactive rebase) - Finding which commit introduced a bug (bisect) - Working on multiple branches simultaneously (worktree) - Recovering lost commits or undoing mistakes (reflog) - Managing shared code across repos (subtree/submodule) - Resolving complex merge conflicts - Cherry-picking commits across branches or forks - Working with large monorepos (sparse checkout)
## Interactive Rebase
### Squash, reorder, edit commits
```bash # Rebase last 5 commits interactively git rebase -i HEAD~5
# Rebase onto main (all commits since diverging) git rebase -i main ```
The editor opens with a pick list:
``` pick a1b2c3d Add user model pick e4f5g6h Fix typo in user model pick i7j8k9l Add user controller pick m0n1o2p Add user routes pick q3r4s5t Fix import in controller ```
Commands available: ``` pick = use commit as-is reword = use commit but edit the message edit = stop after this commit to amend it squash = merge into previous commit (keep both messages) fixup = merge into previous commit (discard this message) drop = remove the commit entirely ```
### Common patterns
```bash # Squash fix commits into their parent # Change "pick" to "fixup" for the fix commits: pick a1b2c3d Add user model fixup e4f5g6h Fix typo in user model pick i7j8k9l Add user controller fixup q3r4s5t Fix import in controller pick m0n1o2p Add user routes
# Reorder commits (just move lines) pick i7j8k9l Add user controller pick m0n1o2p Add user routes pick a1b2c3d Add user model
# Split a commit into two # Mark as "edit", then when it stops: git reset HEAD~ git add src/model.ts git commit -m "Add user model" git add src/controller.ts git commit -m "Add user controller" git rebase --continue ```
### Autosquash (commit messages that auto-arrange)
```bash # When committing a fix, reference the commit to squash into git commit --fixup=a1b2c3d -m "Fix typo" # or git commit --squash=a1b2c3d -m "Additional changes"
# Later, rebase with autosquash git rebase -i --autosquash main # fixup/squash commits are automatically placed after their targets ```
### Abort or continue
```bash git rebase --abort # Cancel and restore original state git rebase --continue # Continue after resolving conflicts or editing git rebase --skip # Skip the current commit and continue ```
## Bisect (Find the Bug)
### Binary search through commits
```bash # Start bisect git bisect start
# Mark current commit as bad (has the bug) git bisect bad
# Mark a known-good commit (before the bug existed) git bisect good v1.2.0 # or: git bisect good abc123
# Git checks out a middle commit. Test it, then: git bisect good # if this commit doesn't have the bug git bisect bad # if this commit has the bug
# Repeat until git identifies the exact commit # "abc123 is the first bad commit"
# Done — return to original branch git bisect reset ```
### Automated bisect (with a test script)
```bash # Fully automatic: git runs the script on each commit # Script must exit 0 for good, 1 for bad git bisect start HEAD v1.2.0 git bisect run ./test-for-bug.sh
# Example test script cat > /tmp/test-for-bug.sh << 'EOF' #!/bin/bash # Return 0 if bug is NOT present, 1 if it IS npm test -- --grep "login should redirect" 2>/dev/null EOF chmod +x /tmp/test-for-bug.sh git bisect run /tmp/test-for-bug.sh ```
### Bisect with build failures
```bash # If a commit doesn't compile, skip it git bisect skip
# Skip a range of known-broken commits git bisect skip v1.3.0..v1.3.5 ```
## Worktree (Parallel Branches)
### Work on multiple branches simultaneously
```bash # Add a worktree for a different branch git worktree add ../myproject-hotfix hotfix/urgent-fix # Creates a new directory with that branch checked out
# Add a worktree with a new branch git worktree add ../myproject-feature -b feature/new-thing
# List worktrees git worktree list
# Remove a worktree when done git worktree remove ../myproject-hotfix
# Prune stale worktree references git worktree prune ```
### Use cases
```bash # Review a PR while keeping your current work untouched git worktree add ../review-pr-123 origin/pr-123
# Run tests on main while developing on feature branch git worktree add ../main-tests main cd ../main-tests && npm test
# Compare behavior between branches side by side git worktree add ../compare-old release/v1.0 git worktree add ../compare-new release/v2.0 ```
## Reflog (Recovery)
### See everything git remembers
```bash # Show reflog (all HEAD movements) git reflog # Output: # abc123 HEAD@{0}: commit: Add feature # def456 HEAD@{1}: rebase: moving to main # ghi789 HEAD@{2}: checkout: moving from feature to main
# Show reflog for a specific branch git reflog show feature/my-branch
# Show with timestamps git reflog --date=relative ```
### Recover from mistakes
```bash # Undo a bad rebase (find the commit before rebase in reflog) git reflog # Find: "ghi789 HEAD@{5}: checkout: moving from feature to main" (pre-rebase) git reset --hard ghi789
# Recover a deleted branch git reflog # Find the last commit on that branch git branch recovered-branch abc123
# Recover after reset --hard git reflog git reset --hard HEAD@{2} # Go back 2 reflog entries
# Recover a dropped stash git fsck --unreachable | grep commit # or git stash list # if it's still there git log --walk-reflogs --all -- stash # find dropped stash commits ```
## Cherry-Pick
### Copy specific commits to another branch
```bash # Pick a single commit git cherry-pick abc123
# Pick multiple commits git cherry-pick abc123 def456 ghi789
# Pick a range (exclusive start, inclusive end) git cherry-pick abc123..ghi789
# Pick without committing (stage changes only) git cherry-pick --no-commit abc123
# Cherry-pick from another remote/fork git remote add upstream https://github.com/other/repo.git git fetch upstream git cherry-pick upstream/main~3 # 3rd commit from upstream's main ```
### Handle conflicts during cherry-pick
```bash # If conflicts arise: # 1. Resolve conflicts in the files # 2. Stage resolved files git add resolved-file.ts # 3. Continue git cherry-pick --continue
# Or abort git cherry-pick --abort ```
## Subtree and Submodule
### Subtree (simpler — copies code into your repo)
```bash # Add a subtree git subtree add --prefix=lib/shared https://github.com/org/shared-lib.git main --squash
# Pull updates from upstream git subtree pull --prefix=lib/shared https://github.com/org/shared-lib.git main --squash
# Push local changes back to upstream git subtree push --prefix=lib/shared https://github.com/org/shared-lib.git main
# Split subtree into its own branch (for extraction) git subtree split --prefix=lib/shared -b shared-lib-standalone ```
### Submodule (pointer to another repo at a specific commit)
```bash # Add a submodule git submodule add https://github.com/org/shared-lib.git lib/shared
# Clone a repo with submodules git clone --recurse-submodules https://github.com/org/main-repo.git
# Initialize submodules after clone (if forgot --recurse) git submodule update --init --recursive
# Update submodules to latest git submodule update --remote
# Remove a submodule git rm lib/shared rm -rf .git/modules/lib/shared # Remove entry from .gitmodules if it persists ```
### When to use which
``` Subtree: Simpler, no special commands for cloners, code lives in your repo. Use when: shared library, vendor code, infrequent upstream changes.
Submodule: Pointer to exact commit, smaller repo, clear separation. Use when: large dependency, independent release cycle, many contributors. ```
## Sparse Checkout (Monorepo)
### Check out only the directories you need
```bash # Enable sparse checkout git sparse-checkout init --cone
# Select directories git sparse-checkout set packages/my-app packages/shared-lib
# Add another directory git sparse-checkout add packages/another-lib
# List what's checked out git sparse-checkout list
# Disable (check out everything again) git sparse-checkout disable ```
### Clone with sparse checkout (large monorepos)
```bash # Partial clone + sparse checkout (fastest for huge repos) git clone --filter=blob:none --sparse https://github.com/org/monorepo.git cd monorepo git sparse-checkout set packages/my-service
# No-checkout clone (just metadata) git clone --no-checkout https://github.com/org/monorepo.git cd monorepo git sparse-checkout set packages/my-service git checkout main ```
## Conflict Resolution
### Understand the conflict markers
``` <<<<<<< HEAD (or "ours") Your changes on the current branch ======= Their changes from the incoming branch >>>>>>> feature-branch (or "theirs") ```
### Resolution strategies
```bash # Accept all of ours (current branch wins) git checkout --ours path/to/file.ts git add path/to/file.ts
# Accept all of theirs (incoming branch wins) git checkout --theirs path/to/file.ts git add path/to/file.ts
# Accept ours for ALL files git checkout --ours . git add .
# Use a merge tool git mergetool
# See the three-way diff (base, ours, theirs) git diff --cc path/to/file.ts
# Show common ancestor version git show :1:path/to/file.ts # base (common ancestor) git show :2:path/to/file.ts # ours git show :3:path/to/file.ts # theirs ```
### Rebase conflict workflow
```bash # During rebase, conflicts appear one commit at a time # 1. Fix the conflict in the file # 2. Stage the fix git add fixed-file.ts # 3. Continue to next commit git rebase --continue # 4. Repeat until done
# If a commit is now empty after resolution git rebase --skip ```
### Rerere (reuse recorded resolutions)
```bash # Enable rerere globally git config --global rerere.enabled true
# Git remembers how you resolved conflicts # Next time the same conflict appears, it auto-resolves
# See recorded resolutions ls .git/rr-cache/
# Forget a bad resolution git rerere forget path/to/file.ts ```
## Stash Patterns
```bash # Stash with a message git stash push -m "WIP: refactoring auth flow"
# Stash specific files git stash push -m "partial stash" -- src/auth.ts src/login.ts
# Stash including untracked files git stash push -u -m "with untracked"
# List stashes git stash list
# Apply most recent stash (keep in stash list) git stash apply
# Apply and remove from stash list git stash pop
# Apply a specific stash git stash apply stash@{2}
# Show what's in a stash git stash show -p stash@{0}
# Create a branch from a stash git stash branch new-feature stash@{0}
# Drop a specific stash git stash drop stash@{1}
# Clear all stashes git stash clear ```
## Blame and Log Archaeology
```bash # Who changed each line (with date) git blame src/auth.ts
# Blame a specific line range git blame -L 50,70 src/auth.ts
# Ignore whitespace changes in blame git blame -w src/auth.ts
# Find when a line was deleted (search all history) git log -S "function oldName" --oneline
# Find when a regex pattern was added/removed git log -G "TODO.*hack" --oneline
# Follow a file through renames git log --follow --oneline -- src/new-name.ts
# Show the commit that last touched each line, ignoring moves git blame -M src/auth.ts
# Show log with file changes git log --stat --oneline -20
# Show all commits affecting a specific file git log --oneline -- src/auth.ts
# Show diff of a specific commit git show abc123 ```
## Tags and Releases
```bash # Create annotated tag (preferred for releases) git tag -a v1.2.0 -m "Release 1.2.0: Added auth module"
# Create lightweight tag git tag v1.2.0
# Tag a past commit git tag -a v1.1.0 abc123 -m "Retroactive tag for release 1.1.0"
# List tags git tag -l git tag -l "v1.*"
# Push tags git push origin v1.2.0 # Single tag git push origin --tags # All tags
# Delete a tag git tag -d v1.2.0 # Local git push origin --delete v1.2.0 # Remote ```
## Tips
- `git rebase -i` is the single most useful advanced git command. Learn it first. - Never rebase commits that have been pushed to a shared branch. Rebase your local/feature work only. - `git reflog` is your safety net. If you lose commits, they're almost always recoverable within 90 days. - `git bisect run` with an automated test is faster than manual binary search and eliminates human error. - Worktrees are cheaper than multiple clones — they share `.git` storage. - Prefer `git subtree` over `git submodule` unless you have a specific reason. Subtrees are simpler for collaborators. - Enable `rerere` globally. It remembers conflict resolutions so you never solve the same conflict twice. - `git stash push -m "description"` is much better than bare `git stash`. You'll thank yourself when you have 5 stashes. - `git log -S "string"` (pickaxe) is the fastest way to find when a function or variable was added or removed.