您的位置:首页 > 其它

Git 如何带你回到过去

2013-04-21 10:20 369 查看
这篇日志是关于如何使用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

 

日志到这就结束了,水平有限,不知道说清楚了没,有任何问题,欢迎来人来函以及来而无往非礼也之交谈~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  版本控制 Git