# sebastian/version: turning git describe into the version string your PHP project reports

> sebastian/version is a small PHP library that wraps git describe so a Git-hosted project can report a release number that includes commit distance and hash. It is a build-time detail, not a release manager.

**sebastianbergmann/version** — Library that helps with managing the version number of Git-hosted PHP projects

- Repository: https://github.com/sebastianbergmann/version
- Stars: 6,568 · Forks: 36
- Language: PHP
- License: BSD-3-Clause
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/sebastianbergmann-version

## The gap between a release tag and the code actually running

A tag tells you what you shipped. It does not tell you what is checked out right now. Between 1.0.0 and the next tag there can be dozens of commits, and a bug report that says only "1.0.0" points at a tag that may be weeks behind the reported behaviour. sebastian/version exists to close that gap for Git-hosted PHP projects: it takes a release number you pass in and a path inside your working tree, and returns a string that identifies the exact commit. The README describes the library as helping with "managing the version number of Git-hosted PHP projects", and the single public method, asString(), is the whole surface. The intended audience is narrow: PHP library and tool authors who tag releases on Git and want an internal version string, typically for a --version output or a diagnostic header. It is not a release automation tool, does not create tags, and does not read composer.json.

## What asString() actually does with your release argument

The mechanism is a four-way branch, and the branch you land in depends on two inputs: whether the path is inside a Git repository, and whether the release argument is in X.Y.Z or X.Y form. If the path is not part of a Git repository and the release is X.Y.Z, the release is returned unchanged. If the path is not part of a Git repository and the release is X.Y, the release is returned with -dev appended. If the path is part of a Git repository and the release is X.Y.Z, the output of git describe --tags is returned as-is. If the path is part of a Git repository and the release is X.Y, the result begins with X.Y and ends with information taken from git describe --tags. That last case is the one that produces strings like 1.0.0-17-g00f3408, where 17 is the number of commits since the tag and g00f3408 is the abbreviated commit. The path parameter is documented as the directory, or a subdirectory, where the source lives; passing __DIR__ is described as usually sufficient. The library shells out to git rather than reading .git itself, which is why the fallback behaviour for non-repositories is so plain.

## Installing sebastian/version and printing a real version string

Installation is a single Composer command. The README gives two forms: a normal per-project dependency and a development-time dependency for cases where the library is only needed while running a test suite.

```bash
composer require sebastian/version
```

If the library is only used by tests, the README instead suggests:

```bash
composer require --dev sebastian/version
```

Once installed, the class is constructed with the release number and a path. The README's example passes 1.0.0 and __DIR__, then dumps the result.

```php
<?php declare(strict_types=1);
use SebastianBergmann\Version;

$version = new Version('1.0.0', __DIR__);

var_dump($version->asString());
```

The documented output is string(18) "1.0.0-17-g00f3408". That is the whole first use: a string you can print from a CLI entry point or embed in a diagnostic report. Note the README's warning that when a new release is prepared, the first constructor argument has to be updated by hand. Nothing in the library reads your tag list to infer it.

## Where the design bites: shallow clones, missing tags and non-Git checkouts

The library delegates to git describe --tags, so it inherits that command's failure modes. A shallow clone, which is the default in many CI checkouts, has no tag history to walk, and git describe fails or produces a different answer than it would in a full clone. The README does not document what asString() returns when git describe fails, and it does not describe any error handling around the subprocess call. Treat that as an open question to test in your own pipeline rather than an assurance. The X.Y branch is also a footgun: because it appends -dev only when the path is outside a Git repository, the same release argument can produce a different shape depending on where the code is checked out. And the library assumes Git specifically. A project on Mercurial or Subversion gets the non-repository branch, meaning it silently falls back to the literal release string with no indication that commit information was dropped. If your version number must be authoritative, for example in a package registry or an update check, this library is the wrong tool, because its output depends on local repository state.

## How this differs from letting CI or a static constant own the version

The common alternative is a version constant maintained by hand or rewritten by a release script, which is what many PHP projects do before they adopt a describe-based string. The difference is in what the string means. A static constant says what the maintainer intended to ship. A git describe string says what is actually present in the working tree, including the commit count since the last tag. A second alternative is to run git describe yourself in a Makefile or a Composer script and inject the result into the build. That produces the same string without a runtime dependency, at the cost of writing and testing the four-way branch yourself, including the X.Y and -dev cases that sebastian/version already encodes. The library's value is not the git call, it is the decision table around it. If your build already generates a version file, you gain little from adding a runtime dependency for logic you have already implemented.

## Maintenance cadence, licence and what an upgrade costs

The repository was last pushed on 2026-09-14, and the most recent release listed is 7.0.0 from 2026-02-06, following 6.0.0 in February 2025. The major-version jumps are the thing to watch: 5.x to 6.x to 7.x each landed roughly a year apart, and a major bump on a class this small usually means a PHP version requirement change rather than new API surface. The class has one public method, so the migration surface in your code is tiny, but the constraint that moves is the PHP runtime your project supports. Pin the version in composer.json and read the ChangeLog.md entry before bumping, since that file is present at the repository root. The licence is BSD-3-Clause, which is permissive and permits use in closed-source projects; the LICENSE file sits at the top level. This is not legal advice, and if your organisation has a policy on attribution for redistributed dependencies, check the licence text itself.

## Conclusion

Adopt sebastian/version if your PHP project is Git-hosted, tags its releases, and needs a build-time string that ties a running checkout to a commit. Skip it if your version is assigned by a CI pipeline, a monorepo tag prefix, or a non-Git VCS, because asString() has no fallback for those cases. Before installing, verify that git describe --tags works in your checkout and that your release argument follows the X.Y.Z or X.Y form the constructor expects.

## FAQ

### What does sebastian/version do that a plain version constant does not?

The library returns a string built from git describe --tags when the path is inside a Git repository, so the output includes the commit count since the tag and the abbreviated hash. A constant only records the number a maintainer typed in.

### How do I install sebastian/version?

The README gives composer require sebastian/version for a per-project dependency, or composer require --dev sebastian/version when the library is only needed during development, for instance to run a test suite.

### What arguments does the SebastianBergmann\Version constructor take?

Two: $release, which is the latest release number in X.Y.Z form or the release series in X.Y form when no release has been made from that branch, and $path, the directory or subdirectory where the source can be found. The README notes that passing __DIR__ usually suffices.

### What happens if the project is not hosted in a Git repository?

If the path is not part of a Git repository and the release is in X.Y.Z format, the release is returned as-is; if it is in X.Y format, -dev is appended. No commit information is added in either case.

### Do I have to update the version number by hand when I cut a release?

Yes. The README states that when a new release is prepared, the string passed to the constructor as the first argument needs to be updated.

## Sources

- [Issues](https://github.com/sebastianbergmann/version/issues)
- [License: BSD-3-Clause](https://github.com/sebastianbergmann/version/blob/main/LICENSE)
- [README](https://github.com/sebastianbergmann/version/blob/main/README.md)
- [Releases](https://github.com/sebastianbergmann/version/releases)
- [sebastianbergmann/version on GitHub](https://github.com/sebastianbergmann/version)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/sebastianbergmann-version
