開発
Git literacy : Recovery
denvazh
Getting started
Freedom is a nice thing. Really. However lack of borders and regulations often means that you should maintain them yourself. Git is a very flexible and indeed a tool of those who like freedom along with flexibility in development. But, as any other tool it is a masterpiece in the hands of master, and burden for those who uses tool without thinking about consequences. In case of problems, however, certain ready solutions might not work. Let’s see some small examples how to find list commits ( usually deleted by mistake ) and recover them back in the development tree.
The problem
As soon as it is usually not desired to deliberately make mistakes in the current development tree of active project. Let’s make some testbed and try to mess it up, so that we have a chance to study how to recover it later. First, we create repository and add some file, then we move to different branch and do actual stuff there:
$: mkdir -p /tmp/testbed $: cd /tmp/testbed $: git init $: touch test.sh $: git add test.sh $: git commit -a -m "Initial commit" $: git checkout -b scripting $: git branch master * scripting
Now we have repository with one empty file and the first commit. Now we can add some content to the file:
#!/usr/bin/env bash function greeting() { echo "My name is `basename $SHELL`. Nice to meet you." }
and commit
$: git commit -a -m "greetings now possible"
Let’s append some more content:
function user() { echo "Please introduce yourself" read NAME echo "Welcome, $NAME" }
and commit
$: git commit -a -m "user introduction added"
Now let’s add part that actually uses it:
function init() { greeting user } init
commit and check the log
$: git commit -a -m "Init part added" $: git log --oneline fb4c2b4 Init part added e71b90d user introduction added c49d349 greetings now possible 7d4fc7f Initial commit
Now everything is ready and even code launches correctly:
$: ./test.sh My name is bash. Nice to meet you. Please introduce yourself Denis Welcome, Denis
From riches to rags and the way back
Suppose you didn’t like what you’ve did in the last commit and decided to get rid of it hard way.
$: git reset --hard HEAD^ HEAD is now at e71b90d user introduction added
Now if we check the commit log, we can clarify that commit is gone.
$: git log --oneline e71b90d user introduction added c49d349 greetings now possible 7d4fc7f Initial commit
Now few words about reset. In fact, this command do not actually delete certain commit, but only removes its reference from commit tree. In other words, this is dangling commit, as soon as it has no reference to it, thus its possible to access it with it SHA1 key.
NOTE1: if you run garbage collector( $: git gc ) right after reset, it will be not possible to recover dangling commits.
NOTE2: if you have remote repository as well, you can fix everything with $: git pull ( or $: git pull –all if you not in the master branch )
To recover we have to check git with its filesystem check tool and get necessary SHA1 key of dangling commit.
$: git fsck --lost-found dangling commit fb4c2b4bc4c77c59bf5c232a8ce06ddc385aa3b5
From this point we can directly checkout and check the contents of lost commit:
$: git checkout fb4c2b4bc4c77c59bf5c232a8ce06ddc385aa3b5 Note: checking out 'fb4c2b4bc4c77c59bf5c232a8ce06ddc385aa3b5'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b new_branch_name HEAD is now at fb4c2b4... Init part added
Let’s verify, that we now actually in the lost commit:
$: git log --oneline fb4c2b4 Init part added e71b90d user introduction added c49d349 greetings now possible 7d4fc7f Initial commit $: git branch * (no branch) master scripting
The most safest and easy way to recover, would be to merge dangling commit using its SHA1 key. Let’s do it.
$: git merge fb4c2b4bc4c77c59bf5c232a8ce06ddc385aa3b5 Updating e71b90d..fb4c2b4 Fast-forward test.sh | 11 +++++++++-- 1 files changed, 9 insertions(+), 2 deletions(-)
Checking.
$: git branch master * scripting $: git log --oneline fb4c2b4 Init part added e71b90d user introduction added c49d349 greetings now possible 7d4fc7f Initial commit
Everything is okay so far. Let’s try another way of doing it. This time we create separate branch out of the dangling commit and then merge this branch back to one we’ve used so far.
$: git branch recovery fb4c2b4bc4c77c59bf5c232a8ce06ddc385aa3b5
We can compare to actually check the difference between branches:
$: git log scripting --oneline e71b90d user introduction added c49d349 greetings now possible 7d4fc7f Initial commit $: git log recovery --oneline fb4c2b4 Init part added e71b90d user introduction added c49d349 greetings now possible 7d4fc7f Initial commit
Now we can merge recovery branch to our current ( scripting ) and then delete it, because we no longer need it.
$: git merge recovery Updating e71b90d..fb4c2b4 Fast-forward test.sh | 11 +++++++++-- 1 files changed, 9 insertions(+), 2 deletions(-) $: git branch -d recovery Deleted branch recovery (was fb4c2b4).
Conclusion
There are many ways how to recover the information. However, it is crucial to know what recovery tools is available by default and general recovery pattern: investigate the problem, develop solution, apply solution to the actual problem.
More information about the topic
Recovery: http://gitready.com/advanced/2009/01/17/restoring-lost-commits.html
Maintenance: http://progit.org/book/ch9-7.html