0

I was working on this local branch.

Then I do git fetch origin [new branch that only exists on remote repository].

Then I do git reset --hard FETCH_HEAD.

I believe I am now working on code of this new branch. But I still see the earlier local branch marked active (with an *) when I type git branch. I used to think git branch ALWAYS show the branch I am working on right now but it seems to me now not always so. Can someone who understand this share his / her understanding?

chema989
  • 3,962
  • 2
  • 20
  • 33

1 Answers1

0

You are using the wrong command here.

First, let's note that the word "branch" is ambiguous. It can refer to the data structure in the repository formed by starting from a commit and working backwards in history, or it can refer to a branch name, which is how Git can find the tip-most commit of the branch data structure. That is, the name points to one specific commit, and then from that commit, Git finds its parent commits, and their parents, and so on: the branch data structure. See also What exactly do we mean by "branch"?

git branch lists branch names

You are correct in thinking that git branch shows you your current branch:

$ git branch
  develop
  master
  revise
* target

The asterisk * character shows up in front of your current branch (and, if you have enabled colors, your current branch is shown in green as well, by default, although you can choose other colors).

By default, git branch shows specifically local (ordinary) branch names. You can ask it for remote-tracking branches with git branch -r:

$ git branch -r
  origin/develop
  origin/master

or even "all" branches with git branch -a.

Branch names are shortened forms; the full names are spelled refs/heads/master, refs/heads/develop, and so on. Remote-tracking branch names are spelled with refs/remotes/, followed by the name of the remote such as origin, followed by another slash /, and finally the name of the branch as seen in the other Git repository, on the remote.

Git normally strips off the refs/heads/ part for local branches, and the refs/remotes/ part for remote-tracking branches. This is what we see in git branch and git branch -r output. For some mysterious reason, git branch -a strips only refs/ from remote-tracking branches:

$ git branch -a
  develop
  master
  revise
* target
  remotes/origin/develop
  remotes/origin/master

git checkout changes which branch name you are on

Although there are numerous complications, git checkout is the command to use to switch from one branch to another.

Once you have a local branch, you can use git checkout to switch to it:

$ git checkout master
Switched to branch 'master'

and so on. Once you're on this other branch, git status will say On branch master, and git branch will put the asterisk next to master.1

I was working on [a] local branch.

Then I do git fetch origin [new branch that only exists on remote repository]

Don't do that. It's not that you can't do that, it's that writing it out like that is a bad idea. Just run git fetch origin, which brings over all their branches, and copies them to your remote-tracking branches. Here's what happens when I fetch the latest git from origin:

$ git fetch origin
remote: Counting objects: 1892, done.
remote: Compressing objects: 100% (1878/1878), done.
remote: Total 1892 (delta 1199), reused 16 (delta 12)
Receiving objects: 100% (1892/1892), 1.96 MiB | 2.23 MiB/s, done.
Resolving deltas: 100% (1199/1199), done.
From git://git.kernel.org/pub/scm/git/git
   05219a1..cf4c2cf  master     -> origin/master
   0b65a8d..2ff7dff  maint      -> origin/maint
 + 3dc84b0...38a2ea1 next       -> origin/next  (forced update)
 + b85345d...6eeae5c pu         -> origin/pu  (forced update)
   1ebf750..2b74c92  todo       -> origin/todo

This brought over five branches, copying them to refs/remotes/origin/ remote-tracking branch names. It's a bit slower to bring over everything, if all I wanted was master, but on the other hand, eventually I probably want everything and if I bring it all over now, I won't have to bring it all over later.

It's also a lot easier, and it ensures that I have all the remote-tracking branches, which lets me pick the right remote-tracking branch as my "upstream" for any new local branches I make.

Creating new local branches

If you have a remote-tracking branch, such as origin/newbranch, and you don't have a local branch that corresponds to it yet, you must create that local branch.

It's a good idea to make sure the local branch:

  1. has the same name (minus the origin/ part and in refs/heads/, of course): in this case we want to create local branch newbranch;
  2. starts off in sync with (pointing to the same commit as) the origin/newbranch version; and
  3. has origin/newbranch set as its upstream.

We could do this in several steps, by first creating a local branch named newbranch, then making sure that newbranch points to the same branch-tip-commit as origin/newbranch, and finally setting newbranch's upstream to origin/newbranch. That's pretty much what you are trying to do now (but you are using the wrong command, and trying to make it all happen as step 2).

Now that we have the remote-tracking branch, however, there is a much easier way:

$ git checkout newbranch

You might, quite reasonably, say: "Hey! That makes no sense at all!" And you are right: there is no local branch named newbranch, we can't possibly check it out. And yet, with my copy of the repo for Git itself:

$ git checkout maint
Branch maint set up to track remote branch maint from origin.
Switched to a new branch 'maint'

What happens here is that git checkout starts by trying to check out the local branch maint, but discovers that it does not exist. So it looks at all our remote-tracking branches and finds that there's exactly one remote-tracking maint, namely origin/maint. It then does, all in one command:

  1. create local branch maint
  2. pointing to the same commit as remote-tracking branch origin/maint
  3. with origin/maint set as its upstream, i.e., tracking maint on origin

which is what we see in the message.

(And then I don't actually want a maint, so I did:

$ git checkout master
$ git branch -d maint

to get rid of it.)

git reset is complicated and not what you want

For completeness, let's look briefly at what git reset does.

As with git checkout there are way too many options stuffed into just one command. However, git reset has, as its primary2 function, the effect of changing the commit to which the current branch points. This is definitely not what you want to do here.

The git reset command also, optionally:

  • with --mixed: resets things in Git's "index" (also called the "staging area", which is generally a better name for this)
  • with --hard: does --mixed, and removes in-progress work in your work-tree, replacing files with the version re-set into the index.

There are a few more ways git reset can work, and some of them require that the branch change to its current value (i.e., a no-effect change). These help confuse everyone as to what git reset does. These probably should have been different Git commands, just as git checkout's more dangerous modes (that overwrite files) probably should have been separate commands from git checkout's much safer (never-lose-in-progress-work) forms.


1In Git, you're always "on" some branch. Sometimes, after doing a git checkout, Git says that you're in "detached HEAD" mode and "not on a branch". When you're in this "detached HEAD" state, you are actually on a branch of sorts, though: you're on the (single, special) anonymous (unnamed) branch. Commits you make while in this state are all temporary, unless you create a name for them "soon enough". (The definition of "soon enough" can be left for a different article / question / blog-post / whatever.) Except when you're in the middle of a git rebase—and git status will tell you if you are—if you're in "detached HEAD" mode, you probably want to git checkout a branch name and get out of this mode.

Both git status and git branch will tell you if you're in this detached HEAD state. The git status command is now very good (in the bad old days of Git version 1.6 or so, it was not so great) and is something you should use frequently.

2I say this is the "primary" effect because git reset always changes the current branch, though very often, we "change" the commit from where it is now, to where it is now, which is not a very interesting "change". This is because git reset's other effects, from git reset --mixed or git reset --hard, are often what we want. If we let Git move the current branch from where it is, to where it is, and also do a few other things at the same time, the effect is as if it only did the other things.

Since we use git reset this way a lot, one could call these other effect "primary" as well—but in fact, they're optional, while the branch-moving effect always happens. It's just that if you move the branch from where it is, to where it is, this "move" is very boring and easy to forget.

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775