将已提交但未推送的更改移动到拉取后的新分支

625

我已经做了不少的工作("您的分支领先于'origin/master' 37 次提交。"), 这些工作应该建立一个自己的分支而不是放到master分支中。这些提交只存在于我的本地机器上,没有被推送到origin,但情况有些复杂,因为其他开发人员一直在向origin/master推送更改。

如何将我的37个本地提交移动到一个新分支中?根据文档,似乎git rebase --onto my-new-branch master...origin/master 应该可以实现,但两者都只给我返回错误信息"fatal: Needed a single revision"。 man git-rebase 对提供给rebase的修订版本没有任何说明,并且它的示例也没有这样做,所以我不知道如何解决这个错误。

(注意,这不是Move existing, uncommited work to a new branch in GitHow to merge my local uncommitted changes into another Git branch?的重复问题,因为这些问题涉及的是本地工作树中未提交的更改,而不是已经本地提交的更改。)


2
看看这个解决方案。看起来很简单和干净。 - Tony
12个回答

629

在这种情况下应该没问题,因为您还没有将提交推送到其他地方,并且您可以重写origin/master分支的历史记录。 首先,我会运行git fetch origin以确保origin/master是最新的。 假设您目前在master分支上,您应该能够执行:

git rebase origin/master

这将会重新播放除了origin/master上没有的所有提交到origin/master中。rebase的默认操作是忽略合并提交(例如,那些可能由你的git pull引入的提交),它将尝试将每个提交引入的补丁应用到origin/master上。(你可能需要在此过程中解决一些冲突。)然后,你可以基于结果创建你的新分支:

git branch new-work

然后将你的master重置为origin/master:

# Use with care - make sure "git status" is clean and you're still on master:
git reset --hard origin/master

当使用 git branchgit reset 等命令操作分支时,我发现经常使用 gitk --all 或类似的工具查看提交图表,以确保理解不同引用指向的位置。

另外,你也可以直接基于主分支创建一个主题分支 (git branch new-work-including-merges),再像上面那样重置 master。然而,由于主题分支中包含了来自 origin/master 的合并内容,并且你还没有推送你的更改,建议进行变基以使历史更清晰。(此外,当你最终将主题分支合并回主分支时,更改将更加明显。)


8
@Olie:不,问题中的假设以及我在答案开头列出的假设下,答案是正确的。应该放在一个新分支上的提交已经在“master”分支中了;rebase 会重写“master”分支,使得新提交线性地放在“origin/master”的顶部,然后“git branch new-work”创建了一个“new-work”分支,指向“master”(当前分支)的末尾,而不会切换到“new-work”。现在,“new-work”包含所有新提交。然后reset操作将当前分支(仍然是“master”)移回“origin/master”。 - Mark Longair
1
@Quintesse:非常抱歉如果您丢失了工作,但我相信这个答案对原问题描述的情况是正确的。(我刚刚回复了Olie的评论,希望有所澄清。)无论如何,以防能够帮助您找回工作,我应该说,如果您的工作在最近几天内提交(这个问题和答案中的一个假设),您应该可以通过git reflog轻松检索到它。 - Mark Longair
7
@Olie:也许更好的解释方式是:git中的分支就像指向特定提交的标签;如果你在该分支上创建它们,则它们会自动移动到新的提交,或者可以使用git reset和其他各种方法进行移动。git branch new-work的意思只是说“在我保持当前分支(在本例中为主分支)时创建一个指向此提交的分支”。因此,没有必要有一个“移动”提交从主分支到新分支的命令 - 你只需在那里创建一个新分支,当你重置主分支时,新分支将留在主分支原来的位置。 - Mark Longair
1
有点晚了,但是@Olie,当你在新分支上执行git status时,如果没有显示超前于主分支的提交,这并不意味着它们实际上不存在(假设这就是你担心的原因)。尝试将新分支推送到远程仓库:你会看到提交记录在那里。 - Félix Adriyel Gagnon-Grenier
2
@FélixGagnon-Grenier,不用担心“晚了”——总会有人查找旧问题,每一个澄清都是有帮助的。谢谢! :) - Olie
显示剩余12条评论

236

我遇到了同样的问题。我找到了最简单的解决方案,现在想分享给大家。

1)创建一个带有你所做更改的新分支。

git checkout -b mybranch

2)(可选)将新分支代码推送到远程服务器上。

git push origin mybranch

3) 切换回主分支(master branch)并进行结帐(checkout)。

git checkout master

4) 重置主分支代码与远程服务器同步并移除本地提交记录。

git reset --hard origin/master

4
可以省略第二步。我假设自顶部答案以来,git发生了变化并且此过程之前是不允许的。 - Sebastian
11
希望进一步阐述为什么这样做有效。您拥有一组提交,这些提交已提交到本地主分支,但尚未更新到原始分支。然后,您从本地更新中创建一个分支,该分支反映了所有新的更改。当您切换回主分支并进行硬重置时,它会重置回原始主分支。您的新分支仍然具有在重置之前从本地主分支继承的更改。新分支不会随着本地主分支的重置而重置。如果您是第一次这样做,建议您备份存储库并确认更改已在备份中。 - rocking_ellipse
这是最清晰、最安全的选择,因为它首先创建了分支。我本来不会意识到你可以在没有将相同的提交推送到主分支的情况下,在分支下进行推送,但确实有效。然后,您可以验证您的更改已安全推送到远程,并可以开始一个PR。从那里,只需要回到主分支并重置到出站之前的最后一次提交(--hard)。 - b_levitt

