简体   繁体   中英

How do I find if a git SHA points to a branch HEAD?

To avoid unnecessarily detached HEADs when checking out a certain git SHA with a (python) script, I'd like to check out a branch instead, if that SHA happens to be the current HEAD of a branch.

Ideally, I want to feed git an SHA, and it returns a branch name if the SHA is on a branch's current tip, or error out if not.

git describe --all --exact-match <SHA> very nearly is what I need, only that it is primarily aimed at tags, and so if a branch and a tag point at my SHA (which often happens in our release branches, for example), only the tag is given. This is not useful, because checking out the tag leads to a detached HEAD (even if a branch points at the same SHA).

Note, I don't want to do git branch --contains - I don't need to know which branches contain my commit.

If there's no command like git describe , just for branches, I know I can cross-check my SHA against branch SHAs via git show-ref . That's not the most elegant solution, though.

I could also do git name-rev --name-only <hash> , but I'd have to manually check the output for ~ characters, which feels unelegant if there's a git command somewhere to do the same thing.

You could try something like this:

git for-each-ref --format="%(refname:short) %(objectname)" 'refs/heads/' |
    grep SHA1 | cut -d " " -f 1

That should give you a list of branches that are currently at the revision SHA1 .

If you are going to use python, this is what I would do:

import subprocess

def get_name(target):
    p = subprocess.Popen(['git', 'for-each-ref', 'refs/heads/'], stdout=subprocess.PIPE)
    for line in p.stdout:
        sha1, kind, name = line.split()
        if sha1 != target:
            continue
        return name
    return None

Another option is to use the power of eval to construct a dictionary:

d = '{' + subprocess.check_output(['git', 'for-each-ref', '--python', '--format=%(objectname): %(refname),', 'refs/heads/']) + '}'
name = eval(d)[sha1]

With the upcoming git 2.7 (Q4 2015) you will get a more complete version of git for-each-ref , which now support the --points-at

git for-each-ref --points-at <branch_name>

With the doc:

--points-at <object>:

Only list refs which points at the given object.

That will allow to check if a commit is part of that list or not.


See commit 4a71109 , commit ee2bd06 , commit f266c91 , commit 9d306b5 , commit 7c32834 , commit 35257aa , commit 5afcb90 , ..., commit b2172fd (07 Jul 2015), and commit af83baf (09 Jul 2015) by Karthik Nayak ( KarthikNayak ) .
(Merged by Junio C Hamano -- gitster -- in commit 9958dd8 , 05 Oct 2015)

Some features from " git tag -l " and " git branch -l " have been made available to " git for-each-ref " so that eventually the unified implementation can be shared across all three, in a follow-up series or two.

* kn/for-each-tag-branch:
  for-each-ref: add '--contains' option
  ref-filter: implement '--contains' option
  parse-options.h: add macros for '--contains' option
  parse-option: rename parse_opt_with_commit()
  for-each-ref: add '--merged' and '--no-merged' options
  ref-filter: implement '--merged' and '--no-merged' options
  ref-filter: add parse_opt_merge_filter()
  for-each-ref: add '--points-at' option
  ref-filter: implement '--points-at' option  

For my case, I wanted to know if HEAD pointed to a branch post-checkout and then exit 0 if pointing to a branch, or exit non-zero if not pointing to a branch, which is most likely detached HEAD state assuming the checkout succeeded. git symbolic-ref --short HEAD does this and also returns the name of the branch when pointing to one. Obviously, there's better solutions for what the OP needs, but I wanted to throw this in just in case anyone wanted to make use of the information.

Get only branch names

To get the branch names referenced by a commit or tag, I came up with this .gitconfig :

[alias]
  branch-name = !"git for-each-ref --format=\"%(refname:short) %(objectname)\" 'refs/heads/' | sed -En 's/^(\\S+)\\s+'\"$(git rev-parse \"$1\" 2>/dev/null)\"'$/\\1/p' | grep . || { echo \"$1: not a branch\" >&2; false;}

Eg:

$ git branch-head-name da8ecd90
master

Edge cases:

  • Output is null if commit-ish isn't a branch.
  • With no arguments, lists all branch names.

Note: the alias without .gitconfig string quoting is:

branch-name=!git for-each-ref --format="%(refname:short) %(objectname)" 'refs/heads/' | sed -En 's/^(\S+)\s+'"$(git rev-parse "$1" 2>/dev/null)"'$/\1/p' | grep . || { echo "$1: not a branch" >&2; false;}

我认为解决方案之一,也许不是很优雅,但相当可靠:是列出所有分支branch --contains $sha然后检查git rev-parse $branch == $sha

What about grep :

grep -R [sha] .git/refs/heads/

Or since you are calling it from a python script, why not just create a function that searches through those files, and returns the branch name or exception as you wanted.

try something like this to test each branch tip hash against your given $sha:

 git branch -a| cut -d " " -f 2,3 | xargs -n 1 git rev-parse | grep $sha

or just local

git branch | xargs -n 1 git rev-parse | grep $sha

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