将最新的提交转移到另一个分支

8

我为了测试目的创建了一个额外的分支。

在开始工作之前,我切换回主分支,喝完茶后开始在主分支中添加文件和修改其他一些文件。

只有在提交后,我才想起自己应该在开始更改之前切换到我的第二个分支。

请问能否将此提交发送到第二分支并从主分支中删除它?

谢谢。


1
请在尝试以下操作之前备份(不保证100%成功):git reset --soft HEAD^git checkout second-branchgit commit - Dogbert
2个回答

11

如果我理解正确,您可以做这样的事情:

git checkout master
git log
now check hash commit you want to move - for example: 0123456
git checkout branch
git cherry-pick 0123456
git checkout master
git revert 0123456

就这些。


1
请注意,这将允许您选择一个不在“分支顶端”的提交,然后撤消它(因此它比我的回答更通用)。我的回答,使用回滚的git reset步骤,是专门针对“您在错误分支的顶端进行了提交”的情况。 - torek
我有一个需要丢弃的提交。该提交仅在本地机器上可用,而不在远程上。我尝试了一下,现在主分支上有2个提交。然而,我希望主分支没有这些提交。有什么办法可以解决这个问题吗? - Divij Sehgal

8
这个问题的答案是“有点,但你可能不必要这样做”。
要复制提交,请使用`git cherry-pick commit`。这实际上告诉 git:“去查看我要求你挑选的提交。无论它所作出的更改是什么,在当前分支上再次进行相同的更改。然后使用相同的提交消息创建一个新的提交。”
因此,如果您已经在`master`上进行了提交,并且您希望将其提交到`newbranch`而不是`master`上:
$ git checkout newbranch
$ git cherry-pick master

此时,您将在 newbranch 分支上,并且提交记录已被复制。现在,您需要处理 master 分支上的旧提交记录。

如果您还没有在任何地方发布(推送或让其他人拉取)此提交记录,您只需“回滚”即可:

$ git checkout master
$ git reset --hard HEAD^  # be careful here! I always run "git status" first

请注意,git reset --hard会清除任何正在进行的工作。
如果您已经发布了提交,则通常应该撤消它(请参见user2699113的答案)。 "撤消"与"拣选"非常相似,都是复制提交,但它将反转更改:添加的内容现在被删除,删除的内容现在被添加回来。(而且,与cherry-pick一样,如果不告诉它不要这样做,revert将自动提交。)
(请注意,您可以通过SHA-1 ID指定提交,但是如果有名称 - 分支或标记名称或其他引用名称 - 您只需编写名称即可。)

1除非使用-n--no-commit来抑制它,或者git无法进行相同的更改并告诉您存在冲突并让您自己解决。


关于“也许您不必”的部分是什么?

这里是关于git及其提交和分支的事情。与大多数其他版本控制系统不同,git在分支上执行提交时,分支名称实际上不是提交的一部分。2 分配分支名称(或标签,无论您想如何称呼这些),但实际的分支结构 -“结构性分支”-是由每个提交的历史记录形成的。

当进行提交时,该提交记录其父提交。分支名称只是标签,为您(和git)提供起点,然后git遵循提交父ID。这意味着我们可以手动绘制提交图(提交的有向无环图或DAG)。
A <-- B <-- C     <-- master
            ^
             \
              D   <-- branch

这里,提交D是分支branch的最新版本,并将其父提交指向C。提交C将其父提交指向BB将其父提交指向初始提交A(根提交,没有父提交)。
当您处于“主分支”并创建一个新分支时,只需创建一个指向当前位置的新标签。所以假设之前有AC(这次我不会用那么多箭头画出它们,只需假定箭头一般指向左边)。
A - B - C      <-- HEAD=master

这次我添加了HEAD=,以展示git如何跟踪你所在的分支。(在.git目录下的HEAD文件只包含分支的名称:cat .git/HEAD,通常会看到ref:refs/heads/master或其他类似的内容。)如果你现在运行git checkout -b 分支名,你会得到如下结果:

A - B - C      <-- master, HEAD=branch

现在.git/HEAD包含的是分支而不是主分支master

当你创建一个新的提交(包括使用cherry-pick和revert创建的任何新提交)时,Git会查看HEAD以确定你所在的分支以及该分支的SHA-1 ID。这就是“分支的末端”。然后,Git会创建一个新的提交,其父提交是旧的分支末端,并将新的提交ID写入分支文件中,使得分支名称指向新的提交。(当然,HEAD仍然只有分支名称。)

因此,如果你“在分支branch上”并创建一个新的提交D,那么你会得到以下结果:

A - B - C      <-- master
          \
            D  <-- HEAD=branch

如果你出了一点差错,并且处于“主分支 master”上,你会得到以下结果:

A - B - C      <-- branch
          \
            D  <-- HEAD=master

等等,这正是你想要的,除了分支标签。你所需要做的就是让名称为master的指针指向branch所在的位置,让branch指向master所在的位置,并将HEAD切换为显示branch而不是master
事实证明,这很容易...好吧,相当容易。 :-) 由于Git不允许您使用相同名称的两个分支,因此您需要一个临时名称。首先,重命名分支branch以便更改master。然后将master重命名为branch,并将branch的新名称重命名为master
$ git branch -m branch temp
$ git branch -m master branch
$ git branch -m temp master

第一步更改标签branchtemp。第二步更改masterbranch(并且您仍然在其中,因此也会重写HEAD)。最后一步更改tempmaster,从而为您最初想要的提交DAG和标签设置。与其他部分一样,您应仅尝试对未发布(未推送)的提交进行此类“标签交换”。这样做的原因很简单:当您发布提交时,它是通过名称到提交ID映射来完成的。如果您重新洗牌标签并“重新发布”,则提交ID不会更改,但名称到ID的映射会更改,因此任何捡起您先前广告的人,例如 master 表示 bb71dde ... ,现在获得一个与旧映射不“快进相关”的新映射. 人们和git都希望分支映射发生变化,但通常只以“快进方式”进行。例如,在Mercurial中,分支名称实际上记录在提交数据内。

1
非常好的答案!不过,如果修改已经被推送,分支标签交换技巧是否有效?我担心它会搞乱提交历史。 - Gabriele Petronella
1
如果已经推送/发布了,那么不,你不想这样做!除非所有与您一起工作的人都同意。 :-) - torek
1
@GabrielePetronella:「不可逆转的点」是「发布」而非「推送(push)」;当然,在许多情况下,这两个事件会同时发生,但并非总是如此;例如,如果您将push到只有您可以访问的存储库中,则可以使用后续的push -f覆盖它。 - kjo
@kjo同意。从“不让你的同事讨厌你”的角度来看,这是有道理的。 - Gabriele Petronella
我认为对于“但也许你不必这样做”的答案应该是:如果你刚刚提交了一个错误的分支,那么你刚刚提交的内容(现在你想要提交到另一个分支)仍然在你的工作目录中。因此,另一个更直接的解决方法是执行git reset HEAD^不带 --hard,然后再提交你本来想提交的内容。(我想。我还在学习这个东西。我可能会将其作为单独的答案发布。) - Steve Summit
显示剩余3条评论

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