DevOps.Academy
CoursesCurriculumPricing

    Loading…

    ↑ ↓ move · ↵ open · Ctrl K anywhere

    ◔My learningSign in
    DevOps.Academy

    Free, hands-on DevOps lessons. Made with care in India.

    Learn

    All coursesLinux roadmapCurriculumMy learning

    Academy

    PricingCertificates

    Legal

    PrivacyTermsContact

    devops.rajeev.pro

    Git & GitHubLesson 8 of 13
    Your progress · 0%
    On this page
    1. Which undo?
    2. Revert: undo a commit everyone has
    3. Reset: move your branch back
    4. Stash: put work aside
    5. Reflog: your safety net
    ↖ Course roadmap
    Lesson 8 · Undoing changes

    Undoing changes: revert, reset, stash and reflog

    Pick the right undo every time: discard an edit, unstage a file, take back a local commit with reset, undo a pushed one with revert, set work aside with stash, recover with reflog.

    35 min read · intermediate · hands-on

    By the end of this lesson you'll be able to

    0 of 5 complete

    Which undo?

    Every undo starts with one question: where is the mistake now? The answer picks the command:

    The mistake is…Undo withSafe after pushing?
    an edit you haven't stagedgit restore filen/a (never left your machine)
    staged, not committedgit restore --staged filen/a
    the last commit, message or missing filegit commit --amend (Rewriting history)no
    in local commits, not pushedgit resetno
    in a pushed commitgit revertyes
    "I just lost something"git reflogit's local

    The dividing line is pushed. Before pushing, you may rewrite your own history freely. After pushing, other people have those commits, so you add new commits instead of removing old ones.

    Revert: undo a commit everyone has

    git revert makes a new commit that does the opposite of an old one. History only grows, so nobody's clone is disturbed:

    $ git log --oneline
    26ec4e7 Lower the timeout to 1 second
    5a5f6da Add config
    5b4574d Add app
    $ git revert --no-edit HEAD
    [main f390571] Revert "Lower the timeout to 1 second"
     1 file changed, 1 insertion(+), 1 deletion(-)
    $ git log --oneline -2
    f390571 Revert "Lower the timeout to 1 second"
    26ec4e7 Lower the timeout to 1 second
    $ cat config.py
    TIMEOUT = 30
    $ git push
    
    git log --oneline
    • gitversion control
    • loghistory
    cat config.py
    • catprint a file
    git push
    • pushupload commits

    Revert any commit by ID, not only the last: git revert 26ec4e7. To revert a merge commit, say which parent is the "main line": git revert -m 1 <merge-commit>. On GitHub, every merged pull request has a Revert button that opens a pull request doing exactly this.

    During an incident

    When a deploy breaks production, revert first and investigate second. A revert is reviewed, small and goes through the normal pipeline. The fix can come later, without the pressure of a live outage.

    Reset: move your branch back

    git reset moves your current branch to another commit, as if the later commits had never happened. Its three modes differ only in what they do with the changes from those commits:

    All three move the branch back. They differ in where the changes land: staged, kept, or thrown away.© Animation: DevOps Academy

    All three move the branch back. They differ in where the changes land: staged, kept, or thrown away.© Animation: DevOps Academy
    $ git reset --soft HEAD~1         # undo the commit, keep its changes staged
    $ git status --short
    M  app.py
    $ git reset HEAD~1                # --mixed (default): undo the commit, keep changes unstaged
    $ git status --short
     M app.py
    $ git reset --hard HEAD~1         # undo the commit and throw its changes away
    HEAD is now at d62b38c Commit A
    
    git reset --soft HEAD~1
    • gitversion control
    git status --short
    • statuswhat changed
    ModeUse it to
    --softcombine your last few commits into one: git reset --soft HEAD~3, then one git commit
    --mixedredo the last commit differently: split it, or leave a file out
    --hardthrow away local commits and edits entirely: start again from a known point

    HEAD~1 means one commit before HEAD; HEAD~3 is three back. git reset --hard origin/main is the classic "make my branch exactly match GitHub's", and it deletes anything you had that GitHub doesn't.

    Two ways to really lose work

    git reset --hard and git restore delete uncommitted edits, and Git can't bring those back: they were never saved. Run git status first, and commit or stash anything you might want. And never reset a branch others have pulled: use revert there.

    Stash: put work aside

    You're halfway through something and need a clean working folder: an urgent fix, a pull, a quick look at another branch. git stash saves your changes and tidies the folder:

    $ git stash                        # tracked changes only
    Saved working directory and index state WIP on main: 0bb860e B3
    $ git status --short
    ?? new.py
    $ git stash -u -m "half-done search"   # -u: include untracked (new) files, -m: a label
    $ git stash list
    stash@{0}: On main: half-done search
    stash@{1}: WIP on main: 0bb860e B3
    $ git stash show -p stash@{1}      # what's in it
    $ git stash pop                    # apply the newest and remove it from the list
    $ git stash apply stash@{1}        # apply one, but keep it in the list
    $ git stash drop stash@{1}         # delete one
    
    git stash
    • gitversion control
    git status --short
    • statuswhat changed

    Without -u, new files stay behind in your folder; that's the most common stash surprise. Stashes are for minutes or hours, not weeks: anything longer belongs on a branch (git stash branch fix/x turns a stash into one).

    Reflog: your safety net

    Git quietly records every place HEAD has been: every commit, checkout, reset, rebase and merge. That log, the reflog, is how you get back "lost" commits:

    $ git reset --hard HEAD~1          # oops: B3 is gone from the branch
    HEAD is now at d62b38c Commit A
    $ git reflog -3
    d62b38c HEAD@{0}: reset: moving to HEAD~1
    0bb860e HEAD@{1}: commit: B3
    d62b38c HEAD@{2}: reset: moving to HEAD~1
    $ git reset --hard HEAD@{1}        # go back to where HEAD was one step ago
    HEAD is now at 0bb860e B3
    
    git reset --hard HEAD~1
    • gitversion control

    It works for a deleted branch too: git branch -D spike printed (was 1bae2b4), so git branch spike 1bae2b4 brings it back. If you didn't see the ID, look for the last commit on that branch in git reflog.

    The reflog is local (it's not pushed or cloned) and entries expire, by default after 90 days. A commit that was committed is almost never truly lost within that time; it's just unlabelled.

    ⌘
    Mini mission

    A bad deploy and a bad reset

    A commit lowered a timeout and broke production after it was pushed. Undo it the safe way. Then, while tidying up locally, you hard-reset away a good commit by mistake. Get it back. End with the timeout restored and no work lost.

    Input: Any folder, plus a bare repository standing in for GitHubOutput: TIMEOUT = 30 on main, and the 'Add retries' commit recovered
    Reveal one safe solution
    $ git init --bare remote.git && git clone remote.git shop && cd shop
    $ echo "TIMEOUT = 30" > config.py && git add . && git commit -m "Add config" && git push -u origin main
    $ echo "TIMEOUT = 1" > config.py && git commit -am "Lower the timeout to 1 second" && git push
    $ git revert --no-edit HEAD && git push            # 1. production fix: a new commit
    $ cat config.py
    TIMEOUT = 30
    $ echo "RETRIES = 3" >> config.py && git commit -am "Add retries"
    $ git reset --hard HEAD~1                          # 2. the mistake
    $ grep -c RETRIES config.py
    0
    $ git reflog -2
    5c40083 HEAD@{0}: reset: moving to HEAD~1
    be5be3f HEAD@{1}: commit: Add retries
    $ git reset --hard HEAD@{1}                        # 3. the recovery
    $ grep RETRIES config.py && git push
    RETRIES = 3
    $ git log --oneline
    
    git init --bare remote.git && git clone remote.git shop && cd shop
    • gitversion control
    • initcreate a repository here
    • &&run the next command only if this one worked
    • clonecopy a repository
    • cdchange directory
    echo "TIMEOUT = 30" > config.py && git add . && git commit -m "Add config" && git push -u origin main
    • echoprint text
    • >write output to a file, replacing it
    • addstage changes
    • .this folder
    • commitsave staged changes
    • pushupload commits
    cat config.py
    • catprint a file
    echo "RETRIES = 3" >> config.py && git commit -am "Add retries"
    • >>append output to a file
    grep -c RETRIES config.py
    • grepfind lines that match a pattern
    • -ccount matching lines
    git log --oneline
    • loghistory

    The final log reads: Add retries, Revert "Lower the timeout…", Lower the timeout…, Add config. The bad commit is still in history, deliberately: it records what happened, and the revert records the fix.

    Try it yourself

    0 of 6 steps done

    Run each task in your own account or terminal, then tick it off.

    Knowledge check

    Quick check

    Question 1 of 5

    A commit that broke production is already on main and pushed. How do you undo it?

    Go deeper

    Official reading for this lesson

    • GuidePro Git: undoing thingsPro Git ↗
    • GuidePro Git: reset demystifiedPro Git ↗
    • GuidePro Git: stashing and cleaningPro Git ↗
    • Docsgit-reflog documentationGit ↗
    • ArticleOh Shit, Git!?!Katie Sylor-Miller ↗

    Or get every quiz answer right and it completes itself.

    ← PreviousCollaboration: issues, pull requests and code reviewNext lesson →History and diffs: find what changed, who changed it, and when it broke