Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a6a65ce80 | |||
| 3fd8dc2a49 |
@@ -41,9 +41,8 @@
|
||||
- git config user.name "${GITLAB_USER_NAME:-Releaser CI}"
|
||||
|
||||
script:
|
||||
- releaser
|
||||
--branch "$CI_COMMIT_BRANCH"
|
||||
$RELEASER_EXTRA_ARGS
|
||||
# No --branch needed: releaser falls back to CI_COMMIT_BRANCH on detached HEAD.
|
||||
- releaser $RELEASER_EXTRA_ARGS
|
||||
|
||||
artifacts:
|
||||
reports:
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [1.9.0] - 2026-07-16
|
||||
|
||||
### Added
|
||||
|
||||
- **CI branch detection fallback** — on detached HEAD (the normal state in CI checkouts), the branch name now falls back to `CI_COMMIT_BRANCH`, then `CI_COMMIT_REF_NAME` (GitLab CI), then `GITHUB_REF_NAME` (GitHub Actions) before erroring; `--branch` is no longer required in CI
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Detached HEAD push** — the go-git push paths (token and SSH agent) used the refspec `refs/heads/<branch>`, a ref that never exists in a detached CI checkout; go-git silently skipped the branch update and pushed only the tag, so the release commit never reached the remote branch. Both paths now push HEAD's commit hash to the branch instead
|
||||
|
||||
### Changed
|
||||
|
||||
- **`.releaser.gitlab-ci.yml` template** — dropped the now-redundant `--branch "$CI_COMMIT_BRANCH"` from the job script
|
||||
|
||||
## [1.8.0] - 2026-07-12
|
||||
|
||||
### Added
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<img src="docs/static/images/releaser-logo-128.png" alt="releaser logo" width="128">
|
||||
|
||||

|
||||

