从两个提交前Git回滚

时间:2013-10-29 21:24:13

标签: git

我几天前从大师那里分道扬and,并对newbranch进行了两次后续提交。

后来,我注意到我所做的第一次提交中的文件不应该被更改。我还没有向主分支推送任何东西,我的所有更改都在newbranch。如何将newbranch中的这个文件回滚到我最初从master分支时所处的位置?

3 个答案:

答案 0 :(得分:2)

git checkout master -- filepath

答案 1 :(得分:1)

在new_branch上:

git checkout HEAD~1
git checkout HEAD~1 file_not_needed
git commit -a --amend
git cherry-pick new_branch
git branch -f new_branch HEAD

答案 2 :(得分:1)

根据您想要的结果,有几种不同的方法可以做到这一点。

answer from euphoria83执行此操作...

开始时,您有一个类似于此的提交历史记录(每个字母代表一个提交):

A - B - C          <-- master
          \
            D
              \
                E  <-- HEAD=newbranch

提交C是您开始时掌握的内容。您做了一些更改并提交(创建D),然后更多并提交(创建E)。

现在你运行git checkout HEAD~1。这会更新您的工作目录以匹配提交D,并为您提供一个“分离的HEAD”(法国大革命的阴影!),它直接指向提交D,而不是包含分支名称:

A - B - C          <-- master
          \
            D      <-- HEAD
              \
                E  <-- newbranch

(这种结帐形式git checkout <branch-name-or-rev>告诉git更改HEAD个名称。<rev>在这种情况下,HEAD~1,意思是“查找提交的内容名称并向后移动一个“,所以这表示从提交E返回一个。由于这是一个特定的<rev>而不是分支名称,它也具有”分离HEAD“效果。)< / p>

现在你运行git checkout HEAD~1 filename。为了更明确地对git你可以说git checkout HEAD~1 -- filename--表示“其余参数是文件名,而不是分支或修订名称” - 如果省略--,git会尝试猜测它是分支名称还是文件名。

这种checkout形式带有-- filename参数,它做了一些不同的事情:它表示像往常一样查找修订版HEAD~1,但这一次,不要更改 HEAD,只提取给定文件。由于HEAD现在命名为修订版D,因此git会再备份一个修订版C,并从该修订版中提取文件filename的版本并将其放入工作目录中

(请注意,分支名称master 指定修订版C,因此您此时可以编写git checkout master -- filename。)

接下来,git commit -a --amend告诉git添加任何已更改的文件 - 在这种情况下,您实际上并不需要它,但通常这会添加您已修复的其他文件 - 然后执行“修改提交“。 “修改提交”意味着“创建一个新提交,但使其父提交与我们的父提交相同”。这会创建一个新的commit-let,称之为D',其父级为C。与往常一样,新提交成为HEAD。由于HEAD已分离,因此尚未移动分支名称。生成的提交树如下所示:

A - B - C               <-- master
        | \
        \   D
          \   \
            \   E       <-- newbranch
              \
                D'      <-- HEAD

D'中的文件与D中的文件相同,但文件filename除外,现在与修订版C中的文件相同。

接下来,git cherry-pick newbranch说要获取newbranch命名的修订版 - 那是修订版E - 并对HEAD修订版进行相同的更改,作为新提交(让我们称之为{ {1}}):

E'

现在您只需要为A - B - C <-- master | \ \ D \ \ \ E <-- newbranch \ D' - E' <-- HEAD 提供分支名称。然后可以安全地删除HEAD,或将其重命名,然后将newbranch重命名为newnew

newbranch

(你可以稍微缩短一下,但让我们继续......)


有一种更简单的方法可以做同样的事情。只需运行git checkout -b newnew # now HEAD=newnew which points to E' git branch -m newbranch oldnew # rename out of the way git branch -m newnew newbranch # and rename newnew to newbranch 。这表示使用git rebase -i master作为其“上游”来重新绑定当前分支(newbranch)。也就是说,找到master之后的所有提交(这意味着masterD)并将其重新绑定到E(这是之前的位置)。如果没有master,这将是愚蠢的变种-iD,就像以前那样,这只是一种不做任何事情的昂贵方式 - 但是E git打开了一系列命令的编辑。这些命令将-i提交pickD。将提交E的{​​{1}}更改为pick,然后写出文件。

然后,rebase将为您挑选D并停止并允许您修改提交。在shell中,输入:

edit

和以前一样,并保存修改后的提交,后者变为D。然后运行:

git checkout master -- filename
git commit -a --amend

Git现在将挑选提交D',提交提交git rebase --continue 。它现在全部完成,因此rebase完成,分支E具有您想要的提交。


answer from ДМИТРИЙ МАЛИКОВ做了不同的事情。它包含相同的E'(拼写为newbranch)但没有类似rebase的序列。

所以,和以前一样,你从这开始:

git checkout master -- filename

然后,将版本filepath中的A - B - C <-- master \ D \ E <-- HEAD=newbranch 版本提取到工作目录中。如果你现在filename(或C,在这个特例-git中同样的事情,这是因为上面的git commit首先将提取的rev-C文件写入临时区域,你'将获得一个新的提交git commit -a,将文件checkout更改回F中的方式。

换句话说,提交filenameC仍然会对文件进行更改;新提交D将撤消更改。