Advanced Git Workflows: rebase, cherry-pick, and reflog
Knowing add/commit/push is not the same as knowing Git. This article explains rebase vs. merge, interactive rebase, cherry-pick, reflog, stash, and practical recovery from common mistakes so Git is no longer intimidating.
Key Points
- Topic Focus: Vibe Coding、DevOps
- Suitable for: indie hackers, full-stack engineers, AI application developers
- Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
Advanced Git Workflows: rebase, cherry-pick, and reflog
If your understanding of Git stops at add / commit / push / pull, your fear of Git probably comes from two experiences: first, history becomes a tangled mess and merge conflicts are impossible to understand; second, after a mistake, you think your code is "gone" and rewrite it overnight.
Knowing the basic commands ≠ knowing Git. What really separates beginners from experienced users is the advanced workflow built around rebase, cherry-pick, and reflog. These tools do two things: make collaborative history easier to follow and pull you back out after a mistake. This article proceeds in the order of "what they are, when to use them, and finally how to recover." By the end, you should be able to use them confidently in production repositories.
I. merge vs. rebase: Two philosophies
There are two ways to merge a branch back into the mainline.
merge: Preserve the original history of both sides and generate a new "merge commit."
git switch main
git merge feature/login
The history looks like this:
* Merge branch 'feature/login' (merge commit)
|\
| * add login API (feature)
| * add login UI
* | fix typo in readme (main)
|/
* initial commit
The advantage is fidelity: you can see what each branch did and when it was merged. The disadvantage is that history becomes a graph, and it gets messy as the team grows.
rebase: Lift the commits from the feature branch and replay them one by one on top of the latest commit on main, making the history linear.
git switch feature/login
git rebase main
The result:
* add login API (after replay)
* add login UI
* fix typo in readme
* initial commit
It is clean, linear, and reads like a timeline. The cost is that these commits have been "rewritten," so their commit hashes are no longer the originals.
When should you use each one?
- Shared team branches (
main/release): Use merge or squash merge to preserve traceability for when changes were integrated. - Personal feature branches syncing with main: Use
git rebase mainto replay your work on the latest code and keep the PR history clean. - Cleanup before a release: Use interactive rebase to compress more than a dozen WIP commits into two or three meaningful commits.
The golden rule of rebase: Never rebase commits that have already been pushed to a shared branch. Rebase rewrites hashes, so when others pull, they will encounter duplicate commits and extremely difficult conflicts. Once history is shared, treat it as immutable.
II. interactive rebase: The Swiss Army knife for rewriting history
git rebase -i is one of Git's most underrated commands. It opens an editor and lists every commit in the specified range, allowing you to decide the fate of each one:
git rebase -i HEAD~5 # Clean up the last 5 commits
git rebase -i main # Clean up all commits on the current branch relative to main
You will see:
pick a1b2c3 wip
pick d4e5f6 add login form
pick g7h8i9 fix typo
pick j0k1l2 wip
pick m3n4o5 finalize login
Simply replace pick with an action keyword:
- squash / s: Merge this commit into the previous one and combine their commit messages. This is the usual treatment for WIP commits.
- fixup / f: Same as squash, but discard this commit's message. Use it for pure junk commits.
- reword / r: Keep the commit but change its message. Replace "fix bug" with a meaningful description.
- reorder: Move the lines directly to rearrange the commits.
- drop / d: Delete this commit.
- edit / e: Pause at this commit so you can modify files on the spot. Use it to split a commit or add missing changes.
Cleaning up a feature branch by compressing 12 messy commits into three clear ones dramatically improves the PR review experience. Remember that interactive rebase also rewrites history, so use it only on your own branches.
III. cherry-pick: Move a commit precisely
Sometimes you do not want to merge an entire branch; you only want to move one specific commit. Typical scenarios include:
- Production has a bug that you fixed on main, but you need to sync only that fix to the release branch.
- A colleague happened to fix an issue relevant to you on another branch, and you need only that one change.
git switch release-1.2
git cherry-pick a1b2c3d # Apply this commit to the current branch
git cherry-pick a1b2c3d d4e5f6g # Pick multiple commits
git cherry-pick a1b2c3d..e5f6g7 # Pick a range (exclusive on the left, inclusive on the right)
cherry-pick creates a new commit because the hash changes, even though the content is identical. If a conflict occurs, Git pauses so you can resolve it, then run git cherry-pick --continue or use --abort to abandon the operation.
Note that if you later merge the source branch, Git can usually identify the duplicate commit and avoid applying it twice. However, cherry-picks across different contexts can still create conflicts, so do not overuse them.
IV. reflog: Git's undo button
This is the most valuable section of the article.
Many people think that after git reset --hard or git branch -D, their work is "gone." It almost never is. Git maintains a reflog in the background that records every movement of HEAD and every branch pointer, retaining entries for 90 days by default.
git reflog
The output looks roughly like this:
a1b2c3d HEAD@{0}: reset: moving to HEAD~3
e5f6g7h HEAD@{1}: commit: finalize login
d4e5f6g HEAD@{2}: commit: add tests
c3d4e5f HEAD@{3}: commit: wip
...
HEAD@{1} is "where HEAD was previously." This means:
Accidentally ran reset --hard? Find the hash from before the reset, or use HEAD@{1}:
git reset --hard HEAD@{1}
Deleted a branch by mistake?
git switch -c recovered-branch HEAD@{2}
# Or use the hash directly
git switch -c recovered-branch a1b2c3d
Accidentally overwrote the original commit with amend? An amend also moves HEAD, so the original commit remains in the reflog and can be recovered directly.
Did rebase lose your commits? The position from before the rebase began remains in the reflog. Run git reset --hard <that-point> to return to it immediately.
Remember this: As long as you committed it, even if you never pushed it, reflog can almost always recover it. Only two kinds of data are truly lost: changes that were never committed, such as a stash or working-tree content wiped out by git clean -fd, and objects older than the reflog retention period.
V. stash: Temporary storage for an urgent context switch
You are halfway through a change when you are called away to fix an urgent production bug, but the code is not ready to commit. What do you do? git stash temporarily stores changes from the working tree and staging area, restores a clean working tree, and lets you switch branches.
git stash push -m "wip: login form validation" # Stash with a note
git switch main
# ...make the hotfix, open a PR, and merge it...
git switch feature/login
git stash pop # Restore the latest stash and remove it
Advanced usage:
git stash list # Show all stashes
git stash show -p stash@{0} # Show the diff for a stash
git stash apply stash@{1} # Restore without deleting it; it can be applied repeatedly
git stash drop stash@{0} # Delete a specific stash
git stash clear # Delete all stashes; use with caution
If pop encounters a conflict, it preserves the stash. Resolve the conflict, then run drop manually. Practical advice: Always add a note with -m "description" when stashing. Otherwise, several unnamed entries in stash list will leave you unable to remember which is which, and three days later you may have no option but to run clear.
VI. Rescue manual: Recovering from mistakes
Here is a quick reference that brings the previous recovery scenarios together.
Scenario 1: You just committed and discover that you missed a file or wrote the wrong commit message
As long as you have not pushed yet:
# After editing the file
git add .
git commit --amend # Amend the commit and open an editor to change its message
git commit --amend --no-edit # Keep the original message and fold in only the new changes
An amend essentially "replaces the old commit with a new one," so the old commit can still be recovered from the reflog.
Scenario 2: You already pushed but want to change a commit message or combine commits
If you are the only person using the branch, you can amend or rebase and then run git push --force-with-lease. Use --force-with-lease instead of --force: the former refuses to push if someone else has added a new remote commit, preventing you from overwriting a colleague's work.
Scenario 3: You accidentally deleted a local branch
git reflog # Find the hash the branch pointed to most recently
git switch -c feature/login <hash>
Scenario 4: git reset --hard erased your work
git reflog
git reset --hard HEAD@{1} # Return to the state before the reset
Scenario 5: You made a mess during rebase and want to abandon everything
git rebase --abort
At any point during a rebase, --abort cleanly returns you to the starting state.
Scenario 6: You cannot resolve the merge conflicts
git merge --abort # Abandon this merge and return to the pre-merge state
Scenario 7: You want to undo a commit that has already been pushed without deleting history
git revert a1b2c3d # Create an "inverse" commit that cancels the changes
git push
On shared branches, always use revert instead of reset. Revert does not rewrite history and is safe to push.
Once these recovery actions become second nature, your fear of Git will diminish substantially because you know that if it was committed, it can almost always be recovered.
Summary
- merge preserves history; rebase makes it linear. Use merge or squash merge on shared branches, and use rebase to sync personal branches with main.
- The golden rule of rebase: Do not rebase shared commits that have already been pushed; rewrite private branches as needed.
- interactive rebase (
rebase -i) is the Swiss Army knife for cleaning up history: squash combines commits, reword changes messages, reorder changes their order, and drop deletes them. - cherry-pick precisely moves individual commits, making it useful for syncing hotfixes and transplanting changes across branches.
- reflog is Git's undo button: it can recover from
reset --hard, deleted branches, and accidental amend operations, as long as you committed the work. - stash provides temporary storage for urgent context switches. Build the habit of adding notes with
stash push -m. - Use
reverton shared branches; useamend/rebase+push --force-with-leaseon private branches.
One line to remember: Git is frightening because you think it is "irreversible." Once you learn reflog and rebase, you discover that almost nothing in Git is truly irreversible; there are only reversals you have not learned yet.
Related
Related Content
Article
What Is Vibe Coding? Will It Change How Programmers Work?
Technical trends, engineering practices, and an indie developer perspective on Vibe Coding, AI, and independent development.
Article
From Copilot to Codex: AI Coding Tools Enter the Agent Era
Technical trends, engineering practices, and indie-developer perspectives around AI, AI Agents, and Vibe Coding.
Article
Security Risks of AI Code Generation: Don't Blindly Trust Vibe Coding
Around Vibe Coding and AI: technology trends, engineering practices, and an indie developer's perspective.
Article
Comparing Cursor, Claude Code, Codex, and Copilot
Centered on Vibe Coding and AI technology trends, engineering practices, and the independent developer perspective.