上一节中,我们学习了如何在代码仓库中记录版本。我们之所以不辞辛苦的记录,那只是为了,如果某一天项目进展失败至少还有可用的版本,并且我们还可以由此确定麻烦代码被引入的位置。
不过,保存下来的”安全可用”版本如果不能恢复那就一文不值了。接下来将要学习的是如何查看项目的先前状态,并恢复之,最后重置未提交的更改。
点击下载本节的代码仓库
如果你学习过前面的章节,那就不需要下载上面的代码仓库了。如果没有,直接下载并解压就可以继续了。
显示提交校验和(Display Commit Checksums)
首先快速预览一下仓库的提交历史。在命令行界面(或Git Bash中)切换到my-git-repo
目录中,然后执行
git log --oneline
输出类似如下的信息:
1c310d2 Add navigation links
54650a3 Create blue and orange pages
b650e4b Create index page
Git仅仅输出了校验和的前7个字符(查看完整的校验和使用git log)。不过起始的几个字符足以用来唯一标识一次提交了。
查看老版本
使用git checkout
命令,可以查看以前的快照内容。请确保将以下命令中的54650a3
替换为你的第二次提交ID。
git checkout 54650a3
执行命令后,会输出很多关于detached HEAD
状态的信息,暂时忽略这些。你只需要知道上面的命令将my-git-repo
目录中内容变成了第二次快照的内容(参看上一章的基础知识)。
使用编辑器或者浏览器打开HTML文件验证我们在第三次提交时添加的链接已经消失了。此时git log
命令不再显示第三次提交的信息了。在检出第二次提交后,我们的仓库历史看起来如下图(红色的圆圈代表当前的提交)。
查看更老的版本
让我们回溯到更久远的历史。注意将b650e4b
替换成你的第一次提交ID。
git checkout b650e4b
现在,blue.html
和orange.html
文件都消失了,下面是git log的历史记录。
在上一章中,我说过Git被设计为从不舍弃任何提交记录。那么,我们的第二次和第三次提交哪儿去了?简单的git status
命令可以告诉我们。它应该输出以下信息:
# Not currently on any branch.
nothing to commit (working directory clean)
将之与上一章的输出相比较:
# On branch master
nothing to commit (working directory clean)
上一章中我们所有的操作都发生在master分支,第二次和第三次提交也存在于该分支上。要获取我们所有的历史记录,仅仅需要检出该分支。现在仅仅只是对分支做简要的说明,然而有这些知识就足以让我们在各个提交之间切换了。下一章我们会详细讨论分支。
返回当前的版本
我们使用相同的git checkout
命令来返回master分支。
git checkout master
该命令使得Git更新我们的工作目录来反映master分支的快照状态。它重新生成了blue.html
和orange.html
文件,并且更新了index.html
的内容。我们已经回到了项目的当前状态,此时提交历史记录如下:
标记一个发布版本
假如现在这个实例网站项目是一个稳定版本。我们可以给最近的提交标记一个版本数字。
git tag -a v1.0 -m "Stable version of the website"
标记是软件项目的正式发本版本和一些重大的里程碑。方便开发者浏览和检出重要的版本。例如,现在我们可以使用v1.0标签替代随机ID来标识第三次提交。查看标记列表,不带参数执行git tag
命令。
上面的命令中,-a
选项使得Git创建一个带注释的标记,意味着我们可以记录我们的名字,日期,以及一个描述性的文字(通过-m
选项指定)。使用这个标记,在我们进行一些疯狂的实验之后还能找到稳定的版本。
疯狂实验
现在我们可以随意的向示例网站中添加一些试验性的更改。创建文件crazy.html
添加如下HTML.
<!DOCTYPE html>
<html lang="en">
<head>
<title>A Crazy Experiment</title>
<meta charset="utf-8" />
</head>
<body>
<h1>A Crazy Experiment</h1>
<p>We're trying out a <span style="color: #F0F">crazy</span>
<span style="color: #06C">experiment</span>!
<p><a href="index.html">Return to home page</a></p>
</body>
</html>
暂存并提交快照
照旧执行如下命令
git add crazy.html
git status
git commit -m "Add a crazzzy experiment"
git log
在通过git commit -m
提交前,最好是使用git status
确认你要提交的内容。这样可以防止无意中提交了不属于该快照的文件。
不出意料的话该次提交会出现在仓库的提交历史中。如果输出的信息太多,你可以通过Space键滚动查看或者键入q退出到命令行。
查看稳定的提交
让我们回到稳定版本。现在v1.0
标记可以代替第三次的提交ID使用。
git checkout v1.0
在查看稳定版本后,我们决定舍弃该次的疯狂实验。但是,在我们撤销更改之前,我们需要回到主分支。如果不这样,那么所有的更新都将发生在一个不存在的分支上。正如下一章将要介绍的那样,绝不要在老版本上直接更改。
git checkout master
git log --oneline
此时我们的仓库历史记录类似如下:
514fbe7 Add a crazzzy experiment
1c310d2 Add navigation links
54650a3 Create blue and orange pages
b650e4b Create index page
撤销已提交的更改
我们准备通过删除最近的提交来恢复到稳定的版本。在执行以下命令前,请确保将514fbe7
替换成为你的疯狂实验提交ID。
git revert 514fbe7
该操作会弹出一个默认的提交信息Revert "Add a crazy experiment"
…保持默认然后关闭文件即可。此时crazy.html
文件已经消失,执行git log --oneline
查看历史记录
506bb9b Revert "Add a crazzzy experiment"
514fbe7 Add a crazzzy experiment
1c310d2 Add navigation links
54650a3 Create blue and orange pages
b650e4b Create index page
值得注意的是,Git没有删掉crazzzy experiment
这次提交,而是根据此次提交进行相应的撤销,然后使用撤销后的内容进行一次新的提交。因此,我们的第五次提交和第三次提交代表了相同的快照内容,如下图。重申一次,Git不会丢失历史:第四次快照仍然可以访问,以备我们需要继续开发。
在使用git revert
命令时,记得指定你需要撤销的提交——而不是你想要恢复到的稳定提交。最好是将该命令理解为”撤销该提交”而不是“恢复该版本”。
进行一次小型实验
接下来尝试一次小型的实验。创建dummy.html
空白文件。然后在index.html
中的“Navigation”中添加一个链接,如下:
<h2>Navigation</h2>
<ul>
<li style="color: #F90">
<a href="orange.html">The Orange Page</a>
</li>
<li style="color: #00F">
<a href="blue.html">The Blue Page</a>
</li>
<li>
<a href="dummy.html">The Dummy Page</a>
</li>
</ul>
在接下来的小节中,我们准备放弃该实验。由于git revert
命令需要指定一个提交ID,因此我们不能使用前面讨论的方法。
撤销未提交的更改
在撤销之前,首先查看仓库的状态。
git status
我们有一个已跟踪的和一个未跟踪的文件需要更改。首先处理index.html
:
git reset --hard
该命令将所有已跟踪文件恢复到最近提交的状态。注意--hard
选项是必需的,否则git reset
命令仅仅将index.html置为未跟踪状态,保留其中的内容。不过在两种情况下,git reset
命令仅仅作用于工作目录和暂存区,因此我们的git log
历史记录不变。
接下来,我们删除dummy.html
。我们当然可以手动删除掉它,但是借助于Git来重置(reset)可以消除掉大型团队处理多个文件时可能会有的错误。运行以下命令:
git clean -f
这将删除掉所有未跟踪文件。在删除掉dummy.html
后,git status
会显示工作目录为clean
状态,意味着我们的工作目录跟最近的提交匹配。
注意git reset
和git clean
命令都是作用于工作目录,而不是已提交的快照。跟git revert
不同的是,这些命令会永久的撤销更改,所以使用之前请三思。
总结
正如我们在上一章中提到的,大部分的Git命令都是作用于Git仓库的三个主要组成部分:工作目录,暂存快照区,提交快照区。git reset
的撤销动作作用于工作目录和暂存区,git revert
则作用于已提交的快照。同理,git status
和git log
也是如此(git status
作用于工作目录和暂存区,git log
作用于提交区)。
我提到过,git revert
不会删除一次提交,以备你再次访问。当然这只是git保留提交快照的一个原因,我们会看到,在多个开发者协同工作时删除一个提交会造成严重的后果。
本章同样介绍了如何使用git checkout
在不同的提交和分支之间切换。加上分支,我们就完成了对git核心组件的讨论,并且分支的概念使得我们的工作流程更加优雅。下一章中,我们将会讨论分支的基本命令。
快速参考
git checkout <commit-id>
View a previous commit.
git tag -a <tag-name> -m "<description>"
Create an annotated tag pointing to the most recent commit.
git revert <commit-id>
Undo the specified commit by applying a new commit.
git reset --hard
Reset tracked files to match the most recent commit.
git clean -f
Remove untracked files.
git reset --hard / git clean -f
Permanently undo uncommitted changes.