Git revert / cherry-pick to merge in merge

时间:2015-10-11 12:45:53

标签: git merge revert cherry-pick

这是我的情况:

我正在尝试在我的最新提交之前放置先前提交的完全副本。但是,

  1. 我已经推送到远程,所以不要只是简单地git reset --hard(或者,如果我能以某种方式在早期的提交之后添加先前的提交,我会很乐意这样做)
  2. 在我最近的提交和所需的提交之间,我创建并合并了一个分支,这似乎给了我问题/冲突(否则会做一个恢复)
  3. 我尝试的任何解决方案似乎都保留了其他分支的其他更改,我不想要(我希望我的副本看起来与早期提交的结帐完全相同)
  4. 我知道这可能是一个简单的问题,但经过多次搜索/试错后,我仍然无法找到解决方案。任何帮助将不胜感激。

    如果您想知道我为什么要这样做,最初的动机是为了轻松使用git-latexdiff来比较由合并分隔的两个版本。

    要重现此场景/请参阅示例:

    为了使这个场景具有可重现性,下面的命令将创建一个名为tryrevert的文件夹,其中包含必要的结构:

    mkdir tryrevert; cd tryrevert; git init; echo 'line 1 commit 1' > file1.txt; git add .; git commit -m 'commit 1'; echo 'line 2 commit 2 only want these two lines' >> file1.txt; git add .; git commit -m 'commit 2 - WANT THIS ONE'; git branch sidebranch; git checkout sidebranch; echo 'another file - only in sidebranch' > sidefile.txt; git add .; git commit -m 'commit 3 - sidebranch work'; git checkout master; echo 'extra work' >> file1.txt; git add .; git commit -m 'commit 4 - some more work on master'; git merge sidebranch -m 'commit 5 - the merge'; echo 'final work after the merge' >> file1.txt; git add .; git commit -m 'commit 6 - latest commit '
    git log --graph --all --decorate --oneline
    

    并输出以下提交树(origin/master除外):

    * 7c4c7a1 (HEAD, origin/master, master) commit 6 - latest commit
    *   5b0077c commit 5 - the merge
    |\
    | * 20a1164 (sidebranch) commit 3 - sidebranch work
    * | 3a5a24f commit 4 - some more work on master
    |/
    * 925228b commit 2 - WANT THIS ONE
    * 52af39e commit 1
    

    (为了清楚起见,我将sidebranch留在了master而不是将其移至* abcdefg (HEAD, master) COPY OF commit 6 * hijklmn COPY OF commit 2 * 7c4c7a1 (origin/master) commit 6 - latest commit * 5b0077c commit 5 - the merge |\ | * 20a1164 (sidebranch) commit 3 - sidebranch work * | 3a5a24f commit 4 - some more work on master |/ * 925228b commit 2 - WANT THIS ONE * 52af39e commit 1 。)

    所以我想要的是

    sidebranch

    注意:

    i)sidefile.txt创建sidefile.txt,我恢复提交2 的所有努力最终都包含sidefile.txt(我不想提交2的COPY 以包含file1.txt)。我还想避免在rebase -i中处理合并冲突。

    ii)我尝试了cherry-pickgit revert --no-commit master...HEAD~3; git add .; git commit -m 'reverting to commit 2' ,但没有成功(也许我做错了) - 我一直遇到冲突。

    iii)如果我没有合并,那就像:

    file1.txt

    对我有用,但合并似乎会停止这条确切的线路。

    iv)我花了很多时间在stackoverflow上查看很多帖子,但找不到处理这个特定场景的帖子......

    编辑:

    (提前为长编辑道歉,但想澄清两次提交的状态)

    在回答@Juan的问题时,为了说清楚,提交6包含

    sidefile.txt

      

    第1行提交1
      第2行提交2只想要这两行   额外的工作
      合并后的最后工作

    file1.txt

      

    另一个文件 - 仅限于sidebranch

    另一方面,在提交2中sidefile.txt仅包含前两行:

      

    第1行提交1
      第2行提交2只想要这两行

    并且在提交2中没有 file1.txt

    @ Juan的答案适用于还原/挑选sidefile.txt,但创建的提交仍然包含git checkout master~1^1~1 git checkout -b commit2branch git merge -s ours master -m 'revert to commit2' git checkout master git merge commit2branch git branch -D commit2branch (我不想要)。

    在玩了“他们的”,“我们的”和其他旗帜后,the following post提供了一个解决方案:

    file1.txt

    在使文件处于正确状态(正确sidefile.txt,无* 4236c61 (HEAD, master) revert to commit2 |\ | * 7c4c7a1 (origin/master) commit 6 - latest commit | * 5b0077c commit 5 - the merge | |\ | | * 20a1164 commit 3 - sidebranch work | |/ |/| | * 3a5a24f commit 4 - some more work on master |/ * 925228b commit 2 - WANT THIS ONE * 52af39e commit 1 方面有效,但提交历史记录包含额外的分支:

    $dql   = "SELECT p FROM MyBundle:Post p
                       WHERE p.from in 
                        (SELECT u FROM MyBundle:User u JOIN u.friendsWithMe f where f = :user )";
    
    $query = $em->createQuery($dql);
    $query->setParameter(':user', $this->getUser());
    

    虽然我不想要这个额外的分支...

    最终编辑

    我有一个答案,我在下面发布,并基于@ Juan的第二个解决方案。但是,该解决方案使用了几个恢复,而我真的很喜欢单行解决方案

2 个答案:

答案 0 :(得分:2)

首先,我运行你的脚本来生成repo,我会在这里发布并在答案中使用我的提交来避免复制/粘贴/更改错误:

* f2cb927 (HEAD, master) commit 6 - latest commit
*   93e6ede commit 5 - the merge
|\  
| * 61f0cfe (sidebranch) commit 3 - sidebranch work
* | c1d2916 commit 4 - some more work on master
|/  
* 79570e8 commit 2 - WANT THIS ONE
* 5e7f2fb commit 1

然后有不同的可能性:
第一个:以你的第二个图表结束,你可以用两个樱桃选择,第一个提交2,然后提交6.这将产生冲突,因为你正在进行提交如果发生冲突,您可能希望使用相同的分支历史,因此假设您已在master中签出,那么您可能希望这样做:

$ git cherry-pick --strategy=recursive -X theirs 79570e8  

然后对于提交6:

$ git cherry-pick --strategy=recursive -X theirs f2cb927

有了这个,您最终会得到一个图表:

* f6e3ff7 (HEAD, master) commit 6 - latest commit
* 7563975 commit 2 - WANT THIS ONE
* f2cb927 commit 6 - latest commit
*   93e6ede commit 5 - the merge
|\  
| * 61f0cfe (sidebranch) commit 3 - sidebranch work
* | c1d2916 commit 4 - some more work on master
|/  
* 79570e8 commit 2 - WANT THIS ONE
* 5e7f2fb commit 1

但我理解正确这不是你想要的,因为repo将包含File1:

  

第1行提交1
  第2行提交2只想要这两行   额外的工作
  合并后的最后工作

和File2:

  

另一个文件 - 仅限于sidebranch

Git会考虑分支中包含的每一个提交,优先考虑更新的提交,但是一个樱桃选择不会“撤消”来自分支的提交,为此你需要使用revert。
第二种可能性:这很复杂,因为需要以某种方式处理冲突,这在回复事情时可能会很混乱。好吧,我在提交5中创建了一个名为“new”的新分支:

$ git checkout 93e6ede
$ git branch new
$ git checkout new

从那里我跑了:

$ git revert c1d2916
$ git revert 61f0cfe
$ git revert -m 1 93e6ede

我最终得到了以下树(注意分支):

* 9eeac5c (HEAD, new) Revert "commit 3 - sidebranch work"
* e1f3678 Revert "commit 4 - some more work on master"
| * f6e3ff7 (master) commit 6 - latest commit
| * 7563975 commit 2 - WANT THIS ONE
| * f2cb927 commit 6 - latest commit
|/  
*   93e6ede commit 5 - the merge
|\  
| * 61f0cfe (sidebranch) commit 3 - sidebranch work
* | c1d2916 commit 4 - some more work on master
|/  
* 79570e8 commit 2 - WANT THIS ONE
* 5e7f2fb commit 1

仅在'new'中使用File1:

  

第1行提交1
  第2行提交2只想要这两行   然后,如果你想要提交6而不必修改历史记录,你需要在提交6之后添加你的工作,因为你可以正确处理冲突,这是最后一个:

$ git rebase f2cb927

除了处理冲突和手动选择你想要的东西外,没有其他解决方案。

第三种可能性,可能最好看到第二种是多么混乱:使用不同的分支(查看更改),说new2,checkout in commit 2.然后只需执行:

$ git cherry-pick f2cb927

在这里你可能会遇到冲突,但是他们应该非常直接地解决,因为在提交的实际内容中你想要的就是所有。一旦冲突得到解决:

$ git add files
$ git cherry-pick --continue

然后你的最终树有三种可能性:

* 2ded558 (HEAD, new2) commit 6 - latest commit
| * ec8ef14 (new) Revert "commit 3 - sidebranch work"
| * 9f8bb91 Revert "commit 4 - some more work on master"
| | * f6e3ff7 (master) commit 6 - latest commit
| | * 7563975 commit 2 - WANT THIS ONE
| |/  
| * f2cb927 commit 6 - latest commit
| *   93e6ede commit 5 - the merge
| |\  
| | * 61f0cfe (sidebranch) commit 3 - sidebranch work
| |/  
|/|   
| * c1d2916 commit 4 - some more work on master
|/  
* 79570e8 commit 2 - WANT THIS ONE
* 5e7f2fb commit 1

只是选择你喜欢的选项,你显然不需要它们中的三个。假设您需要强制推送git push --force origin branchName。这样做之前在遥控器上做的任何事情都会丢失,并被推送的新数据取代。

答案 1 :(得分:2)

感谢@Juan - 他的第二个解决方案可以改写为:

git checkout master
git revert --no-commit HEAD...HEAD~1
git revert --no-commit -m 1 HEAD~1
git revert --no-commit --strategy=recursive -X theirs HEAD~1^1
git commit -m 'COPY of commit 2 using revert'

和:

git revert --no-commit HEAD
git commit -m 'COPY of commit 6 using revert'

这将生成所需的提交树:

* a30d0b1 (HEAD, master) COPY of commit 6 using revert
* bcbd36f COPY of commit 2 using revert
* 7c4c7a1 (origin/master) commit 6 - latest commit
*   5b0077c commit 5 - the merge
|\
| * 20a1164 commit 3 - sidebranch work
* | 3a5a24f commit 4 - some more work on master
|/
* 925228b commit 2 - WANT THIS ONE
* 52af39e commit 1

但是,我希望有一种方法可以在一行中做到这一点......