|
||||
|
||||
A CI-friendly release automation tool for GitFlow workflows using Conventional Commits.
|
||||
|
||||
@@ -65,7 +65,8 @@ releaser --no-commit
|
||||
# … then commit manually and re-run:
|
||||
releaser --tag-only
|
||||
|
||||
# Explicitly target a branch (useful in detached HEAD CI)
|
||||
# Explicitly target a branch (detached HEAD falls back to
|
||||
# CI_COMMIT_BRANCH / CI_COMMIT_REF_NAME / GITHUB_REF_NAME automatically)
|
||||
releaser --branch release/1.2
|
||||
|
||||
# Write changelog to a custom file
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
- [x] Commit range walker (last tag → HEAD, or full history on first release)
|
||||
- [x] Version bump calculator — returns plain `X.Y.Z` (prefix kept separate)
|
||||
- [x] `--dry-run` flag: print next version and exit
|
||||
- [x] `--branch` flag: override branch detection (detached HEAD in CI)
|
||||
- [x] `--branch` flag: override branch detection (detached HEAD in CI); detached HEAD auto-falls back to `CI_COMMIT_BRANCH` / `CI_COMMIT_REF_NAME` / `GITHUB_REF_NAME` — ✓ shipped v1.9.0, along with detached-HEAD push fix
|
||||
- [x] Exit code 2 when no releasable commits
|
||||
|
||||
## v0.2 — Config + Maven + local git ops ✅
|
||||
|
||||
@@ -24,6 +24,12 @@ tasks:
|
||||
generates:
|
||||
- "{{.BIN}}"
|
||||
|
||||
install:
|
||||
desc: installs the binary in /usr/local/bin/
|
||||
cmds:
|
||||
- task: build
|
||||
- sudo cp ./bin/releaser /usr/local/bin/releaser
|
||||
|
||||
run:
|
||||
desc: Build and run releaser (pass args with -- e.g. task run -- --dry-run)
|
||||
deps: [build]
|
||||
|
||||
+1
-1
@@ -245,7 +245,7 @@ func newRootCmd() *cobra.Command {
|
||||
root.Flags().BoolVar(&noRelease, "no-release", false, "push commit and tag but skip creating the release")
|
||||
root.Flags().BoolVar(&noCommit, "no-commit", false, "update files but do not commit, tag, or push")
|
||||
root.Flags().BoolVar(&tagOnly, "tag-only", false, "tag HEAD without updating files (assumes version was already committed)")
|
||||
root.Flags().StringVar(&branchOverride, "branch", "", "override branch name detection (required in detached HEAD)")
|
||||
root.Flags().StringVar(&branchOverride, "branch", "", "override branch name detection (detached HEAD falls back to CI_COMMIT_BRANCH, CI_COMMIT_REF_NAME, GITHUB_REF_NAME)")
|
||||
root.Flags().StringVar(&repoPath, "repo", ".", "path to git repository")
|
||||
root.Flags().StringVar(&pomOverride, "pom", "", "override maven.pom_path from config")
|
||||
root.Flags().StringVar(&gradleOverride, "gradle", "", "override gradle.build_file from config")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -3,6 +3,16 @@ title: Changelog
|
||||
weight: 50
|
||||
---
|
||||
|
||||
## v1.9.0 — 2026-07-16
|
||||
|
||||
### Added
|
||||
|
||||
- **CI branch detection fallback** — on detached HEAD, the branch name falls back to `CI_COMMIT_BRANCH`, then `CI_COMMIT_REF_NAME` (GitLab CI), then `GITHUB_REF_NAME` (GitHub Actions); `--branch` is no longer required in CI
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Detached HEAD push** — go-git pushes silently skipped the branch update in detached CI checkouts (only the tag was pushed); the branch is now pushed from HEAD's commit hash
|
||||
|
||||
## v1.8.0 — 2026-07-12
|
||||
|
||||
### Added
|
||||
|
||||
@@ -95,7 +95,13 @@ jobs:
|
||||
|
||||
## Detached HEAD
|
||||
|
||||
In CI environments where `git checkout` leaves the repository in detached HEAD state, pass the branch name explicitly:
|
||||
CI runners check out a commit SHA, leaving the repository in detached HEAD state. `releaser` detects this and falls back to the branch name from the CI environment, in order:
|
||||
|
||||
1. `CI_COMMIT_BRANCH` (GitLab CI, branch pipelines)
|
||||
2. `CI_COMMIT_REF_NAME` (GitLab CI)
|
||||
3. `GITHUB_REF_NAME` (GitHub Actions)
|
||||
|
||||
So on branch pipelines no extra configuration is needed. To override the detected name (or on runners that set none of these variables), pass it explicitly:
|
||||
|
||||
```yaml
|
||||
script:
|
||||
|
||||
@@ -35,7 +35,7 @@ releaser --verbose --dry-run
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `--dry-run` | false | Print next version and exit without making any changes |
|
||||
| `--branch <name>` | auto-detected | Override branch name (useful in detached HEAD / CI) |
|
||||
| `--branch <name>` | auto-detected | Override branch name (detached HEAD falls back to `CI_COMMIT_BRANCH`, `CI_COMMIT_REF_NAME`, `GITHUB_REF_NAME`) |
|
||||
| `--branch-pattern <regex>` | `^(?:.*/)?release/(\d+)\.(\d+)$` | Override branch pattern (two capture groups: major, minor) |
|
||||
| `--tag-prefix <prefix>` | `""` | Prefix for version tags (e.g. `v` → `v1.2.3`) |
|
||||
| `--pom <path>` | `pom.xml` | Path to pom.xml relative to repo root |
|
||||
|
||||
+45
-12
@@ -42,17 +42,31 @@ func IsWorkingTreeClean(repo *gogit.Repository) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ciBranchEnvVars are checked in order when HEAD is detached: CI runners
|
||||
// (GitLab, GitHub Actions) check out a commit SHA, so the branch name is only
|
||||
// available through the environment. CI_COMMIT_BRANCH is unset on tag and
|
||||
// merge-request pipelines, where CI_COMMIT_REF_NAME would hold a non-branch
|
||||
// ref — a name that fails branch pattern parsing with a clear error.
|
||||
var ciBranchEnvVars = []string{"CI_COMMIT_BRANCH", "CI_COMMIT_REF_NAME", "GITHUB_REF_NAME"}
|
||||
|
||||
// CurrentBranch returns the short branch name from HEAD.
|
||||
// Returns an error when HEAD is detached (common in CI pipelines).
|
||||
// When HEAD is detached (common in CI pipelines), it falls back to the CI
|
||||
// environment variables that carry the branch name, and errors only when
|
||||
// none of them is set.
|
||||
func CurrentBranch(repo *gogit.Repository) (string, error) {
|
||||
head, err := repo.Head()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read HEAD: %w", err)
|
||||
}
|
||||
if !head.Name().IsBranch() {
|
||||
return "", fmt.Errorf("HEAD is detached — use --branch to specify the release branch")
|
||||
}
|
||||
if head.Name().IsBranch() {
|
||||
return head.Name().Short(), nil
|
||||
}
|
||||
for _, name := range ciBranchEnvVars {
|
||||
if v := os.Getenv(name); v != "" {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("HEAD is detached and no CI branch variable is set (%s) — use --branch to specify the release branch", strings.Join(ciBranchEnvVars, ", "))
|
||||
}
|
||||
|
||||
type tagCandidate struct {
|
||||
@@ -269,6 +283,21 @@ func isSSHURL(u string) bool {
|
||||
return strings.HasPrefix(u, "git@") || strings.HasPrefix(u, "ssh://")
|
||||
}
|
||||
|
||||
// pushRefSpecs builds the refspecs for the release push. The branch source is
|
||||
// HEAD's commit hash rather than refs/heads/<branch>: in CI checkouts HEAD is
|
||||
// detached and the local branch ref does not exist, in which case go-git
|
||||
// silently skips the branch update and pushes only the tag.
|
||||
func pushRefSpecs(repo *gogit.Repository, branchName, tagName string) ([]gitconfig.RefSpec, error) {
|
||||
head, err := repo.Head()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read HEAD: %w", err)
|
||||
}
|
||||
return []gitconfig.RefSpec{
|
||||
gitconfig.RefSpec(fmt.Sprintf("%s:refs/heads/%s", head.Hash(), branchName)),
|
||||
gitconfig.RefSpec(fmt.Sprintf("refs/tags/%s:refs/tags/%s", tagName, tagName)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func pushWithSSHAgent(repo *gogit.Repository, branchName, tagName string) error {
|
||||
auth, err := newSSHAgentAuth("git")
|
||||
if err != nil {
|
||||
@@ -280,11 +309,13 @@ func pushWithSSHAgent(repo *gogit.Repository, branchName, tagName string) error
|
||||
return fmt.Errorf("remote origin not found: %w", err)
|
||||
}
|
||||
|
||||
refSpecs, err := pushRefSpecs(repo, branchName, tagName)
|
||||
if err != nil {
|
||||
return 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)),
|
||||
},
|
||||
RefSpecs: refSpecs,
|
||||
Auth: auth,
|
||||
}
|
||||
|
||||
@@ -300,11 +331,13 @@ func pushWithGoGit(repo *gogit.Repository, branchName, tagName, token string) er
|
||||
return fmt.Errorf("remote origin not found: %w", err)
|
||||
}
|
||||
|
||||
refSpecs, err := pushRefSpecs(repo, branchName, tagName)
|
||||
if err != nil {
|
||||
return 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)),
|
||||
},
|
||||
RefSpecs: refSpecs,
|
||||
Auth: &githttp.BasicAuth{
|
||||
Username: "oauth2",
|
||||
Password: token,
|
||||
|
||||
@@ -157,16 +157,29 @@ func TestCurrentBranch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentBranchDetached(t *testing.T) {
|
||||
repo, dir := newTestRepo(t)
|
||||
addCommit(t, repo, dir, "chore: init", "initial")
|
||||
// clearCIBranchEnv unsets the CI branch variables so detached-HEAD tests
|
||||
// behave the same on a developer machine and inside an actual CI job.
|
||||
func clearCIBranchEnv(t *testing.T) {
|
||||
t.Helper()
|
||||
for _, name := range ciBranchEnvVars {
|
||||
t.Setenv(name, "")
|
||||
}
|
||||
}
|
||||
|
||||
// Detach HEAD by replacing the symbolic ref with a hash ref
|
||||
func detachHead(t *testing.T, repo *gogit.Repository) {
|
||||
t.Helper()
|
||||
head, _ := repo.Head()
|
||||
detached := plumbing.NewHashReference(plumbing.HEAD, head.Hash())
|
||||
if err := repo.Storer.SetReference(detached); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentBranchDetached(t *testing.T) {
|
||||
repo, dir := newTestRepo(t)
|
||||
addCommit(t, repo, dir, "chore: init", "initial")
|
||||
detachHead(t, repo)
|
||||
clearCIBranchEnv(t)
|
||||
|
||||
_, err := CurrentBranch(repo)
|
||||
if err == nil {
|
||||
@@ -174,6 +187,67 @@ func TestCurrentBranchDetached(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentBranchDetachedCIFallback(t *testing.T) {
|
||||
repo, dir := newTestRepo(t)
|
||||
addCommit(t, repo, dir, "chore: init", "initial")
|
||||
detachHead(t, repo)
|
||||
|
||||
t.Run("CI_COMMIT_BRANCH wins over CI_COMMIT_REF_NAME", func(t *testing.T) {
|
||||
clearCIBranchEnv(t)
|
||||
t.Setenv("CI_COMMIT_BRANCH", "release/1.2")
|
||||
t.Setenv("CI_COMMIT_REF_NAME", "release/9.9")
|
||||
|
||||
name, err := CurrentBranch(repo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if name != "release/1.2" {
|
||||
t.Errorf("got %q, want release/1.2", name)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CI_COMMIT_REF_NAME when CI_COMMIT_BRANCH unset", func(t *testing.T) {
|
||||
clearCIBranchEnv(t)
|
||||
t.Setenv("CI_COMMIT_REF_NAME", "release/3.4")
|
||||
|
||||
name, err := CurrentBranch(repo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if name != "release/3.4" {
|
||||
t.Errorf("got %q, want release/3.4", name)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GITHUB_REF_NAME as last resort", func(t *testing.T) {
|
||||
clearCIBranchEnv(t)
|
||||
t.Setenv("GITHUB_REF_NAME", "release/5.6")
|
||||
|
||||
name, err := CurrentBranch(repo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if name != "release/5.6" {
|
||||
t.Errorf("got %q, want release/5.6", name)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("env ignored when HEAD is on a branch", func(t *testing.T) {
|
||||
attached, attachedDir := newTestRepo(t)
|
||||
addCommit(t, attached, attachedDir, "chore: init", "initial")
|
||||
clearCIBranchEnv(t)
|
||||
t.Setenv("CI_COMMIT_BRANCH", "release/9.9")
|
||||
|
||||
name, err := CurrentBranch(attached)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if name == "release/9.9" {
|
||||
t.Error("CI env var must not override an attached HEAD")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ── LatestTag ─────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestLatestTagNoTags(t *testing.T) {
|
||||
@@ -702,6 +776,64 @@ func TestPushWithBareRemote(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushDetachedHeadUpdatesRemoteBranch(t *testing.T) {
|
||||
// Regression: in a CI checkout HEAD is detached and refs/heads/<branch>
|
||||
// does not exist locally — go-git silently skipped the branch refspec and
|
||||
// pushed only the tag. The push must carry HEAD's commit to the branch.
|
||||
remoteDir := t.TempDir()
|
||||
if _, err := gogit.PlainInit(remoteDir, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
repo, dir := newTestRepo(t)
|
||||
hash := addCommit(t, repo, dir, "fix: something", "v1")
|
||||
addTag(t, repo, "v1.2.1")
|
||||
detachHead(t, repo)
|
||||
if err := repo.Storer.RemoveReference(plumbing.NewBranchReferenceName("master")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := repo.CreateRemote(&gitconfig.RemoteConfig{
|
||||
Name: "origin",
|
||||
URLs: []string{remoteDir},
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := Push(repo, "release/1.2", "v1.2.1", "dummy-token"); err != nil {
|
||||
t.Fatalf("Push from detached HEAD failed: %v", err)
|
||||
}
|
||||
|
||||
bare, err := gogit.PlainOpen(remoteDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ref, err := bare.Reference(plumbing.NewBranchReferenceName("release/1.2"), false)
|
||||
if err != nil {
|
||||
t.Fatalf("remote branch ref missing after push: %v", err)
|
||||
}
|
||||
if ref.Hash() != hash {
|
||||
t.Errorf("remote branch at %s, want HEAD commit %s", ref.Hash(), hash)
|
||||
}
|
||||
if _, err := bare.Reference(plumbing.NewTagReferenceName("v1.2.1"), false); err != nil {
|
||||
t.Errorf("remote tag ref missing after push: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushWithGoGitUnbornHead(t *testing.T) {
|
||||
// Repo with a remote but no commits: pushRefSpecs fails reading HEAD.
|
||||
repo, _ := newTestRepo(t)
|
||||
if _, err := repo.CreateRemote(&gitconfig.RemoteConfig{
|
||||
Name: "origin",
|
||||
URLs: []string{t.TempDir()},
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := pushWithGoGit(repo, "master", "v1.0.0", "some-token"); err == nil {
|
||||
t.Error("expected error for unborn HEAD")
|
||||
}
|
||||
}
|
||||
|
||||
// ── IsWorkingTreeClean: w.Status() error path ────────────────────────────────
|
||||
|
||||
func TestIsWorkingTreeCleanCorruptIndex(t *testing.T) {
|
||||
@@ -1040,6 +1172,26 @@ func TestPushWithGoGitNoRemote(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushWithSSHAgentUnbornHead(t *testing.T) {
|
||||
orig := newSSHAgentAuth
|
||||
newSSHAgentAuth = func(user string) (*gitssh.PublicKeysCallback, error) {
|
||||
return &gitssh.PublicKeysCallback{User: user}, nil
|
||||
}
|
||||
defer func() { newSSHAgentAuth = orig }()
|
||||
|
||||
// Repo with a remote but no commits: pushRefSpecs fails reading HEAD.
|
||||
repo, _ := newTestRepo(t)
|
||||
if _, err := repo.CreateRemote(&gitconfig.RemoteConfig{
|
||||
Name: "origin",
|
||||
URLs: []string{t.TempDir()},
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := pushWithSSHAgent(repo, "master", "v1.0.0"); err == nil {
|
||||
t.Error("expected error for unborn HEAD")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushWithGoGitPushFails(t *testing.T) {
|
||||
repo, dir := newTestRepo(t)
|
||||
addCommit(t, repo, dir, "fix: c1", "v1")
|
||||
|
||||
Reference in New Issue
Block a user