仅还原推送提交的单个文件 [英] Revert only a single file of a pushed commit

查看:52
本文介绍了仅还原推送提交的单个文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Below is the the pushed commits history.

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中对该文件进行更改.

I want to revert the changes happened to File b of Commit 3. But i want the changes happened to this file in commit 4 and 5.

推荐答案

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

In Git, each commit saves a snapshot—that is, the state of every file—rather than a set of changes.

但是,每个提交(几乎每个提交 )也都有一个 parent (上一个)提交.如果您问Git,例如在提交 a123456 中发生了什么?,Git所做的就是找到 a123456 parent >,提取那个快照,然后提取 a123456 本身,然后将两者进行比较.无论 a123456 中的不同是什么,Git都会告诉您.

However, every commit—well, almost every commit—also has a parent (previous) commit. If you ask Git, e.g., what happened in commit a123456?, what Git does is find the parent of a123456, extract that snapshot, then extract a123456 itself, and then compare the two. Whatever is different in a123456, that's what Git will tell you.

由于每个提交都是完整的快照,因此很容易在特定提交中将还原为特定文件的特定版本.您只是告诉Git:例如,从 a123456 提交中获取 b.ext 文件,现在您有了文件的版本.提交 a123456 中的 b.ext .这就是您似乎要问的问题,因此,所链接的问题和当前答案(截至我键入此内容时)提供了什么.但是,您编辑了问题,以寻求完全不同的东西.

Since each commit is a complete snapshot, it's easy to revert to a particular version of a particular file in a particular commit. You just tell Git: Get me file b.ext from commit a123456, for instance, and now you have the version of file b.ext from commit a123456. That's what you seemed to be asking, and hence what the linked question and current answer (as of when I am typing this) provide. You edited your question, though, to ask for something rather different.

