If this content overflows, use the left and right arrow keys to scroll horizontally.

On this page

Specification

GitCalVer specification—version format, branch model, deterministic interfaces, and publication contract

Version: 0.2

Abstract

GitCalVer derives version numbers deterministically from git history. Each commit on one selected first-parent chain maps to a unique, strictly increasing version based on the commit’s UTC committer date and its position within that date’s consecutive block.

Calculation is local and offline. Publication may refresh remote state and claim an immutable tag, resolving the possibility that independent clones calculate the same candidate before either publishes it.

Terminology

  • The selected branch is the branch whose first-parent history defines versions.
  • The selected chain is the selected branch tip followed repeatedly through first parents.
  • The target is the commit being calculated. It is HEAD when no explicit revision is supplied.
  • A date block is a maximal consecutive run of selected-chain commits with the same UTC committer date.
  • A canonical tag is a caller-defined tag prefix followed by a clean GitCalVer version.

Version format

Base format

YYYYMMDD.N

Where:

  • YYYYMMDD is the Gregorian UTC date of the commit’s committer timestamp, using a four-digit year, two-digit month, and two-digit day.
  • N is a positive integer without leading zeroes. It is the target’s one-based position in its date block, counted from oldest to newest.

For example:

CommitCommitter date (UTC)Version
e12026-04-10 09:00:0020260410.1
e22026-04-10 14:30:0020260410.2
e32026-04-10 17:00:0020260410.3
e42026-04-11 10:00:0020260411.1
e52026-04-11 11:00:0020260411.2

Prefix

A caller MAY prepend a single-line literal prefix. The default prefix is empty.

Use casePrefixExample
Base formempty20260412.3
Initial or unstable SemVer0.0.20260412.3
Initial or unstable Go tagv0.v0.20260412.3

The prefix is not part of the base version and implementations MUST NOT infer one. A nonempty prefix MUST NOT contain a newline.

Projects maintaining incompatible release lines MUST use distinct caller-managed major prefixes or distinct package namespaces. The 0. prefix denotes an initial or unstable line; it is not a universal compatibility boundary.

Guarantees and prerequisites

Within one fixed, complete selected chain whose committer dates do not decrease:

  1. Every commit maps to exactly one clean base version.
  2. Every clean base version maps back to at most one commit.
  3. Versions strictly increase from parent to child.

Within a date block, N increases by one. Across a date boundary, YYYYMMDD increases and therefore the whole version increases regardless of the previous count.

These guarantees do not compare unrelated histories, different selected branches, or different caller-managed prefixes. History rewrites that change the selected chain or make committer dates decrease invalidate them. Dirty versions are explicitly outside the uniqueness and reversibility guarantees.

Core calculation

Committer date and UTC

GitCalVer MUST use the committer timestamp, not the author timestamp. Git stores the timestamp as Unix epoch seconds plus a timezone offset; calculation MUST interpret the instant in UTC and ignore the stored offset when choosing the date.

The committer timestamp represents when a commit was applied to the branch and changes under operations such as amend, rebase, and cherry-pick.

First-parent traversal

Only first parents define the selected chain.

  • A merge commit itself counts.
  • Commits reachable only through a merge commit’s later parents do not count.
  • A squash merge contributes the resulting commit.
  • A fast-forward contributes each forwarded commit.

A target is a clean selected-chain member only if it is exactly present in that first-parent traversal. Ordinary reachability is insufficient.

Calculation algorithm

For a selected-chain target:

  1. Convert the target’s committer timestamp to YYYYMMDD in UTC.
  2. Walk backward through first parents.
  3. Count the consecutive commits with that same date, including the target.
  4. Stop after observing the first older date, or after proving the walk reached a true root.
  5. Reject the history if the observed older boundary has a later date than the target.
  6. Return YYYYMMDD.N.

An implementation MUST NOT return a result when local history ends before the date block’s beginning can be proved.

Selected branch

Detection

Unless the caller explicitly selects a branch, implementations MUST use this precedence:

  1. The symbolic cached remote default at refs/remotes/REMOTE/HEAD.
  2. Cached REMOTE/main, then REMOTE/master.
  3. Local main, then local master.
  4. Error if no branch can be selected.

REMOTE defaults to origin and MAY be selected explicitly. Remote selection chooses cached refs only; it MUST NOT fetch.

After selecting the branch name, implementations MUST prefer its local branch tip and otherwise use its cached remote-tracking tip. This keeps clean, unpushed commits on the selected local branch calculable. The resolved tip MUST be cached for the invocation so concurrent ref movement cannot combine two views of the chain.

Target relationship

Implementations MUST classify the target against the selected chain:

  • On-chain: The target is an exact selected-chain member. Calculate it directly.
  • Off-chain but traceable: The target can reach at least one selected-chain commit through any parent path. Use the newest such selected-chain commit as the calculation anchor and mark the result dirty.
  • Not traceable: Complete local history proves that no common ancestry exists. Return the not-traceable result.
  • Unprovable: Missing local history prevents membership, anchor, or nonrelationship proof. Return the incomplete-history result.

