โ† Back to Git & GitHub
Git & GitHub by @gitgoodordietrying

git-workflows

Advanced git operations beyond add/commit/push

0
Source Code

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

# 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

# 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)

# 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

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

# 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)

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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)

# 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)

# 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

# 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)

# 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

# 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

# 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)

# 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

# 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

# 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

# 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.