这篇日志是关于如何使用Git恢复过去的代码,可以猛击 这里 补充一点背景知识。
在本文中出场的主角有:
- git checkout :用来从仓库中取出文件或者将标签"HEAD"指向某个提交
- git reset :移动 HEAD 的同时,修改 工作目录 和 暂存区 中的内容
本文的内容包括:
- 回到x分钟前
- 回到x小时前
- 回到x天前
- 在过去和现在自由往返(总结)
git 在管理版本时使用一个三层结构,如下图所示:
在工作目录里做出的更改可以分次保存到暂存区中,提交时再将暂存区的内容作为一个版本存入仓库中(下文中一个“提交”就代表版本仓库中的一个版本)。回到过去,实际上就是从暂存区或版本仓库中将文件取出,然后覆盖工作目录里的相同文件。
回到 x 分钟前
在 x 分钟这样比较短的时间里,通常只更改了工作目录里的内容,所以用暂存区中的文件就可以带我们回到 x 分钟以前。使用下面的命令
git checkout -- <file-name>
例子:输入下面命令,建立一个仓库,其中仅包含一个文件 readme.txt,文件内容为"x minutes passed"。
$ mkdir TimeMachine #新建目录
$ cd TimeMachine
[TimeMachine]$ git init #初始化版本仓库
Initialized empty Git repository in /home/cm/cmgit/TimeMachine/.git/
[TimeMachine]$ echo "x minutes passed" > readme.txt #添加文件
[TimeMachine]$ git add readme.txt #开始追踪新文件
[TimeMachine]$ git status #查看状态
# On branch master
#
# Initial commit
#
# Changes to be committed:
# (use "git rm --cached <file>..." to unstage)
#
# new file: readme.txt
#
从 git 给出的提示可以知道,readme.txt 已经被保存到暂存区中了。
接下来验证下 git checkout -- <filename> 的作用
[TimeMachine]$ echo "df;aei;akjfaiej" > readme.tx #用一些乱码替换原来的内容
[TimeMachine]$ cat readme.txt #查看文件内容
df;aei;akjfaiej
[TimeMachine]$ git checkout -- readme.txt #从暂存区取出 readme.txt 覆盖工作目录中的文件
[TimeMachine]$ cat readme.txt #再次查看文件内容
x minutes passed
注:如果是意外删除了 readme.txt 也可以用这种方式恢复
回到 x 小时前
经过了 x 小时,大家至少已经进行过一次提交(commit),x 小时前的版本已经被保存到版本仓库中了,这时候如何恢复到 x 小时前呢?分两种情况来看:1. 指定恢复某个文件:git checkout <commit-name> <file-name>
这条命令所做的工作其实就是从提交中取出文件,然后覆盖到工作目录和暂存区里,commit-name 可以是任意的你希望的提交名称,一个小技巧是,可以用HEAD表示当前分支中的最后一次提交,HEAD^表示倒数第二次提交,HEAD~n表示倒数第n次提交。
2. 恢复提交中的所有文件:git reset --hard <commit-name>
想要让所有文件都回到过去?用上面这条命令就行了,它会将提交中的所有内容覆盖到暂存区和工作区。
例子:沿着之前的例子继续往下,首先要提交上次的修改
[TimeMachine]$ git commit -m "version:001" #提交暂存区中的内容,标记为“version:001”
[master 256c6f3] version:001
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 readme.txt
然后开始新的工作,向 readme.txt 中添加一行 “x hours passed”,然后提交
[TimeMachine]$ echo "x hours passed" >> readme.txt #向 readme.txt 中添加内容
[TimeMachine]$ git commit -a -m "version:002" #暂存并提交,相当于先执行 git add 再执行 git commit
[master af78cb8] version:002
1 files changed, 1 insertions(+), 0 deletions(-)
[TimeMachine]$ git log #查看日志,日志中记录了提交的顺序和内容
commit af78cb8154a6cdabdb3a67eb8b7f151219a13105
Author: cndmt <**@126.com>
Date: Wed Jul 27 00:15:50 2011 +0800
version:002 #这是本次的提交
commit 256c6f3d183ba1dbe769d542999938a6dca752b6
Author: cndmt <**@126.com>
Date: Mon Jul 25 23:24:30 2011 +0800
version:001 #这是上次的提交
最后看看上面两条命令能做什么,这里或许有细心的童鞋会问,例子里只有一个文件,上面的两条命令不是一样了么?这个是对的,在恢复文件方面的确是相同的,但是~还有一点不同,马上就可以看到。
先看 git checkout
[TimeMachine]$ git checkout HEAD^ readme.txt # 回到过去~~
[TimeMachine]$ cat readme.txt # x hours passed 木有了
x minutes passed
[TimeMachine]$ git log # 再看下日志,木有变化
commit af78cb8154a6cdabdb3a67eb8b7f151219a13105
Author: cndmt <**@126.com>
Date: Wed Jul 27 00:15:50 2011 +0800
version:002
commit 256c6f3d183ba1dbe769d542999938a6dca752b6
Author: cndmt <**@126.com>
Date: Mon Jul 25 23:24:30 2011 +0800
version:001
再看 git reset --hard
[TimeMachine]$ git checkout HEAD readme.txt # 先复原刚才的更改
[TimeMachine]$ cat readme.txt # 验证一下
x minutes passed
x hours passed
[TimeMachine]$ git reset --hard HEAD^ # 又回到过去~ 为什么要说又?~~
\HEAD is now at 256c6f3 version:001 # 这里有提示,git checkout 可没有阿?
[cm@cm TimeMachine]$ cat readme.txt # 再看文件内容
x minutes passed
[cm@cm TimeMachine]$ git log # 差别就在这了,日志显示 少了一个提交
commit 256c6f3d183ba1dbe769d542999938a6dca752b6
Author: cm <ender35@126.com>
Date: Mon Jul 25 23:24:30 2011 +0800
version:001
git reset 为什么会 改变日志的内容呢? 在最上面介绍主角的时候说 git reset 会移动 HEAD ,那为什么移动了HEAD 日志就变了?请看下图
HEAD (指向 version:002)
|
v
version:001 <-- version:002 <-- master (指向 version:002)
git 把每次的提交都保存成一个 commit 文件(每次查看日志的时候显示的 commit 256c6f... 神马的 就是commit 文件的名字)。文件中包含一个指针指向上一次的提交,比如 version:002的commit 文件中包含一个指向 version:001 的指针。可以看出来,git实际上是用链表的方法管理 commit 文件的。同时为了支持分支功能,git 为每一个分支设置一个分支头(保存在 .git/refs/heads/ 文件夹中),分支头都指向分支中的最后一个提交,上图中master分支的分支头指向的就是version:002。而 HEAD 默认指向的是当前分支的最后一次提交,与当前分支的分支头一样。
既然是链表,git log 的做了什么事就很好猜了,从 HEAD 开始 向前查找,直到遇到某个提交的指针为空,说明它是第一次的提交,然后把找到的内容显示出来就ok了。
啰嗦了这么多,终于讲到 git reset --hard 了,结合之前的那条提示(\HEAD is now at 256c6f3 version:001 ),可以知道这条命令实际上同时移动了 分支头 和 HEAD ,移动以后变成了这样
HEAD (指向 version:001)
|
v
version:001 <-- version:002
^
|
master (指向 version:001)
于是 git log 从 HEAD 一路向前,只找到了 version:001。
但是~ 按照链表理论,这时候 version:002 应该已经永远的离开了我们,还有可能找回来么? 答案是肯定滴~方法就是下面要讲的,回到 x 天前。
回到 x 天前
x 天过去了,执行了n次 git commit、git reset 后,会出现不少类似 version:002 的不在日志中列出的提交,git 仍然会将它们保存一段时间(默认为30天),使用 git reflog 可以具体查看之前使用过的所有提交。
继续上面的例子:使用 git reflog 看看会显示些什么
[TimeMachine]$ git reflog
...... # 写日志的时候产生了一些多余内容,省略之
af78cb8 HEAD@{9}: commit: version:002 # 这样就找到了 version:002
256c6f3 HEAD@{10}: commit (initial): version:001
再次使用 git reset --hard 就可以将分支头和HEAD重新指向 version:002
[TimeMachine]$ git reset --hard af78c # af78c 就是上面查出来的 commit 文件名
HEAD is now at af78cb8 version:002
[TimeMachine]$ git log --pretty=format:"%h %s" # 再看日志文件
af78cb8 version:002
256c6f3 version:001
补充的一点是 每次使用 git reset 时 git 会自动把移动前的 HEAD 复制到 ORIG_HEAD 里,可以简单的使用 git reset --hard ORIG_HEAD 回到上一次使用 git reset 之前(回到 x 分钟前 例子中的情况)。
在过去和现在自由往返(总结)
命令使用的流程:
- working directory <-
/ \
git add git checkout -- <filename>
\ /
----> staging area ----
/ \
git checkout <commit> <file> # 单个文件 git commit
git reset <commit> # 全部文件 |
\ /
------- repository <-----
注:使用 git reset 时 若不加入 --hard 选项,则只覆盖暂存区的内容,不改变工作目录中的文件
git reset ORIG_HEAD 可以立即撤销上一次 git reset 所做出的更改
git reflog 显示近期使用过的 commit 文件,然后用 git reset <commit-name> 将提交重新加入到日志里
一些有用的链接:
为什么git更加优秀呢:http://zh-tw.whygitisbetterthanx.com/
官方中文教程:http://gitbook.liuhui998.com/
git 魔法:http://www-cs-students.stanford.edu/~blynn/gitmagic/intl/zh_cn/book.html
日志到这就结束了,水平有限,不知道说清楚了没,有任何问题,欢迎来人来函以及来而无往非礼也之交谈~