Rob Luca
Rob Luca

Reputation: 193

How is fast-forward implemented in Git so that two branches preserve?

In libgit2 headers I read that after fetch, if fast forward is signalized to be available during analysis, then what's needed is simple checkout of the fetched tip (and altering of heads/<branch>). It seemed logical, however I wonder, how can the refs/heads/<branch> be differentiated from remotes/<remote>/<branch> ? Commits have parents embedded in them – that constitutes a single history, while there should be two histories, with additional "Merge" commits in both of them. How is fast-forward implemented then? It really leads to SHA hashes to repeat in both histories, so libgit2 seems to have its point.

Upvotes: 0

Views: 787

Answers (2)

ElpieKay
ElpieKay

Reputation: 30858

Since Git is a CVCS, the local repo and the remote one can be considered logically as one repo. refs/heads/master(r) is refs/heads/master in the remote repo.

after clone

The remote master has been updated by others.

update remote

Now we want to update the local master with the remote master via git pull origin master.

First git fetch origin master is run internally and remotes/origin/master is updated. This is actually a fast-forward merge. remotes/origin/master moves from C to E.

after fetch

And then git merge remotes/origin/master is run. refs/heads/master is updated via a fast-forward merge. It moves from C to E too.

enter image description here

If refs/heads/master was updated by us before the pull, it would be a true merge instead. Before the pull it was like

another case

Here we have two options, git pull origin master or git pull origin --rebase master.

The fetch process is the same.

another fetch

Without --rebase, it would be a true merge.

true merge

With --rebase, it would be a rebase.

rebase

P could be just ignored now since it's not referred to by any branch or tag now.

Taking the last case for example, if we now push refs/heads/master to the remote, refs/heads/master(r) would be updated by a fast-forward merge in the remote repo.

final graph

Upvotes: 3

Edward Thomson
Edward Thomson

Reputation: 78623

A "fast forward" isn't a merge at all, it just sets the branch pointer to "their" side of the merge.

Consider some history:

1 -- 2 -- 3 -- 4 -- 5 -- 6

And consider that my master branch is at commit 4, and the server's master branch is at commit 6.

If I wish to do a merge of the parent's master branch into mine (perhaps as part of a pull), the first step of a merge is to find the merge base, or the common ancestor between those two commits.

In this case (above), 4 is the common ancestor between 4 and 6. So there are no commits locally that the server doesn't have, so you can simply "fast forward" your branch, updating its pointer to point to 6. You will not have lost any data by doing this (though you will have also not created any - if you had forced an actual merge with a "no fast forward" option then you would create a new merge commit between 4 and 6.

So libgit2's git_merge_analysis function, which identifies whether a merge can be fast-forwarded or not, literally just checks to see if the "yours" branch is your own common ancestor with the "theirs" branch.

This is, of course, not the typical merge case. Consider:

             F -- G
            /
A -- B -- C -- D -- E

In this case, if you are at commit G locally and the upstream is at commit E then your common ancestor is C and a fast forward is not possible, you must perform a true merge.

Upvotes: 3

Related Questions