Git revert/cherry-pick 到合并之外的提交
Git revert/cherry-pick to a commit beyond a merge
这是我的情况:
我正在尝试将较早提交的 精确 副本置于我最新提交之上。然而,
- 我已经推送到远程,所以不想简单地
git reset
--hard
(或者,如果我能以某种方式将较早的提交添加回最新的提交之上,我会很乐意这样做)
- 在我最新的提交和所需的提交之间,我创建并合并了一个分支,这似乎给了我 problems/conflicts(否则会恢复)
- 我尝试过的任何解决方案似乎都保留了来自其他分支的额外更改,这是我不想要的(我希望我的副本看起来完全像早期提交的签出)
我知道这可能是一个简单的问题,但经过多次 searching/trial 和错误后我仍然找不到解决方案。任何帮助将不胜感激。
如果你想知道我为什么要做这样的事情,最初的动机是为了方便地使用 git-latexdiff
来比较被合并分开的两个版本。
要重现此 scenario/see 示例:
为了使这种情况可重现,下面的命令将创建一个名为 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
(而且我不希望 提交 2 的副本包含 sidefile.txt
)。我还想避免在 file1.txt
.
中处理合并冲突
ii) 我试过 rebase -i
和 cherry-pick
,但没有成功(也许我做的不对)- 我让 运行 陷入冲突。
iii) 如果我没有合并,类似于:
git revert --no-commit master...HEAD~3; git add .; git commit -m 'reverting to commit 2'
对我有用,但合并似乎阻止了这条确切的行。
iv) 我花了很多时间查看 Whosebug 上的许多帖子,但找不到处理这种特定情况的帖子...
编辑:
(提前为冗长的编辑道歉,但想澄清两次提交的状态)
针对@Juan 的问题,为了说明清楚,commit 6 contains
file1.txt
:
line 1 commit 1
line 2 commit 2 only want these two lines
extra work
final work after the merge
sidefile.txt
another file - only in sidebranch
另一方面,在提交 2 file1.txt
中仅包含前两行:
line 1 commit 1
line 2 commit 2 only want these two lines
并且提交 2 中没有 sidefile.txt
。
@Juan 下面的回答适用于 reverting/cherry-picking file1.txt
,但创建的提交仍然包含 sidefile.txt
(我不想要)。
在玩弄了"theirs"、"ours"等flags后,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 的第二个解决方案。但是,该解决方案使用了多个还原,而 我真的想要一个单行解决方案。
首先,我 运行 你的脚本来生成 repo,我会 post 在这里并在答案中使用我的提交来避免 copy/paste/change 错误:
* 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。这将有冲突,因为你正在采取commit 包含在相同的 b运行ch 历史中,您可能希望在发生冲突时使用它们的更改,因此假设您在 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:
line 1 commit 1
line 2 commit 2 only want these two lines
extra work
final work after the merge
和文件 2:
another file - only in sidebranch
Git 将考虑 b运行ch 中包含的每个提交,优先考虑较新的提交,但 cherry-pick 不会 'undo' 来自 b 的提交运行ch,为此您需要使用还原。
第二种可能性:这很复杂,因为需要以某种方式处理冲突,并且在恢复时这可能会很混乱。好吧,我在提交 5 中创建了一个名为 'new' 的新 b运行ch:
$ git checkout 93e6ede
$ git branch new
$ git checkout new
然后我 运行:
$ git revert c1d2916
$ git revert 61f0cfe
$ git revert -m 1 93e6ede
我最终得到了以下树(注意 new b运行ch):
* 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' 文件 1:
line 1 commit 1
line 2 commit 2 only want these two lines
Then, if you want commit 6 as well without having to modify the history, you would need to add your work after commit 6 now that you can deal with conflicts properly, which is the last one:
$ git rebase f2cb927
这里除了处理冲突和手动选择你想要的没有其他解决方案。
第三种可能性,可能是最好的看到第二种是多么混乱:使用不同的 b运行ch(以查看更改),比如 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 b运行chName。执行此操作之前远程中的任何其他内容都会丢失并被推送中的新数据替换。
感谢@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
但是,我希望有一种方法可以在一行中做到这一点...
这是我的情况:
我正在尝试将较早提交的 精确 副本置于我最新提交之上。然而,
- 我已经推送到远程,所以不想简单地
git reset --hard
(或者,如果我能以某种方式将较早的提交添加回最新的提交之上,我会很乐意这样做) - 在我最新的提交和所需的提交之间,我创建并合并了一个分支,这似乎给了我 problems/conflicts(否则会恢复)
- 我尝试过的任何解决方案似乎都保留了来自其他分支的额外更改,这是我不想要的(我希望我的副本看起来完全像早期提交的签出)
我知道这可能是一个简单的问题,但经过多次 searching/trial 和错误后我仍然找不到解决方案。任何帮助将不胜感激。
如果你想知道我为什么要做这样的事情,最初的动机是为了方便地使用 git-latexdiff
来比较被合并分开的两个版本。
要重现此 scenario/see 示例:
为了使这种情况可重现,下面的命令将创建一个名为 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
(而且我不希望 提交 2 的副本包含 sidefile.txt
)。我还想避免在 file1.txt
.
ii) 我试过 rebase -i
和 cherry-pick
,但没有成功(也许我做的不对)- 我让 运行 陷入冲突。
iii) 如果我没有合并,类似于:
git revert --no-commit master...HEAD~3; git add .; git commit -m 'reverting to commit 2'
对我有用,但合并似乎阻止了这条确切的行。
iv) 我花了很多时间查看 Whosebug 上的许多帖子,但找不到处理这种特定情况的帖子...
编辑:
(提前为冗长的编辑道歉,但想澄清两次提交的状态)
针对@Juan 的问题,为了说明清楚,commit 6 contains
file1.txt
:
line 1 commit 1
line 2 commit 2 only want these two lines
extra work
final work after the merge
sidefile.txt
another file - only in sidebranch
另一方面,在提交 2 file1.txt
中仅包含前两行:
line 1 commit 1
line 2 commit 2 only want these two lines
并且提交 2 中没有 sidefile.txt
。
@Juan 下面的回答适用于 reverting/cherry-picking file1.txt
,但创建的提交仍然包含 sidefile.txt
(我不想要)。
在玩弄了"theirs"、"ours"等flags后,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 的第二个解决方案。但是,该解决方案使用了多个还原,而 我真的想要一个单行解决方案。
首先,我 运行 你的脚本来生成 repo,我会 post 在这里并在答案中使用我的提交来避免 copy/paste/change 错误:
* 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。这将有冲突,因为你正在采取commit 包含在相同的 b运行ch 历史中,您可能希望在发生冲突时使用它们的更改,因此假设您在 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:
line 1 commit 1
line 2 commit 2 only want these two lines
extra work
final work after the merge
和文件 2:
another file - only in sidebranch
Git 将考虑 b运行ch 中包含的每个提交,优先考虑较新的提交,但 cherry-pick 不会 'undo' 来自 b 的提交运行ch,为此您需要使用还原。
第二种可能性:这很复杂,因为需要以某种方式处理冲突,并且在恢复时这可能会很混乱。好吧,我在提交 5 中创建了一个名为 'new' 的新 b运行ch:
$ git checkout 93e6ede
$ git branch new
$ git checkout new
然后我 运行:
$ git revert c1d2916
$ git revert 61f0cfe
$ git revert -m 1 93e6ede
我最终得到了以下树(注意 new b运行ch):
* 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' 文件 1:
line 1 commit 1
line 2 commit 2 only want these two lines
Then, if you want commit 6 as well without having to modify the history, you would need to add your work after commit 6 now that you can deal with conflicts properly, which is the last one:
$ git rebase f2cb927
这里除了处理冲突和手动选择你想要的没有其他解决方案。
第三种可能性,可能是最好的看到第二种是多么混乱:使用不同的 b运行ch(以查看更改),比如 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 b运行chName。执行此操作之前远程中的任何其他内容都会丢失并被推送中的新数据替换。
感谢@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
但是,我希望有一种方法可以在一行中做到这一点...