Introduction
Git is a distributed version control system. Each git working directory has a local copy of repository. A common Git workflow includes the following:
- Make changes in working directory
- Add changes to staging area
- Commit to local repository
- Push to origin repository on remote
In real practice, we may regret of what we have done at any stages above. We may want to revert or undo our works.
Revert untracked files
Untracked files are simply new files that have not added to staging area nor committed before. The most easiest way to remove untracked file is delete it from working directory. However, if there are bunch of untracked files exist in working directory, you may remove them using “git clean” command:
$ echo "This is new file" > newfile ... // do some work and revert the work fianlly $ git status –s ?? newfile $ git clean -f Removing newfile $ git status # On branch master nothing to commit (working directory clean) $ ls -a . .. .git install newfile readme
Revert modified tracked files
Assume there are 2 files in a clean git repository:
$ ls -a . .. .git install readme $ git status # On branch master nothing to commit (working directory clean)
Make some changes to the working tree:
$ echo "modified" >> install $ echo "modified" >> readme
And the working tree is now become
$ ls -a . .. .git install newfile readme $ git status –s M install M readme
Both readme and install file has modified status.
Revert a single file
To revert only a readme file, run
$ cat readme this is a readme modified $ git checkout readme $ cat readme this is a readme $ git status –s M install ?? newfile
Revert a working directory
To revert whole working directory, run
$ git status –s M install M readme ?? newfile $ git reset --hard HEAD is now at 5543e79 second commit $ git status –s ?? newfile
Un-staging files
By using “git add” on new or modified files will move them into staging area for commit in later stage. To un-stage files, use “git reset HEAD <file>...":
Add 3 new files and make changes to readme file:
$ ls -a . .. .git install readme $ touch newfile1 newfile2 newfile3 $ ls -a . .. .git install newfile1 newfile2 newfile3 readme $ cat readme this is a readme $ echo "modified" >> readme $ git status -s M readme ?? newfile1 ?? newfile2 ?? newfile3
Add files to staging area:
$ git add * $ git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # new file: newfile1 # new file: newfile2 # new file: newfile3 # modified: readme #
Un-stage files:
$ git reset HEAD Unstaged changes after reset: M readme $ git status # On branch master # Changed but not updated: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # modified: readme # # Untracked files: # (use "git add <file>..." to include in what will be committed) # # newfile1 # newfile2 # newfile3 no changes added to commit (use "git add" and/or "git commit -a")
Run this to un-stage only a single file, newfile3:
$ git reset HEAD newfile3
Revert committed changes in working directory
Assume 3 commits has been done on a working directory:
$ ls -a . .. .git install readme $ git status # On branch master nothing to commit (working directory clean) $ echo "commit 1" >> readme $ git commit -a -m "commit 1" [master fdaa7d3] commit 1 1 files changed, 1 insertions(+), 0 deletions(-) $ echo "commit 2" >> readme $ git commit -a -m "commit 2" [master 7ad2d0a] commit 2 1 files changed, 1 insertions(+), 0 deletions(-) $ echo "commit 3" >> readme $ git commit -a -m "commit 3" [master 8a56cd9] commit 3 1 files changed, 1 insertions(+), 0 deletions(-) $ git log --oneline 8a56cd9 commit 3 7ad2d0a commit 2 fdaa7d3 commit 1 5543e79 second commit f9d7ae7 this is first commit
Revert to last commit
$ git reset --hard HEAD^ HEAD is now at 7ad2d0a commit 2 $ cat readme this is a readme commit 1 commit 2 $ git log --oneline 7ad2d0a commit 2 fdaa7d3 commit 1 5543e79 second commit f9d7ae7 this is first commit
Revert to specific commit
We may revert to specific commit by specify sha1 hash of the commit in “git reset”
$ git log --oneline 07187ef commit 3 ca79a77 commit 2 37194a2 commit 1 5543e79 second commit f9d7ae7 this is first commit $ git reset --hard 37194a2 HEAD is now at 37194a2 commit 1 $ git log --oneline 37194a2 commit 1 5543e79 second commit f9d7ae7 this is first commit
Revert to last few commit
Using “HEAD~n” with “git reset” allow us to revert to last n committed:
$ git log --oneline fdbde67 commit 3 54d5cef commit 2 37194a2 commit 1 5543e79 second commit f9d7ae7 this is first commit $ git reset --hard HEAD~3 HEAD is now at 37194a2 commit 1 $ git log --oneline 5543e79 second commit f9d7ae7 this is first commit
You may also use something like “HEAD^” to revert a file to last commit:
$ git reset –hard HEAD^
Add more caret (^) symbol to indicate revert for last few commits. One caret represent one backward commit. For example, to revert to last 5 commits:
$ git reset –hard HEAD^^^^^
Revert committed pushed to remote
It is not encourage to revert pushed commit to origin repository if other has pulled what you have pushed. However, human makes mistake. An unwanted push may be reverted too if you are aware that nobody has pulled before.
There are few ways to revert pushed commit. This may be the most simple example:
$ git push Everything up-to-date $ git log --oneline f6abcfa commit 3 0660af9 commit 2 37194a2 commit 1 5543e79 second commit f9d7ae7 this is first commit $ git reset --hard HEAD^^^ HEAD is now at 5543e79 second commit $ git push origin +master Total 0 (delta 0), reused 0 (delta 0) To /tmp/test.git + f6abcfa...5543e79 master -> master (forced update) $ git pull Already up-to-date. $ git push Everything up-to-date $ git log --oneline 5543e79 second commit f9d7ae7 this is first commit
You may encounter error while revert pushed commit:
$ git push origin +master Total 0 (delta 0), reused 0 (delta 0) remote: error: denying non-fast-forward refs/heads/master (you should pull first) ! [remote rejected] e3fce6 -> master (non-fast-forward)
This is due to the origin repository has disable non fast forward (revert) commit. Edit .git/config in origin repository to allow non fast forward commit:
$ cat .git/config [receive] denyNonFastforwards =truefalse
Revert again and it should work:
$ git push origin +master
Prune loose committed objects
Even if committed works has been undone, it still exists in the repository as loose object. Run reflog command shows loose objects:
# git reflog 704b63a HEAD@{0}: checkout: moving from 05fec4a9b062d7d9883969283a3ba18e5e06aad0 05fec4a HEAD@{1}: HEAD^ --: updating HEAD 9fa2a61 HEAD@{2}: checkout: moving from 806a6fd6ae4198a20af0f301d27c13d0dd052931 806a6fd HEAD@{3}: checkout: moving from master to 806a6fd6ae4198a20af0f301d27c13 704b63a HEAD@{4}: 704b63ab353cf32b5d6a9a54b8eb2d4d52b824e8: updating HEAD 9fa2a61 HEAD@{5}: merge origin/master: Merge made by recursive. 05fec4a HEAD@{6}: HEAD^ --: updating HEAD 5f3b0a1 HEAD@{7}: rebase finished: returning to refs/heads/master 5f3b0a1 HEAD@{8}: checkout: moving from master to 5f3b0a1e2167652b32e765b9431727 704b63a HEAD@{9}: checkout: moving from test to master 5f3b0a1 HEAD@{10}: checkout: moving from 806a6fd6ae4198a20af0f301d27c13d0dd05293 806a6fd HEAD@{11}: checkout: moving from master to 806a6fd6ae4198a20af0f301d27c1 704b63a HEAD@{12}: merge 704b63ab353cf32b5d6a9a54b8eb2d4d52b824e8: Fast-forward 806a6fd HEAD@{13}: checkout: moving from 704b63ab353cf32b5d6a9a54b8eb2d4d52b824e 704b63a HEAD@{14}: checkout: moving from master to 704b63ab353cf32b5d6a9a54b8eb2 806a6fd HEAD@{15}: 806a6fd6ae4198a20af0f301d27c13d0dd052931: updating HEAD 5f3b0a1 HEAD@{16}: 5f3b0a1e2167652b32e765b94317279868faa82b: updating HEAD 4441dc5 HEAD@{17}: HEAD^ --: updating HEAD 05fec4a HEAD@{18}: HEAD^ --: updating HEAD 5f3b0a1 HEAD@{19}: checkout: moving from 5f3b0a1e2167652b32e765b94317279868faa82 5f3b0a1 HEAD@{20}: checkout: moving from master to 5f3b0a1e2167652b32e765b943172 5f3b0a1 HEAD@{21}: checkout: moving from 5f3b0a1e2167652b32e765b94317279868faa82 5f3b0a1 HEAD@{22}: checkout: moving from master to 5f3b0a1e2167652b32e765b943172 5f3b0a1 HEAD@{23}: checkout: moving from 704b63ab353cf32b5d6a9a54b8eb2d4d52b824e 704b63a HEAD@{24}: checkout: moving from master to 704b63ab353cf32b5d6a9a54b8eb2 5f3b0a1 HEAD@{25}: merge 5f3b0a1e2167652b32e765b94317279868faa82b: Fast-forward 806a6fd HEAD@{26}: merge origin/master: Fast-forward
These objects may expire and will be pruned by “git gc” after 30 days (default). It has no harm exist in local repository except occupy some hard drive space.
It is possible to prune loose objects immediately by executing
# git reflog expire --expire=now --all # git gc Counting objects: 35395, done. Delta compression using up to 8 threads. Compressing objects: 100% (7225/7225), done. Writing objects: 100% (35395/35395), done. Total 35395 (delta 26533), reused 35393 (delta 26533) # git reflog #
No comments:
Post a Comment