Git: 拉取一个已经变基的分支

111

让我描述一下我的情况:

Blond先生和Orange先生正在分支A上工作,该分支是从主分支M1提交创建的。 分支A有2个提交:A1和A2。

M1  
   \
    \
     A1 - A2

同时,橙先生在主分支上提交并推送了另外两个提交:M2和M3。

M1  - M2 - M3
   \
    \
     A1 - A2

布隆先生从远程拉取代码,一段时间后决定将代码库变基到主分支:

M1  - M2 - M3
   \         \
    \         \
     A1 - A2   A1` - A2`

现在A1`和A2`是Mr Blond本地存在的变基提交,而A1和A2则存在于远程。Mr Blond使用-f强制推送他的提交并“重写”历史记录。现在远程存储库看起来像这样:

M1  - M2 - M3
             \
              \
               A1` - A2`

但是Mr. Orange也在A分支上工作。他的本地代码库仍然是这样的:

M1  - M2 - M3
   \
    \
     A1 - A2

为了与远程仓库中的A分支同步,Mr. Orange需要做什么?

普通的pull不起作用。使用pull -f会强制从远程拉取更改吗?我知道删除本地版本的A并再次从远程仓库获取可以实现这一点,但这似乎不是一个好的方法。

4个回答

127
如果Orange先生不介意失去他的更改,他可以从服务器获取,然后检出分支:git checkout A以进入本地的A分支。然后,假设远程名称为"origin",则将本地分支重置为远程分支:git reset --hard origin/A
如果他担心丢失更改,他可以将服务器的更改合并以解决它们(从他自己的A分支开始,并再次假定远程命名为“origin”):git merge origin/A。这将创建一个新的提交,位于他和远程的A分支之上,并将两个分支的更改合并在一起。然后可以将此提交推回到远程。

4
注意:--hard之间没有空格 - 我花了一会儿时间才找到答案,因为它恰好包裹在这两个词之间,但不要改变原来的意思。 - qbolec
如果没有要跟踪的更改,删除本地分支可能也同样容易;从远程获取;然后再次检出新获取的分支。 - Ben Collins
1
我喜欢这个解决方案,特别是当你可能已经从源获取了更新时,它只会重置分支的本地引用,而不会发出另一个网络请求。 - Mike Moore
那应该是 origin/A 而不是 origin/A2,因为 A2 是指一个提交而不是一个分支。 - Khouri Giordano
9
我认为这个答案已经过时了。“git pull --rebase” 可以很好地处理这种情况。 - Christian
我猜你需要在git reset之前使用git fetch从远程下载更改,是这样吗? - Ferran Maylinch

37

我的建议是(或者,如果我是Mr Orange的话,我会这样做),从git fetch开始。现在我将拥有这个库,这就是Mr Blond在他的变基之后并且在运行"git push -f"之前所拥有的。

M1  - M2 - M3
   \         \
    \         \
     A1 - A2   A1' - A2'

唯一重要的区别是,我的本地标签 A 指向 rev A2,而远程标签 remotes/origin/A 指向 A2'(Mr Blond 的情况相反,本地标签 A 指向 A2',remotes/origin/A 指向 A2)。

如果我一直在处理名为“A”的分支的副本,则会有以下内容:

M1  ---- M2 ---- M3
   \               \
    \               \
     A1 - A2 - A3    A1' - A2'

(我的本地标签指向A3,而不是A2;或者A4或A5等,这取决于我应用了多少更改。)现在我所要做的就是将我的A3(如果需要,还有A4等)变基到A2'。一个明显的直接方式:

$ git branch -a
  master
* A
  remotes/origin/master
  remotes/origin/A
$ git branch new_A remotes/origin/A
$ git rebase -i new_A

然后完全删除A1和A2的版本,因为已经修改的版本现在是新版本A1'和A2',存储在new_A中。或者:

$ git checkout -b new_A remotes/origin/A
$ git format-patch -k --stdout A3..A | git am -3 -k

git am -3 -k 方法在 git-format-patch 手册页中描述。)

这些需要弄清楚我在 Mr Blond 进行 rebase 之前拥有什么,即识别 A1、A2、A3 等。

如果第二种方法成功,最终会得到:

M1  ---- M2 ---- M3
   \               \
    \               \
     A1 - A2 - A3    A1' - A2' - A3'