我现在必须猜测您的五次提交中的每一个的实际哈希ID.(每个提交都具有唯一的哈希ID.哈希ID(丑陋的字母和数字字符串)是提交的真实名称".此哈希ID永不改变;使用它总是让您那个 commit,只要该commit存在就行.)但是它们是由字母和数字组成的丑陋字符串,因此,不用猜测,例如 8858448bb49332d353febc078ce4a3abcc962efe ,我将其称为"commit 1" D ,您的"commit 2" E ,依此类推.

I now have to guess at the actual hash IDs for each of your five commits. (Every commit has a unique hash ID. The hash ID—the big ugly string of letters and numbers—is the "true name" of the commit. This hash ID never changes; using it always gets you that commit, as long as that commit exists.) But they're big ugly strings of letters and numbers, so instead of guessing, say, 8858448bb49332d353febc078ce4a3abcc962efe, I'll just call your "commit 1" D, your "commit 2" E, and so on.

由于大多数提交都具有单亲,这使得Git可以从较新的提交向后跳转到较旧的提交,因此我们将它们排列成与这些向后的箭头对齐:

Since most commits have a single parent, which lets Git hop backwards from the newer commits to the older ones, let's arrange them in a line with these backwards arrows:

... <-D <-E <-F <-G <-H   <--master

master 这样的分支名称实际上只是保存该分支上 latest 提交的哈希ID.我们说名称​​指向提交,因为它具有让Git检索提交的哈希ID.因此, master 指向 H .但是 H 的哈希ID为 G ,因此 H 指向其父级 G G 的哈希ID为 F ;等等.这就是Git设法向您显示提交 H 的方式:您问Git 哪个提交是 master ?,它说 H.您要求Git向您显示 H ,然后Git提取 G H 并进行比较,然后告诉您更改为 H .

A branch name like master really just holds the hash ID of the latest commit on that branch. We say that the name points to the commit, because it has the hash ID that lets Git retrieve the commit. So master points to H. But H has the hash ID of G, so H points to its parent G; G has the hash ID of F; and so on. That's how Git manages to show you commit H in the first place: you ask Git Which commit is master? and it says H. You ask Git to show you H and Git extracts both G and H and compares them, and tells you what changed in H.

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

I want to revert the changes happened to File b of Commit 3 [F]. But I want the changes [that] happened to this file in commit 4 and 5 [G and H].

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

Note that this version of the file probably does not appear in any commit. If we take the file as it appears in commit E (your Commit 2), we get one without the changes from F, but it does not have the changes from G and H added to it. If we go ahead and do add the changes from G to it, that's probably not the same as what's in G; and if we add the changes from H to it after that, that's probably not the same as what's in H.

那么,显然,这将变得有些困难.

Obviously, then, this is going to be a little harder.

git revert 命令旨在执行此类操作,但它是在 commit范围的基础上进行的.实际上, git revert 的作用是弄清楚某些提交中的更改,然后(尝试) undo 那些更改.

The git revert command is designed to do this sort of thing, but it does it on a commit-wide basis. What git revert does, in effect, is to figure out what changed in some commit, and then (try to) undo just those changes.

这是思考 git revert 的一种相当不错的方法:通过比较快照,它将提交(快照)变成一组更改,就像查看该提交的所有其他Git命令一样.提交给其父代.因此,对于提交 F ,它将与提交 E 进行比较,找到对文件 a b 的更改,和 c .然后,这是第一个棘手的问题,它将这些更改反向应用到您的当前提交中.也就是说,由于您实际上是在提交 H ,所以 git revert 可以获取所有三个文件中的任何内容- a b c -并(尝试)撤消准确地完成了 在提交 E .

Here is a fairly good way to think of git revert: It turns the commit—the snapshot—into a set of changes, just like every other Git command that views a commit, by comparing the commit to its parent. So for commit F, it would compare it to commit E, finding the changes to files a, b, and c. Then—here's the first tricky bit—it reverse-applies those changes to your current commit. That is, since you're actually on commit H, git revert can take whatever is in all three files—a, b, and c—and (try to) undo exactly what got done to them in commit E.

(实际上稍微复杂一点,因为这种撤消更改"的想法还考虑了使用Git的三向方式提交 F 后所做的 other 更改.合并机器.)

(It's actually a bit more complicated, because this "undo the changes" idea also takes into account the other changes made since commit F, using Git's three-way merge machinery.)

已经反向应用了某些特定提交中的所有更改,现在 git revert 进行了 new 提交.因此,如果您执行了 git恢复< F的哈希值,则会得到 new 提交,我们可以将其称为 I :

Having reverse-applied all the changes from some particular commit, git revert now makes a new commit. So if you did a git revert <hash of F> you'd get a new commit, which we can call I:

...--D--E--F--G--H--I   <-- master

,其中撤消了对所有三个文件的 F 更改,从而生成了三个版本,这些版本可能不在早期提交的 any 中.但这太多了:您只想撤消 F b 的更改.

in which F's changes to all three files are backed out, producing three versions that probably aren't in any of the earlier commits. But that's too much: you only wanted F's changes to b backed-out.

我们已经将 git revert 操作描述为:找到更改,然后反向应用相同的更改.我们可以使用一些Git命令手动完成此操作.让我们从 git diff 或简写版本 git show 开始:这两个都将快照变为更改.

We already described the git revert action as: find the changes, then reverse-apply the same changes. We can do this manually, on our own, using a few Git commands. Let's start with git diff or the short-hand version, git show: both of these turn snapshots into changes.

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

  • With git diff, we point Git to the parent E and the child F and ask Git: What's the difference between these two? Git extracts the files, compares them, and shows us what changed.

使用 git show ,我们指向Git提交 F ;Git自行找到父 E ,然后提取文件并进行比较,并向我们显示更改的内容(以日志消息为前缀).也就是说, git show commit 等于 git log (仅用于一次提交),其后是 git diff (从该提交的父对象到该提交).

With git show, we point Git to commit F; Git finds the parent E on its own, and extracts the files and compares them and shows us what changed (prefixed with the log message). That is, git show commit amounts to git log (for just that one commit) followed by git diff (from that commit's parent, to that commit).

Git将显示的更改实质上是指令::它们告诉我们,如果我们从 E 中的文件开始,请删除一些行,然后插入其他几行,我们将获得 F 中的文件.因此,我们只需要对差异进行 reverse 即可,这很容易.实际上,我们有两种方法可以执行此操作:我们可以将哈希ID与 git diff 交换,或者可以使用 -R 标志对 git diff进行更改. git show .然后,我们将获得从本质上讲的指令:如果您从 F 中的文件开始,并应用这些指令,那么您将从 E .

The changes that Git will show are, in essence, instructions: they tell us that if we start with the files that are in E, remove some lines, and insert some other lines, we'll get the files that are in F. So we just need to reverse the diff, which is easy enough. In fact, we have two ways to do this: we can swap the hash IDs with git diff, or we can use the -R flag to either git diff or git show. Then we'll get instructions that say, in essence: If you start with the files from F, and apply these instructions, you'll get the files from E.

当然,这些说明将告诉我们对所有三个文件 a b c .但是现在我们可以删除三个文件中两个文件的指令,只留下我们想要的指令.

Of course, these instructions will tell us to make changes to all three files, a, b, and c. But now we can strip away the instructions for two of the three files, leaving only the instructions we want.

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

There are, again, multiple ways to do this. The obvious one is to save all the instructions in a file, and then edit the file:

git show -R F哈希>/tmp/指令

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

(然后编辑/tmp/instructions ).不过,还有一种更简单的方法,那就是告诉Git:只为显示特定文件的说明而烦恼.我们关心的文件是 b ,所以:

(and then edit /tmp/instructions). There's an even easier way, though, which is to tell Git: only bother showing instructions for particular files. The file we care about is b, so:

git show -R F哈希-b>/tmp/instructions

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

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

If you check the instructions file, it should now describe how to take what's in F and un-change b to make it look like what's in E instead.

现在,我们只需要让Git应用这些指令,除了我们将使用 current commit F 中的文件> H ,它已经位于我们的工作树中,可以进行修补了.应用补丁的Git命令(关于如何更改某些文件集的一组指令)是 git apply ,因此:

Now we just need to have Git apply these instructions, except that instead of the file from commit F, we'll use the file from the current commit H, which is already sitting in our work-tree ready to be patched. The Git command that applies a patch—a set of instructions on how to change some set of files—is git apply, so:

git apply < /tmp/instructions

应该可以解决问题.(不过请注意,如果指令说要更改 b 中的行,而随后又由提交 G H .这是 git revert 更聪明的地方,因为它可以完成整个合并"的事情.)

should do the trick. (Note, though, that this will fail if the instructions say to change lines in b that were subsequently changed by commits G or H. This is where git revert is smarter, because it can do that whole "merge" thing.)

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

Once the instructions are successfully applied, we can look over the file, make sure it looks right, and use git add and git commit as usual.

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

(Side note: you can do this all in one pass using:

git show -R 哈希-b |git apply

git show -R hash -- b | git apply

而且, git apply 也有其自己的 -R -reverse 标志,因此您可以这样拼写:

And, git apply has its own -R or --reverse flag as well, so you can spell this:

git show 哈希-b |git apply -R

git show hash -- b | git apply -R

执行相同的操作.还有其他的 git apply 标志,包括 -3 /-3way 标记,可以使其执行更奇特的操作,就像 git revert.)

which does the same thing. There are additional git apply flags, including -3 / --3way that will let it do fancier things, much like git revert does.)

处理此问题的另一种相对简单的方法是让 git revert 完成所有工作.当然,这将撤消您不想撤消的对 other 文件的更改.但是我们在顶部显示了从 any 提交中获取 any 文件多么容易.那么,假设我们让Git撤消 all 中所有 F 中的更改:

The other relatively easy way to deal with this is to let git revert do all of its work. This will, of course, back out the changes to the other files, that you didn't want backed-out. But we showed at the top how ridiculously easy it is to get any file from any commit. Suppose, then, that we let Git undo all the changes in F:

git恢复 F哈希

这将使新提交 I 撤消 F 中的一切:

which makes new commit I that backs out everything in F:

...--D--E--F--G--H--I   <-- master

git checkout 现在两个文件 a c from commit H :

It's now trivial to git checkout the two files a and c from commit H:

git checkout Hash-of-H -c

git checkout hash-of-H -- a c

,然后重新提交 J :

...--D--E--F--G--H--I--J   <-- master

我们想要的是 I J 中的文件 b ,而文件 a J 中的 c 是我们想要的方式-它们与中的 a c 文件匹配H -这样我们就完成了,除了烦人的额外提交 I .

The file b in both I and J is the way we want it, and the files a and c in J are the way we want them—they match the files a and c in H—so we're now pretty much done, except for the annoying extra commit I.

我们可以通过几种方式摆脱 I :

We can get rid of I in several ways:

  • 在制作 J 时使用 git commit --amend :通过提交> J 使用commit H 作为 J 的父级.提交 I 仍然存在,只是被放弃了.最终(大约一个月后)它消失了,并且真的消失了.

  • Use git commit --amend when making J: this pushes I out of the way, by making commit J use commit H as J's parent. Commit I still exists, it's just abandoned. Eventually (after roughly a month) it expires and really goes away.

如果执行提交图,则提交图如下所示:

The commit graph, if we do this, looks like this:

                   I   [abandoned]
                  /
...--D--E--F--G--H--J   <-- master

  • 或者, git revert 具有一个 -n 标志,该标志告诉Git:执行还原,但不提交结果.(这还使您可以使用脏索引和/或工作树进行还原,尽管如果您确保以干净的提交 H 结帐开始,则无需担心这意味着.)在这里,我们从 H 开始,还原 F ,然后告诉Git 获取文件 a c从提交 H 返回:

  • Or, git revert has a -n flag that tells Git: Do the revert, but don't commit the result. (This also enables doing a revert with a dirty index and/or work-tree, though if you make sure you start with a clean checkout of commit H you don't need to worry about what this means.) Here we'll start with H, revert F, then tell Git get files a and c back from commit H:

    git revert -n F散列
    git checkout HEAD-一个c
    git commit

    由于执行此操作时正在提交 H ,因此可以使用名称 HEAD 来引用 a 和提交 H 中的 c .

    Since we're on commit H when we do this, we can use the name HEAD to refer to the copies of a and c that are in commit H.

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

    (Git being Git, there are a half dozen additional ways to do this; I'm just using the ones that we're illustrating in general here.)

    这篇关于仅还原推送提交的单个文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