将一个提交从一个分支移动到另一个分支的步骤,作为更改。

12

通常情况下,如果我在一个分支上进行更改,然后意识到我应该在另一个分支上进行更改,那么我可以简单地存储更改,然后检出我想要的分支。

如果我已经在本地提交了更改(但尚未推送到远程),如何有效地完成相同的操作?

我希望我的本地分支就像我从未进行过任何更改一样……特别是我不想推送两个提交(一个包含更改,另一个撤消更改)。我只想让提交消失,就好像它从未发生过一样。当我检出我想要的分支时,我希望我的更改在那里……或者我希望能够恢复它们(作为更改,但显然不是提交)。

我已经搜索了一些答案,但找到的答案主要集中在我提到的第一种情况,即未提交更改。很可能我已经找到了我需要的确切答案,但没有意识到,因为我不能百分之百确定场景是否与我的相同。所以显然我对git还有点陌生,我相信这肯定是一个重复的问题,但也许我只是难以确定哪些现有问题是重复的。

我同时标记了VS 2017和2019,因为我两个都使用,并且欢迎任何解释如何通过IDE在这两个版本中完成以及如何在shell中实现此操作的答案。

这个特定的提交是唯一的。虽然我有兴趣听取两种情况的解决方案(这一个和其他有效提交的情况)。

4个回答

23

需要记住的事情:

  • Git 处理的是提交,而不是更改或文件。

  • 分支只是一个提交的标签。

  • 你可以使用 git reset 移动一个分支,将其附加到任何你喜欢的提交上。

让我们从一个双分支的情况开始:

% git log --oneline --all --graph
* 102fa13 (br) z
| * 7e0ddf5 (HEAD -> master) d
| * 3a460a5 c
|/  
* e7547cb b
* 0bcb421 a

因为我后悔在master上做了cd,我希望它们本应该在其他分支上。所以我想到了两种主要情况。那个分支已经存在还是不存在呢?

如果不存在,那就在这里创建一个新的分支(就在现在master的位置),把master退回到c之前:

% git branch newbranch
% git reset --hard e7547cb

结果:

% git log --oneline --all --graph              
* 102fa13 (br) z
| * 7e0ddf5 (newbranch) d
| * 3a460a5 c
|/  
* e7547cb (HEAD -> master) b
* 0bcb421 a

这是简单的情况。更复杂的情况是第二个分支已经存在:假设在上面的第一个图中,它是br。然后将cd提交变基br。之后,将br重置到master现在所在的位置,然后将master滑动回到c之前的位置:

% git rebase --onto br e7547cb master
% git switch br
% git reset --hard master
% git switch master
% git reset --hard e7547cb

结果:

% git log --oneline --all --graph
* f9ff4a0 (br) d
* 5c43690 c
* 102fa13 z
* e7547cb (HEAD -> master) b
* 0bcb421 a

请注意,这里的 dc 提交记录是副本(它们具有新的唯一标识符);当您移动提交记录时,这是不可避免的,因为提交记录本身是不可变的。


3
许多人在不了解基础知识的情况下使用Git。一旦您掌握了一些基础知识,Git实际上是非常透明的。它有许多复杂和令人惊讶的角落,但是一旦您对它的心理形象准确无误,基本用法就很简单了。我相信我的回答方法过于冗长,但希望您清楚地知道我在做什么以及它的作用和原因。 - matt
许多人被迫在没有正式培训的情况下使用GiT。这不可避免地导致了一种我称之为“不情愿的巫师”的心理模型。因此,在SO等平台上会有很多关于咒语的请求等等。 - davidjmcclelland
@davidjmcclelland 我不知道正式的培训是否重要。我只是说,了解Git的基本概念是一个好主意。当你了解了这些,很多问题的答案就变得显而易见了。 - matt
没有起作用,开心地报告OK但是丢失了所有的提交。 - teknopaul
@teknopaul,我不知道你的情况是什么;这个答案是针对出现在本页面顶部的问题而提出的。如果你有一个新问题(包括如何取回你的提交,这很容易),请点击右上角的“提问”按钮并提出它。 - matt

14
您可以将当前分支上的提交挑选出来并移到另一个分支,然后将当前分支上的提交清除。
# from current branch, get SHA-1 of latest commit (e.g. ABCD1234)
git log
git checkout other_branch
git cherry-pick ABCD1234

# now return to original branch and nuke commit
git checkout current_branch
git reset --hard HEAD~1

请注意,本答案严重依赖于您尚未将当前分支推送带有错误提交的情况。如果您已经这样做了,那么答案需要更改。


2
您可以将已经提交的情况减少到尚未提交的情况:
撤销提交,但保留更改:
git reset HEAD^

此时,您已经删除了不需要的提交并将当前分支还原到没有该提交的状态。

同时,您现在有未提交的更改,并且在“错误”的分支上,即现在出现了“第一种情况”。

然后将更改转移到另一个分支:

git checkout -m other-branch

选项-m很神奇。此时,您有未提交的更改可以提交。

尝试这个是因为它似乎最简单。但结果并不如预期,但这可能是我的错(不确定?)。我没有澄清的一件事是,我已经采取了一个行动,创建了一个新的提交来撤消之前的提交。我尝试了“git reset HEAD ^”,但我在Windows cmd提示符下,^具有特殊含义,会使其出现问题。我在其他地方读到,“HEAD〜1”会产生相同的效果,所以我尝试了一下,但它只删除了“还原”提交。所以我再次执行了同样的命令,它们都消失了。太好了...除了提交没有转换回更改。它们就像我从未更改过任何内容一样消失了。 :( - BVernon
谢天谢地,我的更改并不多,而且我把它作为一个学习的经验,知道我可能会搞砸事情并不得不重新做出改变...所以没有什么大不了的。但是我的尝试绝对是一场灾难,哈哈。哎,算了。 - BVernon

0

我曾经遇到过类似的情况,其中有2个分支在历史的不同时刻从相同的stage分支上分支出来。分支A有一个不在stage中的提交,而分支B有3个不在stage中的提交。我希望将分支B的最后两个提交放到分支A中。

由于stage已经合并了其他分支,因此分支A和B现在都落后于stage。我犯了一个错误,将stage合并到了A和B中,因为我以为它们共享stage的末端会使事情变得更容易。但实际上并非如此。

当我尝试按照@matt's answer中的说明重新定位(rebase)我想要的两个来自分支B的提交时,我得到了这两个提交以及一堆来自将stage合并到B中的新提交,这在重新定位过程中导致了不必要的合并冲突。为了解决这个问题,我添加了-i选项以便能够编辑待办事项列表,并且只是drop掉了我不想要的提交。重新定位顺利完成,没有出现任何问题和合并冲突。

我将这个答案保留在这里,以防有人遇到类似的情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接