A commit reachable only through a merge’s second or later parent is off-chain, not clean. An off-chain target that has merged a newer selected-chain commit anchors at that newest reachable chain member.

Targets and workspace state

With no supplied target, implementations MUST calculate HEAD and inspect the current workspace when one exists. Staged changes, unstaged changes, and untracked nonignored files make it dirty. Ignored files do not.

An explicit revision—including explicit HEAD—describes only that commit. Implementations MUST ignore workspace state for explicit revisions. Its chain relationship still applies, so an explicit off-chain revision remains dirty.

An explicitly supplied empty target is invalid; it is not equivalent to omission. Bare repositories MUST support explicit revisions, reverse lookup, and omitted-target calculation without attempting a workspace check.

Dirty output

By default, implementations MUST refuse a dirty result. A caller MAY opt in with a nonempty dirty string.

With a hash:

PREFIXYYYYMMDD.NDIRTY.HASH

Without a hash:

PREFIXYYYYMMDD.NDIRTY

HASH MUST be the first seven lowercase characters of the original target commit’s full object ID. It MUST NOT use git’s repository-dependent abbreviation machinery. The separator before HASH is exactly one literal period; there is no g prefix.

A no-hash option is valid only when dirty output is enabled. An explicit no-dirty option overrides dirty output configuration.

Dirty output is not uniquely reversible: different workspaces and different off-chain histories can share the same dirty string.

Deterministic interfaces

Input precedence

A single positional target MAY be either a version or a revision. After removing the exact configured prefix, an input with the complete shape YYYYMMDD.N MUST enter reverse mode before revision parsing, even if a git ref has the same name or the date is invalid. Trailing text and multiline values do not match.

A reverse input MUST include the exact configured prefix when that prefix is nonempty. Implementations MUST validate the date as a Gregorian date and reject counts with leading zeroes.

Forward output

Forward calculation outputs the configured prefix, the base version, and any permitted dirty suffix. An explicit revision ignores workspace state as defined above.

Reverse output

Reverse lookup walks only the selected chain. It MUST validate the complete requested date block and its older boundary, or prove that the block reaches a true root.

The default reverse output is the full object ID. A short-output option returns exactly its first seven lowercase characters. Dirty versions are not reversible.

Exit codes

CLI implementations MUST use these exit codes:

CodeMeaning
0Success
1Invalid input, repository state, version date, or decreasing history
2Dirty workspace or off-chain target refused without dirty output enabled
3Complete history proves the target is unrelated to the selected chain
4Local history cannot prove membership, anchor, or a complete date block

Library implementations SHOULD expose distinguishable errors with equivalent semantics.

Incomplete and partial histories

Core calculation and reverse lookup MUST NOT access the network, including lazy fetches from partial-clone promisor remotes.

Shallow and partial histories MAY succeed when their local commit objects prove:

  1. the target’s selected-chain relationship or reachable anchor; and
  2. the complete relevant date block plus its older date boundary, or a true repository root.

A blanket shallow-repository rejection is not conforming. Missing trees and blobs do not matter when all required commit objects are present. Missing required commits produce exit code 4, not a guessed version.

Implementations MUST ignore replacement refs while calculating. They MUST reject a repository with a legacy info/grafts file as incomplete because grafts silently replace stored ancestry.

Conformance requires SHA-1 repositories. Implementations MAY support SHA-256 repositories and SHOULD report that capability separately.

Canonical publication

Core calculation does not coordinate independent clones. Two offline clones can temporarily calculate the same candidate when their local histories diverge. Canonical publication resolves that collision by claiming an immutable, protected tag without force after CI succeeds.

Publication is a separate, explicit operation and MAY access the network. For one caller-defined canonical tag namespace, it MUST:

  1. Refresh the selected remote branch and require the publishing commit to be its current tip.
  2. Enumerate all globally published matching tags, including tags whose commits are otherwise unreachable from the selected tip. A registry release remains part of publication history even if it is later yanked.
  3. Select the numerically greatest matching GitCalVer version.
  4. Validate that tag’s date segment against its target commit.
  5. Require its target to remain a first-parent ancestor of the publishing commit.
  6. Require the new candidate to be greater, unless the candidate tag already points to the publishing commit.
  7. Create the candidate tag without force.

A retry is idempotent only when the matching canonical tag points to the same commit and that commit remains the selected remote tip. A matching tag at a different commit, a newer published tag, a continuity failure, or a lost race MUST block publication.

Tag namespaces MUST be configured as immutable before automated publication. Publication workflows SHOULD use constant concurrency so only one claimant runs at a time; nonforce tag creation remains the final collision guard.

Compatibility

The base algorithm is ecosystem-independent. Prefixes, local dirty forms, dependency ranges, and constrained platform fields are nonnormative integration choices documented on the Compatibility page.