| 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 | |
| | 114 | Your 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 | |
| | 118 | Git 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 | |
| | 120 | Say 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 |
| 124 | | o---o---o---o---o <-- master |
| 125 | | }}} |
| 126 | | and decided to remove {{{B}}} from our history. The actual repository now looks like this: |
| | 124 | V---W---X---Y---Z <-- master |
| | 125 | }}} |
| | 126 | That 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 | |
| | 142 | The 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 | |
| | 148 | Now the repository looks like this: |
| 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}}}. |
| | 154 | V---W---X---Y---Z <-- master |
| | 155 | }}} |
| | 156 | Notice 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 | }}} |
| | 169 | We 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 | |
| | 173 | The 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 |
| | 176 | 32ee6f7 HEAD@{0}: rebase: D |
| | 177 | a62b28d HEAD@{1}: rebase: C |
| | 178 | a092126 HEAD@{2}: checkout: moving from feature1 to a092126e339d4c7b9a3c9afb5d456cc1ddf4be4a^0 |
| | 179 | b0f2a28 HEAD@{3}: checkout: moving from master to feature1 |
| | 180 | 83052e6 HEAD@{4}: pull : Fast-forward |
| | 181 | f8ba9ea HEAD@{5}: checkout: moving from feature1 to master |
| | 182 | b0f2a28 HEAD@{6}: commit: D |
| | 183 | 68f87b0 HEAD@{7}: commit: C |
| | 184 | d311c65 HEAD@{8}: commit: B |
| | 185 | a092126 HEAD@{9}: commit: A |
| | 186 | f8ba9ea HEAD@{10}: checkout: moving from master to feature1 |
| | 187 | }}} |
| | 188 | Reading 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 | |
| | 194 | The 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 | |
| | 212 | If 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 | }}} |
| | 216 | This 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 | }}} |
| | 233 | To instead discard the botched rebase and try again, use {{{git reset}}} while still on the {{{feature1}}} branch: |
| | 234 | {{{ |
| | 235 | $ git reset --hard b0f2a28 |
| | 236 | }}} |
| | 237 | Beware that hard reset is a sharp tool. Like {{{rm -rf}}} it has its uses, but, “measure twice; cut once,” as carpenters say. |
| | 238 | |
| | 239 | See 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 | |
| | 243 | By 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 | }}} |