0

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?

Snowcrash
  • 80,579
  • 89
  • 266
  • 376
  • 1
    Does this answer your question? [Git - What is "Refspec"](https://stackoverflow.com/a/44333810/5625547). The linked answer explains the `+` in some more depth. – 0stone0 Oct 27 '21 at 14:53
  • 1
    The implied question here is "what does git mean by fast-forward", so does this answer your question? [What does "Git push non-fast-forward updates were rejected" mean?](https://stackoverflow.com/questions/4684352/what-does-git-push-non-fast-forward-updates-were-rejected-mean) – IMSoP Oct 27 '21 at 14:58
  • No. It doesn't explain what updating the reference means. Nor does it explain the alternatives to a fast-forward. It explains what "Git push non-fast-forward updates were rejected" mean. – Snowcrash Oct 27 '21 at 15:14
  • So if you put the pieces together, you get your answer: It means "Do not reject non-fast-forward updates". – Raymond Chen Oct 27 '21 at 17:08
  • say `git help glossary` and look up fast-forward. – jthill Oct 28 '21 at 00:43
  • So by `update the reference even if it isn’t a fast-forward` it means `always update the reference`? – Snowcrash Nov 01 '21 at 09:25

1 Answers1

1

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; and
  • a fast-forward is a property of a name update.

The 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 names 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:

  • name identifies commit X now;
  • we ask Git to change name to identify commit Y instead; and
  • X ≺ Y ("X precedes Y")

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:

  • the update is a fast-forward, or
  • the update is forced.

Thus, + forces non-fast-forward updates.

"... in five minutes you will say that it is all so absurdly simple"

torek
  • 448,244
  • 59
  • 642
  • 775