5 min read
On this page

Advanced Git

Beyond the Basics

Most engineers use about 10% of git's capabilities: add, commit, push, pull, branch, merge. These cover daily work, but git has tools that solve problems you're currently solving the hard way — or not solving at all.

This isn't about memorizing obscure commands. It's about knowing what's possible so that when you're stuck, you reach for the right tool instead of brute-forcing a solution.

Interactive Rebase

Interactive rebase (git rebase -i) lets you rewrite commit history on your local branch. You can reorder commits, combine them, split them, edit their messages, or drop them entirely.

Starting a rebase

# Rebase the last 5 commits
git rebase -i HEAD~5

# Rebase everything since branching from main
git rebase -i main

This opens your editor with a list of commits:

pick abc1234 Add user model
pick def5678 Add user API endpoint
pick ghi9012 Fix typo in user model
pick jkl3456 Add user tests
pick mno7890 Fix lint errors

The commands

pick   - Keep the commit as-is
reword - Keep the commit, but edit the message
squash - Combine with previous commit, edit the merged message
fixup  - Combine with previous commit, discard this commit's message
edit   - Pause the rebase at this commit so you can amend it
drop   - Delete the commit entirely

Common rebase operations

Squash fixup commits into their parent:

pick abc1234 Add user model
fixup ghi9012 Fix typo in user model
pick def5678 Add user API endpoint
pick jkl3456 Add user tests
fixup mno7890 Fix lint errors

Result: 3 clean commits instead of 5.

Reorder commits to group related changes:

pick abc1234 Add user model
pick jkl3456 Add user tests
pick def5678 Add user API endpoint

Reword a commit with a bad message:

reword abc1234 Add user model
pick def5678 Add user API endpoint

When the rebase reaches the reword commit, your editor opens to let you change the message.

Edit a commit to split it:

edit abc1234 Add user model and API endpoint (too big!)

When the rebase pauses:

git reset HEAD~1                    # Undo the commit, keep changes
git add src/models/user.ts          # Stage just the model
git commit -m "Add user model"
git add src/api/user-routes.ts      # Stage just the API
git commit -m "Add user API endpoint"
git rebase --continue               # Resume the rebase

Safety rules

Never rebase commits that have been pushed to a shared branch. Rebasing rewrites commit hashes, which breaks history for anyone who has pulled those commits. Rebase is for cleaning up your local branch before merging.

If something goes wrong during a rebase:

git rebase --abort    # Cancel the rebase and go back to where you started

Git Bisect

Bisect uses binary search to find the commit that introduced a bug. Instead of manually checking commits one by one, bisect cuts the search space in half with each step.

Manual bisect

# Start bisecting
git bisect start

# Mark the current commit as bad (has the bug)
git bisect bad

# Mark a known good commit (before the bug existed)
git bisect good abc1234

# Git checks out a commit in the middle. Test it.
# If the bug is present:
git bisect bad

# If the bug is not present:
git bisect good

# Repeat until git identifies the exact commit
# Git will say: "abc1234 is the first bad commit"

# When done, return to your branch
git bisect reset

With 1000 commits between good and bad, bisect finds the culprit in about 10 steps. That's the power of binary search.

Automated bisect

If you have a command that can detect the bug (a test, a script, a build command), bisect can run fully automatically:

# Start
git bisect start
git bisect bad HEAD
git bisect good v2.1.0

# Run automatically — exit code 0 means good, non-zero means bad
git bisect run npm test -- --grep "user login"

# Or with a custom script
git bisect run ./check-for-bug.sh

This is incredibly powerful. You can leave for coffee and come back to the exact commit that broke something, without manually testing a single revision.

When to use bisect

- A bug exists in production but not in last week's release — which
  commit introduced it?
- Performance degraded gradually — which commit caused the regression?
- A test started failing — which commit is responsible?
- Something "just stopped working" and nobody knows when it broke

Worktrees

Worktrees let you check out multiple branches simultaneously in separate directories. Each worktree has its own working directory and staging area, but they share the same git repository.

Why worktrees

The standard workflow for switching context:

1. Stash or commit your current work
2. Switch branches
3. Wait for your editor/tools to reload
4. Do the other work
5. Switch back
6. Pop the stash or find where you were

With worktrees:

1. Open the other worktree directory in a new terminal/editor
2. Do the other work
3. Go back to your original directory — everything is exactly as you left it

No stashing, no branch switching, no context loss.

Using worktrees

# Create a worktree for a branch
git worktree add ../project-hotfix hotfix/payment-bug

# Create a worktree with a new branch
git worktree add ../project-review -b review/pr-123

# List worktrees
git worktree list

# Remove a worktree when done
git worktree remove ../project-hotfix

Practical worktree layout

~/projects/
  myapp/              # Main worktree (feature branch)
  myapp-hotfix/       # Worktree for urgent fixes
  myapp-review/       # Worktree for PR reviews

