diff --git a/.github/workflows/tag-and-release.yaml b/.github/workflows/tag-and-release.yaml index 92b32e1efa..1c0f49243c 100644 --- a/.github/workflows/tag-and-release.yaml +++ b/.github/workflows/tag-and-release.yaml @@ -85,6 +85,14 @@ jobs: - name: Fetch git tags run: git fetch --tags --force + # prepare-release creates an annotated tag, which records a tagger + # identity. Runners have none configured, so git tag -a fails + # without this step. + - name: Configure git identity + run: | + git config --global user.email "ci@coder.com" + git config --global user.name "Coder CI" + - name: Set up mise tools uses: ./.github/actions/setup-mise with: diff --git a/scripts/release-action/cmdexec.go b/scripts/release-action/cmdexec.go index d0515242fe..6af75b7376 100644 --- a/scripts/release-action/cmdexec.go +++ b/scripts/release-action/cmdexec.go @@ -1,11 +1,14 @@ package main import ( + "bytes" "errors" "fmt" "io" "os/exec" "strings" + + "golang.org/x/xerrors" ) // CommandExecutor abstracts running CLI commands so that a dry-run @@ -60,7 +63,18 @@ func (realExecutor) Run(name string, args ...string) error { func (realExecutor) RunMutation(name string, args ...string) error { cmd := exec.Command(name, args...) - return cmd.Run() + // Capture stderr so that a failing mutation surfaces the command's + // error output (e.g. git's "fatal:" message) in the returned error + // instead of only the exit status. + var stderr bytes.Buffer + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + if msg := strings.TrimSpace(stderr.String()); msg != "" { + return xerrors.Errorf("%s: %w", msg, err) + } + return err + } + return nil } func (realExecutor) RunMutationStdout(stdout, stderr io.Writer, name string, args ...string) error { diff --git a/scripts/release-action/cmdexec_test.go b/scripts/release-action/cmdexec_test.go index ae45fb9c4c..b75e0363b8 100644 --- a/scripts/release-action/cmdexec_test.go +++ b/scripts/release-action/cmdexec_test.go @@ -36,6 +36,14 @@ func TestRealExecutor_RunMutation(t *testing.T) { require.Error(t, err) } +func TestRealExecutor_RunMutationSurfacesStderr(t *testing.T) { + t.Parallel() + exec := realExecutor{} + err := exec.RunMutation("sh", "-c", "echo 'fatal: boom' 1>&2; exit 1") + require.Error(t, err) + assert.Contains(t, err.Error(), "fatal: boom") +} + func TestRealExecutor_RunMutationStdout(t *testing.T) { t.Parallel() exec := realExecutor{}