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.
Git · Command
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
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.
git describe [--all] [--tags] [--contains] [--abbrev=<n>] [<commit-ish>…]
git describe [--all] [--tags] [--contains] [--abbrev=<n>] --dirty[=<mark>]
git describe <blob>| Name | Type | Description |
|---|---|---|
<commit-ish>… | commit-ish | One or more commits to describe. If you leave this out, Git uses HEAD. |
--dirty[=<mark>] | flag | Describes the working tree. If it has local changes, Git adds the suffix -dirty, or the <mark> you give. |
--broken[=<mark>] | flag | Like --dirty, but if the repository is corrupt, Git adds a -broken suffix (or <mark>) instead of failing. |
--all | flag | Considers any ref under refs/, including branches, remote-tracking branches and lightweight tags. |
--tags | flag | Considers every tag under refs/tags, including lightweight tags. |
--contains | flag | Finds a tag that comes after the commit, so it contains the commit. This also turns on --tags. |
--abbrev=<n> | integer | Uses 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> | integer | Considers up to n candidate tags instead of the default 10. A value of 0 allows only exact matches. |
--exact-match | flag | Prints a result only if a tag points directly at the commit. It is the same as --candidates=0. |
--debug | flag | Prints details of the tag search to standard error. |
--long | flag | Always prints the full tag-count-hash form, even when the commit is tagged. |
--match <pattern> | glob | Considers only tags that match the glob pattern. You can repeat it, and --no-match clears the list. |
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>.
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.
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.
A changelog script runs git describe --abbrev=0 to find the last release tag, then lists the commits made since that tag.
A deploy script runs git describe --exact-match and stops if HEAD is not exactly on a release tag.
A support engineer runs git describe --contains <sha> to find the first tag that includes a bug-fix commit.
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.
'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.
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 taggit name-revgit rev-parsegit loggit merge-basegitrevisions