Skip to content

Tag a release

CreateTag creates an annotated tag at a commit you already know, and does not push it.

hash, err := r.CreateTag(ctx, repo.TagRequest{
    Name:    "v1.2.3",
    Commit:  merged, // a full 40-character SHA
    Message: "Release v1.2.3",
    Tagger:  &object.Signature{
        Name:  "release-bot",
        Email: "releases@example.com",
        When:  time.Now(),
    },
})

Then publish it with the refspec you want:

err = r.Push(ctx, &git.PushOptions{
    RemoteName: "origin",
    RefSpecs:   []config.RefSpec{"refs/tags/v1.2.3:refs/tags/v1.2.3"},
})

Creating and pushing are separate for the same reason CreateBranch and Push are: combining them would hide a decision about what a failed push should do with the local tag.

The commit is validated, not resolved

Commit must be a full SHA. A branch name is refused rather than looked up.

That is not fussiness. In a release workflow the caller has already established which commit to tag — typically by asking the forge which commit a merge request actually landed, because a forge can report one as merged before the commit is reachable. Resolving a name here would reopen exactly that race.

An abbreviation is refused too, and this is the one worth knowing about:

you pass plumbing.NewHash gives IsZero()
main 0000…0000 true
0173e42 0173e40000000000000000000000000000000000 false

An abbreviated SHA is silently zero-padded into a different, valid-looking hash that points at nothing — and it is not zero, so the obvious guard misses it. That is why this takes a string and checks it, rather than taking a plumbing.Hash and trusting the caller to have built it from something real.

The tagger is required

Leave Tagger nil and go-git reads a name and email from the machine's git configuration. The same release then carries a different author depending on where it was cut, and in CI carries whatever identity the runner image happens to have. CreateTag refuses instead.

Tags are not moved

Creating a tag that exists returns repo.ErrTagAlreadyExists, whatever commit it currently points at. A published tag has been fetched by consumers, and moving it means two different artefacts answer to one version.

There is no force option. A caller that genuinely needs to move a tag can reach the repository through WithRepo, where it is visibly an escape hatch.

Errors

See errors for the full set: ErrInvalidCommit, ErrMissingTagger, ErrMissingTagMessage, ErrTagAlreadyExists, and ErrNoRepository when nothing is open.