feat(releaser): release v1.4.0 — GitHub, SSH agent, configurable bumps
ci / vet, staticcheck, test, build (push) Successful in 3m54s
release / Build and publish release (push) Successful in 4m11s

- GitHub release support: internal/ghclient (no SDK, minimal HTTP client);
  GITHUB_TOKEN env var; github.token/github.repo config; GitHub takes
  precedence over GitLab when both are configured; publisher interface
  (releasePublisher) in cmd/main.go makes providers interchangeable
- SSH agent push: gitutil.Push() now tries gitssh.NewSSHAgentAuth for
  git@/ssh:// remotes before falling back to the system git binary
- --release-env-file flag: override dotenv artifact path; pass "" to disable
- git.releasable_types config: filter which commit types trigger a bump
  (defaults to fix, feat, breaking); useful for maintenance branches
- commits.Group(), ExtractSubject(), ReleasableSet() exported helpers:
  notes.go and changelog.go now delegate to these instead of duplicating
- CHANGELOG deduplication guard: changelog.Update() is idempotent — skips
  write if ## [version] section already exists
- Always load config sources: LoadWithSources() called unconditionally;
  removes the dual config path that previously only tracked sources in
  --verbose mode
- .releaser.gitlab-ci.yml: adds artifacts: reports: dotenv: release.env

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 00:15:09 +02:00
parent 62a702fb89
commit 5af107b06d
15 changed files with 428 additions and 175 deletions
+43 -3
View File
@@ -6,14 +6,16 @@ import (
"os"
"os/exec"
"sort"
"strings"
"time"
gogit "github.com/go-git/go-git/v5"
gitconfig "github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/plumbing/storer"
githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
"git.k3nny.fr/releaser/internal/branch"
)
@@ -241,15 +243,53 @@ func CreateTag(repo *gogit.Repository, tagName string) error {
// Push pushes the given branch and tag to the "origin" remote.
// When token is non-empty, go-git is used with HTTPS basic auth (oauth2/token) — suitable for CI.
// When token is empty, the system git binary is invoked so that credential helpers,
// SSH agents, and netrc are all available as they would be for a regular git push.
// When token is empty and the remote URL is SSH, go-git SSH agent auth is attempted first.
// Falls back to the system git binary so that credential helpers, netrc, and SSH keys work normally.
func Push(repo *gogit.Repository, branchName, tagName, token string) error {
if token != "" {
return pushWithGoGit(repo, branchName, tagName, token)
}
// Try SSH agent auth when the remote URL uses SSH transport.
if remote, err := repo.Remote("origin"); err == nil {
urls := remote.Config().URLs
if len(urls) > 0 && isSSHURL(urls[0]) {
if err := pushWithSSHAgent(repo, branchName, tagName); err == nil {
return nil
}
}
}
return pushWithCLI(repo, branchName, tagName)
}
func isSSHURL(u string) bool {
return strings.HasPrefix(u, "git@") || strings.HasPrefix(u, "ssh://")
}
func pushWithSSHAgent(repo *gogit.Repository, branchName, tagName string) error {
auth, err := gitssh.NewSSHAgentAuth("git")
if err != nil {
return err
}
remote, err := repo.Remote("origin")
if err != nil {
return fmt.Errorf("remote origin not found: %w", err)
}
opts := &gogit.PushOptions{
RefSpecs: []gitconfig.RefSpec{
gitconfig.RefSpec(fmt.Sprintf("refs/heads/%s:refs/heads/%s", branchName, branchName)),
gitconfig.RefSpec(fmt.Sprintf("refs/tags/%s:refs/tags/%s", tagName, tagName)),
},
Auth: auth,
}
if err := remote.Push(opts); err != nil && !errors.Is(err, gogit.NoErrAlreadyUpToDate) {
return fmt.Errorf("git push via SSH agent: %w", err)
}
return nil
}
func pushWithGoGit(repo *gogit.Repository, branchName, tagName, token string) error {
remote, err := repo.Remote("origin")
if err != nil {