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

时间:2018-11-29 06:32:02

标签: 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中对该文件进行更改。

2 个答案:

答案 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指向其父节点HG的哈希ID为G;等等。这就是Git设法向您显示提交F的方式:您问Git 哪个提交是H并说master。您要求Git向您显示H,然后Git提取HG 并进行比较,并告诉您H中发生了什么变化。 / p>

您的要求

  

我想还原对提交3 [H]的文件b所做的更改。但是我想要在提交4和5 [FG中对该文件进行的更改。

请注意,此版本的文件可能不会出现在任何提交中。如果我们采用提交H(您的提交2)中显示的文件,则会得到一个文件,但没有E的更改,但是文件没有F和{{ 1}}添加到它。如果我们继续进行 do ,将G中的更改添加到其中,则可能与 H中的内容不同;如果之后再添加从G到它的更改,则可能与 G中的内容不同。

那么,显然,这会有点困难。

Git提供了H来做到这一点,但是它做得太多了

H命令旨在执行这种操作,但它是在整个提交范围内进行的。实际上,git revert所做的是找出某些提交中的更改,然后(尝试) undo 那些更改

这里是思考git revert的一种相当不错的方法:通过将提交与其父级进行比较,它将提交(快照)转变为一组更改,就像查看该提交的其他所有Git命令一样。因此,对于提交git revert,它将与提交git revert进行比较,找到对文件FEa的更改。然后,这是第一个棘手的问题,它反向将这些更改应用于您的 current 提交。也就是说,由于您实际上正在提交b,因此c可以获取所有三个文件(Hgit reverta)中的任何内容,并且(尝试)撤消确切地完成了在提交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获得文件。

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

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

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 applygit apply < /tmp/instructions 之后更改的b中的行,这将失败。在这里G更聪明,因为它可以完成整个“合并”的事情。)

成功应用说明后,我们可以查看文件,确保它看起来正确,然后照常使用Hgit revert

(旁注:您可以使用以下命令一次完成所有操作:

git add

而且,git commit也有自己的git show -R hash -- b | git applygit 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中的文件Ja这就是我们想要它们的方式-它们与c中的文件Ja匹配-因此我们已经完成了很多工作,除了烦人的额外提交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 中获取文件HH

    F
    a
    c

    由于执行此操作时我们正在提交H,因此我们可以使用名称git revert -n hash-of-F来引用提交中的git checkout HEAD -- a cgit commit的副本H

(Git是Git,还有六种其他方法可以执行此操作;我仅使用我们在此处大致说明的方法。)

相关问题