231
如果您提交的代码数量较少,且不介意将其合并为一个巨大的提交,那么这种方法效果很好,而且不像使用"git rebase"那样危险。请取消暂存文件(将1替换为提交数)。
git reset --soft HEAD~1

创建一个新分支

git checkout -b NewBranchName

添加更改

git add -A

提交一个commit

git commit -m "Whatever"

6
请使用命令 git log --all --decorate --oneline --graph 生成一张易于理解的图形。 - EliuX
1
嘿@EliuX - 我在这里缺少相关性。你能详细说明一下吗? - Stachu
8
谢谢!这个解决方案非常简单,而且完美地解决了问题! - Chris Sim
不确定出了什么问题,但是我在做这件事时失去了一些工作。当我切换到新的分支时,我的工作丢失了 - 在此时所有更改都消失了。 - Noobie3001
这是一个非常简单的解决方案。谢谢! - pritampanhale
显示剩余2条评论

36

另一种方式 假设 branch1 - 是已提交更改的分支 branch2 - 是期望的分支

git fetch && git checkout branch1
git log

选择需要移动的提交 ID

git fetch && git checkout branch2
git cherry-pick commit_id_first..commit_id_last
git push

现在从初始分支中撤销未推送的提交

git fetch && git checkout branch1
git reset --soft HEAD~1

5
在处理历史记录成为负担时,挑选单个提交的命令“cherry-pick”是最好的复制/移动命令。 - John Neuhaus
这是目前为止对这个问题最方便的答案。感谢您的指令! - Farah
你可以在上一个评论中更新一下,其中1或n是未推送的提交数量。这仍然是这个问题的非常好的解决方案。 - chAlexey

17

如果你提交到了错误的分支,可以按照以下步骤执行:

  1. git log
  2. git diff {上一个提交} {最新提交} > 你的变更.patch
  3. git reset --hard origin/{你当前的分支}
  4. git checkout -b {新分支}
  5. git apply 你的变更.patch

我可以想象出在第一步和第二步中有一个更简单的方法。


我这样做了,但是我用git reset --hard {commit_hash}替换了第三步,以回到我想要分离分支的提交,然后将我的更改应用到一个新分支上。缺点是你必须重新编写提交消息。 - Jonno_FTW
刚刚做了这个,完美地运行了,谢谢分享。 - Sitnik

6

如何操作:

  1. 从当前HEAD创建分支。
  2. 确保你在 master 分支上,而不是你的新分支。
  3. git reset 到你开始修改之前的最后一次提交。
  4. git pull 只重新拉取你用reset丢弃的远程更改。

那么,在尝试重新合并该分支时是否会出现问题?


2
啊,这基本上就是@Mark-Longair上面描述的B选项。 - Tim Keating

5

这里有一种更简单的方法:

  1. 创建一个新分支

  2. 在你的新分支上执行 git merge master - 这将合并你提交(未推送)的更改到你的新分支

  3. 删除你本地的主分支 git branch -D master 使用 -D 而不是 -d,因为你想强制删除该分支。

  4. 只需在主分支上执行 git fetch 并在主分支上执行 git pull,以确保你拥有你团队最新的代码。


将本地主分支合并到目标分支是一个很好的提示。删除和重新拉取主分支是不必要的。只需git reset --hard origin/master来放弃您的主分支中未推送的更改。 - Matt Hulse

2

我不喜欢使用git reset --hard origin/master的解决方案,因为它们会不必要地干扰工作树;我更喜欢

git branch new-work
git switch new-work
git branch --force master origin/master

这将会 (1) 创建一个指向当前提交的新分支;(2) 将该分支设置为当前分支(并且不会影响工作树,因为它是相同的提交,因为该分支刚刚被创建);(3) 重新将主分支指向 origin/master,而不会影响工作树(因为我们不再处于主分支上)。


这对我来说很有效。实际上,我已经在一个分支上(而不是主分支)了,但相同的概念步骤完美地奏效。完全同意不要干涉工作树的想法。 - MemeDeveloper

2

一种更简单的方法,我一直在使用它(假设您想移动4个提交):

git format-patch HEAD~4

请在您执行最后一个命令的目录中查找4个.patch文件。

git reset HEAD~4 --hard

git checkout -b tmp/my-new-branch

然后:

git apply /path/to/patch.patch

无论您想以何种顺序进行。

0

对我来说,这是最好的方法:

  1. 检查更改和合并冲突 git fetch
  2. 创建一个新分支 git branch my-changes 并推送到远程
  3. 将上游更改为新创建的分支 git master -u upstream-branch remotes/origin/my-changes
  4. 将您的提交推送到新的上游分支。
  5. 切换回以前的上游 git branch master --set-upstream-to remotes/origin/master

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