作为Mort said in a comment,您不一定要在任何机器上执行此操作。
真的,我们必须考虑的不是机器,而是存储库。问题变成了:哪一组存储库拥有或拥有X,我们如何找到它?我们称之为什么(“谁”或“什么”或“存储库”或其他任何东西)并不重要,只要我们记住,一旦我们找到这个集合,它实际上就是一个单独的存储库列表。
谁有提交?
由于 Git 是分布式的,存储库可能有很多副本。每个副本都可能有点不同。 一些份副本——尽管数量可能是0——将拥有原始提交,而一些不会。第一个数字可能为零的原因正是您所说的:
...那个开发者可能会消失或者他的[副本]被毁
如果他是唯一一个拥有可用副本的人,而现在它已经消失了,那么您的原件副本将减少到零,而且很不走运。但由于他在他的 Git 存储库中进行了原始操作,我们知道他一定在某个时候使用散列 X 进行了原始提交。所以问题扩展到:还有谁——还有哪些其他存储库——可能有 X? 其次,对于那些可能拥有它的人,我们如何找到它?
第一个问题的答案是这样开始的:任何捡起它的人,在某个时候都拥有它。如果是这样,他们可能仍然拥有它。如果没有,他们显然没有。
一般来说,大多数人获取提交的方式是运行git fetch。 (注意git clone 是一个包装器,一旦它创建了一个新的存储库,就会运行git fetch,所以即使git clone 也是git fetch 的情况。)当你运行git fetch 时,你指示你的Git 连接到另一个 Git。你的 Git 询问另一个 Git:你有哪些分支和标签名称?他们的 Git 向你发送一个列表,你的 Git 挑选出它喜欢的名称并要求他们的 Git 发送任何 可访问的名称 提交和其他带有这些名称的对象,你的 Git 还没有。
一旦您的 Git 拥有这些对象,您的 Git 就会采用这些名称并(通常)以某种方式更改它们,并将这些名称放入您自己的存储库中。如果他们有一个名为feature 的分支,你的Git 会取他们的名字feature 并将它变成你自己的名字origin/feature。然后,您的 Git 将您自己的 reference refs/remotes/origin/feature 设置为其分支 feature 的提示提交的哈希 ID。
(对于标签名称,您自己的 Git 通常会 no 更改,这就是标签名称比分支名称“更全局”的原因。)
通常,大多数运行git fetch 的人只会带来一个分支——例如,如果我运行git pull origin master(运行git fetch origin master),我最终会指示我的Git 连接到你的Git,询问你的Git你有什么分支和标签等等,然后只带来你的master,然后我称之为我的origin/master——或者带来所有分支。因此,任何运行 git fetch 到另一个 Git 的 Git,在当时以名称 feature 具有“好”提交,并带来了 feature,将带来“好提交 X”。
这为您提供了候选池:谁可能最初选择了 X?任何运行git fetch(包括git clone 和git pull 运行的git fetch-es,尽管后者倾向于限制获取)到具有X 的存储库的人,而该存储库有X。
还有一组存储库需要考虑,因为还有另一种获取提交的方法:git push 可以发送提交。 git push 操作有点像git fetch,只是角色颠倒了。我不是让我的 Git 与您的 Git 对话,以便我的 Git 可以从您的 Git 中获取提交,而是让我的 Git 与您的 Git 对话,以便我的 Git 可以给予对您的提交.我提供了提交对象和任何其他对象,然后我发送了一个请求:请将您的分支或标签名称(例如 refs/heads/feature)设置为此哈希 ID。 --force 我发送一个命令:将你的名字设置为此哈希 ID! 强制标志覆盖在接收端完成的默认“是快进”检查(但 不是 em> 在接收端强制执行的任何其他检查:该命令仍然可以被拒绝)。
因此,任何因git push 而收到良好提交的人都可以拥有它。即使随后的强制推送覆盖了会导致良好提交的名称 X,这 可能 仍然是正确的——但默认情况下,它不是,原因在下一节。
如果你有X,你还有X吗?
现在我们已经排列好所有候选存储库,我们需要检查它们。要查看的位置在 reflogs 中。 reflog 是 Git 用来保留引用所发生事件的历史记录。
如上所述,我们一直在使用“参考”,但没有正确定义它。引用只是一个以refs/ 开头的名称。 Git 通过这些引用找到了几乎所有的东西。例如,分支名称是以refs/heads/ 开头的引用,标签名称是以refs/tags/ 开头的引用。每个引用都存储一个(单个)哈希 ID。
每当您的 Git update 引用(将当前哈希 ID 替换为新 ID)时,您都可以选择让 Git 保存旧的 值,在日志条目中。这些日志条目是您的 reflogs。
每个引用都有一个reflog,特殊名称HEAD也有一个reflog。每个 reflog 都有一个潜在的无限长度——Git 只是在每次引用更新时添加它——所以为了防止 reflogs 占用无限空间,Git 也为每个 reflog 条目分配一个 时间戳。 Reflog 条目最终会过期。
任何给定 reflog 条目的到期时间默认为 30 或 90 天。这部分有点棘手,我没有时间在这里写,所以让我们用较短的时间,30 天。但是,当 reflog 条目处于活动状态时,它会保留较早的哈希 ID,并通过保留它们,使这些对象在存储库数据库中保持“活动”。
同时,还有另外两个棘手的部分:
- 默认情况下,接受
git push 请求的服务器会禁用 reflogs。如果推送了错误的替换,这使得他们不太可能保留良好的提交 X。
- 客户(更可能拥有 X 的客户)已重命名
feature 为 origin/feature。
因此,查看是否可以在 reflogs 中查找是否可以找到具有良好哈希值的提交的位置,可能是 origin/feature 的每个位置一直在运行的客户端git fetch。
在任何给定的 Git 存储库上查看 reflog 的命令是 git reflog <em>reference</em>。 (这实际上运行git log -g,因此它受用户配置git log 选项的影响。)这是我的Git 存储库副本上git reflog origin/master 和git reflog origin/pu 的sn-p:
3dc57ebfb refs/remotes/origin/master@{2}: fetch: fast-forward
ca0964be6 refs/remotes/origin/pu@{1}: fetch: forced-update
我展示这些是为了说明带来新提交而不丢弃旧提交的更正常获取之间的区别:origin/master@{2} 指向提交3dc57ebfb,当更新发生时,它是正常的样式更新;但origin/pu@{1} 指向ca0964be6,当更新发生时,它是“强制更新”。
如果有人选择了好的提交 X,然后 feature 被重新定位并强制推送到 origin 并且有人选择了新的 bad 提交链,他们的origin/feature 将经历强制更新。这意味着您将在 reflog 中看到强制更新,这反过来意味着 earlier 引用有一定机会指向良好的提交 X,或者历史领先的提交好好提交X。
它实际上是直接指向 X 还是指向 X 的某个后代是一个偶然的问题,您必须手动检查这些提交以找出答案。