下面是推送的提交历史记录。
Commit Changed Files
Commit 1| (File a, File b, File c)
Commit 2| (File a, File b, File c)
Commit 3| (File a, File b, File c)
Commit 4| (File a, File b, File c)
Commit 5| (File a, File b, File c)
我想还原对提交3 的文件b 所做的更改。 但是我希望在提交4和5中对该文件进行更改。
答案 0 :(得分:3)
假设所需提交的哈希值为c77s87:
git checkout c77s87-- file1 / to / restore file2 / to / restore
git checkout主页提供了更多信息。
答案 1 :(得分:2)
在Git中,每个提交都保存一个快照-即每个 文件的状态-而不是一组更改。
但是,每个提交(几乎每个提交 )也都有一个 parent (上一个)提交。如果您问Git,例如在提交a123456
中发生了什么?,那么Git所做的就是找到a123456
的 parent ,提取 快照,然后提取a123456
本身,然后将两者进行比较。 a123456
中的不同都是Git会告诉您的。
由于每个提交都是完整的快照,因此很容易将还原为特定提交中特定文件的特定版本。您只是告诉Git:例如,从提交b.ext
中获取文件a123456
给我,现在您有了文件 b.ext
来自提交a123456
。这就是您似乎要问的问题,因此,所链接的问题和当前答案(截至我键入此内容时)提供了什么。不过,您编辑了问题,以寻求完全不同的东西。
我现在必须猜测您的五次提交中的每一个的实际哈希ID。 (每个提交都具有唯一的哈希ID。哈希ID(丑陋的字母和数字字符串)是提交的“真实名称”。此哈希ID永不改变;使用它总是让您那个 commit,只要该commit存在就行。)但是它们是由字母和数字组成的丑陋字符串,因此不用猜测8858448bb49332d353febc078ce4a3abcc962efe
,而是将您的“ commit 1”称为{{1} },您的“提交2” D
,依此类推。
由于大多数提交都有一个单亲,这使得Git可以从较新的提交向后跳转到较旧的提交,因此我们将它们排列成与这些向后的箭头对齐:
E
类似于... <-D <-E <-F <-G <-H <--master
的分支名称实际上只是保存该分支上 latest 提交的哈希ID。我们说名称指向提交,因为它具有让Git检索提交的哈希ID。因此master
指向master
。但是H
的哈希ID为H
,因此G
指向其父节点H
; G
的哈希ID为G
;等等。这就是Git设法向您显示提交F
的方式:您问Git 哪个提交是H
?并说master
。您要求Git向您显示H
,然后Git提取H
和G
并进行比较,并告诉您H
中发生了什么变化。 / p>
我想还原对提交3 [
H
]的文件b所做的更改。但是我想要在提交4和5 [F
和G
中对该文件进行的更改。
请注意,此版本的文件可能不会出现在任何提交中。如果我们采用提交H
(您的提交2)中显示的文件,则会得到一个文件,但没有E
的更改,但是文件没有F
和{{ 1}}添加到它。如果我们继续进行 do ,将G
中的更改添加到其中,则可能与 H
中的内容不同;如果之后再添加从G
到它的更改,则可能与 G
中的内容不同。
那么,显然,这会有点困难。
H
来做到这一点,但是它做得太多了 H
命令旨在执行这种操作,但它是在整个提交范围内进行的。实际上,git revert
所做的是找出某些提交中的更改,然后(尝试) undo 仅那些更改。
这里是思考git revert
的一种相当不错的方法:通过将提交与其父级进行比较,它将提交(快照)转变为一组更改,就像查看该提交的其他所有Git命令一样。因此,对于提交git revert
,它将与提交git revert
进行比较,找到对文件F
,E
和a
的更改。然后,这是第一个棘手的问题,它反向将这些更改应用于您的 current 提交。也就是说,由于您实际上正在提交b
,因此c
可以获取所有三个文件(H
,git revert
和a
)中的任何内容,并且(尝试)撤消确切地完成了在提交b
中对他们所做的。
(实际上有点复杂,因为这种“撤消更改”的想法还考虑了使用Git的三向合并机制在提交c
之后所做的 other 更改。 )
已经对某个特定提交的所有更改进行了反向应用,因此E
现在进行了 new 提交。因此,如果您进行了F
,将得到一个 new 提交,我们可以将其称为git revert
:
git revert <hash of F>
,其中撤消I
对所有三个文件的更改,从而生成三个版本,这些版本可能不在早期提交的 any 中。但这太多了:您只希望撤消...--D--E--F--G--H--I <-- master
对F
的更改。
我们已经将F
操作描述为:找到更改,然后反向应用相同的更改。我们可以使用一些Git命令手动完成此操作。让我们从b
或简写git revert
开始:这两个都将快照变为更改。
使用git diff
,我们将Git指向父git show
和孩子git diff
,然后问Git:这两者有什么区别? Git提取文件,进行比较,然后向我们显示更改的内容。
使用E
,我们指向Git提交F
; Git自行查找父git show
,然后提取文件并进行比较,然后向我们显示更改的内容(以日志消息为前缀)。也就是说,F
等于E
(仅用于一次提交),其后是git show commit
(从该提交的父级到该提交)。
Git将显示的更改实质上是指令::它们告诉我们,如果我们从git log
中的文件开始,请删除一些行,然后插入其他一些行行,我们将获得git diff
中的文件。因此,我们只需对差异进行 reverse 即可,这很容易。实际上,我们有两种方法可以执行此操作:可以将哈希ID与E
交换,或者可以将F
标志用于git diff
或-R
。然后,我们将获得从本质上讲的说明:如果您从git diff
开始使用文件,并应用这些说明,那么您将从git show
获得文件。
当然,这些说明将告诉我们对所有三个文件F
,E
和a
进行更改。但是现在我们可以删除三个文件中两个文件的指令,只留下我们想要的指令。
同样,有多种方法可以做到这一点。显而易见的是将所有指令保存在一个文件中,然后编辑该文件:
b
(然后编辑c
)。不过,还有一种更简单的方法,那就是告诉Git:只花时间显示特定文件的说明。我们关心的文件是git show -R hash-of-F > /tmp/instructions
,所以:
/tmp/instructions
如果您查看说明文件,它现在应该描述如何获取b
中的内容并取消更改git show -R hash-of-F -- b > /tmp/instructions
以使其看起来像F
中的内容。
现在,我们只需要让Git应用这些指令,除了我们将使用来自 current 提交b
的文件代替提交E
的文件,它已经坐在我们的工作树中,可以进行修补了。应用补丁的Git命令(一组有关如何更改某些文件集的说明)为F
,因此:
H
应该可以解决问题。 (不过请注意,如果指令说要更改git apply
或git apply < /tmp/instructions
之后更改的b
中的行,这将失败。在这里G
更聪明,因为它可以完成整个“合并”的事情。)
成功应用说明后,我们可以查看文件,确保它看起来正确,然后照常使用H
和git revert
。
(旁注:您可以使用以下命令一次完成所有操作:
git add
而且,git commit
也有自己的git show -R hash -- b | git apply
或git apply
标志,因此您可以这样拼写:
-R
执行相同的操作。还有其他--reverse
标志,包括git show hash -- b | git apply -R
/ git apply
,使其可以做得更出色,就像-3
一样。)
处理此问题的另一种相对简单的方法是让--3way
完成所有工作。当然,这将撤消您不想撤消的对 other 文件的更改。但是我们在顶部显示了从 any 提交中获取 any 文件多么容易。那么,假设我们让Git撤消{em> all 中所有git revert
中的更改:
git revert
进行新的提交F
,该提交将退回git revert hash-of-F
中的一切:
I
现在F
和...--D--E--F--G--H--I <-- master
两个文件git checkout
来自提交a
很简单:
c
然后重新提交H
:
git checkout hash-of-H -- a c
J
和...--D--E--F--G--H--I--J <-- master
中的文件b
是我们想要的方式,I
中的文件J
和a
这就是我们想要它们的方式-它们与c
中的文件J
和a
匹配-因此我们已经完成了很多工作,除了烦人的额外提交c
。
我们可以通过几种方式摆脱H
:
在制作I
时使用I
:这将git commit --amend
推开,通过使提交J
将提交I
用作{{ 1}}的父母。提交J
仍然存在,只是被放弃了。最终(大约一个月后)它消失了,并且真的消失了。
如果执行提交图,则提交图如下所示:
H
或者,J
有一个I
标志,告诉Git:执行还原操作,但不提交结果。(这还可以执行使用肮脏的索引和/或工作树来还原,尽管如果您确保以干净的提交 I [abandoned]
/
...--D--E--F--G--H--J <-- master
检出开始,则不必担心这意味着什么。)在这里,我们将从{ {1}},还原git revert
,然后告诉Git 从提交-n
中获取文件H
和H
:
F
a
c
由于执行此操作时我们正在提交H
,因此我们可以使用名称git revert -n hash-of-F
来引用提交中的git checkout HEAD -- a c
和git commit
的副本H
。
(Git是Git,还有六种其他方法可以执行此操作;我仅使用我们在此处大致说明的方法。)