skip to content

What does git describe output like v1.4.0-14-g2414721 mean, and what does it need to work?

level: middleimportance: should knowfreq 40%

answer

  1. nearest reachable tag, then a count
  2. the letter before the hash is a marker
  3. zero commits away prints something shorter
  4. which tag kind qualifies by default
  5. shallow clones cannot see far enough back

basics

~20 s

It names the nearest reachable tag (v1.4.0), the number of commits since it (14), and the abbreviated commit id prefixed by g for git (2414721). By default it searches annotated tags only, so a repository without one cannot be described.

solid answer

~40 s

`git describe` answers "where am I relative to the last release". It walks back from the given commit, finds the closest reachable tag, and prints `<tag>-<commits since>-g<abbrev hash>`; the `g` is a literal marker meaning git, not part of the hash. On a commit that *is* the tag, it prints just the tag name. Defaults and flags that matter: only annotated tags are considered unless you pass `--tags`; `--abbrev=0` prints the bare tag name; `--dirty` appends `-dirty` when the working tree has uncommitted changes; `--long` always uses the three-part form so output is machine-parseable; `--match 'v[0-9]*'` restricts which tags qualify, and `--always` falls back to a bare abbreviated hash instead of failing. It needs history — a shallow clone can walk past its own boundary and find nothing.

code

console · 11 lines
console
$ git describe
v1.4.0-14-g2414721

$ git describe --tags --abbrev=0
v1.4.0

$ git describe --long --dirty
v1.4.0-14-g2414721-dirty

$ git describe --contains 2414721
v1.5.0~3

go deeper

for a junior

Be able to read the output: nearest tag, commits since it, and the abbreviated commit id after a literal g. Know that an exact tag match prints just the tag name.

for a middle

Explain the search — backwards through reachable ancestry, annotated tags only unless --tags — and name the flags that make it script-safe: --long, --abbrev=0, --dirty, --match.

for a senior

Diagnose the failures you meet in real pipelines: shallow clones with no reachable tag, lightweight release tags being skipped, and stray non-release tags polluting the version string.

for a principal

Decide the versioning contract — describe-derived versions instead of a checked-in version file, a tag naming scheme that filters cleanly, and a rule that any artifact is traceable to one commit.

## What the three parts mean Given `v1.4.0-14-g2414721`: - `v1.4.0` — the closest **reachable** tag found by walking backwards from the commit being described. "Closest" means fewest commits away along the first-parent-preferring search Git performs; ties are broken by the traversal. - `14` — how many commits are between that tag and this commit. Zero commits away is a special case: `git describe` then prints just `v1.4.0` with no suffix at all. - `g2414721` — the abbreviated object id of the described commit. The leading `g` stands for *git* and is a marker, not a hex digit; strip it before feeding the id to another command. Abbreviation length adapts to repository size and can be forced with `--abbrev=<n>`. The result is a human-readable version string that is also unambiguous: it identifies a specific commit while telling you which release it descends from and how far past it you are. ## What it requires **A reachable tag.** `describe` only walks the ancestry of the commit. A tag on a sibling branch is invisible. If nothing reachable is tagged, `describe` fails rather than guessing; `--always` makes it degrade to a bare abbreviated hash instead. **An annotated tag, by default.** This is the detail that trips people up. Plain `git describe` considers only annotated tags. `--tags` widens the search to lightweight ones, and `--all` widens it further to any ref including branches (with a `heads/` prefix in the output). A repository that marks releases with lightweight tags will therefore see `describe` skip straight past them to some older annotated tag, or fail entirely — and the build stamps the wrong version with no error. **Enough history.** In a shallow clone (`git clone --depth 1`), the ancestry stops at the graft boundary. If the nearest tag is older than that boundary, `describe` cannot find it. This is the single most common CI failure for version stamping, and the fix is to deepen the clone or fetch tags with history rather than to change the describe invocation. ## The flags worth knowing - `--abbrev=0` — suppress the hash and the distance, printing only the tag name. The idiomatic way to ask "what was the last release?", usually written `git describe --tags --abbrev=0`. - `--long` — always print all three parts, even exactly on a tag. Essential when a script parses the output, because the two-shape default (`v1.4.0` vs `v1.4.0-14-g2414721`) breaks naive parsing. - `--dirty[=<mark>]` — append `-dirty` if tracked files differ from HEAD. Marks an artifact built from an uncommitted tree, which is exactly the artifact you will later fail to reproduce. - `--match <glob>` / `--exclude <glob>` — restrict which tag names qualify. With `--match 'v[0-9]*'` a repository that also carries `nightly-*` or `deploy-*` tags still describes against real releases. - `--first-parent` — follow only the first parent, so merged side branches cannot supply a nearer tag. Useful on a mainline where feature branches sometimes carry their own tags. - `--contains` — inverts the question: instead of the nearest ancestor tag, report the earliest tag that *contains* this commit. That answers "which release shipped this fix", which is the question you actually ask during an incident. ## Why release engineering leans on it Build systems use `git describe` to derive a version string with no hand-edited version file to forget. Tagged commits produce a clean `v1.4.0`; everything else produces a string that is visibly a development build and points at its exact commit. When a customer reports a problem with build `v1.4.0-14-g2414721`, you can check out `2414721` and have precisely their source. The corollary is that `describe` output is only as trustworthy as the tagging discipline behind it. Annotated tags, a consistent naming scheme, `--match` to filter out non-release tags, and full-enough clones in CI are what make the version string mean something. ## `describe` versus `--contains`, in one line Default `describe` looks *backwards* — the last release before this commit. `--contains` looks *forwards* — the first release that includes this commit. Interviewers like the second one because it is the question a release engineer actually gets asked mid-incident, and because `git tag --contains <commit>` is the blunter tool that answers it for every release at once.

  • In CI, `git describe` fails with no tags found even though the repository has release tags. What is the usual cause?
    The clone is shallow, so the walk hits the graft boundary before reaching any tag; the tags may not have been fetched at all. Deepen the fetch or fetch tags with enough history. A secondary cause is a repository whose release tags are lightweight, which default describe skips unless you pass `--tags`.
  • How do you ask which release first contained a given commit, rather than which release preceded it?
    `git describe --contains <commit>` reports the earliest tag that can reach the commit, inverting the default backwards search. `git tag --contains <commit>` is the broader form, listing every tag that includes it, which is usually what you want mid-incident when several maintenance lines received the fix.
  • Why do scripts that parse git describe output often add --long?
    Without it the output has two shapes: bare `v1.4.0` exactly on a tag, and `v1.4.0-14-g2414721` elsewhere. A parser splitting on dashes silently misreads the first form. `--long` forces the three-part form always, so field extraction is uniform, and `--dirty` flags builds made from an uncommitted tree.

saying these in an interview costs you the question

  • Reads the g in g2414721 as part of the commit hash
  • Assumes lightweight tags are found by default
  • Expects describe to find a tag on an unrelated branch
  • Parses the output without handling the exactly-on-a-tag form
  • Blames describe rather than the shallow CI clone

context