介绍
在这个实验中,你将学习如何判断某个特定的 Git 提交是否可以从当前的 HEAD 访问到。这是理解仓库历史以及识别属于活跃开发线的提交的一项基本技能。
我们将探讨两种主要方法:使用 git log --ancestry-path 命令来可视化两个提交点之间的提交谱系,以及使用 git merge-base --is-ancestor 命令直接检查提交的祖先关系。你将通过设置一个包含多个分支和提交的示例仓库来练习这些技巧,其中一些提交会被故意设置为无法从 HEAD 访问,这样你就可以测试并确认自己的理解。
使用 git log --ancestry-path
在这一步中,我们将探讨如何使用 git log --ancestry-path 命令。该命令有助于查看两个提交之间特定路径上的提交历史,能让你了解变更的谱系。
首先,让我们创建一个简单的 Git 仓库,并进行几次提交,为使用 --ancestry-path 命令设置场景。
导航到你的项目目录:
cd ~/project
为这个实验创建一个新目录,并初始化一个 Git 仓库:
mkdir ancestry-lab
cd ancestry-lab
git init
你应该会看到输出,表明一个空的 Git 仓库已初始化:
Initialized empty Git repository in /home/labex/project/ancestry-lab/.git/
现在,让我们创建一个文件并进行第一次提交:
echo "Initial content" > file1.txt
git add file1.txt
git commit -m "Initial commit"
你会看到确认提交的输出:
[master (root-commit) <commit-hash>] Initial commit
1 file changed, 1 insertion(+)
create mode 100644 file1.txt
接下来,让我们进行另一次提交:
echo "Adding more content" >> file1.txt
git add file1.txt
git commit -m "Add more content"
你会看到第二次提交的输出:
[master <commit-hash>] Add more content
1 file changed, 1 insertion(+)
现在,让我们创建一个新分支,并在该分支上进行一次提交:
git branch feature
git checkout feature
echo "Feature work" > file2.txt
git add file2.txt
git commit -m "Add feature file"
你会看到创建分支、切换到该分支以及新提交的输出:
Switched to a new branch 'feature'
[feature <commit-hash>] Add feature file
1 file changed, 1 insertion(+)
create mode 100644 file2.txt
让我们回到主分支并进行另一次提交:
git checkout master
echo "More master work" >> file1.txt
git add file1.txt
git commit -m "More master content"
你会看到切换分支和新提交的输出:
Switched to branch 'master'
[master <commit-hash>] More master content
1 file changed, 1 insertion(+)
现在我们有了一个包含分支的提交历史。让我们使用 git log 查看完整的历史:
git log --all --decorate --oneline
你会看到类似这样的日志(提交哈希和顺序可能会有所不同):
<commit-hash> (HEAD -> master) More master content
<commit-hash> Add more content
<commit-hash> (feature) Add feature file
<commit-hash> Initial commit
现在,让我们使用 git log --ancestry-path。该命令需要两个提交引用,它将显示作为第二个提交的祖先且是第一个提交的后代的提交。
让我们找到 "Initial commit" 和 "More master content" 的提交哈希。你可以从 git log --all --decorate --oneline 的输出中获取这些哈希。将 <initial-commit-hash> 和 <master-commit-hash> 替换为你输出中的实际哈希。
git log --ancestry-path <initial-commit-hash> <master-commit-hash> --oneline
这个命令将显示从初始提交到主分支上最新提交路径上的提交。你应该会看到 "Initial commit"、"Add more content" 和 "More master content" 这些提交。
--ancestry-path 选项有助于理解历史中两个点之间的直接开发脉络,忽略后续可能合并进来的其他分支上的提交。
运行 git merge-base --is-ancestor
在这一步中,你将学习 git merge-base --is-ancestor 命令。该命令用于检查一个提交是否是另一个提交的祖先。这是一个简单的检查,它返回一个状态码(0 表示是,1 表示否),而不是输出提交信息。这在脚本编写或快速检查中特别有用。
你将继续使用上一步创建的 ancestry-lab 仓库。确保你处于正确的目录中:
cd ~/project/ancestry-lab
回顾上一步的提交历史,你在 master 和 feature 分支上都有提交。
让我们找出“Initial commit”和 master 分支上最新提交(“More master content”)的提交哈希。你可以使用 git log --oneline 查看最近的提交。
git log --oneline
输出将类似于:
<master-commit-hash> (HEAD -> master) More master content
<commit-hash> Add more content
<initial-commit-hash> Initial commit
现在,让我们使用 git merge-base --is-ancestor 来检查“Initial commit”是否是 master 分支上最新提交的祖先。将 <initial-commit-hash> 和 <master-commit-hash> 替换为实际的哈希。
git merge-base --is-ancestor <initial-commit-hash> <master-commit-hash>
echo $?
echo $? 命令会打印上一个命令的退出状态。如果第一个提交是第二个提交的祖先,退出状态将为 0;否则,将为 1。
由于“Initial commit”确实是 master 分支上最新提交的祖先,echo $? 的输出应该是 0。
现在,让我们检查“Initial commit”是否是 feature 分支上最新提交的祖先。首先,找出“Add feature file”提交的哈希。
git log --all --decorate --oneline
输出将类似于:
<master-commit-hash> (HEAD -> master) More master content
<commit-hash> Add more content
<feature-commit-hash> (feature) Add feature file
<initial-commit-hash> Initial commit
现在,使用 git merge-base --is-ancestor 来检查“Initial commit”是否是“Add feature file”提交的祖先。将 <initial-commit-hash> 和 <feature-commit-hash> 替换为实际的哈希。
git merge-base --is-ancestor <initial-commit-hash> <feature-commit-hash>
echo $?
同样,echo $? 的输出应该是 0,因为“Initial commit”是两个分支的起点。
最后,让我们检查 feature 分支上的最新提交是否是 master 分支上最新提交的祖先。将 <feature-commit-hash> 和 <master-commit-hash> 替换为实际的哈希。
git merge-base --is-ancestor <feature-commit-hash> <master-commit-hash>
echo $?
在这种情况下,feature 分支上的最新提交不是 master 分支上最新提交的祖先(在初始分支分离后,它们位于不同的分支上)。因此,echo $? 的输出应该是 1。
理解提交之间的祖先关系是理解 Git 如何跟踪历史以及合并和变基等操作如何工作的基础。--is-ancestor 标志提供了一种简单的方法来检查这种关系。
测试不可达提交
在这一步中,我们将探讨 Git 中“不可达”提交的概念。不可达提交是指无法从任何分支、标签或其他引用访问到的提交。这些提交不属于通过 git log 等标准命令所看到的当前项目历史。
我们将继续使用 ancestry-lab 仓库。确保你处于正确的目录中:
cd ~/project/ancestry-lab
目前,我们所有的提交都可以从 master 或 feature 分支访问到。让我们创建一个使某个提交变为不可达的场景。
首先,让我们在 master 分支上进行一次新的提交:
echo "Temporary commit" >> file1.txt
git add file1.txt
git commit -m "Temporary commit"
你将看到这次新提交的输出:
[master <commit-hash>] Temporary commit
1 file changed, 1 insertion(+)
现在,让我们将 master 分支重置到上一个提交。这将使“Temporary commit”无法从 master 分支访问到。我们将使用 git reset --hard HEAD~1。HEAD~1 指的是当前 HEAD 之前的那个提交。
使用 git reset --hard 时要小心,因为它会丢弃更改! 在这种情况下,我们是有意从 master 分支的历史中丢弃“Temporary commit”。
git reset --hard HEAD~1
你将看到输出表明 HEAD 现在指向了上一个提交:
HEAD is now at <previous-commit-hash> More master content
现在,让我们查看标准的 git log:
git log --oneline
你会发现“Temporary commit”不再出现在 master 分支的日志输出中。
<previous-commit-hash> (HEAD -> master) More master content
<commit-hash> Add more content
<initial-commit-hash> Initial commit
“Temporary commit”仍然存在于 Git 数据库中,但没有任何分支或标签引用它。现在它是一个“不可达”提交。
我们如何查看不可达提交呢?Git 有一个名为 reflog 的特殊引用,它会记录分支顶端和其他引用的更新。我们可以使用带有 --walk-reflogs 选项的 git log 命令,或者直接使用 git reflog 来查看这些提交。
让我们使用 git reflog:
git reflog
你将看到一个操作记录,包括我们刚刚进行并随后重置的提交:
<master-commit-hash> (HEAD -> master) master@{0}: reset: moving to HEAD~1
<temporary-commit-hash> master@{1}: commit: Temporary commit
<previous-commit-hash> master@{2}: commit: More master content
<commit-hash> master@{3}: commit: Add more content
<initial-commit-hash> master@{4}: commit (initial): Initial commit
注意“Temporary commit”的记录。通过 reflog 中的 master@{1} 可以访问到它。然而,它无法从当前的 HEAD 或任何分支顶端访问到。
不可达提交最终会被 Git 的垃圾回收机制(git gc)清理掉,但在默认的一段时间内(通常是 30 天或 90 天),你仍然可以通过 reflog 访问它们。如果你不小心重置或删除了提交,这可能会帮你挽回损失。
理解不可达提交有助于你掌握 Git 的内部对象数据库与指向该数据库中提交的引用(分支、标签、HEAD)之间的区别。
总结
在本次实验中,我们学习了如何使用两种主要方法来检查一个 Git 提交是否可以从 HEAD 访问到。首先,我们探索了 git log --ancestry-path 命令,该命令能让我们可视化两个指定提交之间路径上的提交历史。我们创建了一个包含多个分支和提交的简单仓库,以此展示该命令如何帮助我们理解提交的谱系,并确定一个提交是否是另一个提交的祖先。
其次,我们学习了如何使用 git merge-base --is-ancestor 命令,它提供了一种更直接、更适合编程的方式来判断一个提交是否是另一个提交的祖先。最后,我们使用不可达提交对这些方法进行了测试,以巩固我们对如何在 Git 仓库中验证可达性的理解。



