Git: 拉一个基于重新设置的分支

让我描述一下我的情况:

Blond 先生和 Orange 先生正在处理分支 A,该分支在提交 M1的主分支之外。分支 A 有2次提交: A1和 A2。

M1
\
\
A1 - A2

与此同时,Orange 先生在主分支 M2和 M3上提交并推出了另外两个提交。

M1  - M2 - M3
\
\
A1 - A2

布隆德先生从遥控器里拿出来,过了一会儿,他决定重新调整到主支架上:

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

现在 A1’和 A2’是存在于金先生本地的重新定位提交,而 A1和 A2则远程存在。Blond 先生推动他的提交,使用 F来强制他的更改和“重写”历史。现在远程存储库看起来像这样:

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

但奥兰治先生也在 A 分公司工作,他当地的仓库看起来还是这个样子:

M1  - M2 - M3
\
\
A1 - A2

Orange 先生需要做什么才能与远程存储库中的 A 分支同步?

正常的拉力不起作用。会强制从远程本地进行更改吗?我知道,删除 A 的本地版本并从远程存储库中再次使用它会有所帮助,但这似乎不是实现这一目标的好方法。

42693 次浏览

If Mr. Orange doesn't mind losing his changes, he can fetch from the server, then checkout the the branch:

git checkout A to get onto his local A branch.

Then, assuming the remote is named "origin", reset your local branch in favor of the remote branch:

git reset --hard origin/A

If he is concerned with losing changes, he can merge the server's changes in to resolve them (from his own A branch, and presuming again that the remote is named "origin") with git merge origin/A. This will make a new commit that's on top of both his and the remote's A branches with the changes from both merged together. Then this can be pushed back to the remote.

My recommendation (or, "what I would do if I were Mr Orange") is, start with git fetch. Now I'll have this in my repo, which is what Mr Blond had after his rebase and just before he ran "git push -f".

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

The one important difference is, I'll have my local label A pointing to rev A2, and the remote label remotes/origin/A pointing to A2' (Mr Blond had it the other way around, local label A pointing to A2' and remotes/origin/A pointing to A2).

If I've been working on my copy of the branch named "A" I'll have this instead:

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

(with my local label pointing to A3 rather than A2; or A4 or A5, etc, depending on how many changes I have applied.) Now all I have to do is rebase my A3 (and A4 if needed, etc) onto A2'. One obvious direct way:

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

and then drop revs A1 and A2 entirely, since the modified ones are in new_A as A1' and A2'. Or:

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

(the git am -3 -k method is described in the git-format-patch manual page).

These do require figuring out what I have that Mr Blond didn't before he did his rebase, i.e., identifying A1, A2, A3, etc.

If the second approach is successful I end up with:

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

where my branch name new_A points to A3' (my existing A branch still points to the old A3). If I use the first approach and it succeeds, I end up with the same thing, it's just that my existing branch name A will now point to A3' (and I have no name for the old branch with A1-A2-A3, even though it's still in my repo; finding it requires going through reflogs or similar).

(If my A3 needs modification to become A3', both the interactive rebase and the "git am" method will require work by me, of course.)

Of course it's also possible to just git merge (as in the answer by Gary Fixler), but that will create a merge commit ("M" with no number, below) and keep revs A1 and A2 visible, giving:

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

If you want to preserve the original A1 and A2, this is a good thing; if you want to get rid of them, it's a bad thing. So "what to do" depends on "what you want the result to be".

Edit to add: I like the format-patch method better as it leaves my old A branch name around while I make sure everything is good. Assuming it all works and is good, here's the last few steps:

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

and then, if old_A can be abandoned entirely:

$ git branch -D old_A

or, equivalently, start with the branch delete, then rename new_A to A.

(Edit: see also git rebase --onto documentation, for the goal of rebasing A3, etc., onto the new_A branch.)

I develop concurrently on two virtual machines, for configuration purposes. As a result, I frequently rebase on one machine and need the changes to appear on the other without difficulty.

Suppose my branch is named feature/my-feature-branch. After finishing the rebase on the first VM, I do a git fetch on the second. The following message appears:

$ 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)

Well, don't do a git pull, because then you end up with a meaningless merge commit after quite a bit of fussing.

Instead, run

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

Once the text editor pops up, delete all commits, and replace it with the following (this makes it so the rebase completes without any commits being kept).

exec echo test

If you do have commits that need to be kept, then those can be applied here. In either case, the rebase will complete, and now both machines are in sync again, as evidenced by:

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

TLDR

Unless I have misunderstood the question, the answer is for Mr Orange to git pull --rebase.

Longer read

Simple case

If we assume that the changes made by A1 and A2 are the same as A1' and A2' (even if their commit hashes aren't the same) then Mr Orange can go from having

M1  - M2 - M3
\
\
A1 - A2

locally to having

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

by either doing

git pull --rebase

or

git branch -d A
git checkout A

A more interesting case

If we assume, as above, that the changes made by A1 and A2 are the same and that Mr Orange has made an extra commit then he will locally have

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

and origin will be

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

and, again, Mr Orange can get to

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

with

git pull --rebase

A more realistic case

If we assume, as above, that the changes made by A1 and A2 are the same, that Mr Orange has made an extra commit and that Mr Blonde has made an extra commit then Mr Orange will have this locally

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

and, assuming Mr Blonde has pushed, origin will be

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

and Mr Orange can get to

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

by doing

git pull --rebase

though, this time, he may need to fix conflicts.