Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ func NewIssue(ctx context.Context, client *github.Client, settings *Settings, in
log.Fatal("title cannot be empty")
}

description, err = PostProcessDescription(ctx, settings, description)
if err != nil {
return 0, err
}

gir = &github.IssueRequest{
Title: &title,
Body: &description,
Expand All @@ -73,6 +78,44 @@ func NewIssue(ctx context.Context, client *github.Client, settings *Settings, in
return *i.Number, nil
}

// PostProcessDescription runs the configured PostProcess hook against the
// supplied description and returns the processed result. The description is
// written to a temp file (the hook edits files in place, matching the
// interactive flow) and the file contents are read back afterwards. If no
// PostProcess hook is configured the description is returned unchanged.
func PostProcessDescription(ctx context.Context, settings *Settings, description string) (string, error) {
if settings.PostProcess == "" {
return description, nil
}

tempFile, err := os.CreateTemp("", "git-open-pull")
if err != nil {
return "", err
}
defer os.Remove(tempFile.Name())

if _, err := io.WriteString(tempFile, description); err != nil {
tempFile.Close()
return "", err
}
if err := tempFile.Close(); err != nil {
return "", err
}

cmd := exec.CommandContext(ctx, settings.PostProcess, tempFile.Name())
out, err := cmd.CombinedOutput()
if err != nil {
log.Printf("error running post process template: %s\n error: %v\n output: %s", settings.PostProcess, err, out)
return "", err
}

processed, err := os.ReadFile(tempFile.Name())
if err != nil {
return "", err
}
return string(processed), nil
}

// PopulateIssueInteractive creates a template, parses the template and returns the Issue number if the user is in interactive mode
func PopulateIssueInteractive(ctx context.Context, client *github.Client, settings *Settings, inputTitle, inputDescription string, labelSlice []string) (ir *github.IssueRequest, err error) {
labels, err := Labels(ctx, client, settings)
Expand Down
51 changes: 51 additions & 0 deletions issues_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package main

import (
"context"
"fmt"
"os"
"path/filepath"
"testing"
)

Expand Down Expand Up @@ -30,3 +33,51 @@ func TestDetectIssueNumber(t *testing.T) {
})
}
}

func TestPostProcessDescription(t *testing.T) {
type testCase struct {
name string
hook string // shell script body; empty means no hook configured
input string
expected string
expectErr bool
}
tests := []testCase{
{"no hook configured", "", "resolves II-3573\n", "resolves II-3573\n", false},
{
"hook edits file in place",
`sed -i 's#II-3573#[II-3573](https://example.test/browse/II-3573)#' "$1"`,
"resolves II-3573\n",
"resolves [II-3573](https://example.test/browse/II-3573)\n",
false,
},
{"hook failure returns error", "exit 1", "resolves II-3573\n", "", true},
}
for i, tc := range tests {
tc := tc
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
t.Parallel()
settings := &Settings{}
if tc.hook != "" {
hook := filepath.Join(t.TempDir(), "hook.sh")
if err := os.WriteFile(hook, []byte("#!/bin/sh\n"+tc.hook+"\n"), 0o755); err != nil {
t.Fatal(err)
}
settings.PostProcess = hook
}
got, err := PostProcessDescription(context.Background(), settings, tc.input)
if tc.expectErr {
if err == nil {
t.Errorf("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != tc.expected {
t.Errorf("got %q expected %q", got, tc.expected)
}
})
}
}
Loading