Git Course 0%

Level 5 — Advanced: rebase, cherry-pick, reset, reflog

Advanced Git CLI ≈ 60 min

What you will learn

  • Tidy a branch's history before anyone reviews it
  • Rebase onto a moved main and publish the result safely
  • Copy a fix to a release branch, undo a pushed commit, and recover from a hard reset

After this lesson you can

  • The commands that used to look dangerous are ones I have run and undone

Objective

Everything in this lab is recoverable, and by the end you will have recovered from most of it on purpose. Six short exercises, in one branch of one project: stash, interactive rebase, rebase onto a moved main with a safe force push, cherry-pick, revert, and a hard reset undone through the reflog.

Starting state

  • Your own private project with the playground pushed, from Lab 2, and main up to date.
  • A second clone, or a colleague, to play the person who pushes to main while you work. The instructions use a second clone; Lab 4 set one up.
  • About an hour, and a willingness to break things deliberately.

Instructions

  1. Start work, then get interrupted. On a new branch, begin a change and do not commit it:

    Terminal
    $ git switch -c docs/21-difficulty-filter

    Add a section to docs/getting-started.md, then check what Git sees:

    Terminal
    $ git status --short
     M docs/getting-started.md
  2. Park it with a name. Something urgent has come up on main:

    Terminal
    $ git stash push -m "difficulty filter draft"
    Saved working directory and index state On docs/21-difficulty-filter: difficulty filter draft

    git status --short now prints nothing: the tree is clean, and you can switch branches freely.

  3. Come back and unpark.

    Terminal
    $ git stash list
    stash@{0}: On docs/21-difficulty-filter: difficulty filter draft
    Terminal
    $ git switch docs/21-difficulty-filter
    $ git stash pop

    Your change is back, and the stash is dropped. Now commit it as docs: add filtering section.

  4. Make a mess on purpose. Add two more commits with deliberately poor messages: wip and fix typo. This is what a real branch looks like after an afternoon:

    Terminal
    $ git log --oneline -4
    c449a24 fix typo
    6ac7e98 wip
    874ce55 docs: add filtering section
    d3b92ec chore: add CI configuration and contributing guide
  5. Tidy it with an interactive rebase. Nothing is pushed yet, so rewriting is safe:

    Terminal
    $ git rebase -i main
    Successfully rebased and updated refs/heads/docs/21-difficulty-filter.

    An editor opens listing your three commits, each with pick. Change the second and third from pick to squash, save and close. A second editor opens for the combined message: keep docs: add filtering section and delete the rest. Then:

    Terminal
    $ git log --oneline -3
    534c2c2 docs: add filtering section
    d3b92ec chore: add CI configuration and contributing guide
    b81702c docs: add getting started guide and FAQ

    Three commits became one, with the same content.

  6. Push the branch, then let main move underneath you. In your second clone, commit something to main and push it. Back in the first:

    Terminal
    $ git fetch
    $ git log --oneline --graph -4 origin/main docs/21-difficulty-filter
    * 9d4a39f docs: add a support section to the README
    | * 534c2c2 docs: add filtering section
    |/
    * d3b92ec chore: add CI configuration and contributing guide

    The fork in that graph is your branch and main diverging.

  7. Rebase onto the new main.

    Terminal
    $ git rebase origin/main
    Successfully rebased and updated refs/heads/docs/21-difficulty-filter.

    Your commit now sits on top of the support-section commit, with a new hash.

  8. Meet the rejection, then push safely. An ordinary push is refused, because you rewrote a commit the server already had:

    Terminal
    $ git push
     ! [rejected]        docs/21-difficulty-filter -> docs/21-difficulty-filter (non-fast-forward)
    error: failed to push some refs
    hint: Updates were rejected because the tip of your current branch is behind

    Do not follow that hint. Pulling would merge the old version back in. This is your own branch, so replace it:

    Terminal
    $ git push --force-with-lease
     + 534c2c2...0fec326 docs/21-difficulty-filter -> docs/21-difficulty-filter (forced update)
  9. Merge it, tag a release, and cut a release branch. Merge the branch into main however your project does it, then:

    Terminal
    $ git tag -a v1.2.0 -m "Release 1.2.0"
    $ git push origin main v1.2.0
    $ git switch -c release/1.2 v1.2.0
  10. Fix something on main and backport it. On main, commit a one-line fix, note its hash, then:

    Terminal
    $ git switch release/1.2
    $ git cherry-pick -x 3fe0fe5
    [release/1.2 a4147aa] fix: ignore trails with an empty distance (#15)
     1 file changed, 2 insertions(+)

    Read the resulting message:

    Terminal
    $ git log -1 --format=%B
    fix: ignore trails with an empty distance (#15)
    
    (cherry picked from commit 3fe0fe5…)

    Two hashes, one change, and a record of the relationship. That is a backport.

  11. Undo something that is already pushed. Back on main, push the fix, then decide it was wrong:

    Terminal
    $ git switch main
    $ git revert --no-edit HEAD
    [main ae7615f] Revert "fix: ignore trails with an empty distance (#15)"
     1 file changed, 2 deletions(-)
    Terminal
    $ git log --oneline -3
    ae7615f Revert "fix: ignore trails with an empty distance (#15)"
    3fe0fe5 fix: ignore trails with an empty distance (#15)
    0fec326 docs: add filtering section

    Both commits are there: the mistake and its undo. Nobody else has to do anything.

  12. Destroy two commits and get them back. The finale:

    Terminal
    $ git reset --hard HEAD~2
    HEAD is now at 0fec326 docs: add filtering section

    They are gone from git log. Now look at what Git still remembers:

    Terminal
    $ git reflog -5
    0fec326 HEAD@{0}: reset: moving to HEAD~2
    ae7615f HEAD@{1}: revert: Revert "fix: ignore trails with an empty distance (#15)"
    3fe0fe5 HEAD@{2}: checkout: moving from release/1.2 to main
    a4147aa HEAD@{3}: cherry-pick: fix: ignore trails with an empty distance (#15)
    0fec326 HEAD@{4}: checkout: moving from main to release/1.2

    And bring them back:

    Terminal
    $ git reset --hard ORIG_HEAD
    HEAD is now at ae7615f Revert "fix: ignore trails with an empty distance (#15)"

    git log --oneline -3 shows all three commits again.

Expected result

  • One tidy commit on a branch that started as three messy ones.
  • A branch rebased onto a moved main and published with --force-with-lease.
  • The same fix on main and on release/1.2, under two hashes, with the relationship recorded.
  • A revert commit sitting above the commit it undoes.
  • Two commits destroyed and restored, with the reflog as the evidence.

If something goes wrong

Symptom Fix
The interactive rebase editor is confusing or opens the wrong program git rebase --abort, set your editor (lesson 3.3), try again
A conflict during the rebase Resolve, git add, git rebase --continue; or git rebase --abort (lesson 12.6)
--force-with-lease is rejected with "stale info" Someone pushed to your branch. git fetch, look at what arrived, then decide (lesson 12.8)
The cherry-pick conflicts The release branch has diverged; resolve as you would a merge, or git cherry-pick --abort
git revert complains about a merge Add -m 1 (command page)
You have lost track entirely git reflog, find a line whose message you recognize, and git reset --hard to it

What you learned

Every command in this lab has a reputation, and the reputation is mostly about not knowing the way back. You now have the way back for each: --abort during an operation, --force-with-lease instead of --force, revert instead of reset on pushed work, and git reflog when something has vanished.

The one rule that survives all of it: rewrite only what nobody else has. Steps 5, 7 and 8 were safe because the branch was yours. Step 11 used revert precisely because the commit was not.

Next

Level 6 puts the whole course together: one sprint at Northwind Trails, with issues, a hotfix, a red pipeline, a conflict and a release.

Key terms

Rebase Stash Revert Reflog Squash