Git 仅还原推送提交的单个文件

Git 仅还原推送提交的单个文件,git,version-control,git-reset,git-revert,Git,Version Control,Git Reset,Git Revert,下面是推送提交的历史记录 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中

下面是推送提交的历史记录

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中对此文件所做的更改。

假设所需提交的哈希为c77s87:

git checkout c77s87-- file1/to/restore file2/to/restore

git checkout主页提供了更多信息。

在git中,每次提交都会保存一个快照,即每个文件的状态,而不是一组更改

然而,每一次提交几乎每一次提交都有一个父(上一次)提交。如果您询问Git,例如,在提交a123456时发生了什么,Git所做的是找到a123456的父项,提取快照,然后提取a123456本身,然后比较两者。无论a123456中有什么不同,Git都会告诉您

由于每次提交都是一个完整的快照,因此在特定提交中很容易恢复到特定文件的特定版本。例如,您只需告诉Git:从commit
a123456
获取文件
b.ext
,现在您就拥有了commit
a123456
的文件
b.ext
版本。这就是你似乎在问的问题,因此链接的问题和当前的答案(在我键入此内容时)提供了什么。不过,你编辑了你的问题,提出了一些完全不同的问题

多一点背景知识 现在,我必须猜测五次提交中每一次提交的实际哈希ID。(每个提交都有一个唯一的散列ID。散列ID——一大串丑陋的字母和数字——是提交的“真实名称”。这个散列ID永远不会改变;只要提交存在,使用它总是可以得到提交。)但它们是一大串丑陋的字母和数字,所以不要猜测,比如,
8858448BB4933D353FEBC078CE4A3ABCC962EFE
,我将调用您的“提交1”
D
、您的“提交2”
E
,依此类推

由于大多数提交都有一个父级,这使得Git可以从较新的提交向后跳到较旧的提交,因此让我们用这些向后箭头将它们排列成一行:

... <-D <-E <-F <-G <-H   <--master
其中,
F
对所有三个文件所做的更改都会被备份,从而生成三个可能不在任何早期提交中的版本。但这太过分了:您只想退出
F
b
的更改

因此,解决办法是少做一点,或做得太多,然后再修复它 我们已经将git revert的操作描述为:查找更改,然后反向应用相同的更改。我们可以通过使用一些Git命令手动完成这项工作。让我们从
git diff
或简写版本开始,即
git show
:这两个版本都将快照转变为更改

  • 使用
    git diff
    ,我们将git指向父
    E
    和子
    F
    ,然后问git:这两者之间有什么区别?Git提取文件,比较它们,并向我们显示更改的内容

  • 使用git show
    git,我们将git指向commit
    F
    ;Git自己查找父级
    E
    ,提取文件并比较它们,并向我们显示更改的内容(以日志消息为前缀)。也就是说,
    git show commit
    相当于
    git log
    (仅用于一次提交),然后是
    git diff
    (从该提交的父级提交到该提交)

Git将显示的更改本质上是指令:它们告诉我们,如果我们从
E
中的文件开始,删除一些行,然后插入一些其他行,我们将得到
F
中的文件。所以我们只需要反转差异,这很容易。事实上,我们有两种方法可以做到这一点:我们可以用
git diff
交换散列ID,或者我们可以使用
-R
标志来
git diff
git show
。然后我们会得到一些说明,本质上是这样的:如果您从
F
中的文件开始,然后应用这些说明,您将从
E
中获得文件

当然,这些说明将告诉我们对所有三个文件进行更改,
a
b
c
。但是现在我们可以去掉三个文件中两个的指令,只留下我们想要的指令

同样,有多种方法可以做到这一点。显而易见的做法是将所有说明保存在一个文件中,然后编辑该文件:

git show-R hash-of-F>/tmp/instructions

(然后编辑
/tmp/说明
)。不过,还有一种更简单的方法,那就是告诉Git:只需要显示特定文件的指令。我们关心的文件是
b
,因此:

git apply < /tmp/instructions
git show-R hash-of-F--b>/tmp/指令

如果您检查说明文件,它现在应该描述如何取走
F
中的内容,并取消更改
b
,使其看起来像
E
中的内容

现在我们只需要让Git应用这些指令,不同的是,我们将使用当前提交的文件,而不是提交
F
,该文件已经位于我们的工作树中,准备修补。应用补丁程序的Git命令(关于如何更改某组文件的一组说明)是
Git apply
,因此:

git apply < /tmp/instructions
现在,从commit
H
中签出两个文件
a
c
非常简单:

git签出hash-of-H--a c

然后进行新的提交
J

...--D--E--F--G--H--I--J   <-- master
  • 或者,
    git revert
    有一个
    -n
    标志,告诉git:执行还原,但不提交结果。(这还允许使用脏索引和/或工作树进行还原,但如果确保从提交
    H
    的干净签出开始,则无需担心这意味着什么。)下面我们从
    
    
    ...--D--E--F--G--H--I--J   <-- master
    
                       I   [abandoned]
                      /
    ...--D--E--F--G--H--J   <-- master