首页 文章

使用Git将最近的提交移动到新分支

提问于
浏览
3996

我想将我已经承诺掌握的最后几个提交移动到一个新的分支,然后在完成这些提交之前将主人带回去 . 不幸的是,我的Git-fu还不够强大,有什么帮助吗?

即我该怎么做呢?

master A - B - C - D - E

这个?

newbranch     C - D - E
             /
master A - B

11 回答

  • 5069

    要在不重写历史记录的情况下执行此操作(即,如果您已经推送了提交):

    git checkout master
    git revert <commitID(s)>
    git checkout -b new-branch
    git cherry-pick <commitID(s)>
    

    然后可以无力推动两个分支!

  • 27

    对于那些想知道它为什么起作用的人(就像我最初的那样):

    您想要返回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,它仍然指向它最初的提交 .

  • 12

    使用git stash的更简单的解决方案

    如果:

    • 您的主要目的是回滚 master ,和

    • 您希望保留文件更改,但不要特别关心错误的提交消息,以及

    • 你还没推,而且

    • 您希望这很容易,并且不会出现临时分支,提交哈希和其他令人头疼的问题

    然后以下更简单(从分支 master 开始有三个错误的提交):

    git reset HEAD~3
    git stash
    git checkout newbranch
    git stash pop
    

    这是做什么的,按行号

    • 将最后三次提交(及其消息)撤消到 master ,但保留所有正常工作的文件

    • 删除所有工作文件更改,使 master 工作树完全等于HEAD~3状态

    • 切换到现有分支 newbranch

    • 将隐藏的更改应用于工作目录并清除存储

    您现在可以像往常一样使用 git addgit commit . 所有新提交都将添加到 newbranch .

    这不做什么

    • 它不会让随机的临时分支混乱你的树

    • It doesn't preserve the mistaken commits and commit messages, 因此您需要向此新提交添加新的提交消息

    目标

    OP表示目标是“在做出这些提交之前将主人带回来”而不会丢失变更,这个解决方案就是这样做的 .

    我每周至少做一次这样的事情,当我意外地提交 master 而不是 develop 时 . 通常我只有一次提交回滚,在这种情况下使用第1行的 git reset HEAD^ 是一种简单的方法来回滚一次提交 .

    Don't do this if you pushed master's changes upstream

    其他人可能已经取消了这些变化 . 如果你只是重写你的本地主人,当它被推向上游时没有任何影响,但是将重写的历史推送给协作者可能会引起麻烦 .

  • 361

    另一种方法是使用2个命令 . 同时保持当前工作树的完整性 .

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

    Old version - 在我了解 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. 是一个很好的诀窍 .

  • 26

    只是这种情况:

    Branch one: A B C D E F     J   L M  
                           \ (Merge)
    Branch two:             G I   K     N
    

    我表演了:

    git branch newbranch 
    git reset --hard HEAD~8 
    git checkout newbranch
    

    我期望提交我将成为HEAD,但提交L现在是......

    为了确保在历史上的正确位置,它更容易使用提交的哈希

    git branch newbranch 
    git reset --hard #########
    git checkout newbranch
    
  • 262

    以前的大多数答案都是错误的!

    不要这样做:

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

    当你下次运行 git rebase (或 git pull --rebase )时,这些3次提交将被静默地从 newbranch 中丢弃! (见下面的解释)

    Instead do this:

    git reset --keep HEAD~3
    git checkout -t -b newbranch
    git cherry-pick ..HEAD@{2}
    
    • 首先它丢弃最近的3个提交( --keep 就像 --hard ,但更安全,因为失败而不是丢弃未提交的更改) .

    • 然后它分叉 newbranch .

    • 然后樱桃挑选那些3次提交回 newbranch . 因为're no longer referenced by a branch, it does that by using git' s reflogHEAD@{2}HEAD 之前用于引用2个操作的提交,即在我们1.签出之前 newbranch 和2.使用 git reset 丢弃3个提交 .

    警告:默认情况下启用reflog,但是如果在运行 git reset --keep HEAD~3 之后've manually disabled it (e.g. by using a 425479 git repository), you won'能够获得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 选项,它使用本地reflog尝试对强制推送的上游分支进行强制 .

    假设你在包含提交M1,M2,M3的情况下对origin / master进行分支,然后自己进行三次提交:

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

    然后有人通过强制推动origin / master来删除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个提交视为主题分支的一部分 . 为了实现这一点,我们需要在不包含3次提交的早期版本中分离上游 . 这就是我建议的解决方案,因此他们都将reflog保持在正确的状态 .

    有关更多详细信息,请参阅git rebasegit merge-base文档中 --fork-point 的定义 .

  • 875

    一般......

    在这种情况下,sykora公开的方法是最佳选择 . 但有时并不是最容易的,也不是一般的方法 . 对于一般方法,使用git cherry-pick:

    要实现OP想要的,它需要两个步骤:

    步骤1 - 注意在新分支上从主设备提交的内容

    执行

    git checkout master
    git log
    

    注意 newbranch 上的(例如3)提交的哈希值 . 我将在这里使用:
    C commit: 9aa1233
    D commit: 453ac3d
    E commit: 612ecb3

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

    步骤2 - 将它们放在新分支上

    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 .

  • 2

    1)创建一个新分支,将所有更改移动到new_branch .

    git checkout -b new_branch
    

    2)然后回到旧的分支 .

    git checkout master
    

    3)做git rebase

    git rebase -i <short-hash-of-B-commit>
    

    4)然后打开的编辑器包含最后3个提交信息 .

    ...
    pick <C's hash> C
    pick <D's hash> D
    pick <E's hash> E
    ...
    

    5)在所有这3次提交中将 pick 更改为 drop . 然后保存并关闭编辑器 .

    ...
    drop <C's hash> C
    drop <D's hash> D
    drop <E's hash> E
    ...
    

    6)现在最后3个提交从当前分支( master )中删除 . 现在强行推动分支,在分支名称之前使用 + 符号 .

    git push origin +master
    
  • 20

    你可以做到这只是我使用的3个简单步骤 .

    1)在你想要提交最新更新的地方 Build 新的分支 .

    git branch <branch name>

    2)在新分支上查找提交的最近提交ID .

    git log

    3)复制该提交ID,注意最新提交列表发生在顶部 . 所以你可以找到你的提交 . 你也可以通过消息找到这个 .

    git cherry-pick d34bcef232f6c...

    你也可以提供一些提交id .

    git cherry-pick d34bcef...86d2aec

    现在你的工作完成了 . 如果你选择了正确的id和正确的分支,那么你就会成功 . 所以在此之前要小心 . 否则会出现另一个问题 .

    现在你可以推送你的代码了

    git push

  • 239

    搬到新的分行

    WARNING: 此方法有效,因为您使用第一个命令创建新分支: git branch newbranch . 如果要将提交移动到 existing branch ,则需要在执行 git reset --hard HEAD~3 之前将更改合并到现有分支中(请参阅下面的 Moving to an existing branch ) . If you don't merge your changes first, they will be lost.

    除非涉及其他情况,否则可以通过分支和回滚轻松完成 .

    # Note: Any changes not committed will be lost.
    git branch newbranch      # Create a new branch, saving the desired commits
    git reset --hard HEAD~3   # Move master back by 3 commits (GONE from master)
    git checkout newbranch    # Go to the new branch that still has the desired commits
    

    但要确保要返回多少次提交 . 或者,你可以代替 HEAD~3 ,只需在主(/ current)分支上提供你想要"revert back to"的提交(或者像origin / master这样的引用)的哈希,例如:

    git reset --hard a1b2c3d4
    
    • 1你将从主分支 only 提交"losing"提交,但不要在newbranch中提交这些提交!

    WARNING: 使用Git 2.0及更高版本,如果您稍后 git rebase 在原始( master )分支上的新分支,您可能需要在rebase期间使用显式 --no-fork-point 选项以避免丢失结转提交 . 设置 branch.autosetuprebase always 会使这更有可能 . 有关详细信息,请参阅John Mellor's answer .

    移动到现有分支

    如果要将提交移动到 existing branch ,它将如下所示:

    git checkout existingbranch
    git merge master
    git checkout master
    git reset --hard HEAD~3 # Go back 3 commits. You *will* lose uncommitted work.
    git checkout existingbranch
    
  • 0

    这不会在技术意义上“移动”它们,但它具有相同的效果:

    A--B--C  (branch-foo)
     \    ^-- I wanted them here!
      \
       D--E--F--G  (branch-bar)
          ^--^--^-- Opps wrong branch!
    
    While on branch-bar:
    $ git reset --hard D # remember the SHAs for E, F, G (or E and G for a range)
    
    A--B--C  (branch-foo)
     \
      \
       D-(E--F--G) detached
       ^-- (branch-bar)
    
    Switch to branch-foo
    $ git cherry-pick E..G
    
    A--B--C--E'--F'--G' (branch-foo)
     \   E--F--G detached (This can be ignored)
      \ /
       D--H--I (branch-bar)
    
    Now you won't need to worry about the detached branch because it is basically
    like they are in the trash can waiting for the day it gets garbage collected.
    Eventually some time in the far future it will look like:
    
    A--B--C--E'--F'--G'--L--M--N--... (branch-foo)
     \
      \
       D--H--I--J--K--.... (branch-bar)
    

相关问题