Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue and Pull Request Templates, with addendums #1187

Merged
merged 13 commits into from Aug 21, 2016
6 changes: 4 additions & 2 deletions commands/issue.go
Expand Up @@ -108,13 +108,15 @@ func createIssue(cmd *Command, args *Args) {
}

func writeIssueTitleAndBody(project *github.Project) (string, string, error) {
message := `
message := `%s
# Creating issue for %s.
#
# Write a message for this issue. The first block of
# text is the title and the rest is the description.
`
message = fmt.Sprintf(message, project.Name)

template := github.GetIssueTemplate()
message = fmt.Sprintf(message, template, project.Name)

editor, err := github.NewEditor("ISSUE", "issue", message)
if err != nil {
Expand Down
8 changes: 6 additions & 2 deletions commands/pull_request.go
Expand Up @@ -184,7 +184,7 @@ func pullRequest(cmd *Command, args *Args) {
headTracking = fmt.Sprintf("%s/%s", remote.Name, head)
}

message, err := pullRequestChangesMessage(baseTracking, headTracking, fullBase, fullHead)
message, err := createPullRequestMessage(baseTracking, headTracking, fullBase, fullHead)
utils.Check(err)

editor, err = github.NewEditor("PULLREQ", "pull request", message)
Expand Down Expand Up @@ -258,7 +258,7 @@ func pullRequest(cmd *Command, args *Args) {
}
}

func pullRequestChangesMessage(base, head, fullBase, fullHead string) (string, error) {
func createPullRequestMessage(base, head, fullBase, fullHead string) (string, error) {
var (
defaultMsg string
commitLogs string
Expand All @@ -278,6 +278,10 @@ func pullRequestChangesMessage(base, head, fullBase, fullHead string) (string, e
}
}

if template := github.GetPullRequestTemplate(); template != "" {
defaultMsg = github.GeneratePRTemplate(defaultMsg)
}

cs := git.CommentChar()

return renderPullRequestTpl(defaultMsg, cs, fullBase, fullHead, commitLogs)
Expand Down
4 changes: 2 additions & 2 deletions commands/pull_request_tpl.go
Expand Up @@ -16,8 +16,8 @@ const pullRequestTmpl = `{{if .InitMsg}}{{.InitMsg}}
{{.CS}} of text is the title and the rest is the description.{{if .HasCommitLogs}}
{{.CS}}
{{.CS}} Changes:
{{.CS}}{{if .HasCommitLogs}}
{{.FormattedCommitLogs}}{{end}}{{end}}`
{{.CS}}
{{.FormattedCommitLogs}}{{end}}`

type pullRequestMsg struct {
InitMsg string
Expand Down
10 changes: 10 additions & 0 deletions fixtures/test_repo.go
Expand Up @@ -59,6 +59,16 @@ func (r *TestRepo) AddRemote(name, url, pushURL string) {
}
}

func (r *TestRepo) AddFile(filePath string, content string) {
path := filepath.Join(r.dir, filePath)
err := os.MkdirAll(filepath.Dir(path), 0771)
if err != nil {
panic(err)
}

ioutil.WriteFile(path, []byte(content), os.ModePerm)
}

func (r *TestRepo) clone(repo, dir string) error {
cmd := cmd.New("git").WithArgs("clone", repo, dir)
output, err := cmd.CombinedOutput()
Expand Down
85 changes: 85 additions & 0 deletions github/template.go
@@ -0,0 +1,85 @@
package github

import (
"io/ioutil"
"os"
"path/filepath"
"strings"

"github.com/github/hub/utils"
)

const (
pullRequestTemplate = "pull_request_template"
issueTemplate = "issue_template"
githubTemplateDir = ".github"
)

func GetPullRequestTemplate() string {
return getGithubTemplate(pullRequestTemplate)
}

func GetIssueTemplate() string {
return getGithubTemplate(issueTemplate)
}

func GeneratePRTemplate(defaultMsg string) string {
return strings.Split(defaultMsg, "\n")[0] + "\n\n" + GetPullRequestTemplate()
}

func getGithubTemplate(pat string) (body string) {
var path string

if _, err := os.Stat(githubTemplateDir); err == nil {
if p := getFilePath(githubTemplateDir, pat); p != "" {
path = p
}
}

if path == "" {
if p := getFilePath(".", pat); p != "" {
path = p
}
}

if path == "" {
return
}

body, err := readContentsFromFile(path)
utils.Check(err)
return
}

func getFilePath(dir, pattern string) string {
files, err := ioutil.ReadDir(dir)
utils.Check(err)

for _, file := range files {
fileName := file.Name()
path := fileName

if ext := filepath.Ext(fileName); ext == ".md" {
path = strings.TrimRight(fileName, ".md")
} else if ext == ".txt" {
path = strings.TrimRight(fileName, ".txt")
}

path = strings.ToLower(path)

if ok, _ := filepath.Match(pattern, path); ok {
return filepath.Join(dir, fileName)
}
}
return ""
}

func readContentsFromFile(filename string) (contents string, err error) {
content, err := ioutil.ReadFile(filename)
if err != nil {
return
}

contents = strings.Replace(string(content), "\r\n", "\n", -1)
return
}
170 changes: 170 additions & 0 deletions github/template_test.go
@@ -0,0 +1,170 @@
package github

import (
"path/filepath"
"testing"

"github.com/bmizerany/assert"
"github.com/github/hub/fixtures"
)

var prContent = `Description
-----------
[Enter your pull request description here]
`

var issueContent = `Description
-----------
[Enter your issue description here]
`

func TestGithubTemplate_withoutTemplate(t *testing.T) {
repo := fixtures.SetupTestRepo()
defer repo.TearDown()

assert.Equal(t, "", GetPullRequestTemplate())
assert.Equal(t, "", GetIssueTemplate())
}

func TestGithubTemplate_withInvalidTemplate(t *testing.T) {
repo := fixtures.SetupTestRepo()
defer repo.TearDown()

addGithubTemplates(repo, map[string]string{"dir": "invalidPath"})

assert.Equal(t, "", GetPullRequestTemplate())
assert.Equal(t, "", GetIssueTemplate())
}

func TestGithubTemplate_WithMarkdown(t *testing.T) {
repo := fixtures.SetupTestRepo()
defer repo.TearDown()

addGithubTemplates(repo,
map[string]string{
"prTemplate": pullRequestTemplate + ".md",
"issueTemplate": issueTemplate + ".md",
})

assert.Equal(t, prContent, GetPullRequestTemplate())
assert.Equal(t, issueContent, GetIssueTemplate())
}

func TestGithubTemplate_WithTemplateInHome(t *testing.T) {
repo := fixtures.SetupTestRepo()
defer repo.TearDown()

addGithubTemplates(repo, map[string]string{})

assert.Equal(t, prContent, GetPullRequestTemplate())
assert.Equal(t, issueContent, GetIssueTemplate())
}

func TestGithubTemplate_WithTemplateInGithubDir(t *testing.T) {
repo := fixtures.SetupTestRepo()
defer repo.TearDown()

addGithubTemplates(repo, map[string]string{"dir": githubTemplateDir})

assert.Equal(t, prContent, GetPullRequestTemplate())
assert.Equal(t, issueContent, GetIssueTemplate())
}

func TestGithubTemplate_WithTemplateInGithubDirAndMarkdown(t *testing.T) {
repo := fixtures.SetupTestRepo()
defer repo.TearDown()

addGithubTemplates(repo,
map[string]string{
"prTemplate": pullRequestTemplate + ".md",
"issueTemplate": issueTemplate + ".md",
"dir": githubTemplateDir,
})

assert.Equal(t, prContent, GetPullRequestTemplate())
assert.Equal(t, issueContent, GetIssueTemplate())
}

// When no default message is provided, two blank lines should be added
// (representing the pull request title), and the left should be template.
func TestGeneratePRTemplate_NoDefaultMessage(t *testing.T) {
repo := fixtures.SetupTestRepo()
defer repo.TearDown()

addGithubTemplates(repo, map[string]string{})

defaultMessage := ""
expectedOutput := `

Description
-----------
[Enter your pull request description here]
`

assert.Equal(t, expectedOutput, GeneratePRTemplate(defaultMessage))
}

// When a single line commit message is provided, the commit message should
// encompass the first line, then a empty new line, then the template.
func TestGeneratePRTemplate_SingleLineDefaultMessage(t *testing.T) {
repo := fixtures.SetupTestRepo()
defer repo.TearDown()

addGithubTemplates(repo, map[string]string{})

defaultMessage := "Add Pull Request Templates to Hub"
expectedOutput := `Add Pull Request Templates to Hub

Description
-----------
[Enter your pull request description here]
`

assert.Equal(t, expectedOutput, GeneratePRTemplate(defaultMessage))
}

// When a multi line commit message is provided, the first line of the commit
// message should be the first line, then a empty new line, then the template.
//
// TODO (maybe): Allow for templates to support auto filling the description
// section with the rest of the commit message.
func TestGeneratePRTemplate_MultiLineDefaultMessage(t *testing.T) {
repo := fixtures.SetupTestRepo()
defer repo.TearDown()

addGithubTemplates(repo, map[string]string{})

defaultMessage := `Add Pull Request Templates to Hub

Allow repo maintainers to set a default template and allow developers to
continue to use hub!
`
expectedOutput := `Add Pull Request Templates to Hub

Description
-----------
[Enter your pull request description here]
`

assert.Equal(t, expectedOutput, GeneratePRTemplate(defaultMessage))
}

func addGithubTemplates(r *fixtures.TestRepo, config map[string]string) {
repoDir := "test.git"
if dir := config["dir"]; dir != "" {
repoDir = filepath.Join(repoDir, dir)
}

prTemplatePath := filepath.Join(repoDir, pullRequestTemplate)
if prTmplPath := config["prTemplate"]; prTmplPath != "" {
prTemplatePath = filepath.Join(repoDir, prTmplPath)
}

issueTemplatePath := filepath.Join(repoDir, issueTemplate)
if issueTmplPath := config["issueTemplate"]; issueTmplPath != "" {
issueTemplatePath = filepath.Join(repoDir, issueTmplPath)
}

r.AddFile(prTemplatePath, prContent)
r.AddFile(issueTemplatePath, issueContent)
}