{"id":"l-git-4-1","courseId":"git-github","lang":"en","kind":"lesson","summary":"Choose the right undo for any mistake (restore, amend, revert or reset in its three modes) and use the reflog to recover commits you thought were gone.","html":"<p>Everyone breaks something in Git eventually. You commit to the wrong branch, push a bug, reset away a morning’s work. The good news: Git almost never throws data away, and nearly every mistake has a clean undo. The trap is reaching for whichever undo command you half-remember. Pick by asking one question: <strong>where does the mistake live?</strong></p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Pick the tool by location</h2>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"Scrollable table\" tabindex=\"0\"><table><thead>\n<tr>\n<th>Where the mistake is</th>\n<th>Command</th>\n<th>Rewrites history?</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Unstaged edits in a file</td>\n<td><code dir=\"ltr\">git restore &lt;file&gt;</code></td>\n<td>No (but discards the edits)</td>\n</tr>\n<tr>\n<td>Staged, not committed</td>\n<td><code dir=\"ltr\">git restore --staged &lt;file&gt;</code></td>\n<td>No</td>\n</tr>\n<tr>\n<td>Last commit, not pushed</td>\n<td><code dir=\"ltr\">git commit --amend</code></td>\n<td>Yes, your local commit only</td>\n</tr>\n<tr>\n<td>Local commits, not pushed</td>\n<td><code dir=\"ltr\">git reset</code></td>\n<td>Yes, local only</td>\n</tr>\n<tr>\n<td>Commits already pushed and shared</td>\n<td><code dir=\"ltr\">git revert &lt;commit&gt;</code></td>\n<td>No</td>\n</tr>\n</tbody>\n</table></div><p>The table follows a single rule: once other people have a commit, you add new commits on top instead of changing it.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Amend: fix the last commit</h2>\n<p>Forgot a file or wrote a bad message, and haven’t pushed?</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> add</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> contact.css</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> commit</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --amend</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --no-edit</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">              # same message, file included</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> commit</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --amend</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -m</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"Add contact form\"</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  # or a new message</span></span></code></pre></div>\n<p>Amend doesn’t edit the commit; it replaces it with a new one with a new ID. That’s fine locally and a problem if you’ve pushed, for the same reason rebase is.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Revert: undo in public</h2>\n<p>You merged a commit to <code dir=\"ltr\">main</code> that broke the mobile nav. It’s pushed, and teammates have pulled. Don’t rewrite anything. Add a commit that reverses it:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> revert</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> HEAD</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">          # if it's the latest commit</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> revert</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 8f1e2aa</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">       # or any commit by ID</span></span></code></pre></div>\n<p>Git creates a new commit, <code dir=\"ltr\">Revert &quot;Move nav into header&quot;</code>, that applies the exact opposite changes. History still shows the mistake and its fix, which is honest and safe: everyone gets the revert with a normal pull. To revert a merge commit, tell Git which parent to keep as the mainline, usually the first: <code dir=\"ltr\">git revert -m 1 &lt;merge-commit&gt;</code>.</p>\n<p>Sometimes only part of a commit was wrong. If the nav move was fine except for <code dir=\"ltr\">styles.css</code>, you don’t need to revert the whole thing. Bring that one file back to how it was before the commit, check it, and commit the result as an ordinary fix:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> restore</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --source=8f1e2aa~1</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> styles.css</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> diff</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> commit</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -am</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"Restore mobile nav styles\"</span></span></code></pre></div>\n<p>That’s still “adding history, not rewriting it”, just with a smaller scope.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">Reset: move the branch, three ways</h2>\n<p><code dir=\"ltr\">git reset &lt;commit&gt;</code> moves the current branch to another commit. What it does to the index and working tree depends on the mode, and this is where people get hurt.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 260\" role=\"img\" aria-labelledby=\"l-git-4-1-t1\"><defs><marker id=\"arrow-l-git-4-1-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-git-4-1-t1\">A table of three rows. reset --soft moves the branch only; index and working tree keep the changes, which are now staged. reset --mixed, the default, moves the branch and resets the index; the working tree keeps the changes, now unstaged. reset --hard moves the branch and resets both index and working tree, so the changes are discarded.</title>\n  <text class=\"d-label-strong\" x=\"190\" y=\"30\" text-anchor=\"middle\">Branch (HEAD)</text>\n  <text class=\"d-label-strong\" x=\"380\" y=\"30\" text-anchor=\"middle\">Index</text>\n  <text class=\"d-label-strong\" x=\"570\" y=\"30\" text-anchor=\"middle\">Working tree</text>\n  <text class=\"d-code\" x=\"20\" y=\"82\">--soft</text>\n  <rect class=\"d-box-primary\" x=\"120\" y=\"55\" width=\"140\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"190\" y=\"82\" text-anchor=\"middle\">moved</text>\n  <rect class=\"d-box-success\" x=\"310\" y=\"55\" width=\"140\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"380\" y=\"82\" text-anchor=\"middle\">kept</text>\n  <rect class=\"d-box-success\" x=\"500\" y=\"55\" width=\"140\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"570\" y=\"82\" text-anchor=\"middle\">kept</text>\n  <text class=\"d-code\" x=\"20\" y=\"142\">--mixed</text>\n  <rect class=\"d-box-primary\" x=\"120\" y=\"115\" width=\"140\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"190\" y=\"142\" text-anchor=\"middle\">moved</text>\n  <rect class=\"d-box-primary\" x=\"310\" y=\"115\" width=\"140\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"380\" y=\"142\" text-anchor=\"middle\">reset</text>\n  <rect class=\"d-box-success\" x=\"500\" y=\"115\" width=\"140\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"570\" y=\"142\" text-anchor=\"middle\">kept</text>\n  <text class=\"d-code\" x=\"20\" y=\"202\">--hard</text>\n  <rect class=\"d-box-primary\" x=\"120\" y=\"175\" width=\"140\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"190\" y=\"202\" text-anchor=\"middle\">moved</text>\n  <rect class=\"d-box-primary\" x=\"310\" y=\"175\" width=\"140\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"380\" y=\"202\" text-anchor=\"middle\">reset</text>\n  <rect class=\"d-box-warn\" x=\"500\" y=\"175\" width=\"140\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label-strong\" x=\"570\" y=\"202\" text-anchor=\"middle\">overwritten</text>\n  <text class=\"d-label-muted\" x=\"350\" y=\"248\" text-anchor=\"middle\">--mixed is the default when you give no mode</text>\n</svg><figcaption>The three reset modes and the three areas</figcaption></figure>\n<p>Three situations, three modes:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> reset</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --soft</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> HEAD~2</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">    # squash: the last two commits' changes become staged, ready for one commit</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> reset</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> HEAD~1</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">           # un-commit (--mixed): changes return as unstaged edits to rework</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> reset</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --hard</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> HEAD~1</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">    # discard: the commit and its changes are gone from the branch and files</span></span></code></pre></div>\n<p>A classic use: you committed to <code dir=\"ltr\">main</code> when you meant to be on a branch. Commit or stash any uncommitted edits first (<code dir=\"ltr\">--hard</code> would wipe them), create the branch where you are, then move <code dir=\"ltr\">main</code> back:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> branch</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> contact-form</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">        # the branch keeps your commit</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> reset</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --hard</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> origin/main</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">   # main goes back to match GitHub</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> switch</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> contact-form</span></span></code></pre></div>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Using reset on pushed commits</span></p>\n<p><code dir=\"ltr\">git reset</code> on a branch you’ve already pushed makes your local branch disagree with GitHub, and the only way to publish it is a force-push that rewrites everyone else’s history. If the commit is shared, use <code dir=\"ltr\">git revert</code>. Reserve <code dir=\"ltr\">reset</code> for commits that have never left your machine.</p>\n</aside>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Make a bookmark before anything risky</span></p>\n<p>Before a reset, a big rebase or any command you’re unsure of, run <code dir=\"ltr\">git branch backup-nav</code>. It costs nothing, and if the result isn’t what you wanted, <code dir=\"ltr\">git reset --hard backup-nav</code> takes you straight back. Delete the bookmark with <code dir=\"ltr\">git branch -D backup-nav</code> once you’re happy.</p>\n</aside>\n<h2 id=\"sec-5\" tabindex=\"-1\">The reflog: Git’s flight recorder</h2>\n<p>Here’s the safety net that makes all of this less frightening. Every time HEAD moves (commit, switch, reset, rebase, merge), Git writes a line in the <strong>reflog</strong>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> reflog</span></span></code></pre></div>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"text\" data-index=\"6\"><div class=\"code-head\"><span class=\"code-title\">Text</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code>2b7f0c1 (HEAD -&gt; main) HEAD@{0}: reset: moving to HEAD~3\n9e4a1d5 HEAD@{1}: commit: Add contact form validation\n4c8b2e7 HEAD@{2}: commit: Add contact form\na71d3f0 HEAD@{3}: checkout: moving from footer to main</code></pre></div>\n<p>After that accidental <code dir=\"ltr\">git reset --hard HEAD~3</code>, the three commits aren’t on any branch, but they still exist and <code dir=\"ltr\">HEAD@{1}</code> points at the newest. Bring them back:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"7\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> reset</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --hard</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> HEAD@{</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">          # put main back where it was</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\"># or, more cautiously, keep them on a new branch to inspect first</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> switch</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -c</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rescue</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 9e4a1d5</span></span></code></pre></div>\n<p>The reflog is local (it isn’t pushed or cloned) and entries expire: by default after 90 days, or 30 days for commits no branch can reach. That’s plenty of time to notice a mistake.</p>\n<p>What the reflog can’t save is work that was never committed. <code dir=\"ltr\">git restore</code> and <code dir=\"ltr\">git reset --hard</code> overwrite uncommitted edits in your files, and no log knows about them. So the real safety habit is simple: commit early and often on your branch. You can always squash commits before review; you can’t recover edits you never committed.</p>\n<p>Some work-in-progress isn’t ready for a commit, though. Next you’ll park it with <code dir=\"ltr\">git stash</code>, and stop Git from tracking files it never should.</p>\n","toc":[{"id":"sec-1","text":"Pick the tool by location","level":2},{"id":"sec-2","text":"Amend: fix the last commit","level":2},{"id":"sec-3","text":"Revert: undo in public","level":2},{"id":"sec-4","text":"Reset: move the branch, three ways","level":2},{"id":"sec-5","text":"The reflog: Git’s flight recorder","level":2}],"takeaways":["Pick the undo by where the mistake lives (working tree, index, local commit or shared commit), not by which command you remember.","<code dir=\"ltr\">git revert &lt;commit&gt;</code> adds a new commit that cancels an old one, which is the safe way to undo anything already pushed.","<code dir=\"ltr\">git reset</code> moves the current branch: <code dir=\"ltr\">--soft</code> keeps changes staged, <code dir=\"ltr\">--mixed</code> keeps them unstaged, <code dir=\"ltr\">--hard</code> discards them.","<code dir=\"ltr\">git commit --amend</code> replaces the last commit, so use it only before pushing.","<code dir=\"ltr\">git reflog</code> lists where HEAD has been, so commits lost to a reset or rebase can be found and restored for weeks afterwards."],"further":[{"title":"Undoing Things (Pro Git)","url":"https://git-scm.com/book/en/v2/Git-Basics-Undoing-Things"},{"title":"Reset Demystified (Pro Git)","url":"https://git-scm.com/book/en/v2/Git-Tools-Reset-Demystified"},{"title":"git revert reference","url":"https://git-scm.com/docs/git-revert"},{"title":"git reflog reference","url":"https://git-scm.com/docs/git-reflog"}],"quiz":[{"q":"<p>You committed “Add contact form” a minute ago, haven’t pushed, and realise you forgot to stage <code dir=\"ltr\">contact.css</code>. What’s the cleanest fix?</p>","options":[{"text":"<code dir=\"ltr\">git revert HEAD</code>, then commit again with both files.","why":"That leaves two extra commits (the original and its reversal) in history for a mistake nobody else has seen."},{"text":"<code dir=\"ltr\">git reset --hard HEAD~1</code> and redo the work.","why":"<code dir=\"ltr\">--hard</code> discards the committed changes from your files. You’d be rewriting the form by hand."},{"text":"<code dir=\"ltr\">git add contact.css</code>, then <code dir=\"ltr\">git commit --amend --no-edit</code>.","why":"Correct. Amend replaces the unpushed last commit with one that includes the file; <code dir=\"ltr\">--no-edit</code> keeps the message."},{"text":"<code dir=\"ltr\">git restore --staged contact.css</code>","why":"That unstages a file. <code dir=\"ltr\">contact.css</code> isn’t staged yet, and the commit would still be missing it."}],"answer":2},{"q":"<p>Yesterday you merged a commit to <code dir=\"ltr\">main</code> that broke the mobile nav. It’s pushed and your teammates have pulled. How do you undo it?</p>","options":[{"text":"<code dir=\"ltr\">git revert &lt;commit&gt;</code> and push the new commit.","why":"Correct. Revert records a new commit that applies the opposite change, so nobody’s history is rewritten and everyone gets the fix with a normal pull."},{"text":"<code dir=\"ltr\">git reset --hard &lt;commit&gt;~1</code> and <code dir=\"ltr\">git push --force</code>.","why":"That rewrites shared history. Teammates still have the bad commit, and their next push brings it back or creates a mess."},{"text":"<code dir=\"ltr\">git commit --amend</code> to edit the bad commit away.","why":"Amend only touches your latest commit, creates a new ID, and would also need a force-push to a shared branch."},{"text":"<code dir=\"ltr\">git restore --source=HEAD~1 .</code> and push without committing.","why":"Restoring files changes only your working tree. Nothing is pushed until it’s committed."}],"answer":0},{"q":"<p>You run <code dir=\"ltr\">git reset --soft HEAD~2</code> on two unpushed commits. Where are their changes now?</p>","options":[{"text":"Deleted from your files.","why":"That’s <code dir=\"ltr\">--hard</code>. <code dir=\"ltr\">--soft</code> never touches the index or the working tree."},{"text":"In your working tree but unstaged.","why":"That’s the default <code dir=\"ltr\">--mixed</code> mode, which also resets the index."},{"text":"Still in the two commits, which are now on a new branch.","why":"Reset doesn’t create branches. It moves the current branch pointer."},{"text":"Staged in the index, ready to be committed again as one commit.","why":"Correct. The branch moved back two commits but the index and files kept everything, a quick way to squash local commits."}],"answer":3},{"q":"<p>You ran <code dir=\"ltr\">git reset --hard HEAD~3</code> by mistake and three commits vanished from <code dir=\"ltr\">git log</code>. How do you get them back?</p>","options":[{"text":"They’re gone; <code dir=\"ltr\">--hard</code> deletes commits permanently.","why":"Reset moves a pointer. The commits still exist, and the reflog remembers where HEAD was."},{"text":"Run <code dir=\"ltr\">git reflog</code>, find the entry from before the reset, and <code dir=\"ltr\">git reset --hard HEAD@{1}</code>.","why":"Correct. <code dir=\"ltr\">HEAD@{1}</code> means where HEAD was one move ago, which is right before the reset."},{"text":"Run <code dir=\"ltr\">git revert HEAD~3</code>.","why":"Revert creates a new commit undoing a change. It doesn’t restore lost commits to your branch."},{"text":"Run <code dir=\"ltr\">git pull</code>, which restores any missing commits.","why":"Only if they were pushed. Pull fetches what’s on the remote; unpushed commits exist only in your local repository."}],"answer":1}],"exercise":{"kind":"choice","multiple":false,"options":["a","b","c","d"],"correct":["b"],"title":"Pick the undo","prompt":"<p>Your most recent commit on <code dir=\"ltr\">main</code>, <code dir=\"ltr\">8f1e2aa Move nav into header</code>, broke the mobile menu. You pushed it an hour ago and two teammates have already pulled. <code dir=\"ltr\">git status</code> shows a clean working tree.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"text\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Text</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code>8f1e2aa (HEAD -&gt; main, origin/main) Move nav into header\nc39be11 Add projects page\nb2e04f3 Add navigation bar with base styles</code></pre></div>\n<p>Which command should you run before pushing again?</p>\n","hints":["Ask where the mistake lives: your files, the index, a local commit, or a shared commit?","Anything that changes <code dir=\"ltr\">8f1e2aa</code> itself would need a force-push to a branch others have pulled.","You want to add history, not rewrite it."],"explanation":"<p>The bad commit is already shared, so rewriting it (amend or reset) would force everyone else to repair their history. <code dir=\"ltr\">git revert HEAD</code> adds a new commit that applies the exact opposite of <code dir=\"ltr\">8f1e2aa</code>; push it normally and your teammates get the fix with an ordinary pull. Later you can fix the nav properly on a branch and bring the move back through a pull request.</p>\n","checkLabels":{},"optionText":{"a":{"text":"<code dir=\"ltr\">git reset --hard HEAD~1</code>, then <code dir=\"ltr\">git push --force</code>","why":"This deletes a commit your teammates already have. Their next push can bring it back, and the force-push may wipe out anything pushed in the meantime."},"b":{"text":"<code dir=\"ltr\">git revert HEAD</code>, then <code dir=\"ltr\">git push</code>","why":"Correct. The new commit cancels <code dir=\"ltr\">8f1e2aa</code> without rewriting shared history."},"c":{"text":"<code dir=\"ltr\">git restore index.html</code>","why":"The working tree is clean, so there’s nothing to restore. The bug is inside a commit, and only a new commit can undo it publicly."},"d":{"text":"<code dir=\"ltr\">git commit --amend</code>","why":"Amend replaces the latest commit with a new ID, which is a rewrite of a pushed commit and would need a force-push."}}},"words":719,"readMinutes":3}