hasanghaforian
hasanghaforian

Reputation: 14022

Confusing name of Git branch: Upstream or Downstream?

In Pro Git you can see:

Checking out a local branch from a remote-tracking branch automatically creates what is called a tracking branch (and the branch it tracks is called an upstream branch). Tracking branches are local branches that have a direct relationship to a remote branch. If you’re on a tracking branch and type git pull, Git automatically knows which server to fetch from and branch to merge into.

So Git knows it has directly merge from another branch into tracking branch and it seems better to name these branches as downstream branches instead of upstream. But why they are named as upstream?

Upvotes: 0

Views: 495

Answers (1)

torek
torek

Reputation: 488183

It's not entirely clear to me but I think this is the source of your confusion:

So Git knows it has directly merge from another branch into tracking branch ...

This is wrong. That's not what a remote-tracking branch is and does.

First, let's make a note of how git references work. Regardless of the actual reference (branch name, tag name, refs/stash for the git stash command, or whatever), a reference, in git, is simply a name that resolves to a hash, such as 0f90a088a1161e812e7cf9ef9724f9bfcb4267ec. Most (though not all) references have a full name that starts with refs/. (Some references are indirect or symbolic, meaning they contain the name of another reference. The special HEAD reference—this is one of the few that does not start with refs/—is normally symbolic as it normally contains the name of the current branch. A symbolic reference must eventually resolve to a regular reference, which must in turn resolve to one of those SHA-1 hash values, with one special exception for what git calls an unborn branch, which I won't address here.)

Next, let's define the term "remote-tracking branch": A remote-tracking branch, in git, is simply any reference whose full name begins with refs/remotes/. Similarly, a (regular, ordinary, not-remote-tracking) branch1 is any reference whose full name begins with refs/heads/ (and for completeness, a tag is any reference whose full name begin with refs/tags/).

Typically your (single) remote is named origin, since git clone url creates a remote named origin. This means your local git repository has origin/master as one of its remote-tracking branches. The full name of origin/master is actually refs/remotes/origin/master, which—by definition—makes it a remote-tracking branch.

The initial git clone url does one other thing that you normally never need to adjust, but is useful to know because it is how remote-tracking branches are actually implemented. The git clone sets up the fetch refspecs for origin, which you can see with:

git config --get-all remote.origin.fetch

The output from this command will read:

+refs/heads/*:refs/remotes/origin/*

Note that on the left hand side of the colon (:) character, we see refs/heads/*. These name (regular, ordinary, local) branches; the * part matches everything, so this means every branch as seen on the remote. On the right, we see refs/remotes/origin/*. These name remote-tracking branches—specifically, our own remote-tracking branches for the remote named origin. The * in this case is replaced with whatever our git matches when it's connected to origin during a git fetch.2 (For completeness, the + at the front sets the force flag, so that git fetch will always update the remote-tracking branch.)

What this means is that whenever you fetch from the remote, your git makes your remote-tracking branches match their git's branches. It does so not by doing any merging, but simply by copying whatever they have and then creating or replacing the remote-tracking branch so that it exactly matches what they have at the time the git fetch runs.

This means that a remote-tracking branch is your git telling you "this is what I saw on the remote, the last time I checked."

This is also why we can call the remote an "upstream". Suppose, for instance, that remote origin refers to the official github repository for some project. You clone the project to a local repository (not even another github fork, just a regular git repository on your local machine) and maybe make a few changes. Then, after some time, you may decide to find out what's happened in the official repository since you cloned. You need only run git fetch origin to pick up their changes. If they have changed their master branch, you will immediately and automatically see these changes in your origin/master remote-tracking branch.

Since your work is derived from their work, your code is "downstream" of theirs, and theirs is "upstream" of yours. Once you have fetched their new work, it's up to you how, or even whether, to modify your earlier changes to use their new version. You can merge their new work, rebase onto their new work, ignore their work, or do whatever you like—but as long as your work is based on theirs, they are "upstream" of you and you are "downstream" of them.

If your local branch—whatever its name is, master or some other name—has one of your remote-tracking branches set as its "upstream" (with git branch --set-upstream-to or similar), your git status commands will, after you git fetch, tell you about commits you have that they don't ("ahead 3") and commits they have that you don't ("behind 9"). You can then use git merge or git rebase to merge or rebase your changes with theirs, but this is not automatic, it's done only when you tell your git to do it.3


1The term branch in git is actually ambiguous: it can mean either a branch name, which is that thing starting with refs/heads/, or it can refer to a portion of the commit graph. See this question for details.

2For some time, since git 1.8 or so, git also opportunistically updates remote-tracking branches on git push operations. Specifically, if you run git push remote ref and the push successfully updates ref on remote, the push code also updates your remote-tracking branch for remote's ref. For example, after a successful git push origin master, your git knows where origin/master should point, so your git updates your copy of origin/master.

3If you run git pull instead of git fetch, know that git pull starts by doing git fetch, then running either git merge or git rebase. This is meant as a convenience short-cut, since it's pretty common to want to merge or rebase and then remember at the last moment that you also need to git fetch first. For beginners, though, I think it's much better to keep the two steps separate.

Upvotes: 2

Related Questions