Skip to content
masterclass Buddy

Git · Command

git describe

git describe gives a commit a readable name based on the nearest tag it can reach, such as v1.0.4-14-g2414721. Release scripts and build tools often use it to create version strings.

Last updated

How it works

Git starts at the commit you name, or HEAD if you name none. If a tag points straight at that commit, Git prints only the tag name. If not, it walks back through the commit's ancestors to find a tagged commit. It then prints that tag, the number of commits added since it, and a hash suffix made of 'g' plus a short, unique form of the commit's ID. When several tags are reachable, it picks the one with the fewest commits between the tag and the input. You can pass the result to other git commands as a revision.

Syntax

git describe [--all] [--tags] [--contains] [--abbrev=<n>] [<commit-ish>…]
git describe [--all] [--tags] [--contains] [--abbrev=<n>] --dirty[=<mark>]
git describe <blob>

Parameters

NameTypeDescription
<commit-ish>…commit-ishOne or more commits to describe. If you leave this out, Git uses HEAD.
--dirty[=<mark>]flagDescribes the working tree. If it has local changes, Git adds the suffix -dirty, or the <mark> you give.
--broken[=<mark>]flagLike --dirty, but if the repository is corrupt, Git adds a -broken suffix (or <mark>) instead of failing.
--allflagConsiders any ref under refs/, including branches, remote-tracking branches and lightweight tags.
--tagsflagConsiders every tag under refs/tags, including lightweight tags.
--containsflagFinds a tag that comes after the commit, so it contains the commit. This also turns on --tags.
--abbrev=<n>integerUses at least n hex digits for the hash, or more if needed to keep it unique. A value of 0 prints only the closest tag.
--candidates=<n>integerConsiders up to n candidate tags instead of the default 10. A value of 0 allows only exact matches.
--exact-matchflagPrints a result only if a tag points directly at the commit. It is the same as --candidates=0.
--debugflagPrints details of the tag search to standard error.
--longflagAlways prints the full tag-count-hash form, even when the commit is tagged.
--match <pattern>globConsiders only tags that match the glob pattern. You can repeat it, and --no-match clears the list.

Returns

Prints a name to standard output. It is either just the tag name (exact match) or <tag>-<count>-g<abbrev>, with an optional -dirty or -broken suffix. For a blob, it prints <commit-ish>:<path>.

Key points

  • Lightweight tags are ignored unless you add --tags. Use --tags when your project marks releases with plain tags made by 'git tag v1.2'.
  • --all extends the search to every ref under refs/, so the output may be a branch path such as heads/topic-7-g975b instead of a tag.
  • With --dirty, Git also checks the working tree, so builds made from uncommitted changes get a -dirty marker in their version string.
  • Setting --abbrev=0 removes the count and hash, leaving only the nearest tag name. This is handy for finding the previous release.
  • --long keeps the output format the same even on a tagged commit (for example v1.2-0-gdeadbee), which helps scripts that split the output into fields.
  • If more than one tag points at the same commit, Git chooses annotated tags over lightweight ones, and newer tags over older ones.
  • --match and --exclude take glob patterns, so you can pick one tag family, such as v* release tags, and skip others like rc tags.
  • --first-parent ignores tags that only exist on side branches merged into the history, so the name stays tied to the mainline.

Common mistakes

  • By default Git looks at only 10 candidate tags. In repositories with many tags, this can give a less accurate result unless you raise --candidates.
  • The hash suffix can get longer as the repository grows, because it must stay unique. Do not assume it is always 7 characters.
  • Trees, and tags that do not point at commits, cannot be described.
  • On a corrupt repository, --dirty makes the command fail unless you use --broken.

Example: Build a version string from tags

git init -q demo && cd demo
git config user.name dev && git config user.email dev@example.com

git commit -q --allow-empty -m "initial"
git tag -a v1.0.0 -m "release 1.0.0"

git describe                 # v1.0.0 (exact match)
git describe --long          # v1.0.0-0-g<hash>

git commit -q --allow-empty -m "feature"
git commit -q --allow-empty -m "fix"
git describe                 # v1.0.0-2-g<hash>
git describe --abbrev=0      # v1.0.0

echo change > file.txt && git add file.txt
git describe --dirty         # v1.0.0-2-g<hash>-dirty

# Version string for CI: only v* tags, fall back to the hash if none
VERSION=$(git describe --tags --match 'v*' --dirty --always)
echo "Building $VERSION"

The script creates an annotated release tag, then shows how the output changes for an exact match, for later commits, with --abbrev=0, with uncommitted changes and in a typical CI version string.

When to use it

Build version stamping

A Makefile or CI job adds git describe --tags --dirty --always output to the binary, so every build can be traced to an exact commit and you can see if it had local edits.

Finding the previous release

A changelog script runs git describe --abbrev=0 to find the last release tag, then lists the commits made since that tag.

Checking that a deploy uses a tagged release

A deploy script runs git describe --exact-match and stops if HEAD is not exactly on a release tag.

Finding the first release with a fix

A support engineer runs git describe --contains <sha> to find the first tag that includes a bug-fix commit.

Interview questions

QWhat does each part of the output v1.0.4-14-g2414721 mean?

v1.0.4 is the nearest tag that can be reached from the commit. 14 is the number of commits added since that tag, which is the number git log v1.0.4..<commit> would show. g2414721 is 'g' (for git) plus a short, unique form of the described commit's ID.

Follow-up: Can you pass that string straight to other git commands? Yes. Git treats describe output as a valid revision, and it uses the short hash after the 'g' to find the commit.

QWhy might git describe fail or skip a tag you just made with 'git tag v2.0'?

'git tag v2.0' without -a or -s makes a lightweight tag, and by default describe only looks at annotated tags. Add --tags so lightweight tags count, or --all to also include branches and remote refs.

Follow-up: How do you avoid failures in a fresh clone that has no usable tags? Add --always, so Git prints the short commit ID when it cannot find a tag.

QHow is --contains different from the default behavior?

By default, describe looks backwards for a tag the commit is based on. --contains looks forwards for a tag that comes after the commit and therefore includes it, and it turns on --tags automatically. It answers 'which release first shipped this commit?'

Follow-up: When several ancestor tags can be reached, which one does the default mode pick? The one with the fewest commits between the tag and the input. With --first-parent, Git only walks first parents at merge commits.

  • git tag
  • git name-rev
  • git rev-parse
  • git log
  • git merge-base
  • gitrevisions

Source: Git - git-describe Documentation. Checked 2026-11-02.