Skip to content

Errors and return values

Two exported sentinel errors, a set of wrapped errors carrying context, and three cases where an empty result is returned with no error at all. All errors are built with cockroachdb/errors, so errors.Is matches through the wrapping.

The two sentinel errors

ErrSinceTagNotFound

Returned by GenerateFromRepo and CommitsFromRepo when the value given to WithSinceTag matches no semver tag and is not the exact name of any tag in the repository. The output string is empty; nothing is generated.

md, err := changelog.GenerateFromRepo(".", changelog.WithSinceTag("v9.9.9"))
if errors.Is(err, changelog.ErrSinceTagNotFound) {
    // the tag is wrong, not the repository
}

The message wraps the offending value: "v9.9.9": since tag not found in repository.

A tag that exists but is not valid semver is taken by its exact name, so WithSinceTag("lint/v0.6.0") bounds the walk at that tag rather than reporting "not found"; see A tag that is not a version.

Failing here is deliberate. Silently ignoring an unmatched since tag would emit the entire history for a typo, which looks like a working changelog and is not one.

ErrNoReleases

Returned by Parse when the input is non-empty and no release could be recognised — and propagated by ParseFromArchive when the changelog it extracted is in that state.

cl, err := changelog.Parse(raw)
if errors.Is(err, changelog.ErrNoReleases) {
    // present the raw notes instead of a parsed summary
}

The returned *Changelog is non-nil and empty, so a caller that ignores the error gets an empty model rather than a nil dereference.

ErrNoReleases means "nothing was recognised at all", not "no release heading". A document with bullets but no headings parses into one anonymous release whose Version is the empty string, and returns a nil error. Check cl.Releases[i].Version != "" if an unversioned release would be a problem for you.

Wrapped errors from generation

None of these have sentinels; match on them only by string if you must, and prefer to surface them.

Message prefix Cause
resolving repository path The path could not be made absolute
opening git repository No repository at the path or above it — e.g. opening git repository: repository does not exist
listing tags / iterating tags The tag references could not be read
resolving HEAD HEAD exists but could not be resolved
resolving commit for range walk / iterating commits A commit object could not be read — typically a corrupt or partial clone

Generating from a shallow clone fails

A shallow clone is the usual real-world cause, and it fails with a message that does not mention shallowness:

iterating commits: object not found

The history walk needs the commits the clone did not fetch. Generate from a full clone — in GitLab CI that means GIT_DEPTH: "0", which is what this project's own pipeline sets.

Wrapped errors from archive parsing

Message prefix Cause
failed to open gzip reader The stream is not gzip — a plain tar or a zip fails here
failed to read tar entry The tar stream is truncated or corrupt
failed to read changelog from archive The changelog entry could not be read to the end

Empty results that are not errors

Three cases return successfully with nothing in them. All three are deliberate, and all three are easy to mistake for a failure:

Call Result When
GenerateFromRepo "", nil Repository has no commits, or every commit was dropped by classification
Parse empty *Changelog, nil Input is empty or whitespace only
ParseFromArchive nil, nil The archive contains no CHANGELOG.md

ParseFromArchive returns nil, nil when no changelog is found

This is the one that bites. A release archive with no changelog in it is not an error — the design lets a caller fall back to fetching release notes from an API — but the returned *Changelog is nil, not an empty struct, so cl.HasBreakingChanges() on the result panics.

cl, err := changelog.ParseFromArchive(resp.Body)
if err != nil {
    return err
}

if cl == nil {
    // no changelog in the archive; fall back
    return fetchNotesFromAPI(ctx, version)
}

Note the asymmetry with Parse, which never returns a nil *Changelog.

FormatSummary never fails

FormatSummary returns a string and no error. A nil changelog and a changelog with no releases both format to the empty string, so an unchecked call cannot panic and cannot produce a stray heading.