Each directory is a fully functional checkout. You can run servers, tests, and builds in each one independently.

When worktrees shine

- You're mid-feature and need to review a PR or fix an urgent bug
- You want to run two branches side by side to compare behavior
- You're doing a long rebase and want to reference the original branch
- CI is broken on main and you need to investigate without losing
  your current work

Stash Strategies

git stash saves your uncommitted changes and restores a clean working directory. It's simpler than worktrees but more limited.

Essential stash commands

git stash                              # Stash all tracked changes
git stash push -m "WIP: description"   # Stash with a message
git stash -u                           # Include untracked files
git stash list                         # List all stashes
git stash pop                          # Apply and remove most recent
git stash apply stash@{2}             # Apply a specific stash
git stash push -p                      # Stash interactively (by hunk)

Commit when your work is at a logical point, even if incomplete — use a "WIP:" prefix. Stash when your work is too messy to commit. Don't let stashes accumulate. If you haven't popped a stash in a week, it's probably obsolete.

The Reflog

The reflog is git's safety net. It records every change to HEAD — every commit, checkout, rebase, reset, merge, and amend. Even operations that "destroy" commits (like git reset --hard) leave entries in the reflog.

Using the reflog

# View the reflog
git reflog

# Output looks like:
# abc1234 HEAD@{0}: commit: Add user tests
# def5678 HEAD@{1}: rebase (finish): returning to refs/heads/feature
# ghi9012 HEAD@{2}: rebase (squash): Add user model
# jkl3456 HEAD@{3}: rebase (start): checkout main
# mno7890 HEAD@{4}: commit: WIP: payment stuff

Recovery scenarios

Lost commits from a bad reset or rebase? Check the reflog, find the hash from before the operation, and git reset --hard to that point. Amended a commit and want the original? The pre-amend version is in the reflog as the previous entry.

Reflog entries expire after 90 days by default. This means you have a 90-day window to recover from almost any mistake. This is why it's almost impossible to truly lose work in git — you just need to know about the reflog.

Cherry-Pick

Cherry-pick copies a specific commit from one branch to another without merging the entire branch.

# Apply a specific commit to the current branch
git cherry-pick abc1234

# Cherry-pick without committing (stage the changes instead)
git cherry-pick --no-commit abc1234

# Cherry-pick a range of commits
git cherry-pick abc1234..def5678

When to cherry-pick

- A bugfix on a feature branch needs to go to main immediately
- A commit was made on the wrong branch
- You need one specific change from a long-lived branch without
  merging everything else
- Backporting a fix to a release branch

When not to cherry-pick

Don't use cherry-pick as a substitute for proper merging or rebasing. Cherry-picking creates duplicate commits (same changes, different hashes), which can cause confusion and merge conflicts later. Use it for one-off situations, not as a regular workflow.

Putting It All Together

A realistic scenario: you're working on a feature branch. Your teammate reports a bug in production. Your current branch has uncommitted changes and messy commits.

# Step 1: Save current work
git stash push -m "WIP: feature in progress"

# Step 2: Create a worktree for the fix (or just switch branches)
git worktree add ../project-hotfix main

# Step 3: Fix the bug in the worktree
cd ../project-hotfix
# ... make the fix, commit it ...

# Step 4: Go back to your feature branch
cd ../myapp
git stash pop

# Step 5: Clean up your commits before PR
git rebase -i main
# Squash WIP commits, reword messages, reorder logically

# Step 6: Later, if the bug reappears, use bisect to find when it was introduced
git bisect start
git bisect bad HEAD
git bisect good v2.0.0
git bisect run npm test -- --grep "the failing test"

Each tool solved a specific problem without disrupting the others.

Common Pitfalls

Rebasing shared branches. Never rebase commits that others have pulled. This rewrites history and causes painful conflicts for everyone.

Letting stashes accumulate. If git stash list shows 15 entries, most are obsolete. Clean them out. Stashes are temporary by nature.

Cherry-picking when you should merge. Cherry-pick creates duplicate commits. If you find yourself cherry-picking regularly between two branches, those branches should probably be merged instead.

Not knowing about the reflog. Engineers who don't know about reflog panic when they lose work to a bad rebase or reset. The reflog makes git nearly impossible to break permanently.

Using advanced tools for simple problems. If git commit --amend solves your problem, you don't need interactive rebase. Reach for the simplest tool that works.

Key Takeaways

Interactive rebase lets you clean up commit history before merging. Use it to squash fixups, reword messages, and reorder commits into a logical narrative.

Git bisect finds bugs through binary search. Automated bisect with git bisect run is one of the most powerful debugging tools available.

Worktrees let you work on multiple branches simultaneously without stashing or switching. They're ideal for code reviews, hotfixes, and comparisons.

The reflog records everything. If you lose commits to a rebase, reset, or amend, the reflog can recover them for up to 90 days.

Cherry-pick copies individual commits between branches. Use it sparingly for one-off situations, not as a regular workflow pattern.