Changes between Version 28 and Version 29 of GitForDarcsUsers


Ignore:
Timestamp:
Apr 3, 2011 5:14:23 AM (3 years ago)
Author:
gbacon
Comment:

Improved "Uh-oh, I trashed my repo with a rebase! What do I do?"

Legend:

Unmodified
Added
Removed
Modified
  • GitForDarcsUsers

    v28 v29  
    110110See {{{git rebase --help}}} for more usage information and more examples. 
    111111 
    112 === Uh-oh, I trashed my repo with a rebase, what do I do? === 
    113  
    114 Two Options: 
    115  
    116  1. Recover from the backup copy (you ''did'' make backup copy before the rebase, didn't you?) 
    117  
    118  2. In case you were in a hurry and option 1 is not an option for you, there may be some hope.  Git actually has a bit of a functional philosophy in that it doesn't immediately throw away the orignal commits.  Git uses garbage collection for this, which is called automatically every once in a while. 
    119  
    120 Suppose we started with this repository state 
     112=== Uh-oh, I trashed my repo with a rebase! What do I do? === 
     113 
     114Your repository's old state is almost certainly still present, and you have two ways of getting it back. To motivate the easier approach, we'll first consider the more meticulous. 
     115 
     116==== The hard way ==== 
     117 
     118Git actually has a bit of a functional philosophy. Objects in git are ''immutable''. Although rebase appears to be a destructive update, it creates new commits and leaves the original ones to be cleaned up later by the garbage collector. Destroying work after you've committed it to git requires deliberate effort. Even if you forget to do it the easy way, which you'll see in just a moment, [http://tomayko.com/writings/the-thing-about-git git means never having to say “you should have …”] 
     119 
     120Say you began work on a new feature three days ago. In the meantime, other commits have gone into {{{master}}}, producing a history whose structure is 
    121121{{{ 
    122122      A---B---C---D <-- feature1 
    123123     / 
    124 o---o---o---o---o <-- master 
    125 }}} 
    126 and decided to remove {{{B}}} from our history.  The actual repository now looks like this: 
     124V---W---X---Y---Z <-- master 
     125}}} 
     126That is, you've made four commits (referred to as {{{A}}} through {{{D}}} above) toward {{{feature1}}}, and {{{master}}} has added three commits since ({{{X}}}, {{{Y}}}, and {{{Z}}}). We can use [http://blog.kfish.org/2010/04/git-lola.html Scott Chacon's handy git lol alias] to have git draw the history. Assuming {{{feature1}}} is our current branch 
     127{{{ 
     128$ git lol --after=3.days.ago HEAD master 
     129* b0f2a28 (HEAD, feature1) D 
     130* 68f87b0 C 
     131* d311c65 B 
     132* a092126 A 
     133| * 83052e6 (origin/master, master) Z 
     134| * 90c3d28 Y 
     135| * 4165a42 X 
     136| * 37844cb W 
     137|/ 
     138* f8ba9ea V 
     139}}} 
     140'''N.B.''' The commits above have single-letter commit messages for expository benefit. You'd never want to do this in a real repo. 
     141 
     142The initial bright idea (that we'll regret later) is to remove {{{B}}} from our history. 
     143{{{ 
     144$ git rebase --onto feature1~3 feature1~2 feature1 
     145}}} 
     146(If this command seems opaque, you can achieve the same effect with [http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html#_interactive_mode interactive rebase].) 
     147 
     148Now the repository looks like this: 
    127149{{{ 
    128150        C'--D' <-- feature1 
     
    130152      A---B---C---D 
    131153     / 
    132 o---o---o---o---o <-- master 
    133 }}} 
    134 If you happen to have the commit id of {{{D}}} (maybe in your console backlog) you can create a branch {{{feature1_old}}} that points to {{{D}}} via 
    135 {{{ 
    136 $ git checkout -b feature1_old <commit-id-of-D> 
    137 }}} 
    138 Now you have a handle to both {{{D'}}} and {{{D}}}. 
     154V---W---X---Y---Z <-- master 
     155}}} 
     156Notice that no branch head points to the old {{{feature1}}} branch and that {{{feature1}}} contains commits resembling {{{C}}} and {{{D}}} but distinct from them because they have different histories. We can see this with {{{git lol}}}: 
     157{{{ 
     158$ git lol --after=3.days.ago HEAD master 
     159* 32ee6f7 (HEAD, feature1) D 
     160* a62b28d C 
     161* a092126 A 
     162| * 83052e6 (origin/master, master) Z 
     163| * 90c3d28 Y 
     164| * 4165a42 X 
     165| * 37844cb W 
     166|/ 
     167* f8ba9ea V 
     168}}} 
     169We see here that {{{A}}} has the same commit id or SHA1 as in the earlier {{{git lol}}} output, but {{{C}}}'s and {{{D}}}'s SHA1s are different. 
     170 
     171'''Oh no! ''' It turns out we really ''did'' need {{{B}}} and would like to get it back without having to reimplement the change. 
     172 
     173The old branch is still in the repository as unreferenced garbage, but we can dig it out via the reflog, which git updates each time it updates the tip of any branch. 
     174{{{ 
     175$ git reflog --after=3.days.ago 
     17632ee6f7 HEAD@{0}: rebase: D 
     177a62b28d HEAD@{1}: rebase: C 
     178a092126 HEAD@{2}: checkout: moving from feature1 to a092126e339d4c7b9a3c9afb5d456cc1ddf4be4a^0 
     179b0f2a28 HEAD@{3}: checkout: moving from master to feature1 
     18083052e6 HEAD@{4}: pull : Fast-forward 
     181f8ba9ea HEAD@{5}: checkout: moving from feature1 to master 
     182b0f2a28 HEAD@{6}: commit: D 
     18368f87b0 HEAD@{7}: commit: C 
     184d311c65 HEAD@{8}: commit: B 
     185a092126 HEAD@{9}: commit: A 
     186f8ba9ea HEAD@{10}: checkout: moving from master to feature1 
     187}}} 
     188Reading bottom-to-top, these artifacts tell the repository's recent history: 
     189 1. created {{{feature1}}} branch 
     190 2. did some hacking 
     191 3. switched to {{{master}}} and pulled updates 
     192 4. performed surgery back on {{{feature1}}} ({{{a092...}}} is the full 160-bit SHA1 of commit {{{A}}}) 
     193 
     194The old {{{D}}} ({{{b0f2a28}}}) is still around, and we can see it too: 
     195{{{ 
     196$ git lol --after=3.days.ago HEAD master b0f2a28 
     197* 32ee6f7 (HEAD, feature1) D 
     198* a62b28d C 
     199| * b0f2a28 D 
     200| * 68f87b0 C 
     201| * d311c65 B 
     202|/ 
     203* a092126 A 
     204| * 83052e6 (origin/master, master) Z 
     205| * 90c3d28 Y 
     206| * 4165a42 X 
     207| * 37844cb W 
     208|/ 
     209* f8ba9ea V 
     210}}} 
     211 
     212If for some reason you'd like to keep both of the feature branches (i.e., {{{D}}} and {{{D'}}}), create a new branch that points at {{{D}}}. 
     213{{{ 
     214$ git branch old-feature1 b0f2a28 
     215}}} 
     216This produces the following history. 
     217{{{ 
     218$ git lol --after=3.days.ago HEAD master 
     219* eeb9cdb (HEAD, feature1) D 
     220* e7e613e C 
     221| * b0f2a28 (old-feature1) D 
     222| * 68f87b0 C 
     223| * d311c65 B 
     224|/   
     225* a092126 A 
     226| * 83052e6 (origin/master, master) Z 
     227| * 90c3d28 Y 
     228| * 4165a42 X 
     229| * 37844cb W 
     230|/   
     231* f8ba9ea V 
     232}}} 
     233To instead discard the botched rebase and try again, use {{{git reset}}} while still on the {{{feature1}}} branch: 
     234{{{ 
     235$ git reset --hard b0f2a28 
     236}}} 
     237Beware that hard reset is a sharp tool. Like {{{rm -rf}}} it has its uses, but, “measure twice; cut once,” as carpenters say. 
     238 
     239See also the [http://progit.org/book/ch9-7.html#data_recovery Data Recovery section] of ''Pro Git'' by Scott Chacon. 
     240 
     241==== The easy way ==== 
     242 
     243By this point, you've probably figured out how to avoid all this fuss. If you're not sure about a complex rebase (or merge), give yourself an easy mulligan by creating a new branch to act as a checkpoint. Then proceed boldly! 
     244 
     245 1. Create a temporary branch that points to the {{{HEAD}}} of the current branch, assumed to be our feature branch. 
     246{{{ 
     247$ git branch tmp 
     248}}} 
     249 2. Run {{{git rebase}}} with the appropriate arguments. 
     250 3. If you're happy with the result, delete the temporary branch. 
     251{{{ 
     252$ git branch -D tmp 
     253}}} 
     254 4. Otherwise, rewind and try again. 
     255{{{ 
     256$ git reset --hard tmp 
     257}}} 
    139258 
    140259= General Settings =