Why remote branches are shown as tracked, even though they are not tracked locally?

Viewed 138

I recently cloned from this remote repository. https://github.com/dave-githubber/my-first-github-repository

Right after i cloned it, i did:

(base) hakan@hakan-VirtualBox:~/CompleteGitGuide/temp/my-first-github-repository$ git show-ref
1fd2ddb6f29becd8d3f3148271e230490d424f85 refs/heads/main
1fd2ddb6f29becd8d3f3148271e230490d424f85 refs/remotes/origin/HEAD
a45b41988603994aac1292ff086ba913567276e2 refs/remotes/origin/feature-1
1a6fd0202b24acb790e52a9a4dba4a26cc94a39a refs/remotes/origin/feature-2
1fd2ddb6f29becd8d3f3148271e230490d424f85 refs/remotes/origin/main
1fd2ddb6f29becd8d3f3148271e230490d424f85 refs/remotes/origin/temp

Note that there is only one tracking main branch:

(base) hakan@hakan-VirtualBox:~/CompleteGitGuide/temp/my-first-github-repository$ git branch -vv
* main 1fd2ddb [origin/main] Create hello-github.txt

However, when i do git remote show origin , it says that feature-1, feature-2 and temp branches are also tracked:

(base) hakan@hakan-VirtualBox:~/CompleteGitGuide/temp/my-first-github-repository$ git remote show origin
* remote origin
  Fetch URL: https://github.com/dave-githubber/my-first-github-repository.git
  Push  URL: https://github.com/dave-githubber/my-first-github-repository.git
  HEAD branch: main
  Remote branches:
    feature-1 tracked     <<< why?
    feature-2 tracked     <<< why?
    main      tracked
    temp      tracked     <<< why?
  Local branch configured for 'git pull':
    main merges with remote main
  Local ref configured for 'git push':
    main pushes to main (up to date)

Can you explain why  feature-1, feature-2 and temp branches are shown as tracked locally?

1 Answers

TL;DR

This just means that your Git has, and is updating, origin/feature-1, origin/feature-2, and origin/temp.

(It's also yet another example of how Git badly overloads various terms, like "remote", "tracking", and "branch". Compare an untracked file to a tracked branch and to the git branch --set-upstream-to option. In the bad old days, instead of saying that master had origin/master as its upstream, we would say that master "tracked" origin/master. Meanwhile origin/master is a remote-tracking name, or remote-tracking branch name ... which has nothing to do with tracked vs untracked files.)

Long

The git remote command is an odd one. To see why, let's look at some other commands first.

Most Git commands operate purely locally. The big two exceptions are git fetch, which obtains (some or all of their) commits from some other Git repository, and git push, which sends (some or all of your) commits to some other Git repository. A third exception, git ls-remote, consists of running the very first part of git fetch—calling up another Git repository—and then just printing out the information that git fetch would get from that repository, showing their branch and tag names, for instance.

The git pull command just runs git fetch first, then uses a second Git command that operates locally—normally git merge, but you can configure it to use git rebase instead—to incorporate the commits that git fetch just fetched. So, while pull does contact some other Git, it only does so because it starts with fetch.

The three commands that definitely contact some other Git usually do so with a remote, which is a short name like origin. This short name is used as part of a key in a key-value pair, so that your Git can get the correct URL at which to call up that other Git.

It's temping to think of all other Git commands as operating only locally. But for the git remote command, this is only half right:

  • git remote add, git remote remove, and git remote set-url are all purely local operations. Note that these add or remove a remote like origin, or—set-url—change the stored URL.

  • git remote update is mostly a funny way to spell git fetch; git remote prune is sort of a funny way to spell git fetch --prune; and git remote set-head can (but does not always) invoke the same code as git ls-remote: when using git remote set-head --auto, your Git has to figure out what their Git has as their HEAD, which means calling up their Git and asking them.

  • git remote show contacts the remote if you don't use the -n option. Like the others, it uses the same code that git ls-remote or git remote set-head would use, here, to ask the other Git about its various branch names. With -n, it doesn't do that, and just looks at your (local) remote-tracking names.

Some of this stuff predates the invention of remotes. Before Git had remotes, like origin, you had to type in the full URL every time. This was terrible.1 So people tried several options to get around this. Only one of them worked really well, but Git today still supports several others; you'll see these described in the git fetch and git push documentation.

When the concept of remotes was invented, though—short names like origin that can hold the URL—Git grew the notion of remote-tracking names.2 These let your Git remember their Git's main, develop, feature-1, and so on. The way we use them is simple and clean: our Git creates or updates our origin/main from their main because we ran git fetch origin. If our Git saw that they had, at that time, feature-1, our Git creates or updates our origin/feature-1 too. The precise details here changed over time,3 but in modern Git, we have these remote-tracking names. All we have to do to update them is run git fetch.4

You can, however, choose not to have your own Git repository keep track of all of their (origin's) branches. If you like, you can set up a so-called single branch clone. Here, you pick some branch on origin that you want your Git to copy to origin/whatever:

  • You can do this at git clone time with the --single-branch option.

  • Or, you can do this at git remote add time with the -t option.

You can update which branch(es) you have your Git copy to remote-tracking names with git remote set-branches. This way, you can set things up to "track" (as in, create remote-tracking names for) two branches, instead of just one.

The git remote show command will either read their branch names (no -n: git remote show runs git ls-remote internally) or guess at their branch names (-n), and will tell you which of their branch names you're having your Git copy to remote-tracking names. This is what it means when it says tracked.


1To see why, try typing in the full URL every time. It gets tiring, and the opportunities for typos are huge.

2The Git documentation calls these remote-tracking branch names. I find that the word branch here just clutters this up. They're not branch names in your repository, in that git switch won't switch to them, for instance; you have to use git switch --detach and you'll end up with a detached HEAD. This is all fine, but goes to show that the name is not a branch name, so it makes sense not to call it one either.

3Before Git version 1.8.4, some kinds of git fetch didn't bother updating any remote-tracking names at all. Some Linux distributions still ship with these ancient versions of Git. Use git --version to find out which version you have.

4There's one hitch with these remote-tracking names. Suppose origin has a temporary branch name, like temp. You run git fetch origin (or just git fetch, which fetches from origin) and you get origin/temp in your repository.

Then—a minute later, or next week, or whatever—whoever controls origin deletes their temp branch. You run git fetch again; your Git calls up origin and finds the main branch, but no temp branch. So your Git updates your origin/main ... but does nothing with your origin/temp. They don't have one, so there is no need to update yours.

This leaves you with a stale origin/temp. It's just leftover junk. You can delete it manually, or you can run git fetch --prune origin, or git remote prune origin. Your Git will call up their Git, look at their branch names, see that they don't have a temp, see that you do have an origin/temp, and clear out the leftover junk.

Or, you can set fetch.prune to true in your configuration. I do it in my per-user global one:

git config --global fetch.prune true

This makes every git fetch act like git fetch --prune, automatically, to clean out the deadwood remote-tracking names.

This probably should be Git's default, but Git is still mostly compatible with Git version 1.7, and Git version 1.7 doesn't do that, so modern Git still doesn't.

Related