neu242
neu242

Reputation: 16620

How to find the current git branch in detached HEAD state

I can find the current git branch name by doing either of these:

git branch | awk '/^\*/ { print $2 }'
git describe --contains --all HEAD

But when in a detached HEAD state, such as in the post build phase in a Jenkins maven build (or in a Travis git fetch), these commands doesn't work.

My current working solution is this:

git show-ref | grep $(git log --pretty=%h -1) | sed 's|.*/\(.*\)|\1|' | sort -u | grep -v HEAD

It displays any branch name that has the last commit on its HEAD tip. This works fine, but I feel that someone with stronger git-fu might have a prettier solution?

Upvotes: 78

Views: 37311

Answers (9)

jdolan
jdolan

Reputation: 587

For anyone attempting to do this from within Gradle (say, via your own custom task, plugin, etc..) you can use the Grgit Plugin, which provides a Gradle-Git bridge. In fact, if you are using the Nebula Release plugin, then Grgit is already present in your build.

And so with Grgit, resolving the remote branch that points to your current detached HEAD looks like this:

project.getPlugins().apply(GrgitServicePlugin.class);

final var git = project.getExtensions()
    .getByType(GrgitServiceExtension.class)
    .getService()
    .get()
    .getGrgit();

final var rev = git.head().getId();

final var branch = git.lsremote(Map.of("heads", true))
    .entrySet()
    .stream()
    .filter(remote -> remote.getValue().equals(rev))
    .map(remote -> remote.getKey().getName())
    .findFirst()
    .orElseGet(() -> git.getBranch().current().getName());

// do something interesting with branch

Upvotes: 0

Raphael Setin
Raphael Setin

Reputation: 875

This is what I am using in one of my projects:

const { execSync } = require('child_process');

const getBranchName = () => {
  let branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
  if (branch === 'HEAD') branch = execSync(`git branch -a --contains HEAD | sed -n 2p | awk '{ printf $1 }'`).toString().trim();
  return branch;
}

Upvotes: 1

Mecki
Mecki

Reputation: 133189

I prefer to use this one:

git branch --remote --contains | sed "s|[[:space:]]*origin/||"

It works fine if the head of a branch is checked out as well as when the current checkout is a detached head and it requires no network access.

Upvotes: 5

ingyhere
ingyhere

Reputation: 13861

A sed solution:

git log -1 --pretty=%D HEAD | sed 's/.*origin\///g;s/, .*//g'

This uses log to check the last item for its presence on a branch. Then sed finds the branch preceded by origin/ and removes the phrase and everything before it. Then sed does another removal of any possible additional listed branches (comma and everything after it). The reason last log was used as a sanity check to ensure this detached HEAD is not a commit above known branch HEADs.

If this is empty, failsafe logic can be implemented to label the branch "detached" (or "undefined"?) or to ensure it's up-to-date or rolled back to the tip of a known HEAD.

Upvotes: 6

Ruslanas
Ruslanas

Reputation: 61

The shortest I got working on bitbucket pipelines:

git show -s --pretty=%D HEAD | awk '{gsub("origin/", ""); print $2}'

Upvotes: 2

esamatti
esamatti

Reputation: 18973

I needed a bit different solution for Jenkins because it does not have local copies of the branches. So the current commit must be matched against the remote branches:

git ls-remote --heads origin | grep $(git rev-parse HEAD) | cut -d / -f 3

or without network:

git branch --remote --verbose --no-abbrev --contains | sed -rne 's/^[^\/]*\/([^\ ]+).*$/\1/p'

It's also worth noting that this might return multiple branch names when you have multiple branch heads at the same commit.

UPDATE:

I just noticed that Jenkins sets GIT_BRANCH environment variable which contains a value like origin/master. This can be used to get git branch in Jenksin too:

echo $GIT_BRANCH | cut -d / -f 2

Upvotes: 47

jthill
jthill

Reputation: 60547

Here's git nthlastcheckout, it gets the exact string you used for your nth last checkout from the reflog:

git config --global alias.nthlastcheckout '!nthlastcheckout'"() {
        git reflog |
        awk '\$3==\"checkout:\" {++n}
             n=='\${1-1}' {print \$NF; exit}
             END {exit n!='\${1-1}'}'
}; nthlastcheckout \"\$@\""

Examples:

$ git nthlastcheckout
master
$ git nthlastcheckout 2
v1.3.0^2

Upvotes: 2

Seth Robertson
Seth Robertson

Reputation: 31471

git branch --contains HEAD

Obviously discarding (no branch). Of course, you may get an arbitrary number of branches which could describe the current HEAD (including of course none depending on how you got onto no-branch) which might have be fast-forward merged into the local branch (one of many good reasons why you should always use git merge --no-ff).

Upvotes: 19

Cascabel
Cascabel

Reputation: 497532

A more porcelain way:

git log -n 1 --pretty=%d HEAD

# or equivalently:
git show -s --pretty=%d HEAD

The refs will be listed in the format (HEAD, master) - you'll have to parse it a little bit if you intend to use this in scripts rather than for human consumption.

You could also implement it yourself a little more cleanly:

git for-each-ref --format='%(objectname) %(refname:short)' refs/heads | awk "/^$(git rev-parse HEAD)/ {print \$2}"

with the benefit of getting the candidate refs on separate lines, with no extra characters.

Upvotes: 65

Related Questions