#Vibe Coding#DevOps

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
Author @admin·Published July 26, 2026·Updated July 26, 2026

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

bash
git switch main
git merge feature/login

The history looks like this:

text
*   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.

bash
git switch feature/login
git rebase main

The result:

text
* 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 main to 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:

bash
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:

text
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.
bash
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.

bash
git reflog

The output looks roughly like this:

text
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}:

bash
git reset --hard HEAD@{1}

Deleted a branch by mistake?

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

bash
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:

bash
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:

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

bash
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

bash
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

bash
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

bash
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

bash
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 revert on shared branches; use amend / rebase + push --force-with-lease on 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