在 Git 中处理您的历史记录
学习目标
完成本单元后,您将能够:
- 描述 Git 如何存储数据并概述此知识的实际应用。
- 使用 Git 查看项目历史记录和更改。
- 运行 Git 命令以撤销之前的更改。
- 总结变基如何与常见的合并策略相关。
Git 如何存储数据
之前讨论提交时,我们说提交是项目的快照。每个快照都包含大量信息。每个压缩到快照中的文件都被赋予一个唯一的 SHA-1 哈希值,被称为 blob。树引用这些 blob,而提交则引用该树。
当您对文件进行更改并创建新的提交时,Git 会识别更改的文件并对其应用新的 SHA-1 哈希值,未更改的文件则保留其现有的 SHA-1 哈希值。当文件的 SHA-1 哈希值发生变化时,它会将先前的 SHA-1 哈希值引用为父项。GitHub 允许您查看每个提交的 SHA-1 哈希值 (A) 的前 7 个字符 (A)。
这与提交历史的处理方式非常相似。新创建的提交引用先前的提交作为其父项。这个引用点非常重要。这种线性父子关系创建了一个一致的历史记录,允许 Git 将分支合并在一起。
使用 Git 探索历史记录
在处理项目时,查看提交历史记录会很有帮助。在 GitHub.com 上,您可以通过选择项目代码选项卡上的提交按钮来访问项目历史记录。在本地,您可以使用 git log
。
运行 git log
命令将列出当前分支中的所有提交。默认情况下,git log
命令会一次性显示大量信息。
使用一些 git log
修饰符创建一个易于阅读且提供有价值信息的列表。
-
git log -10
只会显示 10 个最近的提交。
-
git log --oneline
是查看提交历史的好方法,它显示当前分支上的提交的 SHA-1 哈希值的前七个字符和提交消息。
-
git log --oneline --graph
在 ASCII 图中显示提交历史记录,其中显示存储库中的不同分支及其提交。
-
git log --oneline --graph --decorate
显示使用 --graph 修饰符显示的相同 ASCII 图,但还包括所显示的不同提交的分支名称。
比较不同文件版本
当您准备创建完美的提交时,查看当前工作目录和暂存区中内容的差异可以帮助您将正确的文件 git add
到您的提交中。
默认情况下,git diff
命令可帮助您查看项目的最后一次提交与文件的各种状态(例如,工作目录或暂存区中的文件)之间的变化。
您还可以使用 git diff
来比较存储库中的任意两个提交、分支或标签。例如,要比较 SHA-1 哈希值引用分别为 4e3dc9b
和 0cd75d4
的两个提交,输入命令:git diff 4e3dc9b 0cd75d4
最后,如果想查看在先前提交中所做的更改,您可以使用 git show <SHA-1>
命令显示该特定提交的详细信息。包括提交作者、提交的时间和日期,以及对存储库中各种资产所做更改的列表。
撤销之前的更改
有时我们会犯一些小错误(或大错误!)。幸运的是,Git 的一些命令可让您修正错误。但请注意,一些可以修正错误的命令会破坏性地修改提交 ID。由于这些提交 ID 是不可变的,因此您可能会给其他协作者带来问题。作为一般规则,只有在您已将提交推送到远程时才应使用 git revert
。
撤销更改
git revert
会创建一个新提交,其中的更改与功能上被“撤销”的提交相反。
例如,假设在存储库的提交历史记录中有一个提交,该提交将所有标头 3 更改为标头 5。您可以使用 git revert
将所有标头还原为标头 3,而不是将标头 5 一个一个还原为标头 3。
git revert
可以在存储库历史记录中的任何时间点用于提交,而不会影响其他工作。
使用 git revert
是撤销特定更改的安全方法,可以避免更改项目的提交历史记录时常见的复杂问题。
修改提交
之前讨论 git commit
时,我们曾提到提交消息是创建详细项目历史记录的重要因素。
有时,当您正忙于提交时,您可能会在提交消息中输入错字。您可以使用 git commit --amend
修改您的最后一次提交。此操作会修改项目的提交历史记录,因此如果您已经将提交推送到远程,我们建议您不要使用 git commit --amend
。
您还可以使用 git commit --amend
重写最新的提交并将文件添加到暂存区。
倒回到历史记录中较早的时间点
我们知道程序员喜欢尝试新鲜事物。然而,有时在实验过程中,我们意识到我们走错了路,我们所做的提交可能没有最初想象的那么有用。
Git 的 git reset
命令可以帮助我们倒回项目历史记录,但是如前所述,它会改变提交历史,这可能会给其他协作者带来问题。我们强烈建议,仅在您尚未将提交推送到远程分支时才使用.git reset
。git reset
有三种不同的风格,分别是 --soft
、--mixed
和 --hard
。
-
git reset --soft
将采用指定的提交并将所有更改放入暂存区。当您想要进行一组提交并将它们打包成一个大型提交时,此命令很有用。
-
git reset --mixed
是git reset
的默认模式,采用指定的提交并将所有更改放在工作目录中。类似于--soft
,如果您想进行一组小型提交并将部分更改组合成一个较大的提交时,此模式很有用。但您也可以使用它对文件进行其他更改,然后重新创建提交历史记录。
-
git reset --hard
将获取指定的提交并销毁它们。请谨慎使用此命令。废弃的文件不会进入回收站,而是会从存储库中永久删除并彻底消失。任何尚未提交到文件且当前位于工作目录或暂存区中的更改也将被删除。git reset--hard
还可能让您丢失工作。
使用 reset 的示例效果如下:
git reset --soft HEAD~2
会将您所在的分支倒回两次提交之前(请记住 HEAD 是指向分支尖端的指针)。在上两次提交中所做的更改将反映在暂存区中。
Git 合并策略
在 Git 中进行合并时,主要有两种方法可以将更改应用于主分支(或您要合并到的分支):递归合并和快进合并。
这两种合并策略没有对错之分,重要的是要了解每个策略如何影响历史记录的显示。乍一看,这二者似乎更像是 Git 处理合并的方式,而不是合并策略,但让我们先来讨论一下这两种合并,再来说这一点。
递归合并
在您的功能分支将要合并到的分支(通常是主分支,但有时不是)中没有最新版本的代码时,就会发生递归合并。
创建功能分支时,是以当前状态下的原始分支为基础。在您对分支进行更改时,其他协作者可能正将他们的更改合并回原始分支。
当您创建拉取请求并合并您的更改时,将创建合并提交。此操作采用您对分支所做的更改和合并目标分支的当前状态,并创建一个结合了这些更改的新提交。
快进合并
当从原始分支创建功能分支后,原始分支中除了您尝试合并的提交之外没有新提交时,就会发生快进合并。
由于原始分支中没有任何更改,因此分支尖端只需快进以合并分支的更改。对于快进合并,Git 不会创建新的合并提交。
将递归合并转换为快进合并
git rebase
命令功能强大,可以在您的存储库中做很多很酷的事情(所有这些都会重写您的历史记录,因此请谨慎使用)。git rebase
的一个最常见用途是在 Git 设置了默认递归合并时创建快进合并。
请记住,当您创建分支后合并目标分支中有新的更改时,就会发生递归合并。变基获取您对分支所做的提交,并在所选分支上的最后一次提交之后重新应用它们。您还可以使用 git rebase
来更改提交历史的视图。将交互式修饰符 -i
与 rebase 结合使用,您可以编辑以前的提交消息,将提交分组为更大型的提交,并重新排列您的提交历史记录。