{"course":{"slug":"building-apps-with-ai","title":"Building Apps with AI","url":"https://addaly.com/learn/building-apps-with-ai","level":"beginner","track":"build"},"lesson":{"slug":"three-ways-back","title":"Three ways back, and the undo for undo","url":"https://addaly.com/learn/building-apps-with-ai/three-ways-back","position":39,"of":87,"minutes":9,"answer":"Restore one file, revert one commit, or reset everything: they are different operations, and once a commit has been pushed only revert is safe.","keyIdea":"Restore one file, revert one commit, or reset everything: they are different operations, and once a commit has been pushed only revert is safe."},"body":{"format":"markdown","text":"## Looking before moving\n\nThe earlier lesson on finding the change that broke it gave you `git log -p -- file` and bisecting. Two more reading commands belong beside them, because most people reach for a destructive command when a reading one would have answered the question.\n\n```bash\ngit show a3f9c21                     # one commit: message plus its full diff\ngit show a3f9c21:src/booking.ts      # one file, exactly as it was then\ngit log -S \"cancelBooking\" --oneline # commits that added or removed that text\n```\n\nThat last one is the pickaxe. When a function has vanished and nobody remembers deleting it, `-S` finds the commit where the string stopped appearing. It searches content, not messages, so it works even when every message says \"update\".\n\n`HEAD` means \"where I am now\". `HEAD~1` is its parent, `HEAD~3` three commits back. `git diff HEAD~3` is everything since then.\n\n## Way one: bring back a single file\n\n```bash\ngit restore --source a3f9c21 src/booking.ts\n```\n\nNothing else moves. Only that file is copied out of that commit into your working tree, where it sits as an ordinary uncommitted change you can look at, keep or discard. The older spelling, `git checkout a3f9c21 -- src/booking.ts`, does the same thing.\n\nThis is the most useful and least known of the three. An agent has rewritten a file it should not have touched; the rest of its work is good. Restore the one file and keep the rest. No reset, no lost work.\n\n## Way two: undo a commit with a new commit\n\n```bash\ngit revert a3f9c21\n```\n\nRevert reads the diff of that commit, applies the reverse of it, and makes a new commit on top. The original stays in the history. The log shows both the change and its undoing, which is the honest record of what happened.\n\nBecause it adds rather than rewrites, revert is safe after pushing. The remote receives one more commit, like any other. Reverting a commit from a week ago may conflict with what came after it; git will say so and you resolve it as in the conflict lesson.\n\n## Way three: move everything back\n\n`git reset --hard a3f9c21` is the command from the first git lesson. It moves your branch pointer back to that commit and makes the working tree match. Every later commit is abandoned; every uncommitted change is destroyed.\n\nIt is the right tool for the two-hour agent session that went nowhere and was never pushed. It is the wrong tool for anything the remote has already seen, and the mechanism is worth understanding: the remote still holds the commits you abandoned, so your next push is rejected as \"non-fast-forward\". The suggested fix in most search results is `git push --force`, which makes the remote match you and deletes those commits from the only other copy. If a collaborator, or the host, or you on another machine has built on them, that work is now orphaned.\n\nThe rule is short. Not pushed: reset is fine. Pushed: revert.\n\n## Detached HEAD is a viewing mode, not an error\n\nRun `git checkout a3f9c21` without a file path and git prints a paragraph that begins \"You are in 'detached HEAD' state\". People read this as something having gone wrong. It has not. You are looking at an old snapshot without being on any branch, which is exactly what bisecting does.\n\nThe one hazard: commits made here belong to no branch, and when you switch away they become unreachable. If you find yourself wanting to keep work made in this state, `git switch -c rescue` turns it into a branch first. Otherwise `git switch main` takes you home.\n\n## The undo for undo\n\n`reset --hard` to the wrong commit is the mistake everyone makes once. The recovery is a command most people meet only after they need it.\n\n```bash\ngit reflog\n```\n\nThe reflog is a private log of every place `HEAD` has pointed, kept for about ninety days, one line per move:\n\n```\ne7c21a0 HEAD@{0}: reset: moving to a3f9c21\n9b4d7f3 HEAD@{1}: commit: Bookings can be cancelled by the customer\n```\n\nLine one is the mistake. Line two is where you were before it. `git reset --hard HEAD@{1}` puts you back, commits and all. The abandoned commits were never deleted, only left without a name; the reflog is the list of names.\n\n## What nothing brings back\n\nThe reflog records commits. It cannot recover what was never committed. `git reset --hard` and `git restore .` both destroy uncommitted edits, and those edits had no id, so no log holds them. Some editors keep a local history of file saves that can rescue you; git cannot.\n\nThat is the mechanism behind the habit this course keeps repeating. A commit is not a backup. It is the thing that makes every other recovery possible.\n\n> Figure: Which way back, and whether it has been pushed. A two by two grid, rows Not pushed and Already pushed, columns One file back and One commit undone: Not pushed and One file back: restore; Not pushed and One commit undone: reset --hard; Already pushed and One file back: restore; Already pushed and One commit undone: revert. Reset moves your branch pointer and abandons what came after it. The remote still holds those commits, so the next push is rejected and the suggested fix online is a force push that deletes them from the only other copy."},"check":{"question":"Yesterday you pushed a commit that broke the cancel button, and the host has deployed it. You want the app back the way it was. Which is the right move, and why?","options":[{"text":"git reset --hard to the commit before it, then push so the remote matches your machine.","correct":false,"tempting":"Rewrites history that the remote already holds; the push is rejected, and the usual fix, force push, overwrites the remote's record."},{"text":"git restore . to throw the change away.","correct":false,"tempting":"restore only discards uncommitted edits in the working tree; the breaking change is a commit and is untouched."},{"text":"git revert the commit and push the new commit it creates.","correct":true},{"text":"git checkout the previous commit and redeploy from there.","correct":false,"tempting":"Puts you in detached HEAD looking at an old snapshot; main has not changed and the host deploys main."}]},"prev":{"slug":"what-a-commit-actually-is","title":"What a commit actually is","url":"https://addaly.com/learn/building-apps-with-ai/what-a-commit-actually-is"},"next":{"slug":"branches-for-the-risky-change","title":"Branches for the risky change","url":"https://addaly.com/learn/building-apps-with-ai/branches-for-the-risky-change"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Three ways back, and the undo for undo', in Building Apps with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-apps-with-ai/three-ways-back (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Three ways back, and the undo for undo","courseTitle":"Building Apps with AI","module":{"index":5,"slug":"save-points-and-shipping","title":"Save points, and shipping to a stranger"},"answer":null,"keyIdea":"Restore one file, revert one commit, or reset everything: they are different operations, and once a commit has been pushed only revert is safe.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"cols":["One file back","One commit undone"],"rows":["Not pushed","Already pushed"],"type":"matrix","cells":[["restore","reset --hard"],["restore","revert"]],"title":"Which way back, and whether it has been pushed","caption":"Reset moves your branch pointer and abandons what came after it. The remote still holds those commits, so the next push is rejected and the suggested fix online is a force push that deletes them from the only other copy.","cellLabels":[["nothing else moves","later commits abandoned"],["nothing else moves","the only safe undo"]]},"alt":"Figure: Which way back, and whether it has been pushed. A two by two grid, rows Not pushed and Already pushed, columns One file back and One commit undone: Not pushed and One file back: restore; Not pushed and One commit undone: reset --hard; Already pushed and One file back: restore; Already pushed and One commit undone: revert. Reset moves your branch pointer and abandons what came after it. The remote still holds those commits, so the next push is rejected and the suggested fix online is a force push that deletes them from the only other copy."}},"blocks":[{"t":"h2","id":"looking-before-moving","runs":[{"text":"Looking before moving"}]},{"t":"p","runs":[{"text":"The earlier lesson on finding the change that broke it gave you "},{"text":"git log -p -- file","code":true},{"text":" and bisecting. Two more reading commands belong beside them, because most people reach for a destructive command when a reading one would have answered the question."}]},{"t":"code","lang":"bash","label":"Bash","text":"git show a3f9c21                     # one commit: message plus its full diff\ngit show a3f9c21:src/booking.ts      # one file, exactly as it was then\ngit log -S \"cancelBooking\" --oneline # commits that added or removed that text"},{"t":"p","runs":[{"text":"That last one is the pickaxe. When a function has vanished and nobody remembers deleting it, "},{"text":"-S","code":true},{"text":" finds the commit where the string stopped appearing. It searches content, not messages, so it works even when every message says “update”."}]},{"t":"p","runs":[{"text":"HEAD","code":true},{"text":" means “where I am now”. "},{"text":"HEAD~1","code":true},{"text":" is its parent, "},{"text":"HEAD~3","code":true},{"text":" three commits back. "},{"text":"git diff HEAD~3","code":true},{"text":" is everything since then."}]},{"t":"h2","id":"way-one-bring-back-a-single-file","runs":[{"text":"Way one: bring back a single file"}]},{"t":"code","lang":"bash","label":"Bash","text":"git restore --source a3f9c21 src/booking.ts"},{"t":"p","runs":[{"text":"Nothing else moves. Only that file is copied out of that commit into your working tree, where it sits as an ordinary uncommitted change you can look at, keep or discard. The older spelling, "},{"text":"git checkout a3f9c21 -- src/booking.ts","code":true},{"text":", does the same thing."}]},{"t":"p","runs":[{"text":"This is the most useful and least known of the three. An agent has rewritten a file it should not have touched; the rest of its work is good. Restore the one file and keep the rest. No reset, no lost work."}]},{"t":"h2","id":"way-two-undo-a-commit-with-a-new-commit","runs":[{"text":"Way two: undo a commit with a new commit"}]},{"t":"code","lang":"bash","label":"Bash","text":"git revert a3f9c21"},{"t":"p","runs":[{"text":"Revert reads the diff of that commit, applies the reverse of it, and makes a new commit on top. The original stays in the history. The log shows both the change and its undoing, which is the honest record of what happened."}]},{"t":"p","runs":[{"text":"Because it adds rather than rewrites, revert is safe after pushing. The remote receives one more commit, like any other. Reverting a commit from a week ago may conflict with what came after it; git will say so and you resolve it as in the conflict lesson."}]},{"t":"h2","id":"way-three-move-everything-back","runs":[{"text":"Way three: move everything back"}]},{"t":"p","runs":[{"text":"git reset --hard a3f9c21","code":true},{"text":" is the command from the first git lesson. It moves your branch pointer back to that commit and makes the working tree match. Every later commit is abandoned; every uncommitted change is destroyed."}]},{"t":"p","runs":[{"text":"It is the right tool for the two-hour agent session that went nowhere and was never pushed. It is the wrong tool for anything the remote has already seen, and the mechanism is worth understanding: the remote still holds the commits you abandoned, so your next push is rejected as “non-fast-forward”. The suggested fix in most search results is "},{"text":"git push --force","code":true},{"text":", which makes the remote match you and deletes those commits from the only other copy. If a collaborator, or the host, or you on another machine has built on them, that work is now orphaned."}]},{"t":"p","runs":[{"text":"The rule is short. Not pushed: reset is fine. Pushed: revert."}]},{"t":"h2","id":"detached-head-is-a-viewing-mode-not-an-error","runs":[{"text":"Detached HEAD is a viewing mode, not an error"}]},{"t":"p","runs":[{"text":"Run "},{"text":"git checkout a3f9c21","code":true},{"text":" without a file path and git prints a paragraph that begins “You are in ‘detached HEAD’ state”. People read this as something having gone wrong. It has not. You are looking at an old snapshot without being on any branch, which is exactly what bisecting does."}]},{"t":"p","runs":[{"text":"The one hazard: commits made here belong to no branch, and when you switch away they become unreachable. If you find yourself wanting to keep work made in this state, "},{"text":"git switch -c rescue","code":true},{"text":" turns it into a branch first. Otherwise "},{"text":"git switch main","code":true},{"text":" takes you home."}]},{"t":"h2","id":"the-undo-for-undo","runs":[{"text":"The undo for undo"}]},{"t":"p","runs":[{"text":"reset --hard","code":true},{"text":" to the wrong commit is the mistake everyone makes once. The recovery is a command most people meet only after they need it."}]},{"t":"code","lang":"bash","label":"Bash","text":"git reflog"},{"t":"p","runs":[{"text":"The reflog is a private log of every place "},{"text":"HEAD","code":true},{"text":" has pointed, kept for about ninety days, one line per move:"}]},{"t":"code","lang":"","label":"Text","text":"e7c21a0 HEAD@{0}: reset: moving to a3f9c21\n9b4d7f3 HEAD@{1}: commit: Bookings can be cancelled by the customer"},{"t":"p","runs":[{"text":"Line one is the mistake. Line two is where you were before it. "},{"text":"git reset --hard HEAD@{1}","code":true},{"text":" puts you back, commits and all. The abandoned commits were never deleted, only left without a name; the reflog is the list of names."}]},{"t":"h2","id":"what-nothing-brings-back","runs":[{"text":"What nothing brings back"}]},{"t":"p","runs":[{"text":"The reflog records commits. It cannot recover what was never committed. "},{"text":"git reset --hard","code":true},{"text":" and "},{"text":"git restore .","code":true},{"text":" both destroy uncommitted edits, and those edits had no id, so no log holds them. Some editors keep a local history of file saves that can rescue you; git cannot."}]},{"t":"p","runs":[{"text":"That is the mechanism behind the habit this course keeps repeating. A commit is not a backup. It is the thing that makes every other recovery possible."}]}],"check":{"question":[{"text":"Yesterday you pushed a commit that broke the cancel button, and the host has deployed it. You want the app back the way it was. Which is the right move, and why?"}],"options":[{"text":[{"text":"git reset --hard to the commit before it, then push so the remote matches your machine."}],"correct":false,"tempting":[{"text":"Rewrites history that the remote already holds; the push is rejected, and the usual fix, force push, overwrites the remote’s record."}]},{"text":[{"text":"git restore . to throw the change away."}],"correct":false,"tempting":[{"text":"restore only discards uncommitted edits in the working tree; the breaking change is a commit and is untouched."}]},{"text":[{"text":"git revert the commit and push the new commit it creates."}],"correct":true,"tempting":null},{"text":[{"text":"git checkout the previous commit and redeploy from there."}],"correct":false,"tempting":[{"text":"Puts you in detached HEAD looking at an old snapshot; main has not changed and the host deploys main."}]}],"order":[0,3,1,2],"why":null},"prev":{"slug":"what-a-commit-actually-is","title":"What a commit actually is","minutes":8},"next":{"slug":"branches-for-the-risky-change","title":"Branches for the risky change","minutes":8}}}