用Git将最近提交的内容移动到一个新的分支。

git git-branch branching-and-merging


我想把最近几次提交给master的内容移到一个新的分支上,然后把master移回到提交之前。不幸的是,我的Git-fu还不够强大,有什么办法吗?

即:我怎样才能从这个。

master A - B - C - D - E

到这个?

newbranch     C - D - E
             /
master A - B 



Answer 1 Duc Filan


迁至现有分支机构

如果要将提交移至现有分支,它将如下所示:

git checkout existingbranch
git merge master         # Bring the commits here
git checkout master
git reset --keep HEAD~3  # Move master back by 3 commits.
git checkout existingbranch

--keep 选项可保留任何提交的修改,你可能有不相关的文件,或中止如果这些改变都被覆盖-类似于什么 git checkout 呢。如果异常终止, git stash 您的更改 --hard 试,或使用--hard来丢失更改(即使来自提交之间未更改的文件!)

搬到新的分公司

此方法通过使用第一个命令( git branch newbranch )创建一个新分支但不切换到该分支来起作用。然后,我们回滚当前分支(主节点)并切换到新分支以继续工作。

git branch newbranch      # Create a new branch, containing all current commits
git reset --keep HEAD~3   # Move master back by 3 commits (Make sure you know how many commits you need to go back)
git checkout newbranch    # Go to the new branch that still has the desired commits
# Warning: after this it's not safe to do a rebase in newbranch without extra care.

但是,请确保要返回多少次提交。或者,代替 HEAD~3 ,可以简单地提供的提交的哈希(或类似的参考 origin/master )要恢复到,例如:

git reset --keep a1b2c3d4

警告:在Git 2.0版及更高版本中,如果稍后 git rebase 将新分支重新设置为原始( master )分支,则在重新设置过程中可能需要显式的 --no-fork-point 选项,以避免丢失从主分支。有 branch.autosetuprebase always 设定使得这种可能性更大。有关详细信息,请参见John Mellor的答案




Answer 2 Ryan Lundy


对于那些想知道为什么能用的人(我一开始也是这样)。

你想回到C,把D和E移到新的分支。这就是一开始的样子。

A-B-C-D-E (HEAD)
        ↑
      master

git branch newBranch 之后:

    newBranch
        ↓
A-B-C-D-E (HEAD)
        ↑
      master

git reset --hard HEAD~2 之后:

    newBranch
        ↓
A-B-C-D-E (HEAD)
    ↑
  master

由于分支只是一个指针,因此master指向最后一次提交。当您创建newBranch时,您仅创建了指向最后一次提交的新指针。然后使用 git reset指针移回两次提交。但是由于您没有移动newBranch,所以它仍然指向它最初执行的提交。




Answer 3 Ivan


一般来说...

在这种情况下,sykora公开的方法是最佳选择。但是有时不是最简单的方法,也不是通用方法。对于一般方法,请使用git cherry-pick

要达到上级的要求,需要分两步走。

第1步-记下您想要在新 newbranch 上提交的master提交的内容

Execute

git checkout master
git log

注意在 newbranch 上想要的(假设3)提交的哈希值。在这里我将使用:
C提交: 9aa1233
D提交: 453ac3d
E提交: 612ecb3

注意:您可以使用前七个字符或整个提交哈希

第2步-将它们放在新 newbranch

git checkout newbranch
git cherry-pick 612ecb3
git cherry-pick 453ac3d
git cherry-pick 9aa1233

或者 (在Git 1.7.2以上,使用范围)

git checkout newbranch
git cherry-pick 612ecb3~1..9aa1233

git cherry-pick将这三个提交应用于newbranch。




Answer 4 John Mellor


之前的大部分答案都是危险的错误!

不要这样做。

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

下次运行 git rebase (或 git pull --rebase )时,这3个提交将被从 newbranch 中静默丢弃!(请参阅下面的说明)

而是这样做:

git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
  • 首先,它丢弃最近的3次提交( --keep 类似于 --hard ,但更安全,因为失败而不是丢弃未提交的更改)。
  • 然后它分叉 newbranch
  • 然后将这3个提交重选到 newbranch 。由于它们不再被分支引用,因此可以使用git的reflog来做到这一点: HEAD@{2}HEAD 之前用来引用2个操作的提交,即在我们1.检出 newbranch 和2.使用 git reset 以丢弃3次提交。

警告:默认情况下已启用reflog,但是如果您手动禁用了reflog(例如,通过使用“裸露”的git仓库),则在运行 git reset --keep HEAD~3 后,您将无法取回3次提交--keep HEAD〜 3。

一个不依赖reflog的替代方案是。

# newbranch will omit the 3 most recent commits.
git checkout -b newbranch HEAD~3
git branch --set-upstream-to=oldbranch
# Cherry-picks the extra commits from oldbranch.
git cherry-pick ..oldbranch
# Discards the 3 most recent commits from oldbranch.
git branch --force oldbranch oldbranch~3

(如果您愿意,可以编写 @{-1} -先前签出的分支-代替 oldbranch )。


技术解释

为什么 git rebase 在第一个示例之后会丢弃3次提交?这是因为不带参数的 git rebase 启用了 --fork-point 默认情况下会 --fork-point选项,该选项使用本地reflog尝试对强行推入的上游分支保持鲁棒性。

假设你从 origin/master 分支,当它包含了 M1、M2、M3 的提交,然后你自己做了三个提交。

M1--M2--M3  <-- origin/master
         \
          T1--T2--T3  <-- topic

但却有人改写历史,强行推动起源/主宰删除M2。

M1--M3'  <-- origin/master
 \
  M2--M3--T1--T2--T3  <-- topic

使用本地reflog, git rebase 可以看到您是从origin / master分支的较早版本分支出来的,因此M2和M3提交实际上不是您的主题分支的一部分。因此,可以合理地假设由于M2已从上游分支中删除,因此一旦主题分支重新建立基础,您就不再希望在主题分支中使用它:

M1--M3'  <-- origin/master
     \
      T1'--T2'--T3'  <-- topic (rebased)

这种行为是有道理的,一般来说,在重基时是正确的。

所以,以下命令失败的原因。

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

是因为它们使reflog处于错误状态。Git认为 newbranch 在包含3个提交的修订中分叉了上游分支,然后 reset --hard 重写了上游的历史记录以删除提交,因此,下次您运行 git rebase 时,它将像其他任何提交一样丢弃它们已从上游移除。

但在这种特殊情况下,我们希望这 3 个提交被视为主题分支的一部分。为了达到这个目的,我们需要在不包括这三个提交的早期版本中分叉掉上游。这也是我建议的解决方案,因此它们都能让 reflog 处于正确的状态。

有关详细信息,请参阅定义 --fork-pointgit的底垫混帐合并基础文档。




Answer 5 aragaer


然而另一种方法,只需使用2个命令。还能保持当前工作树的完整性。

git checkout -b newbranch # switch to a new branch
git branch -f master HEAD~3 # make master point to some older commit

旧版本 -在我了解 git branch -f 之前

git checkout -b newbranch # switch to a new branch
git push . +HEAD~3:master # make master point to some older commit 

能够 push. 是个不错的窍门。