Git 如何带你回到过去

来源:互联网 发布:冒险岛登录器源码 编辑:程序博客网 时间:2024/05/01 08:21

这篇日志是关于如何使用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 af78cb8154a6cdabdb3a67eb8b7f151219a13105Author: cndmt <**@126.com>Date:   Wed Jul 27 00:15:50 2011 +0800    version:002#这是本次的提交 commit 256c6f3d183ba1dbe769d542999938a6dca752b6Author: 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 af78cb8154a6cdabdb3a67eb8b7f151219a13105Author: cndmt <**@126.com>Date:   Wed Jul 27 00:15:50 2011 +0800    version:002commit 256c6f3d183ba1dbe769d542999938a6dca752b6Author: 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 passedx 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 256c6f3d183ba1dbe769d542999938a6dca752b6Author: 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:002256c6f3 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

 

日志到这就结束了,水平有限,不知道说清楚了没,有任何问题,欢迎来人来函以及来而无往非礼也之交谈~

原创粉丝点击