1

When performing a git checkout on, for example, the Linux kernel tree to get a specific branch, I am using a pattern:

git checkout rpi-4.19.y

This branch corresponds to a kernel tree for Raspberry Pi and as far as I can tell, causes git to pull the latest named iteration of this branch, which at the current time would be 4.19.63-v7+.

So that 'y' in the command pattern matches to '63-v7+'. I can't find what this behavior is called, so it's very hard to search for. I am trying to find out what other named branches would be available, before actually checking out in this way. I.e., I'm scripting this and want to capture this detail before checkout occurs.

  • You are checking out **branch**, while talking about **tag** (*named iteration* in your words). – 0andriy Aug 14 '19 at 18:28
  • Is there a way to tell if this is a branch or a tag and do they both have this similar trait that you can ask for them by pattern? – David Shepard Aug 14 '19 at 19:16
  • I think your best bet would be to look at `git ls-remote` and take a look at what comes back, anything starting with `refs/heads/` is a branch. – Kyle K Aug 14 '19 at 19:22

2 Answers2

1

First: be careful with the word pull when talking about Git, because git pull is a Git command that means: run git fetch, then if that succeeds, run a second Git command, usually git merge. To "pull" a branch therefore means to run git fetch and then run git merge with a commit based on the result. Well, that is, unless you've configured or instructed git pull to use a different second command, in which case, it means something different—but it still starts with git fetch.

(In scripts, you should often avoid git pull, because the way it behaves depends on the user's configuration. You have to decide whether you want to change what your script does based on the user's configuration.)

All that said, what git checkout does is ... a little bit complicated. While it sometimes does do a simple kind of pattern matching, it does not invoke git fetch. That means you shouldn't think of it as pulling anything.

