简体   繁体   中英

git checkout <branch> or <commit>

Is there any way to fetch & check out supplied argument, without caring if it's a branch name or a commit hash?

git fetch
git checkout origin/<branch> or origin/<commit>

how? git checkout origin/<commit> says there's no such branch. A simple git checkout <argument> is impossible because it would not check out remote branch the way git checkout origin/<argument> would.

You can to first fetch all remote branches (automatically including the one that has your desired commit) to your local:

git fetch -a

and then simply checkout the commit by its hash:

git checkout <commit-hash>

The fetch is needed first because otherwise your local may or may not be aware of the commit-hash on the remote. Without the fetch , if you were to directly execute the checkout it would complain that the commit-hash is invalid.

The premise of your question is wrong:

A simple git checkout <argument> is impossible because it would not check out remote branch the way git checkout origin/<argument> would.

It's important to realize several interlocking things here about Git:

  1. There is always—well, almost always—a current commit, which you can use the word HEAD to find.
  2. There is not always a current branch, but if there is, it's a branch name , ie, a reference whose full name has the form refs/heads/ name . The same word— HEAD , in all capital letters, finds that branch name. If there isn't a current branch name, Git calls this a detached HEAD .
  3. A remote-tracking name , such as origin/master , is not a branch name . Its full form starts with refs/remotes/ rather than refs/heads/ .
  4. If you tell git checkout to check out a commit, but identify it by something other than a branch name, Git will—if the checkout succeeds, that is—produce the detached HEAD state described in point 2. (You can also produce this same state with a branch name, using git checkout --detach .)

The consequence of point 4 above is that git checkout origin/ name results in a detached HEAD, the same way that git checkout hash-ID would.

This means your script can just use git checkout <argument> , as it will do the same thing—produce a detached HEAD—if the argument is a hash ID or if it is a remote-tracking name like origin/develop .

Note, however, that if we change this statement to read:

A simple git checkout <argument> is unsuitable because it would not first create, then check out, a local branch based on an existing remote-tracking name, the way git checkout <argument minus the leading origin/ part> would.

we get a true statement: git checkout develop will create a new (local) branch named develop using the name origin/develop (provided, of course, that local develop does not exist yet). However, there's no obvious issue with just allowing <argument> here and having the user provide develop as the name:

#! /bin/sh
git fetch && git checkout "$@"

for instance.

Side notes

There is an interesting consequence of points 1 and 2 here, which is that asking what's the value of HEAD at the moment is really asking one of two different questions:

  • Is HEAD attached to a branch? If so, which branch?
  • What is the hash ID of the current commit?

The git symbolic-ref HEAD command answers only the first question; git rev-parse HEAD mostly answers the second, but can be told to answer the first too / instead.


In point 1 above, the almost is there for a particular reason. Imagine you have just created a new, totally-empty repository. There are no commits in this repository , so which commit is the current commit?

This situation is problematic for Git. You're on a branch, namely master , that doesn't exist . Git calls this an orphan branch or a branch yet to be created (depending on which part of Git is doing the calling). The way Git handles this is to store the branch's name into .git/HEAD , without actually creating the branch itself in the reference database. When you make a new commit, that creates the branch itself, and now the problem is resolved: you're on the branch, which identifies the one new commit just made, which is the current commit, so HEAD names both the current commit and the current branch.

(Git can re-create this slightly distressed situation on demand, using git checkout --orphan , which writes a new branch's name into HEAD without actually creating the new branch.)

This neat trick seems to do just what I want:

git show-ref --head --sha | grep -q ^argument

Its exit code will be 0 if the argument is a commit hash, 1 otherwise. Taken from here https://stackoverflow.com/a/29707340/3116571

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM