Git Advanced Techniques: cherry-pick, rebase, bisect, reflog, and more

Git Advanced Techniques: cherry-pick, rebase, bisect, reflog, and more

Once you have mastered branches and merges, you can handle day-to-day development. But Git has many more powerful features. Knowing these will help you navigate complex situations with ease.

cherry-pick: Select a Single Commit

You fixed a bug on the bugfix branch and now want that fix on main too. But the bugfix branch has other commits you do not want on main — merging the whole branch is not ideal.

cherry-pick is designed for this. It "copies" a specific commit to your current branch:

git checkout main
git cherry-pick abc1234

abc1234 is the commit ID you want to copy. After execution, main gets a new commit with identical content but a different ID (because the ID includes parent commit information).

This is especially useful when maintaining multiple product versions. If you maintain both v1 and v2 and fix a bug that applies to both, cherry-pick is far more reliable than manually copying code. Strategy:

git checkout v1
git cherry-pick abc1234
git checkout v2
git cherry-pick abc1234

One warning: cherry-pick creates a new commit, so the original commit may exist in two branches with different IDs. This can cause confusion during later merges since Git treats them as different commits despite having identical content.

Interactive rebase: Clean Up Your Commit History

During development, you might have committed several times: first a framework, then a typo fix, then a code tweak. The history looks messy.

Interactive rebase lets you reorganize commits before pushing:

git rebase -i HEAD~3

This opens an editor showing the last three commits. You can:

  • pick: keep this commit
  • reword: edit the commit message
  • squash: merge this commit into the previous one
  • fixup: like squash but discard the commit message
  • drop: delete this commit
  • reorder: change the order of commits

To merge the last two commits into the first, change the last two picks to squash. Save and exit. Git lets you write a new combined message.

Clean history makes it much easier for others to read. One feature, one commit, clear description, complete changes.

Note: rebase rewrites history, so you will need --force to push afterward. Only do this on your own branches — never force push to shared branches. Use --force-with-lease instead of --force for an extra safety check — it verifies no one else has pushed changes before overwriting.

Interactive Rebase for Splitting Commits

Interactive rebase can also split a commit. Use the "edit" action, and when Git pauses at that commit, use git reset HEAD~1 to unstage the commit while keeping the changes. Then you can create multiple smaller commits from the same changeset.

bisect: Quickly Find Which Commit Introduced a Bug

A feature worked last week but is broken this week. Dozens of commits happened in between. Which one caused it?

git bisect uses binary search to locate the culprit:

git bisect start
git bisect bad          # current version has the bug
git bisect good abc123  # abc123 was working

Git checks out the middle commit. Test it. If it works:

git bisect good

If it is broken:

git bisect bad

Git narrows the range each time, cutting the search space in half. At most a dozen steps will find the offending commit.

Afterward:

git bisect reset

to return to your original state.

For automated bisecting, use git bisect run <script>. The script should exit with code 0 for "good" and non-zero for "bad." Git automatically runs the binary search for you. This is incredibly useful for finding the commit that broke a failing test.

reflog: Your Undo Button

You deleted a branch and need it two days later. Or you reset a commit and realized you should not have.

git reflog records every movement of HEAD, including commits that are "gone":

git reflog

Each record has an ID. Find the one you want to restore:

git checkout abc1234
git checkout -b recovered-branch

The commits are back.

reflog retains entries for 90 days by default, which is enough in most cases.

Recovering from Mistakes with reflog

Common recovery scenarios:

  • Accidental hard reset: Find the reflog entry before the reset and git reset --hard <that-commit>.
  • Accidental branch deletion: Find the last commit of the deleted branch and recreate it with git checkout -b <branch-name> <commit-id>.
  • Force push disaster: If you force pushed and overwrote remote history, find the original commits in the reflog and push them back.

submodule: Manage Sub-projects

A large project sometimes depends on another independent project. For example, your frontend project references an internal component library in a separate Git repository.

git submodule lets you embed one Git repository inside another:

git submodule add https://github.com/xxx/component-lib.git libs/component-lib

This creates a reference to that repository in libs/component-lib. When others clone your project:

git submodule update --init --recursive

submodule can be tricky to use, mainly around updates and branch switching. But in scenarios where you must split repositories, it is the standard solution.

Alternative: git subtree

For many use cases, git subtree is simpler than submodule. It merges a remote repository into a subdirectory of your main repository, making it part of your codebase rather than a reference. This avoids the common submodule pitfalls around detached HEAD state and update complexity.

Other Useful Commands

git blame filename: see who last modified each line of a file. When you see suspicious code, use blame to find who wrote it. For a more visual approach, most IDEs support inline blame annotations that show author information next to each line.

git tag v1.0.0 abc123: tag important commits for version milestones. Tags are much easier to remember than commit IDs. Tags also come in two flavors: lightweight (just a pointer) and annotated (with a message, date, and author). Use annotated tags for releases with git tag -a v1.0.0 -m "Release version 1.0.0".

git diff: see differences between working directory and staging area. git diff --cached sees differences between staging area and repository. Use git diff with branch names to compare entire branches.

git stash: temporarily save changes without committing them. Great for switching branches without losing work. Use git stash list to view stashes and git stash pop to apply them.

git cherry: shows which commits exist in one branch but not in another. Useful for reviewing what has been merged and what is still pending.

git worktree: lets you have multiple working directories for the same repository. Each worktree has its own branch, so you can work on multiple features simultaneously without switching branches or stashing changes.

Wrapping Up

You do not need these advanced techniques every day, but they can save you when it counts. cherry-pick gives you precise control over commits. rebase keeps your history clean. bisect helps you track down bugs fast. reflog is your safety net. submodule manages external dependencies in your project.

Bookmark these commands and look up the specific syntax when you need them. The more you use them, the more natural they become.


If you have never used git bisect before, I encourage you your next encounter with a mysterious regression. Instead of manually checking commit after commit, run git bisect and let Git do the searching for you. The first time you find the offending commit in three minutes instead of three hours, you will wonder how you ever lived without it. Similarly, make git reflog your first instinct whenever you lose work due to a bad reset or branch deletion. These two commands alone are worth the price of admission to the advanced Git club, and they have saved my project history more times than I can count. As you grow more comfortable with these advanced techniques, one practice I strongly recommend is creating a personal "Git cookbook" — a document or repository of the commands and workflows that you find yourself using repeatedly. Every time you solve a tricky Git problem using an advanced command, document the problem, the solution, and the exact commands you used. Over time, this becomes an invaluable personal reference that is far more useful than any generic Git tutorial because it is customized to the specific types of problems and projects you encounter in your own work. One technique that deserves special mention for its ability to prevent entire categories of Git problems is adopting a consistent commit message convention from the very beginning of a project. Whether you choose Conventional Commits or a simpler custom format, having a shared understanding across your team that every commit message should explain the "why" behind a change — not just the "what" — transforms your Git log from a cryptic list of identifiers into a readable narrative of the project's evolution. This practice pays dividends every time a new team member joins, every time you need to track down when and why a particular change was introduced, and every time an AI agent needs to make sense of your project's history. Consistent conventions make collaboration smoother.