@@ -2,11 +2,13 @@ package vcs
22
33import (
44 "errors"
5+ "fmt"
56 "github.com/go-git/go-git/v5"
67 "github.com/go-git/go-git/v5/plumbing"
78 "github.com/go-git/go-git/v5/plumbing/object"
89 "github.com/rogpeppe/go-internal/semver"
910 "github.com/sirupsen/logrus"
11+ "os/exec"
1012 "strings"
1113 "time"
1214)
@@ -177,8 +179,23 @@ func compareSemantically(v *plumbing.Reference, w *plumbing.Reference) int {
177179 return semver .Compare (a , b )
178180}
179181
180- // TagRelease tags the repository with the given version.
182+ // TagRelease tags the repository with the given version. When git is
183+ // configured to sign tags (via tag.gpgSign or tag.forceSignAnnotated) an
184+ // annotated, signed tag is created instead of a lightweight one.
181185func TagRelease (repoPath string , hash string , version string ) error {
186+ shouldSign , err := isTagSigningEnabled (repoPath )
187+ if err != nil {
188+ return fmt .Errorf ("failed to check tag signing config: %w" , err )
189+ }
190+
191+ if shouldSign {
192+ if err := createSignedTag (repoPath , hash , version ); err != nil {
193+ return err
194+ }
195+ logrus .Debugf ("signed tag %s created for %s" , version , hash )
196+ return nil
197+ }
198+
182199 r , err := git .PlainOpen (repoPath )
183200 if err != nil {
184201 return err
@@ -190,3 +207,35 @@ func TagRelease(repoPath string, hash string, version string) error {
190207 logrus .Debugf ("tagged %s with %s" , hash , version )
191208 return nil
192209}
210+
211+ // isTagSigningEnabled returns true if git is configured to sign tags in any
212+ // config scope. Checks tag.gpgSign and tag.forceSignAnnotated.
213+ func isTagSigningEnabled (repoPath string ) (bool , error ) {
214+ for _ , key := range []string {"tag.gpgSign" , "tag.forceSignAnnotated" } {
215+ cmd := exec .Command ("git" , "-C" , repoPath , "config" , "--bool" , "--get" , key )
216+ out , err := cmd .Output ()
217+ if err != nil {
218+ var exitErr * exec.ExitError
219+ if errors .As (err , & exitErr ) && exitErr .ExitCode () == 1 {
220+ // key is not set in any scope
221+ continue
222+ }
223+ return false , err
224+ }
225+ if strings .TrimSpace (string (out )) == "true" {
226+ return true , nil
227+ }
228+ }
229+ return false , nil
230+ }
231+
232+ // createSignedTag creates a signed, annotated tag by delegating to the git
233+ // CLI, which handles GPG/SSH key lookup and passphrase prompting.
234+ func createSignedTag (repoPath , hash , version string ) error {
235+ cmd := exec .Command ("git" , "-C" , repoPath , "tag" , "-s" , "-m" , version , version , hash )
236+ out , err := cmd .CombinedOutput ()
237+ if err != nil {
238+ return fmt .Errorf ("git tag -s failed: %s: %w" , strings .TrimSpace (string (out )), err )
239+ }
240+ return nil
241+ }
0 commit comments