This https://git-scm.com/book/en/v2/Git-Internals-The-Refspec says
The + tells Git to update the reference even if it isn’t a fast-forward.
What does this mean?
This https://git-scm.com/book/en/v2/Git-Internals-The-Refspec says
The + tells Git to update the reference even if it isn’t a fast-forward.
What does this mean?
As Raymond Chen noted in a comment, you must put together several pieces here:
+
sets the "force" flag, i.e., do the update even if the operation is not a fast-forward; andThe first part is pretty straightforward, but the second part can be kind of opaque. It's a bit like the first few paragraphs in Sherlock Holmes and the Adventure of the Dancing Men.
A name, or ref or reference in Git, such as the branch name refs/heads/develop
, contains one hash ID, usually that of a commit. The notion of fast-forwarding only makes sense for refs that identify commits anyway, so from here on we shall assume this.
Commits, in Git, produce a Directed Acyclic Graph or DAG, because each commit stores the raw hash IDs of its parent commits. We may apply a simple transitive closure algorithm on the individual commits to find this graph, for instance. This means that each pair of nodes in the graph can have "precedes" (math-symbol ≺
, kind of a bendy less-than) relationships: X ≺ Y means that node X comes "before" node Y in the DAG. If commits P and C are parent and child, then clearly P ≺ C and C ≻ P (C "succeeds" P).
Similarly, If PP is the parent of P—the arrows go ⟵PP ⟵P ⟵C in Git, as they point backwards instead of forwards—then PP ≺ C as well. Here PP is the grandparent of C.
Because Git finds commits by starting at the commit to which some label points, then working its way backwards, if we take any set of commits:
... ⟵PP ⟵P ⟵C ...
and have some name like develop
pointing to PP, and move that name "forwards"—against the direction of the internal arrows—to point to C, we can still use that name to find PP. We just start from C now and work backwards two hops this time.
This kind of motion, where a name that used to find some commit now finds some descendant of that commit, is a fast-forward operation. The resulting name motion does not "lose" any commits: it merely adds all the descendants. This is true even if the name-motion is rather tangled. For instance, suppose we have this, where each round o
is a commit and the two name
s are branch names:
o--o--o--o--o
/ \
...--o--o <--name1 o <--name2
\ /
o--o--o--o--o
We now propose to Git that it should move the name name1
to point to the same commit as the name name2
, giving:
o--o--o--o--o
/ \
...--o--o o <--name1, name2
\ /
o--o--o--o--o
All of the commits on the left-middle are still reachable from the name name1
; it just requires going through either the top or bottom row.
Mathematically, then, if:
then this name-motion is a fast-forward. For simplification purposes, if we ask Git to "move" from X to X (or equivalently, set Y=X), Git actually tests for X ≼ Y, but the idea should now be clear.
Hence, a reference update, as made by git push
or git fetch
, will work on a branch if:
Thus, +
forces non-fast-forward updates.
"... in five minutes you will say that it is all so absurdly simple"