fix(cli): friendly error when publishing from a repo with no commits

Previously a brand-new repo (git init, no commits) surfaced `git stash
create: exit status 128: You do not have the initial commit yet` from
deep inside the archive helper. Now the publish flow detects this case
via `git rev-parse --verify HEAD` up front and prints "no commits in
repository; run `git add . && git commit` before publishing". Also
updates the init flow's hint to mention `git init && git commit` so
users aren't misled into thinking `git init` alone is enough.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Alex Dunmow 2026-06-03 08:56:27 +08:00
parent 20a7b35e50
commit 4c0104619e
2 changed files with 74 additions and 1 deletions

View File

@ -109,7 +109,7 @@ func newPluginInitCmd() *cobra.Command {
fmt.Fprintf(os.Stderr, "warning: could not commit plugin.mod: %v\n", err)
}
} else {
fmt.Println("Not in a git repo - run `git init` before `ninja plugin publish`")
fmt.Println("Not in a git repo - run `git init && git commit` before `ninja plugin publish`")
}
return nil
},
@ -294,6 +294,10 @@ func newPluginPublishCmd() *cobra.Command {
return fmt.Errorf("plugin.mod must have scope, name, and version")
}
if err := checkRepoHasHEAD("."); err != nil {
return err
}
if !allowDirty {
out, _ := exec.Command("git", "status", "--porcelain").Output()
if len(strings.TrimSpace(string(out))) > 0 {
@ -548,6 +552,20 @@ func runCmd(name string, args ...string) error {
return c.Run()
}
// checkRepoHasHEAD returns a friendlier error than the raw git failure when
// the publish flow is invoked in a freshly-initialised repository that has
// no commits yet. Without this, `git stash create` reports "You do not have
// the initial commit yet" via exit status 128, which surfaces as an internal
// failure to the user.
func checkRepoHasHEAD(repoDir string) error {
cmd := exec.Command("git", "rev-parse", "--verify", "HEAD")
cmd.Dir = repoDir
if err := cmd.Run(); err != nil {
return fmt.Errorf("no commits in repository; run `git add . && git commit` before publishing")
}
return nil
}
func upsertPluginMod(scope, name, kind string, categories []string) error {
const file = "plugin.mod"
existing, _ := os.ReadFile(file)

View File

@ -0,0 +1,55 @@
package cmd
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestCheckRepoHasHEAD_NoCommitsReturnsFriendlyError(t *testing.T) {
dir := t.TempDir()
runGit(t, dir, "init", "-q")
err := checkRepoHasHEAD(dir)
if err == nil {
t.Fatal("expected error for repo with no commits, got nil")
}
if !strings.Contains(err.Error(), "no commits in repository") {
t.Errorf("error %q should mention 'no commits in repository'", err.Error())
}
if !strings.Contains(err.Error(), "git commit") {
t.Errorf("error %q should suggest `git commit`", err.Error())
}
}
func TestCheckRepoHasHEAD_WithCommitReturnsNil(t *testing.T) {
dir := t.TempDir()
runGit(t, dir, "init", "-q")
if err := os.WriteFile(filepath.Join(dir, "f"), []byte("hi"), 0o644); err != nil {
t.Fatal(err)
}
runGit(t, dir, "add", "f")
runGit(t, dir, "commit", "-qm", "init")
if err := checkRepoHasHEAD(dir); err != nil {
t.Errorf("expected nil for repo with a commit, got %v", err)
}
}
func runGit(t *testing.T, dir string, args ...string) {
t.Helper()
cmd := exec.Command("git", args...)
cmd.Dir = dir
cmd.Env = append(os.Environ(),
"GIT_AUTHOR_NAME=t",
"GIT_AUTHOR_EMAIL=t@t",
"GIT_COMMITTER_NAME=t",
"GIT_COMMITTER_EMAIL=t@t",
)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
}