Does the direction or order of a merge/rebase make a difference?

Let's say you pull from a remote repository while you're working on your feature branch and new commits are added to the master branch.

You finish working on the feature branch, commit your changes and then merge/rebase it onto master. Where in the master branch are the feature commits inserted, before or after the pulled commits? Where are they inserted if you never performed a pull? Will it have the same result?

Also, what happens if you merge/rebase master onto the feature branch instead of the other way around? Where is master inserted into the feature branch if you merged/rebase before or after the pull? Will it have the same result? Will its history look any different from the aforementioned merge/rebase of the feature into the master?

More succinctly, do the direction of the merge and/or order of the merge have different outcomes on the resulting merge? I suspect it doesn't matter.

ASIDE: This question may be easy to answer for someone who Gits it, but with the hundreds, if not thousands, of documentation and tutorial resources out there, I've never seen it addressed directly. Without knowing the answer, Git can be extremely confusing to understand.

Because the order of operations is important in some Git concepts and in programming in general, it can lead someone to conclude it makes a difference. Moreover, we normally tend to think of revisions as occuring in chronological sequence and expect Git to work the same way. The user interfaces and commandline also play into this perception by implying an order of operations.

Other version tracking tools do a far better job of abstracting this cognitively corrosive operation.


TL;DR

Moreover, we normally tend to think of revisions as occurring in chronological sequence and expect Git to work the same way.

This is a terrible mistake. Git works in graph order, not chronological order. When the graph order is chronological—which is sometimes, but not always—then it works OK.

Long

(I'm not even going to touch on rebase here. For a proper discussion of rebase, see earlier postings. I'm just going to concentrate on the symmetries in merging, and the places where the seams can show.)

More succinctly, do the direction of the merge and or order of the merge have different outcomes on the resulting merge? I suspect it doesn't matter.

The answer is both no and yes.

To understand why this is the answer, you need to understand several things about Git.

First, let's consider how Git treats commits, in contrast to how many others do it (most others? all others? there must be at least one other VCS that does this too...). In version control systems, the process of making a commit in a branch adds the new commit to the branch; but in Git, the set of branches that hold a commit once it is made can be, and is, something that changes dynamically. That is, the commit, once made, is independent of any branch, and can, if you wish, be made on no branch.

In Mercurial, for instance, this is quite impossible. While Mercurial is otherwise very much like Git in many ways, the existence of a commit requires the existence of its branch. The commit is made on that branch, and forevermore is part of that branch. That is the only branch that holds the commit: the commit is permanently affixed to its branch.

In both Git and Mercurial, each commit has a unique ID, and each commit stores the unique ID of its parent (singular) commit, if it is an ordinary commit. By following this backwards-looking chain, starting from the last commit and working backwards, we can find the history of commits on the branch:

...  <-grandparent  <-parent  <-child

In Mercurial, these commits are forever on their branch, so we can write the branch name on the left:

branch-A:  ...--I--J

Finding the latest commit in Mercurial is easy as commits have a simple sequential number in the local repository (along with a unique hash ID that is used for sharing between repositories).

In Git, though, the branch names are moveable. Before commit J exists, the name branch-A stores the raw hash ID of commit I:

...--H--I   <-- branch-A

When making a new commit, Git simply writes the new commit's hash ID into the branch name, so that the branch points to the new commit:

...--H--I--J   <-- branch-A

Merge is both a noun and a verb

In version control, merging, the verb, or to merge some commits, is a process. The result—at least in both Git and Mercurial—is a merge commit, where we use the word merge as an adjective modifying commit, or for short, a merge (a noun).

What makes merge commits be merge commits is that they have two parents, bringing two lines of development together:

...--I--J
         \
          M
         /
...--K--L

Here, again, Git and Mercurial are basically the same, with one really important difference: In Mercurial, the merge commit is specifically on one branch, that being whichever branch you are on when you run hg merge. Commits—including merge commits—are permanently affixed to their branches. Once the process of merging is complete and the merge commit exists, that merge commit is on that one branch. In Git, the merge commit goes onto the current branch, but because we can move the name, in some sense it does not matter.

