【问题标题】:Git revert/cherry-pick to a commit beyond a mergeGit revert/cherry-pick 到合并之外的提交
【发布时间】:2016-01-08 23:41:09
【问题描述】:

这是我的情况:

我正在尝试在我最近的提交之上放置一个较早提交的精确副本。不过,

  1. 我已经推送到远程,所以不想简单地git reset --hard(或者,如果我能以某种方式将较早的提交添加回最新提交的顶部,我很乐意这样做)
  2. 在我的最新提交和所需提交之间,我创建并合并了一个分支,这似乎给我带来了问题/冲突(否则会进行还原)
  3. 我尝试过的任何解决方案似乎都保留了来自其他分支的额外更改,这是我不想要的(我希望我的副本看起来与之前提交的签出完全一样)

我知道这可能是一个简单的问题,但经过多次搜索/反复试验,我仍然找不到解决方案。任何帮助将不胜感激。

如果您想知道我为什么要做这样的事情,最初的动机是为了方便地使用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

注意事项:

i) sidebranch 创建 sidefile.txt,我为恢复到 commit 2 所做的所有努力最终都包含 sidefile.txt(我不希望 COPY of commit 2 包含sidefile.txt)。我还想避免在file1.txt 中处理合并冲突。

ii) 我尝试了rebase -icherry-pick,但没有成功(也许我做错了) - 我一直遇到冲突。

iii) 如果我没有合并,类似于:

git revert --no-commit master...HEAD~3; git add .; git commit -m 'reverting to commit 2'

对我有用,但合并似乎阻止了这条确切的行。

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

编辑:

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

为了回答@Juan 的问题,为了清楚起见,提交 6 包含

file1.txt:

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

sidefile.txt

另一个文件 - 仅在侧分支中

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

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

在提交 2 中没有sidefile.txt

@Juan 下面的回答非常适合恢复/采摘file1.txt,但创建的提交仍然包含sidefile.txt(我不想要)。

在玩弄了“他们的”、“我们的”和其他标志之后,the following post 提供了一个解决方案:

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

在使文件处于正确状态方面有效(正确的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

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

最终编辑

我有一个答案,我已经在下面发布了,它基于@Juan 的第二个解决方案。但是,该解决方案使用了多个还原,而 我真的想要一个单行解决方案

【问题讨论】:

    标签: git merge revert cherry-pick


    【解决方案1】:

    感谢@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
    

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

    【讨论】:

      【解决方案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 只想要这两行
      额外的工作
      合并后的最后工作

      和文件2:

      另一个文件 - 仅在侧分支中

      Git 会考虑分支中包含的每一个提交,优先考虑较新的提交,但cherry-pick 不会“撤消”分支中的提交,为此您需要使用 revert。
      第二种可能性:这很复杂,因为需要以某种方式处理冲突,并且在还原事物时可能会很混乱。好吧,我在提交 5 中创建了一个名为“new”的新分支:

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

      然后我跑了:

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

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

      * 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
      

      只有“新”文件1:

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

      $ git rebase f2cb927
      

      除了处理冲突并手动选择所需的内容之外,没有其他解决方案。

      第三种可能性,可能是最好的,看看第二种是多么混乱:使用不同的分支(查看更改),比如 new2,在提交 2 中签出。然后只需执行以下操作:

      $ git cherry-pick f2cb927
      

      在这里你可能会遇到冲突,但它们应该很容易解决,正如你在提交 6 的实际内容中想要的那样。一旦冲突解决:

      $ 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。执行此操作之前远程中的任何其他内容都会丢失并被推送时的新数据取代。

      【讨论】:

      • 亲爱的@Juan - 感谢您的详细回复。你的答案接近我想要的。我想要的是(恢复的)回购“提交 2”到 onlyfile1.txt 只包含两行 line 1 commit 1 line 2 \n commit 2 only want these two lines (和 no sidefile.txt)。换句话说,与您使用git checkout 79570e8 完全相同的“快照”(使用commit 2 的提交ID)。您的cherry-pick --strategy=recursive 具有正确的file1.txt,但仍包含sidefile.txt(我不想要)。
      • 您好,院长,请检查帖子上的修改,如果您可以强制推送,我想最简单的方法是第三个选项。
      • 非常感谢您非常的回复。如果我准备好处理冲突,您的第三个解决方案很好,但我正试图找到一种方法来避免必须手动处理任何冲突。但是,您的 第二种解决方案 最接近我想要的。我在下面的帖子中重写了它。我只是希望有一种更简单的方法来做到这一点/一种在一行中做到这一点的方法。我已对您的帖子投了赞成票,但没有必要的声望点让它出现...
      猜你喜欢
      • 2012-05-21
      • 2012-03-11
      • 2016-10-24
      • 1970-01-01
      • 2018-12-08
      • 2018-04-16
      • 2012-11-05
      • 2021-01-18
      • 1970-01-01
      相关资源
      最近更新 更多