我的分支名 new_A 指向 A3'(我的现有分支 A 仍指向旧的 A3)。如果我使用第一种方法并成功了,最后结果是一样的,只是我的现有分支名 A 现在将指向 A3'(而我没有名称来标识具有 A1-A2-A3 的旧分支,尽管它仍然在我的仓库中;查找它需要通过 reflogs 或类似方式进行)。

(如果我的 A3 需要修改才能成为 A3',那么交互式变基和 "git am" 方法都需要我自己做出调整。)

当然,也可以直接使用 git merge(如 Gary Fixler 的答案所示),但这将创建一个合并提交(下面是没有编号的"M"),并保留修订版 A1 和 A2,显示如下:

M1  ---- M2 ---- M3
   \               \
    \               \
     A1 - A2 - A3    A1' - A2' -- M
                 \_______________/

如果你想保留原始的A1和A2,这是一件好事;如果你想摆脱它们,那就不好了。因此,“该怎么做”取决于“你想得到什么结果”。

编辑添加:我更喜欢使用format-patch方法,因为在确保一切都很好之前,它会保留我的旧A分支名称。 假设所有东西都能工作并且是好的,以下是最后几个步骤:

$ git branch -m A old_A
$ git branch -m new_A A

然后,如果旧的A完全可以被放弃:

$ git branch -D old_A

或者等价地,从分支删除开始,然后将新的A重命名为A。

(编辑:另请参阅git rebase --onto文档,以将A3等重新定位到新的A分支的目标。)


31

简短说明

除非我误解了问题,否则答案是让橙先生执行git pull --rebase命令。

详细说明

简单情况

如果我们假设A1和A2所做的更改与A1'和A2'相同(即使它们的提交哈希不同),那么橙先生可以从以下状态转换:

M1  - M2 - M3
   \
    \
     A1 - A2

本地拥有

M1  - M2 - M3
             \
              \
               A1` - A2`

无论是通过做什么

git pull --rebase
或者
git branch -d A
git checkout A

一个更有趣的案例

如果我们像上面那样假设,A1和A2所做的更改是相同的,并且Mr Orange做了额外的提交,那么他本地将会有

M1  - M2 - M3
   \
    \
     A1 - A2 - A3O

并且起源将是

M1  - M2 - M3
             \
              \
               A1` - A2`

并且,再次,橙先生能够到达

M1  - M2 - M3
             \
              \
               A1` - A2` - A3O

git pull --rebase

更加真实的情况

如果我们像上面一样假设A1和A2所做的更改是相同的,Mr Orange进行了额外的提交,而Mr Blonde也进行了额外的提交,那么Mr Orange将在本地拥有这个版本。

M1  - M2 - M3
   \
    \
     A1 - A2 - A3O

假设Mr Blonde已经推了,起点将会是:

M1  - M2 - M3
             \
              \
               A1` - A2` - A3B

而橙先生可以到达

M1  - M2 - M3
             \
              \
               A1` - A2` - A3B - A3O
通过做。
git pull --rebase

虽然这一次,他可能需要解决冲突。


2
这应该是被接受的答案。我不知道自从Gary Fixler在2013年回答以来Git是否有所改进,但现在似乎重新基于已发布的分支不再是一个问题。 - Christian
1
这对我有用。我有一个分支,我会在移动的主干上进行变基。git pull --rebase 检测到更新,如果本地没有额外的提交,则一切都解决了。 - Mat M

22

为了配置目的,我同时在两台虚拟机上进行开发。因此,我经常在其中一台上进行变基,并需要在另一台上轻松查看更改。

假设我的分支名为feature/my-feature-branch。完成第一台虚拟机上的变基后,在第二台虚拟机上执行git fetch时会出现以下消息:

$ git status
On branch feature/my-feature-branch
Your branch and 'origin/feature/my-feature-branch' have diverged,
and have 21 and 24 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

不要进行 git pull 操作,因为这样会在经过一番麻烦后导致一个没有意义的合并提交。

相反地,请运行

git rebase -i origin/feature/my-feature-branch

一旦文本编辑器弹出,请删除所有提交,并将其替换为以下内容(这样可以使重新基础完成时不保留任何提交)。

exec echo test

如果确实有需要保留的提交,则可以在此处应用这些提交。无论哪种情况,重新基础都将完成,现在两台机器再次同步,证明如下:

$ git pull
Already up-to-date.
$ git push
Everything up-to-date

1
这是完美的答案。我想指出,您也可以选择在最后一步“删除”所有提交,而不是运行exec echo test,这与删除提交行没有任何区别。 - Akash Agarwal

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