Reputation: 37501
Despite all descriptions, git pull --rebase
works differently than git fetch/git rebase [branch]
. git pull --rebase
is described as an alias for the fetch
+rebase
command, but I'm trying to find out how else they differ?
In another post, I was given git pull --rebase
as a solution to a rebase problem in which git wasn't properly handling commits which have changed hash values due to merge conflict resolution on an upstream feature branch.
Until now, we've been using a combination of git fetch upstream
and git rebase upstream/a-feature-branch
.
However, when done this way, it acted like any commits that no longer match the hashes on the upstream branch were new work. It tried to re-apply them and caused merge conflicts:
$ git fetch upstream
-- no results, already fetched this morning
$ git rebase upstream/a-feature-branch
First, rewinding head to replay your work on top of it...
Applying: D-06437 (note: this commit already exists, but a merge conflict upstream has changed its hash)
Using index info to reconstruct a base tree...
...
Falling back to patching base and 3-way merge...
Auto-merging (file)
CONFLICT (content): Merge conflict in (file)
Failed to merge in the changes.
Patch failed at 0001 D-06437
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
imac: projectName ((c1452be...)|REBASE) $
However, running a pull instead achieves what we want:
$ git pull --rebase upstream a-branch-name
* branch a-branch-name -> FETCH_HEAD
First, rewinding head to replay your work on top of it...
Applying: B-07241
This solution doesn't cause any conflicts and has properly updated the history with the modified commit/hashes that upstream has.
Update #1:
git pull --rebase upstream feature-branch-name
equates to:
git-rebase --onto c1452be62cf271a25d3d74cc63cd67eca51a127d 634b622870a1016e717067281c7739b1fe08e08d
Here are the three most recent commits on the developers work branch:
92b2194 Rick B-07241
634b622 Sue Merge pull request #254 from dboyle/B-07290
bc76e5b Bob [B-07290] Order Parts Ship To/Comments
And the most recent commit on the "new" feature branch:
c1452be Sue [B-07290] Order Parts Ship To/Comments
Note: The "merge" commit has been lost and the "Order Parts" commit now shows as being done by Sue, not Bob. I'm trying to confirm, but either someone cherry-picked the commit, or somehow ran a rebase in a way that discarded the merge commits.
Here are several variables that git-rebase.sh
is using during each. The only difference is the onto:
"git-rebase" Variables during `git pull --rebase upstream feature-branch-name`
orig_head = 92b2194e3adc29eb3fadd93ddded0ed34513d587
onto_name = c1452be62cf271a25d3d74cc63cd67eca51a127d
onto = c1452be62cf271a25d3d74cc63cd67eca51a127d
mb = 438cc917c6f517913c9531e0a38f308d3aa13f0b
revisions = 634b622870a1016e717067281c7739b1fe08e08d..92b2194e3adc29eb3fadd93ddded0ed34513d587
"git-rebase" Variables during `git rebase upstream/feature-branch-name`
orig_head = 92b2194e3adc29eb3fadd93ddded0ed34513d587
onto_name = upstream/PartsInterface_E-01960
onto = c1452be62cf271a25d3d74cc63cd67eca51a127d
mb = 438cc917c6f517913c9531e0a38f308d3aa13f0b
revisions = c1452be62cf271a25d3d74cc63cd67eca51a127d..92b2194e3adc29eb3fadd93ddded0ed34513d587
The revisions calculated by "git rebase" are different than git pull.
Note: 634b6228
is a merge commit that only exists on the local branch, it no longer exists upstream.
Upvotes: 2
Views: 597
Reputation: 490078
The answer is version-dependent, because the implementation of git pull --rebase
and git rebase
(plain rebase
, without a lot of specific arguments) has evolved rather a lot between git 1.7 and git 2.3.
In general, the more modern your git version, the less difference there should be (I can't say "is", just "should be", :-) in part because I haven't followed the exact path of development over time). Here's the general thrust though:
With no arguments, git rebase
looks for the "upstream" automatically. The definition of "upstream" in this case is the same as what git pull
uses, so if you can git pull --rebase
(with no additional arguments), there must be an automatic upstream.
When you run git pull --rebase
(with no additional arguments), this first runs a git fetch
that brings over new commits as usual. At this point—when you have the new commits, and also all old commits because you haven't yet updated the remote-tracking branch—it is easy to detect an unusual condition that does actually occur, namely, an upstream "history rewrite".
Since it was easy to detect, old versions of git do/did detect this during git pull --rebase
and would automatically compensate for the rebase. (Those old versions of git, however, would (deliberately) fail to update remote-tracking branches. As a result this detection was itself somewhat limited as well.)
When git fetch
was changed in git 1.8.4 to update remote-tracking branches even when invoked by git pull
, the case that git pull
could easily detect, became harder to detect again. If, however, your remote-tracking branches have reflogs (and usually they do), the reflogs could supply the information. So the pull
script, and git in general, was modified / enhanced to use the reflogs to extract the "fork point" information (see the --fork-point
section in the git merge-base
documentation).
Given the ability to find a "fork point", git rebase
can (and does, in recent versions of git) employ the same magic as git pull --rebase
, so there should be no difference in behavior. Depending on which version of git you have (between 1.7 and 2.3), however, git pull
may be finding the correct "fork point" while git rebase
is not. And given your note:
Applying: D-06437
(note: this commit already exists, but a merge conflict upstream has changed its hash)
there was in fact an upstream "history rewrite", so you need this new "smarterized" version of git rebase
to automatically discover it. The older, dumber/simpler rebase simply assumes that if you have this sequence of commits in your repository "now" (after fetch
ing):
OldD <-- (origin/branch used to be here)
/ \
* - * B07241 <-- branch
\
D06437 <-- origin/branch (now)
and you run git rebase
, then you want to take commits OldD
and B07241
and apply them on top of D06437
. The conflict occurs when trying to cherry-pick OldD
and apply the resulting diff to commit D06437
.
It's easy to fix this in an interactive rebase because you can simply delete the "pick" line for the "old" D commit.
Upvotes: 2
Reputation: 11979
This will not address your question about differences between git rebase
and git pull --rebase
, since I don't know the technical details either (and I practically never use git pull
) but I do know the following works:
origin/branch
target some new commit A.branch
and origin/branch
share a common ancestor ancestor
.rebasing branch
(git rebase origin/branch
) can be done like this:
git reset --hard origin/branch # your branch is now the same as origin
git cherry-pick ancestor..B # now you pick up the commit between the two branches.
I noticed that git cherry-pick
worked better than git rebase
, but I think that's only because it reapply the commit instead of replaying the history.
Upvotes: 0