Here's what I did.
- Made changes to my README directly in my github repo with 3 commits.
- made 4 more commits locally without pulling those 3 commits in.
Now when I try to push it says pull those remote commits first but when I do git pull it show error.
warning: Pulling without specifying how to reconcile divergent branches is discouraged. You can squelch this message by running one of the following commands sometime before your next pull:
git config pull.rebase false # merge (the default strategy)
git config pull.rebase true # rebase
git config pull.ff only # fast-forward only
So I just ran the command git config pull.ff only
And now when I pull this is what it says fatal: Not possible to fast-forward, aborting.
CodePudding user response:
Ok.... the problem with your current setup (with pull.ff only
) is that it is a very restrictive setup.
Suppose this startup diagram
A <- B <- C <- D <- (main)
You start a feature branch from main:
A <- B <- C <- D <- (main)
^
\- (feature)
After a while, the chart is like this:
A <- B <- C <- D <- F <- G <- (main)
^
\- (feature)
Notice how feature
has not moved. At this moment, feature
is pointing to an ancestor of main
and so a fast-forward would work, which would do this:
A <- B <- C <- D <- F <- G <- (main)
^
\- (feature)
Because, as the name implies, a fast-forward only moves the pointer, it does not do any rebasing/merging in the process.
However, on a normal feature branch, you would introduce commit, so you would have something like this:
A <- B <- C <- D <- F <- G <- (main)
^
\- I <- J <- K <- (feature)
And in this case, given that feaure
is not pointing to an ancestor of main
, it is not possible to do a FF.... but your setup is asking to only run pulls if it's a FF.... so, it fails. You need to be able to either rebase or merge for this to work so pull.ff only
is a no-go.
CodePudding user response:
Note: I'm not disagreeing with eftshift0's answer here, in that I agree that setting pull.ff
to only
is restrictive. But I would note that this restriction might well be a good idea. It's one I would want, and in fact, I've configured Git this way myself.
When git pull
or git pull origin branch
fails with the fatal: Not possible to fast-forward, aborting
error, it's time to inspect the incoming commits, using git log
, e.g.:
git log --graph --decorate --left-right --boundary ...@{u}
(or the same with --oneline
added), or:
git log --oneline ..@{u}
to show "incoming" commits (already fetched, just not yet merged or rebased-upon) and:
git log --oneline @{u}..
to show "local-only" commits (existing in my repository but not in the other Git from which I've fetched). These git log
commands all make use of the @{u}
construct, which is short for branch@{upstream}
.
The upstream of a branch name is something you can set, but typically it's already set correctly by the time you're doing this. For instance, if you are on your develop
branch, the "upstream" of develop
is probably origin/develop
, which is probably correct. This particular concept of upstream—Git is bad here and uses the word upstream to mean multiple different concepts1—is the one we're talking about here: whatever branch you're "on" right now, it's the "upstream" origin/
version of this same name that you probably want to use with git rebase
, or git merge
, or whatever operation it is you want to do.
Writing out develop@{upstream}
would do the trick, but what if you're not on develop
after all, but rather on feature/long
? Now you have to write feature/long@{upstream}
. Git has a simple solution: if you leave out the branch name, and just write @{upstream}
, it means the upstream of the current branch. So that's what we do here. We can also write HEAD@{upstream}
to be more explicit, but we can leave out the HEAD
part when it's obvious.
Meanwhile, the two-dot notation, HEAD..HEAD@{upstream}
or A..B
or main..origin/main
or whatever also has a very specific meaning. With git log
—like most Git commands2—it means "using the commit I specify on the right, find lots of commits; stop finding commits when you find those using the commit I specify on the left." This means that HEAD..HEAD@{upstream}
or HEAD@{upstream}..HEAD
is actually rather useful.
If you're not used to Git, the last paragraph probably just seems like a lot of random words. But using the same kinds of diagrams that eftshift0 drew, we can make sense out of this. Here's what is going on:
I--J <-- feature/long (HEAD)
/
...--G--H <-- main
\
K--L <-- origin/feature/long
Here, I'm "on" my branch feature/long
. Its upstream is origin/feature/long
. I, and they, both started with common-starting-point commit H
: I made two commits I-J
, and they—whoever they are—made two commits K-L
.
When I run git fetch
against origin—remember that the first command that git pull
runs is always git fetch
—I got their K-L
commits, and my Git software updated my origin/feature/long
to remember commit L
, which I now have. So that's why the diagram looks like this: that's what is now in my Git repository. (Their Git repository lacks commits I-J
entirely, as yet.)
Now, note that HEAD
literally means feature/long
, and HEAD@{upstream}
means origin/feature/long
. Note further that A..B
means "commits found by starting at B
and working backwards, excluding commits you can find by starting at A
and working backwards". If we put these together with HEAD
and HEAD@{upstream}
as the A
and B
parts, we're asking Git to find:
- commits like
L
that we can find withorigin/feature/long
; - including commits like
K
that we can find by starting there and working backwards, as Git likes to do; - but not including commits like
J
,I
, andH
.
The result is that git log HEAD..HEAD@{upstream}
shows commits L
and K
, and then stops! That's exactly the set of "incoming" commits.
Meanwhile, reversing the two arguments, with git log HEAD@{upstream}..HEAD
, shows commits J
and then I
, and then stops: these are exactly the commits I have that they don't.
Adding --graph
and/or --oneline
helps display these sorts of things better. Since we can drop the word HEAD
when it's clear to Git that we mean HEAD
, and since @{u}
is short for @{upstream}
, we're allowed to write ..@{u}
and @{u}..
.
In fact, I use git log ..@{u}
often enough that I made a Git alias for it:
[alias]
lin = log ..@{u}
This allows me to run git lin
to l
og (or look at) in
coming commits. (I have an incoming
alias that includes --oneline
, and a git outgoing
that has the --oneline
and swaps the @{u}
side.)
The three-dot form, as in git log ...@{u}
, is fancier: it means commits found by either left or right side, but not both. This finds commits J-I
and L-K
, in other words, for the graph drawn above. The drawback here is that you can't tell which commit is on which "side". Adding --left-right
fixes that; adding --graph
draws the graph; and adding --boundary
tells Git to include commit H
as well. I don't really use this form though and do not bother with an alias for it: "incoming" and "outgoing" are my go-to aliases here.3
In any case, after inspecting the incoming commits, I choose whether to merge, rebase, or do something else entirely. Then I do that. I only want to do a fast-forward if I can do a fast-forward; in that case, having Git do it automatically with git pull
is probably OK. So setting pull.ff
to only
is what I want.
1I use the analogy of Git being like a party where everyone is named Bruce. Someone comes up to you and says: "Hey Bruce, Bruce asked me to tell Bruce that Bruce is looking for him. If you see Bruce, let him know, will you?" In Git, this crazy re-use of the same word for multiple different things, over and over again, is very confusing—rather like it would be at this Bruce party.
2Again, Git isn't always very nice here: some Git commands use the two-dot notation for some other meaning. The git diff
command is the most obvious culprit here. It uses both the two-dot and the three-dot notation to mean something different. We won't cover that here.
3They are also, not exactly coincidentally, two Mercurial forms of hg log
.