21

I have a bash script which accepts a string of either a branch name (e.g., "master", or "feature/foo") or a commit hash (e.g. "1234abcd").

I have the repository checked out, so I can call git.

What is the best way to determine whether the string is a branch name or a commit hash?

#!/bin/bash
commit_or_branch="$1"
cd /path/to/my_repo
git fetch
if <is_branch $commit_or_branch>
then
    echo "it's a branch"
else
    echo "it's a commit"
fi
k107
  • 15,882
  • 11
  • 61
  • 59
  • Does it really matter for the sake of this question? I want to do different things for a branch and a commit. Like if it's a branch I might want to record the actual commit hash of the current head. And if it's a branch, I might need to `git pull` whereas a commit won't need that. And if it's a commit, I might want to check which branches it's part of. And any number of other things. – k107 Apr 18 '15 at 01:56

4 Answers4

18

If you would like a robust mechanism to tell relative names (e.g. you SHA1 is possibly one or so commits behind a named branch), you can use git name-rev to resolve it.

Examples:

$ git config remote.upstream.url
https://github.com/RobotLocomotion/drake.git

$ git log --oneline -n 5
7530a95 Merge pull request #5743 from soonho-tri/pr-reformat-mathematical_program
ebc8f25 Suppresses console output of speed_bump.obj genrule. (#5726)
d8b9a0b Merge pull request #5735 from david-german-tri/namespaces
79e10e8 Remove redundant 'symbolic::' prefix from mathematical_program code
b68b590 Clean up mathematical_program code by adding using std::*

$ git name-rev HEAD
HEAD master
$ git name-rev 79e10e8
79e10e8 master^2
$ git name-rev HEAD~20
HEAD~20 remotes/origin/issue/5646_return_binding~3

Reference: Git Tips (old commit)

UPDATE: As @kporter mentioned, there is also the --name-only flag (with new commits as of 2020/04/21):

$ git name-rev HEAD
HEAD tags/last_sha_with_original_matlab~313
$ git name-rev --name-only HEAD~20
tags/last_sha_with_original_matlab~333

Command-Line Reference: git name-rev

Eric Cousineau
  • 1,944
  • 14
  • 23
  • 1
    Thanks! This worked perfectly ```git name-rev $COMMIT_HASH | awk '{print $2}'``` – barakbd Jul 23 '18 at 19:40
  • 5
    @barakbd you can drop the awk: `git name-rev --name-only $COMMIT_HASH` – kporter Nov 13 '18 at 00:47
  • 1
    How to do the same but if and only if the reference is to the top of the named object? So, in above examples `79e10e8` and `HEAD~20` should return 'undefined' or fail. – 0andriy Apr 20 '20 at 13:29
  • @0andriy Looks like `git` will just find the closest named object, and use that? Here's the output from a fresh clone (but checking out `7530a95` from above): https://gist.github.com/EricCousineau-TRI/feaf5f8f1ef5e0f491454350fc73cc8b – Eric Cousineau Apr 21 '20 at 14:29
3

You can use git show-ref:

git show-ref --head | grep refs

If it is empty, it is a SHA1 (or an invalid object, which isn't good).
If not, it is a branch name.


A better technique comes from "Validate if commit exists", using git merge-base:

A branch name will result in a different string (the SHA1)

C:\Users\vonc\prog\b2d>git merge-base master master
de4accfd28c5f25fcc057d56996b83450be5dc60

a SHA1 will result in the same result (or at least starts with the same result):

C:\Users\vonc\prog\b2d>git merge-base 03949c3d3f88a378c6a08e57daa97059b52813f1 03949c3d3f88a378c6a08e57daa97059b52813f1
03949c3d3f88a378c6a08e57daa97059b52813f1

foobar will fail:

C:\Users\vonc\prog\b2d>git merge-base xxx xxx
fatal: Not a valid object name xxx

That means something like:

if [[ git merge-base $string $string ]]; then
  if [[ $(git merge-base $string $string) == $string* ]]; then
    echo "SHA1"
  else
    echo "branch"
  fi
else
  echo "Not a valid object name '$string'"
fi
Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 1
    @hek2mgl True, I have updated the filter accordingly. – VonC Apr 17 '15 at 19:00
  • What when I do this: `git checkout -b $(git rev-parse HEAD)` ? The upvote is still yours, but hey! :D – hek2mgl Apr 17 '15 at 19:14
  • These all use HEAD, which is not a sha. – nullsteph Nov 02 '17 at 15:19
  • Why is `git show-ref --head --sha | grep -q ^argument` worse than git merge-base for determining if the argument is a commit hash? In which cases? – Rihad Dec 20 '18 at 05:51
  • 1
    @Rihad Any script should not try to grep command output, but should be able to compare their documented result, as in git merge-base. In term of scripting, that is more robust. That being said, both should work. – VonC Dec 20 '18 at 05:58
2

Imo you can't check this reliably, since a hash is also a valid branch name. Try:

git checkout -b 0c8158f47d7dda89226d4e816fee1fb9ac6c1204

This means there can be a situation where a branch with that name exists but also a commit.

Since you can pass a branch name or a commit to most of the git commands, you don't need to differentiate between them.

hek2mgl
  • 152,036
  • 28
  • 249
  • 266
  • 1
    It's also worth noting that some commands (e.g., `git rev-parse`) will treat this as a raw SHA-1 and others (e.g., `git checkout`) will treat it as a branch name. To force treatment as a branch name, add `refs/heads/` in front. `git checkout` does not have a way to force treatment as a SHA-1 but it's not needed since `git checkout --detach` will detach from a branch and that's the only case where you'd want `git checkout` to treat the name as a raw SHA-1 anyway. – torek Apr 17 '15 at 21:31
  • @torek I'm far from a git guru, just being familiar with the every-day stuff and a little-little bit more. Stupid question, is your comment meant pro or contra my answer? – hek2mgl Apr 17 '15 at 21:40
  • Not contra, just expanding on it - the names indeed can be ambiguous and git doesn't always help much on disambiguating, but you're correct that most of the time it doesn't matter anyway. In those cases where it does, you have to get kind of ad-hoc, hence the comment. – torek Apr 17 '15 at 22:33
-1

As @VonC and @hek2mgl mentioned, this may not be a one or the other test. You could slightly modify your script to something like this (borrowed from this SO answer:

#!/bin/bash
commit_or_branch="$1"
cd /path/to/my_repo
git fetch
if git branch | grep $commit_or_branch 2> /dev/null
then
    echo "it's a branch"
fi

if git cat-file -e $commit_or_branch 2> /dev/null
then
  echo "it's a commit"
fi

Note that this only tests for local branches.. see this post if you're interested in remote branches.

Community
  • 1
  • 1
hineroptera
  • 769
  • 3
  • 10