Git 签出一个提交 2 次提交在哈希之前

Git checkout to a commit 2 commits before hash

我必须恢复对文件所做的更改,在许多分支上,在许多回购协议上。我知道我可以使用 git checkout hash filename 然后推送该更改。

问题是,我只知道在我要恢复到的实际提交之前有两个提交。

我如何询问 git 在此之前两次提交的哈希值是多少并使用它来正确还原?

使用git日志查看对文件所做的修改:

git log myfile

当您找出要还原到的提交哈希后,使用

git checkout thehash myfile

我不清楚你是否需要从某个特定的提交中查看向后向前

不过,所有这一切的关键是认识到 Git 根本不关心分支 names。 Git 关心的是 提交图 。任何时候你在处理这类问题,你都应该画图

这里有一些示例图。 Round o 节点代表提交。 * 是你手上有 ID 的提交,我给 "potentially interesting" 提交 one-letter names:

...--A--o--*--o--B--...--o   <-- branchname
     ^           ^
   2 before    2 after

这张图很简单。提交 A 在提交 * 之前两个,所以如果你有 * 的散列——假设它是 1234567——你可以写 1234567~2~2 表示 "go back two first-parents"(见下文)。查找提交 B 更难(见下文)。

但我们可能会遇到这样的麻烦:

                o--D--o   <-- branch1
               /
              /  E--o     <-- branch2
      A--o   /  /    \
          \ /  /      \
...--B--o--*--o--F-...-o   <-- branch3
            \         /
             o--C----o

在这里,提交 AB * 之前的两个步骤,而所有 CDEF.

之后的两个步骤

在 Git 中更容易找到 "before" 的提交,因为 Git 的内部箭头都指向 "backwards"。分支名称,它们都在右边,因此指向分支上的 latest 提交——这实际上是分支在 Git 中的工作方式:名称指向tip commit,并且 tip commit 向后(在这些图中向左)指向更早的提交——简单地 Git 开始查找提交。

给定一个提交,Git 跟随向后箭头到该提交的 parent(s)。这里 * 是合并提交,将 A--oB--o 行放在一起。

* 的两个 parent 的顺序并未显示在图表本身中,但可以说 A 行实际上是通过 parent。假设 * 又是 1234567。然后 1234567~2 是提交 B(不是 A),因为 ~ 仅跟在 first parents 之后。要查找A,可以写1234567^2(在*之前命名上o),然后添加^1~1返回再 parent 到 A.

换句话说,这两个提交现在是1234567~21234567^2~1。 (还有更多的写法。)但最简单的是 运行 git log --graph --oneline 1234567 并观察图形——Git 将垂直绘制它但你会看到两条线从*,这会让你同时找到 AB

如果有两种以上的方法可以达到两个以上的 "two steps back" 提交,您会在图表中看到这一点,就像我们对 "two steps forward" 的许多提交所做的那样.但是...找到 "two steps forward" 的提交明显要困难得多。

同样,Git 从最新的(在我们的绘图中最右边)开始 提示 提交并向后工作。我画了三个东西,最终合并到 branch31 的提示中,但请注意提交 D——这也是向前两步——可以找到 通过从 branch1.

向后搜索

没有"search forward"

Git 无法从提交中获得 "search forward" 或 "step forward"。所有向前移动的操作(有一个!)实际上是通过向后移动来工作的。基本上,我们从所有可能的起点开始,即所有分支名称/提示。2 然后我们有 Git 列表 一切 返回到我们想要的提交,当且仅当它们是 descendants 时打印这些提交的哈希 ID 3 我们正在查看的提交。

打印所有这些 ID 的命令是 git rev-list(实际上只是 git log4 告诉它只打印提交的哈希 ID记录提交。)表示 "give me descendants" 的 flag--ancestry-path,它必须与更多的信息结合。特别是,我们必须告诉 Git 哪个提交是 "interesting" 提交,我们使用 ^ 前缀(这次不是后缀)来做到这一点:

git rev-list --ancestry-path ^1234567 --branches

^1234567 告诉 Git 哪个提交停止图形遍历 并且 将打印的修订集限制为 [=19= 的后代] ]. --branches 像往常一样告诉 Git 从哪里 开始 搜索:来自所有分支提示。 (我们还可以添加 --tags,或使用 --all 来表示 所有引用:所有分支、所有标签、存储(如果有的话)以及可能存在的任何其他内容。 但是,--branches--branches --tags 可能是您想要的。)

现在,git rev-list 的问题在于它溢出了所有修订 ID。这不仅包括提交 CDEF,还包括提交 "uninteresting" o 之后的所有提交 *。对此有许多可能的解决方案,但也许最简单的是返回使用 git log --graph --oneline: 这样,而不是只打印 (ful) 散列,打印(缩写)散列和提交消息,加上绘制图表。现在您可以观察图表以找到 "two ahead".

的提交

(当然,由于git loggit rev-list基本是同一个命令,所以也可以运行git rev-list --graph,没必要--oneline 因为无论如何输出都是每行一个提交 ID。但是 git rev-list 是一个 plumbing 命令,用于脚本,不是很 user-friendly,而git log是为了与用户互动,尽量immediately-useful.)


1这种 three-parent 合并是一种叫做 "octopus merge" 的东西,在实践中并不常见。更典型的是,您会有两个合并,一个将 branch2 合并到 branch3,另一个(或早或晚)将 now-unnamed bottom-row 行的提交合并到 branch3.

2我们可能还想从标签开始。例如,考虑这个图形片段:

...--o--*--o--o   <-- branch
         \
          o--o    <-- tag: v0.98alpha

这里,版本 0.98alpha 被视为 "bad",并且从标签可访问的两个提交不再在 any 分支上。但是,如果发现这两个提交中的一个包含一些宝贵的数据或代码,您仍然可以通过从标记开始找到它,如果需要,可以回溯到上一个提交。此处标记为 * 的提交 two-steps-back 也在分支 branch 上,因此您会发现即使不从标记 v0.98alpha.

开始

3在 Git 使用的这些有向图中,ancestor 是您可以从一些开始到达的任何提交提交和向后工作:提交本身,它的任何 parents,它的任何 parents' parents——它的任何 grandparents——等等.这与"ancestors"对你自己的意义相同,只是在Git中你被认为是你自己的祖先。

因为Git的箭头是向后的,所以很容易找到祖先。

术语"descendants"作用相同:你是你自己的后代,但是你的children和grandchildren和great-grandchildren也是你的后代,就像你一样会期待。 Git 实际上无法直接 找到 这些 grandchildren,但是给定 的提交,有一个非常"is commit Y a descendant of commit X" 的简单测试:我们只是反转测试。 YX 的后代,X 必须是 的祖先Y。由于Git可以回答"is ancestor"题,我们逆向测试得到答案

4git loggit rev-list 之间存在一些显着差异,因此它们 并不完全 同样的命令。但它们是从相同的源代码构建的,并且 可以 做完全相同的事情,具体取决于选项。他们只是默认设置了不同的选项:log 打印 human-readable 东西,使用寻呼机,使用颜色输出,等等;而 rev-list 打印 machine-readable 东西并且不使用寻呼机。使一个人像另一个人一样行动所需的标志也不总是显而易见的。

您可以使用 git rev-list --parents -n 2 <commithash> 在哈希之前到达提交 2。