To simplify things, let's talk only about the command git checkout X, for some X. (There are about four or five other kinds of git checkout, depending on how you count.) For this one particular kind of git checkout, Git goes through a four-step process to figure out what you want, and then do that:

  1. Is X an existing (local) branch name? That is, if you ran git branch, would it list X as a branch? If so, git checkout X means to check out that (existing) branch. Afterwards, git status will say on branch X.

  2. If X is not an existing branch name, can X be resolved to a Git commit hash ID? To answer this question for yourself, you can run git rev-parse X^{commit}.1 If this works, git checkout will check out that commit as a detached HEAD. Afterwards, git status will say HEAD detached at X, or in very old version of Git, HEAD detached at hash for some hash.

    The checkout itself will begin by printing, e.g.:

    $ git checkout v2.18.1
    Checking out files: 100% (1517/1517), done.
    Note: checking out 'v2.18.1'.
    
    You are in 'detached HEAD' state. You can look around, ...
    

    (though some of this changes depending on your internationalization locale setting, e.g., the messages might be in French).

    The example above could be what you're getting; it's what I got when using git checkout v2.18.1 to name a tag in the Git repository for Git.

  3. If both 1 and 2 fail, git checkout goes on to try what Git calls DWIM mode. It now lists all the remote-tracking names, which you can see for yourself using git branch -r:

    $ git branch -r
      origin/HEAD -> origin/master
      origin/maint
      origin/master
      origin/next
      origin/pu
      origin/todo
    

    This is also a case you might be getting. Note that each of these names has a pattern: each one starts with origin/, which is the name of the remote in this particular Git repository for Git:

    $ git remote
    origin
    

    This particular repository has only one remote. You can have more than one remote, using git remote add to add more. In that case, you'll often have multiple different remote-tracking names. For instance, if I were to add remotes named alice and bob, I might end up with remote-tracking names alice/master, alice/dev, bob/master, and bob/feature-1234 (in addition to my usual origin/* ones).

    For case 3, Git will take the name you gave it and look through all your remote-tracking names. For each one, Git will take off the remote's name and the slash—for instance, origin/master becomes just master, alice/master becomes just master, and bob/master becomes just master. The same goes for alice/dev and bob/feature-1234. If exactly one of these stripped-down names match the argument you used, Git selects this "DWIM mode" checkout option.

    Note that if two or more names match—e.g., if I have both an origin/dev and an alice/dev and I type git checkout dev (and we're in case 3 in the first place)—then "DWIM mode" fails and we move on to case 4. I'll describe "DWIM mode" the rest of the way in a moment; for now, let's move on to case 4.

  4. Having failed at all three of the earlier cases, git checkout now tries to use X as a path name. If that works, it does the equivalent of git checkout -- X. This extracts the copy of file X from the index and uses that to overwrite the copy of X in the work-tree.

If all four cases fail, git checkout itself fails. Note that if cases 1, 2, or 3 succeed at resolving the name X, but the rest of the operation fails for some reason, git checkout does not try any of the later cases. The four steps above are done only to figure out what X should mean in order to decide whether to do a commit checkout (cases 1 and 2), a name-creating checkout (case 3), or a file checkout (case 4).

Branch-creating checkouts, including DWIM mode

The git checkout command has the ability to create a branch. The most basic way to create a branch in Git is to use git branch. Here, you pick some existing commit—often the tip commit of some existing branch—and tell git branch to make a new name that identifies the same commit as the existing name:

git branch new-branch a123456

(where a123456 is the commit hash you want), or:

git branch new-branch master

(where you let the name master resolve to the commit hash, then create the new name new-branch, pointing to that same commit). If the name master currently resolves to the hash ID a123456, these two commands accomplish the exact same thing, which we can draw as:

...--o--o--o   <-- master, new-branch

Now all the commits that end at a123456 are on both branches.

Having made a new branch, you can then git checkout that branch:

git checkout new-branch

That puts you on new-branch while checking out commit a123456 (again, assuming master meant a123456—use git rev-parse master to see the actual hash ID).

To make this all a bit easier, git checkout offers you the ability to:

  1. create a branch name, and then
  2. switch to it (and its commit)

as a single command. Typically, to do this, you do the same thing you do with git branch: you pick out a commit, maybe using an existing branch name to do that, then you say: Make a new name, make it identify that commit, and check it out. So you could write:

git checkout -b new-branch a123456

to do the same thing we did with git branch followed by git checkout. That's not a huge savings, but it's convenient. It's especially convenient when you want to create the new branch from the same commit as whatever branch you're on right now, because then you can just run:

git checkout -b new-branch

and the new branch's commit is the same as the current commit.

But there's more to set for a branch. In particular, any branch can have one upstream. The upstream of a branch helps you out a bit (or helps Git help you out a bit) when you run git status, git push, and various other Git commands. See this answer for more—but in short, you might want to run:

git branch --set-upstream-to=origin/dev dev

after creating your own dev from the commit identified by the name origin/dev. If you did this the hard way, it would be:

git branch dev origin/dev
git branch --set-upstream-to=origin/dev dev
git checkout dev

or:

git branch dev origin/dev
git checkout dev
git branch --set-upstream-to=origin/dev

(you get to leave out one copy of the name dev here as git branch assumes you mean to operate on the current branch, which you set with the git checkout command).

If you use git checkout dev when you don't have a dev, and invoke git checkout's DWIM mode, Git assumes that you meant to do all three of these operations. So a simple:

git checkout dev

does all three, and that's a pretty significant savings: you don't have to type origin/dev at all, because Git figured that out on its own, and you only have to type dev once.


1There a few exceptions for particularly complicated strings. For instance, if we set X to the literal stringHEAD:/fix and try to use git rev-parse X:/fix^{commit}, we'll get the wrong answer. To get the right answer in all cases requires two steps:

hash=$(git rev-parse $X) || ... handle error; do not proceed ...
hash=$(git rev-parse $hash^{commit}) || ... handle error ...

If these two steps both succeed, $hash contains the hash ID of the commit that git checkout would check out.

torek
  • 448,244
  • 59
  • 642
  • 775
0

So it depends on the remote resource. With that command it could either be checking out a branch or checking out a release.

A tag is a pointer to a specific commit. This pointer can be super charged with some additional information (identity of the creator of the tag, a description, a GPG signature, ...).

A tag is a git concept whereas a Release is GitHub higher level concept.

As stated in the official announcement post from the GitHub blog: "Releases are first-class objects with changelogs and binary assets that present a full project history beyond Git artifacts."

A Release is created from an existing tag and exposes release notes and links to download the software or source code from GitHub.

Jeff Sloyer
  • 4,899
  • 1
  • 24
  • 48