Changes between Version 28 and Version 29 of GitForDarcsUsers

Apr 3, 2011 5:14:23 AM (4 years ago)

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


  • GitForDarcsUsers

    v28 v29  
    110110See {{{git rebase --help}}} for more usage information and more examples.
    112 === Uh-oh, I trashed my repo with a rebase, what do I do? ===
    114 Two Options:
    116  1. Recover from the backup copy (you ''did'' make backup copy before the rebase, didn't you?)
    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.
    120 Suppose we started with this repository state
     112=== Uh-oh, I trashed my repo with a rebase! What do I do? ===
     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.
     116==== The hard way ====
     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, [ git means never having to say “you should have …”]
     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
    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
     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 [ Scott Chacon's handy git lol alias] to have git draw the history. Assuming {{{feature1}}} is our current branch
     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
     138* f8ba9ea V
     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.
     142The initial bright idea (that we'll regret later) is to remove {{{B}}} from our history.
     144$ git rebase --onto feature1~3 feature1~2 feature1
     146(If this command seems opaque, you can achieve the same effect with [ interactive rebase].)
     148Now the repository looks like this:
    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
     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}}}:
     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
     167* f8ba9ea V
     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.
     171'''Oh no! ''' It turns out we really ''did'' need {{{B}}} and would like to get it back without having to reimplement the change.
     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.
     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
     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}}})
     194The old {{{D}}} ({{{b0f2a28}}}) is still around, and we can see it too:
     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
     203* a092126 A
     204| * 83052e6 (origin/master, master) Z
     205| * 90c3d28 Y
     206| * 4165a42 X
     207| * 37844cb W
     209* f8ba9ea V
     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}}}.
     214$ git branch old-feature1 b0f2a28
     216This produces the following history.
     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
     225* a092126 A
     226| * 83052e6 (origin/master, master) Z
     227| * 90c3d28 Y
     228| * 4165a42 X
     229| * 37844cb W
     231* f8ba9ea V
     233To instead discard the botched rebase and try again, use {{{git reset}}} while still on the {{{feature1}}} branch:
     235$ git reset --hard b0f2a28
     237Beware that hard reset is a sharp tool. Like {{{rm -rf}}} it has its uses, but, “measure twice; cut once,” as carpenters say.
     239See also the [ Data Recovery section] of ''Pro Git'' by Scott Chacon.
     241==== The easy way ====
     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!
     245 1. Create a temporary branch that points to the {{{HEAD}}} of the current branch, assumed to be our feature branch.
     247$ git branch tmp
     249 2. Run {{{git rebase}}} with the appropriate arguments.
     250 3. If you're happy with the result, delete the temporary branch.
     252$ git branch -D tmp
     254 4. Otherwise, rewind and try again.
     256$ git reset --hard tmp
    140259= General Settings =