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
在这里,提交 A
和 B
是 *
之前的两个步骤,而所有 C
,D
、E
和F
是在.
之后的两个步骤
在 Git 中更容易找到 "before" 的提交,因为 Git 的内部箭头都指向 "backwards"。分支名称,它们都在右边,因此指向分支上的 latest 提交——这实际上是分支在 Git 中的工作方式:名称指向tip commit,并且 tip commit 向后(在这些图中向左)指向更早的提交——简单地 Git 开始查找提交。
给定一个提交,Git 跟随向后箭头到该提交的 parent(s)。这里 *
是合并提交,将 A--o
和 B--o
行放在一起。
*
的两个 parent 的顺序并未显示在图表本身中,但可以说 A
行实际上是通过 秒parent。假设 *
又是 1234567
。然后 1234567~2
是提交 B
(不是 A
),因为 ~
仅跟在 first parents 之后。要查找A
,可以写1234567^2
(在*
之前命名上o
),然后添加^1
或~1
返回再 parent 到 A
.
换句话说,这两个提交现在是1234567~2
和1234567^2~1
。 (还有更多的写法。)但最简单的是 运行 git log --graph --oneline 1234567
并观察图形——Git 将垂直绘制它但你会看到两条线从*
,这会让你同时找到 A
和 B
。
如果有两种以上的方法可以达到两个以上的 "two steps back" 提交,您会在图表中看到这一点,就像我们对 "two steps forward" 的许多提交所做的那样.但是...找到 "two steps forward" 的提交明显要困难得多。
同样,Git 从最新的(在我们的绘图中最右边)开始 提示 提交并向后工作。我画了三个东西,最终合并到 branch3
、1 的提示中,但请注意提交 D
——这也是向前两步——可以找到 仅 通过从 branch1
.
向后搜索
没有"search forward"
Git 无法从提交中获得 "search forward" 或 "step forward"。所有向前移动的操作(有一个!)实际上是通过向后移动来工作的。基本上,我们从所有可能的起点开始,即所有分支名称/提示。2 然后我们有 Git 列表 一切 返回到我们想要的提交,当且仅当它们是 descendants 时打印这些提交的哈希 ID 3 我们正在查看的提交。
打印所有这些 ID 的命令是 git rev-list
(实际上只是 git log
、4 告诉它只打印提交的哈希 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。这不仅包括提交 C
、D
、E
和 F
,还包括提交 "uninteresting" o
之后的所有提交 *
。对此有许多可能的解决方案,但也许最简单的是返回使用 git log --graph --oneline
: 这样,而不是只打印 (ful) 散列,打印(缩写)散列和提交消息,加上绘制图表。现在您可以观察图表以找到 "two ahead".
的提交
(当然,由于git log
和git 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" 的简单测试:我们只是反转测试。 Y 是 X 的后代,X 必须是 的祖先Y。由于Git可以回答"is ancestor"题,我们逆向测试得到答案
4git log
和 git rev-list
之间存在一些显着差异,因此它们 并不完全 同样的命令。但它们是从相同的源代码构建的,并且 可以 做完全相同的事情,具体取决于选项。他们只是默认设置了不同的选项:log
打印 human-readable 东西,使用寻呼机,使用颜色输出,等等;而 rev-list
打印 machine-readable 东西并且不使用寻呼机。使一个人像另一个人一样行动所需的标志也不总是显而易见的。
您可以使用 git rev-list --parents -n 2 <commithash>
在哈希之前到达提交 2。
我必须恢复对文件所做的更改,在许多分支上,在许多回购协议上。我知道我可以使用 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
在这里,提交 A
和 B
是 *
之前的两个步骤,而所有 C
,D
、E
和F
是在.
在 Git 中更容易找到 "before" 的提交,因为 Git 的内部箭头都指向 "backwards"。分支名称,它们都在右边,因此指向分支上的 latest 提交——这实际上是分支在 Git 中的工作方式:名称指向tip commit,并且 tip commit 向后(在这些图中向左)指向更早的提交——简单地 Git 开始查找提交。
给定一个提交,Git 跟随向后箭头到该提交的 parent(s)。这里 *
是合并提交,将 A--o
和 B--o
行放在一起。
*
的两个 parent 的顺序并未显示在图表本身中,但可以说 A
行实际上是通过 秒parent。假设 *
又是 1234567
。然后 1234567~2
是提交 B
(不是 A
),因为 ~
仅跟在 first parents 之后。要查找A
,可以写1234567^2
(在*
之前命名上o
),然后添加^1
或~1
返回再 parent 到 A
.
换句话说,这两个提交现在是1234567~2
和1234567^2~1
。 (还有更多的写法。)但最简单的是 运行 git log --graph --oneline 1234567
并观察图形——Git 将垂直绘制它但你会看到两条线从*
,这会让你同时找到 A
和 B
。
如果有两种以上的方法可以达到两个以上的 "two steps back" 提交,您会在图表中看到这一点,就像我们对 "two steps forward" 的许多提交所做的那样.但是...找到 "two steps forward" 的提交明显要困难得多。
同样,Git 从最新的(在我们的绘图中最右边)开始 提示 提交并向后工作。我画了三个东西,最终合并到 branch3
、1 的提示中,但请注意提交 D
——这也是向前两步——可以找到 仅 通过从 branch1
.
没有"search forward"
Git 无法从提交中获得 "search forward" 或 "step forward"。所有向前移动的操作(有一个!)实际上是通过向后移动来工作的。基本上,我们从所有可能的起点开始,即所有分支名称/提示。2 然后我们有 Git 列表 一切 返回到我们想要的提交,当且仅当它们是 descendants 时打印这些提交的哈希 ID 3 我们正在查看的提交。
打印所有这些 ID 的命令是 git rev-list
(实际上只是 git log
、4 告诉它只打印提交的哈希 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。这不仅包括提交 C
、D
、E
和 F
,还包括提交 "uninteresting" o
之后的所有提交 *
。对此有许多可能的解决方案,但也许最简单的是返回使用 git log --graph --oneline
: 这样,而不是只打印 (ful) 散列,打印(缩写)散列和提交消息,加上绘制图表。现在您可以观察图表以找到 "two ahead".
(当然,由于git log
和git 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" 的简单测试:我们只是反转测试。 Y 是 X 的后代,X 必须是 的祖先Y。由于Git可以回答"is ancestor"题,我们逆向测试得到答案
4git log
和 git rev-list
之间存在一些显着差异,因此它们 并不完全 同样的命令。但它们是从相同的源代码构建的,并且 可以 做完全相同的事情,具体取决于选项。他们只是默认设置了不同的选项:log
打印 human-readable 东西,使用寻呼机,使用颜色输出,等等;而 rev-list
打印 machine-readable 东西并且不使用寻呼机。使一个人像另一个人一样行动所需的标志也不总是显而易见的。
您可以使用 git rev-list --parents -n 2 <commithash>
在哈希之前到达提交 2。