1

In our team we want to maintain a linear git history.

We have been running these commands:

user updates some files

git add .
git commit
git pull --ff-only

But we often see:

fatal: Not possible to fast-forward, aborting.

I am wondering if the git commands I am using could be improved? For example, by using git pull --rebase instead of git pull --ff-only ?

Steps to reproduce.

create 3 directories:

mkdir gitserver
mkdir gitclient1
mkdir gitcleint2

# server side
cd gitserver
git init --bare diverge.git

#client 1
cd gitclient1
git clone ../gitserver/diverge.git
cd diverge
<create index.html file with a few lines of text, save.>
git add .
git commit -m "first commit by user 1"
git push

#client 2
cd gitclient2
git clone ../gitserver/diverge.git
cd diverge
add a new line to index.html
git add .
git commit -m "changed line made by user 2"
git push

# back to client 1
$ git pull --ff-only
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From D:/projects/git/git_diverge_test/gitclient1/../gitserver/diverge
   7208075..ce21832  master     -> origin/master
fatal: Not possible to fast-forward, aborting.

Why do I get the fatal message?

Shouldn't git have been able to pull down the commit on top of the existing commit?

And if I run gitk --all I see that master is on a different 'branch' of the tree from remotes/origin/master.

One approach that works to an extent is to use:

git pull --rebase
<fixup any issues>
git add .
git rebase --continue

Is git pull --rebase more dangerous in any way?

Angus Comber
  • 9,316
  • 14
  • 59
  • 107
  • What's wrong with a regular pull? – evolutionxbox Nov 15 '20 at 15:29
  • @evolutionxbox I think the idea is to maintain a linear git history, ie no merged branch offs. I think that git pull if it fails might cause a non-linear history. Although I might be wrong there. – Angus Comber Nov 15 '20 at 15:32
  • Why is linear history desired? (curious) --- Also, what is the concern about using `--rebase`? What is the thinking that it could be more dangerous? – evolutionxbox Nov 15 '20 at 15:34
  • @evolutionxbox I don't know the reason for the decision. – Angus Comber Nov 15 '20 at 15:35
  • What do you get if you run `git branch -vv` from client 1 and client 2? – prosoitos Nov 15 '20 at 16:08
  • @prosoitos user1: $ git branch -vv -> * master 3e32b6a [origin/master] a 3rd new line added by user 1 – Angus Comber Nov 15 '20 at 17:13
  • @prosoitos user2: $ git branch -vv -> * master fff89e8 [origin/master: ahead 1, behind 1] a 3rd new line added by user – Angus Comber Nov 15 '20 at 17:14
  • 2
    I tried your steps to reproduce. They don't reproduce. You must have committed something else in client1 after client2 cloned, and caused client1 to have a new commit at the tip of the branch. That is what's causing the non fast forwardable error. – jingx Nov 15 '20 at 18:01
  • Yes. And you can see it in the output of `git branch`: `ahead 1` is what you would expect, but `behind 1` clearly tells you that you have created diverging branches. – prosoitos Nov 15 '20 at 19:41

3 Answers3

9

To put your local work on top of the remote branch : use git pull --rebase.

If this is the intended workflow for all your team : have everyone set

git config pull.rebase true

and git pull will now translate to git pull --rebase.


The intended usage of git pull --ff-only is :

  • when you don't have any local work on top of current branch,
  • and you want to update that branch to its remote counterpart,
  • and make sure you don't accidentally create a merge commit.

If you do have local work on top of the branch, git pull --ff-only will never do any action :

  1. either your version of the remote is already up to date (so no updating is needed),
  2. or someone else modified it, and integrating those updates will never be a fast forward merge (so the update will be canceled).

If you want to bring in the remote branch to check if it was updated : use git fetch

git fetch                # will update all remote branches
git fetch origin branch  # if you want to update one single branch from the remote

After running git fetch, you can compare your branch and origin/branch (use a git viewer, or git log --graph --oneline HEAD origin/branch), you can rebase your work on top of origin/branch (running : git rebase origin/branch), etc ...

LeGEC
  • 46,477
  • 5
  • 57
  • 104
3

Short answer: use git pull --rebase

First of all your example does not work, client one would need to commit something else before the pull --ff-only, but lets assume we did this.

Why git pull --ff-only fails?

This command instructs git to fetch the latest state from the remote an then fast forward the branch if possible. So if we have different changes on the remote and local branch it will stop, because that is what we have told it to do with --ff-only

What will git pull --rebase do?

It will also fetch, but after that will do a git rebase origin/master. Witch will take all commits on master that are not already on the remote and rebase them to the end of origin/master. Of course you might have to fix any merge conflicts that arise. The only danger I see is you should test what happens if you have any merge commits in your branch, but since you want a linear history this should not be an issue for you.

ian
  • 1,112
  • 1
  • 5
  • 15
3

Fast-forward only workflow

The workflow you are presenting should allow fast-forward only pulls. However, for it to work, you need a perfect coordination between members of the team working on different clients and you can only work on one client at a time.

For instance, while someone works on client 1, people cannot create commits on client 2 and vice versa. If commits are created concurrently on both clients, you will end up with diverging histories that will need to be merged with a commit or rebased: a fast-forward merge will not be possible.

This is not really practical (as experience taught you: the error message you are getting and the outputs of git branch -vv and gitk --all all show you that you ended up with diverging histories on the 2 clients without meaning to and without realizing it). It is also a non-efficient workflow since all but one clients have to be on stand-by at any one time. A good version control workflow should allow different people to work concurrently and then integrate their work into the project.

Rebase

As soon as different people have created commits concurrently on different clients, you need merge commits or rebases. Merge commits create non-linear histories, so if you want a linear history, you need to rebase.

This can indeed conveniently be done with git pull --rebase.

Rebasing is a re-writing of history and it is dangerous to do on commits that have already been pushed to a remote, but it is safe to do on unpublished commits.

When you run git pull --rebase, the current branch gets rebased on top of the freshly fetched upstream branch. So, it is the history of your local branch that gets re-written, not that of the upstream one. Thus, as long as your team is only using one remote to synchronize the work, you can merge changes from that remote into yours with git pull --rebase without risking to create issues for others.

Re-writing history on a non-linear workflow

Another alternative would be to merge with git pull --ff which will do fast-forward merges when possible, but will create merge commits when not possible, then rewrite the project history every now and then to make it linear. These re-writing of history could be coordinated at the level of the team, blocking any activity of the project when they happen, to prevent any issue.

One way to do this would be to use git reset as is presented under the section Squashing near the bottom of this section of the Pro Git Book.

One disadvantage of this method however is that you will loose the small steps that each commit introduced. It is also a much more tortuous approach and it can be dangerous if someone is not respecting the freezing of activity during the re-writing of history (not to mention that such freezes aren't efficient).

Conclusion

In conclusion, using git pull --rebase is probably the best solution here.

Note

You can set this as the default behaviour for git pull with:

git config --global pull.rebase true

(Remove --global if you only want this for one project).

prosoitos
  • 6,679
  • 5
  • 27
  • 41
  • I think you're being a little bit dramatic about `--ff-only`. You may want to use `git pull --ff-only` so that you can fast forward if possible, but not take any automatic actions otherwise. If it fails, you can investigate for yourself and `git merge` or `git rebase` manually, or possibly do something completely different. Changing the behavior of `git pull` doesn't prevent anyone from merging or rebasing. – portalguy15837 Sep 15 '22 at 22:22