But we have to look at both parts: the verb part, to merge, and then closely at the noun part, a merge. (Aside: Git also allows merge commits to have more than two parents, while Mercurial restricts each merge to two parents. There's nothing that these funky merges, which Git calls octopus merges, can do that you cannot do with a series of pairwise merges, but an octopus merge can show intent more clearly in some cases.)

To merge, the verb

Suppose, for instance, that branch-A and branch-B were like this before the merge:

...--I--J   <-- branch-A

...--K--L   <-- branch-B

Before we can merge these, we have to trace both histories back far enough to find the merge base, the common commit from which these two lines of development diverged. (This is true in Mercurial too.) So let's fill in a bit more:

       I--J   <-- branch-A
      /
...--H
      \
       K--L   <-- branch-B

Here, commit H is the common starting point. In Git, commit H is on both branches at the same time (but not in Mercurial). Still, both version control systems do the to merge process in pretty much the same way: you start by picking one commit to check out, such as commit J using git checkout branch-A or hg update branch-A. Then you pick the other commit using the command's merge verb: git merge branch-B or hg merge branch-B. The VCS finds the appropriate merge base, i.e., commit H, and compares the contents of H to the contents of J to see what you changed:

git diff --find-renames <hash-of-H> <hash-of-J>   # what we changed

and repeats this same comparison to find what they changed:

git diff --find-renames <hash-of-H> <hash-of-L>   # what they changed

Git combines these changes into one big "all changes", applies those changes to the base (commit H), and makes the new commit.

(Mercurial does much the same, though there are some very important differences in terms of how Mercurial knows about renames. These only matter if you have renamed some files since the merge base. If you have renamed files, and have a rename/rename conflict, the merge order becomes very important. But let's assume there are no rename issues.)

The main interesting thing here is that you have control over how the changes are combined. In Git, you do this through the merge strategy and extended strategy arguments; in Mercurial, you do this through the chosen merge tool. If you use an "ours" strategy/tool, the VCS completely ignores their changes: the combined changes are just your changes, so the VCS makes the new merge commit using the same code as in the current commit. Here the merge order obviously matters: if we're ignoring their changes, we had best be sure who is "us"!

Even without an "ours" strategy, there may be conflicts between your changes and their changes. If so, both Git and Mercurial can be told: prefer mine or prefer theirs. These will give different results, so here again, the merge order matters. Of course, at this point there's a symmetric option: choose mine or choose theirs. If you swap roles, you can swap options—so while order matters, it's less crucial.

A merge, the noun

Let's assume there are no conflicts and no special ours/theirs happenings, and run git checkout branch-A; git merge branch-B. If all goes well, the VCS makes the merge commit M with its two parents:

       I--J
      /    \
...--H      M   <-- branch-A
      \    /
       K--L   <-- branch-B

In Mercurial, the merge order matters here, because once commit M is made, it's stuck to its branch forever. But Git lets us move the branch names after the fact. We can make M like this, leave it where it is, shove branch-A back one step to point to J again, and then move branch-B forward to point to M, giving:

       I--J   <-- branch-A
      /    \
...--H      M   <-- branch-B
      \    /
       K--L

which is pretty much the same as what we would get if we did git checkout branch-B; git merge branch-A. So it seems like here, the merge order is irrelevant (provided there were no hitches during the verb part). But there's something missing from the diagram here, and that's the notion of first and not-first parents!

The first parent of merge commit M, in both Git and Mercurial, is the "same branch" parent. That is, since we were on commit J when we ran the merge, the first parent of M is commit J. That leaves L as the second parent. If we shove the branch labels around in Git, we can tell that we have done so, because the first and second parents will still be in the other order.

So we see here that for Git, even when the to merge verb has no ordering issues, the resulting merge commit (adjective or noun) does have an order to it. It's up to you, the user, to decide whether that order matters to you. If not, the symmetry is available. If the commit parent order matters, the direction/order of the merge is as critical in Git as it is in Mercurial.

The graph is the key

In any graph-based version control system—hence in both Git and Mercurial—the graph is the history. In Git in particular, there is no such thing as file history; the graph is not only the primary history, it's the only history. (Mercurial stores file data in a more conventional fashion, so there is both commit history and file history, but the file history is not useful without the commit history anyway.)

To be able to understand what Git does and why, you need to know enough graph theory to drive or ride public transit around a city. That's not all that much much—but if you don't realize that Git has these one-way railroad tracks / streets / bridges, leading from the last commits back towards the first, many things will make no sense. Git starts at the end, and works backwards to the beginning. The branch names get you into the graph, at the branch-tip ends. Everything else is a function of the commit graph.


You finish working on the feature branch, commit your changes and then merge/rebase it onto master.

Where in the master branch are the feature commits inserted, before or after the pulled commits?

After: the merge/rebase will consider the current state of the master branch.
If that branch has evolved (with a pull), then the merge/rebase will use the current master HEAD (which now references the pulled commits)

Where are they inserted if you never performed a pull?

Same: it will use the current master HEAD (which don't reflect any new commits, since no pull has been done)

Will it have the same result?

No, considering master HEAD is different in both cases.

Also, what happens if you merge/rebase master onto the feature branch instead of the other way around?

First, don't: that is not a best practice.
But if you do, that merge/rebase will use the feature HEAD.

Where is master inserted into the feature branch if you merged/rebase before or after the pull? Will it have the same result?

Same result, since in both case, it uses the feature HEAD, which hasn't changed when you do (or do not do) a pull on master.