feat(gitutil): release v1.9.0 — detached HEAD support in CI
docs / Build and deploy docs (push) Failing after 32s
release / Build and publish release (push) Successful in 5m34s
ci / vet, staticcheck, test, build (push) Failing after 18m8s

- CurrentBranch falls back to CI_COMMIT_BRANCH, CI_COMMIT_REF_NAME, then
  GITHUB_REF_NAME when HEAD is detached, so --branch is no longer required
  in GitLab CI / GitHub Actions jobs
- go-git push paths (token and SSH agent) now push HEAD's commit hash to
  the branch instead of refs/heads/<branch>, which never exists in a
  detached CI checkout — go-git silently skipped the branch update and
  pushed only the tag
- .releaser.gitlab-ci.yml template drops the redundant --branch flag
- docs: README, usage, ci-integration, changelog, ROADMAP updated

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 21:12:24 +02:00
parent 3fd8dc2a49
commit 2a6a65ce80
11 changed files with 298 additions and 26 deletions
+57
View File
@@ -150,6 +150,9 @@ func TestRunDetachedHead(t *testing.T) {
// Detach HEAD
head, _ := repo.Head()
repo.Storer.SetReference(plumbing.NewHashReference(plumbing.HEAD, head.Hash()))
for _, name := range []string{"CI_COMMIT_BRANCH", "CI_COMMIT_REF_NAME", "GITHUB_REF_NAME"} {
t.Setenv(name, "")
}
err := execCmd(t, "--dry-run", "--repo", dir) // no --branch
if err == nil {
@@ -157,6 +160,24 @@ func TestRunDetachedHead(t *testing.T) {
}
}
func TestRunDetachedHeadCIBranchEnv(t *testing.T) {
repo, dir := setupRepo(t)
addFile(t, dir, "src.go", "// fix")
w, _ := repo.Worktree()
w.Add("src.go")
w.Commit("fix: patch something", &gogit.CommitOptions{Author: testSig()})
head, _ := repo.Head()
repo.Storer.SetReference(plumbing.NewHashReference(plumbing.HEAD, head.Hash()))
for _, name := range []string{"CI_COMMIT_REF_NAME", "GITHUB_REF_NAME"} {
t.Setenv(name, "")
}
t.Setenv("CI_COMMIT_BRANCH", "release/1.2")
if err := execCmd(t, "--dry-run", "--repo", dir); err != nil {
t.Fatalf("detached HEAD with CI_COMMIT_BRANCH should succeed: %v", err)
}
}
func TestRunBadConfig(t *testing.T) {
_, dir := setupRepo(t)
if err := os.WriteFile(filepath.Join(dir, ".releaser.yml"), []byte("{[invalid"), 0644); err != nil {
@@ -484,6 +505,42 @@ func TestRunNoRelease(t *testing.T) {
}
}
func TestRunCIDetachedHeadPush(t *testing.T) {
repo, dir := setupRepoWithRemote(t)
addFile(t, dir, "x.go", "// fix")
w, _ := repo.Worktree()
w.Add("x.go")
w.Commit("fix: patch something", &gogit.CommitOptions{Author: testSig()})
// Simulate a GitLab CI checkout: detached HEAD, no local branch ref,
// branch name only available through the environment.
head, _ := repo.Head()
repo.Storer.SetReference(plumbing.NewHashReference(plumbing.HEAD, head.Hash()))
if err := repo.Storer.RemoveReference(plumbing.NewBranchReferenceName("master")); err != nil {
t.Fatal(err)
}
t.Setenv("CI_COMMIT_BRANCH", "release/1.2")
t.Setenv("CI_COMMIT_REF_NAME", "")
t.Setenv("GITHUB_REF_NAME", "")
t.Setenv("GITLAB_TOKEN", "test-token")
if err := execCmd(t, "--no-release", "--repo", dir); err != nil {
t.Fatalf("CI-style detached run: %v", err)
}
remote, err := repo.Remote("origin")
if err != nil {
t.Fatal(err)
}
bare, err := gogit.PlainOpen(remote.Config().URLs[0])
if err != nil {
t.Fatal(err)
}
if _, err := bare.Reference(plumbing.NewBranchReferenceName("release/1.2"), false); err != nil {
t.Errorf("release commit was not pushed to the remote branch: %v", err)
}
}
func TestRunMissingToken(t *testing.T) {
_, dir := setupRepoWithRemote(t)
addFile(t, dir, "x.go", "// fix")