{"thread":{"id":"62585","subject":"How to revert to a specific commit?","startedAt":"2024-12-02T11:40:19Z","lastAt":"2024-12-03T02:46:00Z","messageCount":5,"participants":["tao lv","Chris Torek","Matěj Cepl","Junio C Hamano"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"508427","messageId":"CAFePT4zytyuLB0TgyJ+mbrNvz=Xt1x+gJv4e-2j07YK7Cg1bzw@mail.gmail.com","threadId":"62585","inReplyTo":null,"subject":"How to revert to a specific commit?","fromName":"tao lv","fromEmail":"thebookofunknowable@gmail.com","sentAt":"2024-12-02T11:40:07Z","receivedAt":"2024-12-02T11:40:19Z","isPatch":false,"sender":{"key":"thebookofunknowable@gmail.com","avatar":null},"body":"I want to revert the code to a specific commit. This commit is not a\ndirect part of the branch history; there are merge operations, branch\nforks, and other changes in between. Therefore, directly using `revert\nHEAD...commit` fails due to merge need -m.\n\nI don't want to revert each individual commit; I just want to restore\nthe code to this specific commit while retaining all the current\nhistory commits.\n\nHow can I achieve this? Is there a better way than using the revert function?\n"},{"id":"508445","messageId":"CAPx1Gve_dCRux9_cF7NTspomS4K=Hp_y74d94S-Hm0amuovUqQ@mail.gmail.com","threadId":"62585","inReplyTo":"CAFePT4zytyuLB0TgyJ+mbrNvz=Xt1x+gJv4e-2j07YK7Cg1bzw@mail.gmail.com","subject":"Re: How to revert to a specific commit?","fromName":"Chris Torek","fromEmail":"chris.torek@gmail.com","sentAt":"2024-12-02T12:39:13Z","receivedAt":"2024-12-02T12:39:28Z","isPatch":false,"sender":{"key":"chris.torek@gmail.com","avatar":"https://avatars.githubusercontent.com/u/16826774?v=4"},"body":"On Mon, Dec 2, 2024 at 4:15 AM tao lv <thebookofunknowable@gmail.com> wrote:\n> I want to revert the code to a specific commit. ...\n\nOK, first some background:\n\n * A commit _is_ a full snapshot of every file, as of the state\n   it had at the time you (or whoever) made that particular\n   commit. Hence, if you want the particular files from a\n   particular commit, you simply check out that (historical)\n   commit.\n\nNow:\n\n> I don't want to revert each individual commit; I just want to restore\n> the code to this specific commit while retaining all the current\n> history commits.\n>\n> How can I achieve this? Is there a better way than using the revert function?\n\nYes.\n\nThe mechanics of:\n\n    git checkout <hash-ID-or-other-commit-specifier>\n\nare twofold, and mean the same thing as:\n\n    git switch --detach <same-hash-ID-etc>\n\nThat is, you are asking Git to:\n\n 1) switch to the saved snapshot, and\n 2) \"detach HEAD\".\n\nStep 2 is your problem here because the jargon phrase\n\"detach HEAD\" means \"stop being on any branch at all,\njust go to some historical commit\". But you seem to want\nto subsequently make a *new* commit that contains the\n*old snapshot*, as if you had removed all the work you\n(or anyone else) had done since that point in time and put\nall the files back the way they were in the historical commit,\n*while remaining on your branch*, so that you are now\nready to commit all the old versions of the files.\n\nNote that this new commit, if and when you make it,\nsimply sits atop the history (and / but, since every commit\nis a full snapshot of all files, it undoes all the *work* done\nsince the restored commit -- though, since all commits\nare also *permanent*, that undone work can be redone\ntrivially as well).\n\nSo, at this point, you have several options for\nachieving this goal, provided I have recapitulated\nyour goal correctly.  The most obvious, I think, is:\n\n    git checkout [--detach] <hash>   # or git switch --detach <hash>\n    git reset --soft <branch-name>\n\nThe checkout-or-switch does what it does, including\ndetaching HEAD, and then the `git reset --soft` tells Git\nthat it should re-attach `HEAD` to the given branch name,\nbut not alter *either* the index / staging-area, *or* the\nworking tree, leaving the historical versions of all files\nas \"ready to commit\".\n\n=====\n\nA different, and more direct, option is to use:\n\n    git restore --no-overlay -S -W <hash>\n\nThe `git checkout` command can be used in place\nof this `git restore`, because `git checkout` itself is (in\nmy opinion) overly complicated and stuffed full of modes,\nso that it has a mode in which it *doesn't* affect `HEAD`.\nUnderstanding this requires keeping in mind the way\nI described `git checkout` has having a step-1-then-step-2,\neven though the two-step `git checkout` actually very\ncarefully *combines* the two steps (to avoid making any\nchanges should the second step be about to fail for some\nreason). I find that describing checkout as two separate\nsteps, and literally using an additional `git reset --soft`\nstep, more explainable (Git is complicated! Nobody learns\nall of it, it is always changing over time! Those new to it\nfind it very mysterious, and this is part of why).\n\nChris\n\n(PS: the fact that you need `--no-overlay` with the\nsingle-step methods is another one of those picky\nlittle details that make Git tricky.)\n"},{"id":"508446","messageId":"D6187MXMNNS6.11QZW2TCWKKNW@cepl.eu","threadId":"62585","inReplyTo":"CAFePT4zytyuLB0TgyJ+mbrNvz=Xt1x+gJv4e-2j07YK7Cg1bzw@mail.gmail.com","subject":"Re: How to revert to a specific commit?","fromName":"Matěj Cepl","fromEmail":"mcepl@cepl.eu","sentAt":"2024-12-02T12:50:27Z","receivedAt":"2024-12-02T12:50:35Z","isPatch":false,"sender":{"key":"mcepl@cepl.eu","avatar":"https://avatars.githubusercontent.com/u/198999?v=4"},"body":"On Mon Dec 2, 2024 at 12:40 PM CET, tao lv wrote:\n> I don't want to revert each individual commit; I just want to restore\n> the code to this specific commit while retaining all the current\n> history commits.\n\nEither git checkout <commit-id> for an inspection or git reset\n--hard <commit-id> for change of the current branch to that\ncommit.\n\nMatěj\n\n-- \nhttp://matej.ceplovi.cz/blog/, @mcepl@floss.social\nGPG Finger: 3C76 A027 CA45 AD70 98B5  BC1D 7920 5802 880B C9D8\n \nBooks aren’t written - they’re rewritten. Including your own. It\nis one of the hardest things to accept, especially after the\nseventh rewrite hasn’t quite done it.\n  -- Michael Crichton, alluding to Steele MacKaye (1889) article\n     where he said this about theater plays.\n\n"},{"id":"508486","messageId":"xmqqser5h9c6.fsf@gitster.g","threadId":"62585","inReplyTo":"CAPx1Gve_dCRux9_cF7NTspomS4K=Hp_y74d94S-Hm0amuovUqQ@mail.gmail.com","subject":"Re: How to revert to a specific commit?","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-03T00:57:29Z","receivedAt":"2024-12-03T00:57:31Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Chris Torek <chris.torek@gmail.com> writes:\n\n> On Mon, Dec 2, 2024 at 4:15 AM tao lv <thebookofunknowable@gmail.com> wrote:\n>> I want to revert the code to a specific commit. ...\n>\n> OK, first some background:\n>\n>  * A commit _is_ a full snapshot of every file, as of the state\n>    it had at the time you (or whoever) made that particular\n>    commit. Hence, if you want the particular files from a\n>    particular commit, you simply check out that (historical)\n>    commit.\n>\n> Now:\n>\n>> I don't want to revert each individual commit; I just want to restore\n>> the code to this specific commit while retaining all the current\n>> history commits.\n>>\n>> How can I achieve this? Is there a better way than using the revert function?\n>\n>     git checkout [--detach] <hash>   # or git switch --detach <hash>\n>     git reset --soft <branch-name>\n>\n>     git restore --no-overlay -S -W <hash>\n\nWhile that would _work_ in the sense that you can revert the effect\nof what the discarded commits did, I would not recommend any of the\nabove since it does not leave any record of what you discarded.\n\nImagine that I regret doing everything since we tagged, say, v2.47.0\nrelease and want to discard everythig on 'master' newer than that\ncommit.  If I want to \"keep\" the history of failed commits that I\nregret having made since v2.47.0, here is a way to do it:\n\n $ git checkout master\n $ git reset --hard v2.47.0\n $ git merge -s ours --no-ff -m 'Discard everything since v2.47.0' master@{1}\n\nThe first two steps just discard the unwanted commits.  The third\nstep is a trick to\n\n - Keep the tree contents of the current commit (i.e. -s ours) as\n   the result,\n\n - Make sure the result is recorded as a merge commit (i.e.\n   --no-ff), not \"fast-forward\" to the tip of the history I am\n   discarding, and\n\n - Record the discarded history (i.e. master@{1}) as the side branch\n   of the resulting history.\n\nThe last part would help if I later need to merge changes that were\nbased on 'master' I am discarding.  The presense of the side branch\nmakes sure that only the effects from the new work done on the\ndiscarded history, and not from the commits such a new work was\nbased on (which are the commits I regret having made and I am\ndiscarding), are taken into account when computing further merges.\n"},{"id":"508501","messageId":"CAPx1GveJYiMAVLmv1A9-K0Ok54cio5aAqFnULZJ3--GGBnu69Q@mail.gmail.com","threadId":"62585","inReplyTo":"xmqqser5h9c6.fsf@gitster.g","subject":"Re: How to revert to a specific commit?","fromName":"Chris Torek","fromEmail":"chris.torek@gmail.com","sentAt":"2024-12-03T02:45:46Z","receivedAt":"2024-12-03T02:46:00Z","isPatch":false,"sender":{"key":"chris.torek@gmail.com","avatar":"https://avatars.githubusercontent.com/u/16826774?v=4"},"body":"On Mon, Dec 2, 2024 at 4:57 PM Junio C Hamano <gitster@pobox.com> wrote:\n> While [Torek's sequence of commands] would _work_ in the  sense\n> that you can revert the effect of what the discarded commits\n> did, I would not recommend any of the above since it does not\n> leave any record of what you discarded.\n\n[alternate method with `git merge -s ours --no-ff` snipped, but note\nthat it will do this:]\n\n>  - Record the discarded history (i.e. master@{1}) as the side branch\n>    of the resulting history.\n\nThis is a good point and illustrates the key issue I left out.. I was\ndescribing (and kind of deliberately hammering on, for teaching\npurposes) the fact that every commit node in the commit graph\nrecords the _full state of all files_ as of that particular commit.\nHence if the only thing you care about is \"state of files\", that's\nalso the only part of a commit that matters.\n\nBut commits do one other thing, in addition to the usual housekeeping\nwork of remembering who made them and when and a log message.\nThat specific other thing is: each commit remembers a list[1] of\n_previous commits_, and stringing this commit up after its\npredecessor(s) is what produces \"history\".\n\nThat is, Git does not _store_ history (specifically, history of files).\nInstead, it stores the _information needed to discover history\nany time you wish to discover it_. The list-of-previous commits\nis how this works. Using a merge allows you to store a list of\n_two or more_ previous commits. The _first_ entry in this list,\nwhich is often the only entry, is what we think of as \"the immediate\nprevious commit\", and following this list-of-first-entries gives you\nthe history of what happened to each file.\n\nSecond (and additional) list entries, if present, help you figure\nout _how_ you got this particular version of every file in this\nparticular commit, in the usual merge cases. In unusual cases\n(such as Junio's method here) it can help you figure out what\nyou deliberately left behind, and the fact that the new commit is\na merge commit -- a commit with two or more parents, instead\nof just one -- is a clue that something out of the ordinary\noccurred.\n\n[1] I say \"list\" instead of \"set\" here because order matters:\nthe first entry is special. \"Set\" would also imply de-duplication.\nWhile the list entries don't normally contain any duplicates,\nthere's nothing in the machinery to prevent them. What (if\nanything) such duplicates might mean -- well, that's up to you.\n\nChris\n"}]}