未验证 提交 9fc6bc85 编写于 作者: M Mislav Marohnić 提交者: GitHub

Merge pull request #1660 from github/commentchar-auto

Support for `commentchar=auto`, always respect `--edit`
...@@ -357,43 +357,39 @@ func createIssue(cmd *Command, args *Args) { ...@@ -357,43 +357,39 @@ func createIssue(cmd *Command, args *Args) {
gh := github.NewClient(project.Host) gh := github.NewClient(project.Host)
var title string messageBuilder := &github.MessageBuilder{
var body string Filename: "ISSUE_EDITMSG",
var editor *github.Editor Title: "issue",
}
messageBuilder.AddCommentedSection(fmt.Sprintf(`Creating an issue for %s
Write a message for this issue. The first block of
text is the title and the rest is the description.`, project))
if cmd.FlagPassed("message") { if cmd.FlagPassed("message") {
title, body = readMsg(flagIssueMessage) messageBuilder.Message = flagIssueMessage
messageBuilder.Edit = flagIssueEdit
} else if cmd.FlagPassed("file") { } else if cmd.FlagPassed("file") {
title, body, editor, err = readMsgFromFile(flagIssueFile, flagIssueEdit, "ISSUE", "issue") messageBuilder.Message, err = msgFromFile(flagIssueFile)
utils.Check(err) utils.Check(err)
messageBuilder.Edit = flagIssueEdit
} else { } else {
cs := git.CommentChar() messageBuilder.Edit = true
message := strings.Replace(fmt.Sprintf(`
# Creating an issue for %s
#
# Write a message for this issue. The first block of
# text is the title and the rest is the description.
`, project), "#", cs, -1)
workdir, _ := git.WorkdirName() workdir, _ := git.WorkdirName()
if workdir != "" { if workdir != "" {
template, err := github.ReadTemplate(github.IssueTemplate, workdir) template, err := github.ReadTemplate(github.IssueTemplate, workdir)
utils.Check(err) utils.Check(err)
if template != "" { if template != "" {
message = template + "\n" + message messageBuilder.Message = template
} }
} }
editor, err := github.NewEditor("ISSUE", "issue", message)
utils.Check(err)
title, body, err = editor.EditTitleAndBody()
utils.Check(err)
} }
if editor != nil { title, body, err := messageBuilder.Extract()
defer editor.DeleteFile() utils.Check(err)
}
if title == "" { if title == "" {
utils.Check(fmt.Errorf("Aborting creation due to empty issue title")) utils.Check(fmt.Errorf("Aborting creation due to empty issue title"))
...@@ -425,4 +421,6 @@ func createIssue(cmd *Command, args *Args) { ...@@ -425,4 +421,6 @@ func createIssue(cmd *Command, args *Args) {
printBrowseOrCopy(args, issue.HtmlUrl, flagIssueBrowse, flagIssueCopy) printBrowseOrCopy(args, issue.HtmlUrl, flagIssueBrowse, flagIssueCopy)
} }
messageBuilder.Cleanup()
} }
...@@ -202,8 +202,10 @@ func pullRequest(cmd *Command, args *Args) { ...@@ -202,8 +202,10 @@ func pullRequest(cmd *Command, args *Args) {
} }
} }
var editor *github.Editor messageBuilder := &github.MessageBuilder{
var title, body string Filename: "PULLREQ_EDITMSG",
Title: "pull request",
}
baseTracking := base baseTracking := base
headTracking := head headTracking := head
...@@ -223,27 +225,56 @@ func pullRequest(cmd *Command, args *Args) { ...@@ -223,27 +225,56 @@ func pullRequest(cmd *Command, args *Args) {
utils.Check(fmt.Errorf("Can't find remote for %s", head)) utils.Check(fmt.Errorf("Can't find remote for %s", head))
} }
messageBuilder.AddCommentedSection(fmt.Sprintf(`Requesting a pull to %s from %s
Write a message for this pull request. The first block
of text is the title and the rest is the description.`, fullBase, fullHead))
if cmd.FlagPassed("message") { if cmd.FlagPassed("message") {
title, body = readMsg(flagPullRequestMessage) messageBuilder.Message = flagPullRequestMessage
messageBuilder.Edit = flagPullRequestEdit
} else if cmd.FlagPassed("file") { } else if cmd.FlagPassed("file") {
title, body, editor, err = readMsgFromFile(flagPullRequestFile, flagPullRequestEdit, "PULLREQ", "pull request") messageBuilder.Message, err = msgFromFile(flagPullRequestFile)
utils.Check(err) utils.Check(err)
messageBuilder.Edit = flagPullRequestEdit
} else if flagPullRequestIssue == "" { } else if flagPullRequestIssue == "" {
messageBuilder.Edit = true
headForMessage := headTracking headForMessage := headTracking
if flagPullRequestPush { if flagPullRequestPush {
headForMessage = head headForMessage = head
} }
message, err := createPullRequestMessage(baseTracking, headForMessage, fullBase, fullHead) message := ""
utils.Check(err) commitLogs := ""
editor, err = github.NewEditor("PULLREQ", "pull request", message) commits, _ := git.RefList(baseTracking, headForMessage)
utils.Check(err) if len(commits) == 1 {
message, err = git.Show(commits[0])
utils.Check(err)
} else if len(commits) > 1 {
commitLogs, err = git.Log(baseTracking, headForMessage)
utils.Check(err)
}
title, body, err = editor.EditTitleAndBody() if commitLogs != "" {
utils.Check(err) messageBuilder.AddCommentedSection("\nChanges:\n\n" + strings.TrimSpace(commitLogs))
}
workdir, _ := git.WorkdirName()
if workdir != "" {
template, _ := github.ReadTemplate(github.PullRequestTemplate, workdir)
if template != "" {
message = message + "\n\n" + template
}
}
messageBuilder.Message = message
} }
title, body, err := messageBuilder.Extract()
utils.Check(err)
if title == "" && flagPullRequestIssue == "" { if title == "" && flagPullRequestIssue == "" {
utils.Check(fmt.Errorf("Aborting due to empty pull request title")) utils.Check(fmt.Errorf("Aborting due to empty pull request title"))
} }
...@@ -311,8 +342,8 @@ func pullRequest(cmd *Command, args *Args) { ...@@ -311,8 +342,8 @@ func pullRequest(cmd *Command, args *Args) {
} }
} }
if err == nil && editor != nil { if err == nil {
defer editor.DeleteFile() defer messageBuilder.Cleanup()
} }
utils.Check(err) utils.Check(err)
...@@ -361,49 +392,6 @@ func pullRequest(cmd *Command, args *Args) { ...@@ -361,49 +392,6 @@ func pullRequest(cmd *Command, args *Args) {
printBrowseOrCopy(args, pullRequestURL, flagPullRequestBrowse, flagPullRequestCopy) printBrowseOrCopy(args, pullRequestURL, flagPullRequestBrowse, flagPullRequestCopy)
} }
func createPullRequestMessage(base, head, fullBase, fullHead string) (string, error) {
var (
defaultMsg string
commitLogs string
err error
)
commits, _ := git.RefList(base, head)
if len(commits) == 1 {
defaultMsg, err = git.Show(commits[0])
if err != nil {
return "", err
}
} else if len(commits) > 1 {
commitLogs, err = git.Log(base, head)
if err != nil {
return "", err
}
}
workdir, _ := git.WorkdirName()
if workdir != "" {
template, err := github.ReadTemplate(github.PullRequestTemplate, workdir)
if err != nil {
return "", err
} else if template != "" {
if defaultMsg == "" {
defaultMsg = "\n\n" + template
} else {
parts := strings.SplitN(defaultMsg, "\n\n", 2)
defaultMsg = parts[0] + "\n\n" + template
if len(parts) > 1 && parts[1] != "" {
defaultMsg = defaultMsg + "\n\n" + parts[1]
}
}
}
}
cs := git.CommentChar()
return renderPullRequestTpl(defaultMsg, cs, fullBase, fullHead, commitLogs)
}
func parsePullRequestProject(context *github.Project, s string) (p *github.Project, ref string) { func parsePullRequestProject(context *github.Project, s string) (p *github.Project, ref string) {
p = context p = context
ref = s ref = s
......
package commands
import (
"bytes"
"fmt"
"regexp"
"strings"
"text/template"
)
const pullRequestTmpl = `{{if .InitMsg}}{{.InitMsg}}
{{end}}
{{.CS}} Requesting a pull to {{.Base}} from {{.Head}}
{{.CS}}
{{.CS}} Write a message for this pull request. The first block
{{.CS}} of text is the title and the rest is the description.{{if .HasCommitLogs}}
{{.CS}}
{{.CS}} Changes:
{{.CS}}
{{.FormattedCommitLogs}}{{end}}`
type pullRequestMsg struct {
InitMsg string
CS string
Base string
Head string
CommitLogs string
}
func (p *pullRequestMsg) HasCommitLogs() bool {
return len(p.CommitLogs) > 0
}
func (p *pullRequestMsg) FormattedCommitLogs() string {
startRegexp := regexp.MustCompilePOSIX("^")
endRegexp := regexp.MustCompilePOSIX(" +$")
commitLogs := strings.TrimSpace(p.CommitLogs)
commitLogs = startRegexp.ReplaceAllString(commitLogs, fmt.Sprintf("%s ", p.CS))
commitLogs = endRegexp.ReplaceAllString(commitLogs, "")
return commitLogs
}
func renderPullRequestTpl(initMsg, cs, base, head string, commitLogs string) (string, error) {
t, err := template.New("pullRequestTmpl").Parse(pullRequestTmpl)
if err != nil {
return "", err
}
msg := &pullRequestMsg{
InitMsg: initMsg,
CS: cs,
Base: base,
Head: head,
CommitLogs: commitLogs,
}
var b bytes.Buffer
err = t.Execute(&b, msg)
return b.String(), err
}
package commands
import (
"testing"
"github.com/bmizerany/assert"
)
func TestRenderPullRequestTpl(t *testing.T) {
msg, err := renderPullRequestTpl("init", "#", "base", "head", "one\ntwo")
assert.Equal(t, nil, err)
expMsg := `init
# Requesting a pull to base from head
#
# Write a message for this pull request. The first block
# of text is the title and the rest is the description.
#
# Changes:
#
# one
# two`
assert.Equal(t, expMsg, msg)
}
...@@ -7,7 +7,6 @@ import ( ...@@ -7,7 +7,6 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/github/hub/git"
"github.com/github/hub/github" "github.com/github/hub/github"
"github.com/github/hub/ui" "github.com/github/hub/ui"
"github.com/github/hub/utils" "github.com/github/hub/utils"
...@@ -297,27 +296,30 @@ func createRelease(cmd *Command, args *Args) { ...@@ -297,27 +296,30 @@ func createRelease(cmd *Command, args *Args) {
gh := github.NewClient(project.Host) gh := github.NewClient(project.Host)
var title string messageBuilder := &github.MessageBuilder{
var body string Filename: "RELEASE_EDITMSG",
var editor *github.Editor Title: "release",
}
messageBuilder.AddCommentedSection(fmt.Sprintf(`Creating release %s for %s
Write a message for this release. The first block of
text is the title and the rest is the description.`, tagName, project))
if cmd.FlagPassed("message") { if cmd.FlagPassed("message") {
title, body = readMsg(flagReleaseMessage) messageBuilder.Message = flagReleaseMessage
messageBuilder.Edit = flagReleaseEdit
} else if cmd.FlagPassed("file") { } else if cmd.FlagPassed("file") {
title, body, editor, err = readMsgFromFile(flagReleaseFile, flagReleaseEdit, "RELEASE", "release") messageBuilder.Message, err = msgFromFile(flagReleaseFile)
utils.Check(err) utils.Check(err)
messageBuilder.Edit = flagReleaseEdit
} else { } else {
cs := git.CommentChar() messageBuilder.Edit = true
message, err := renderReleaseTpl("Creating", cs, tagName, project.String(), flagReleaseCommitish)
utils.Check(err)
editor, err := github.NewEditor("RELEASE", "release", message)
utils.Check(err)
title, body, err = editor.EditTitleAndBody()
utils.Check(err)
} }
title, body, err := messageBuilder.Extract()
utils.Check(err)
if title == "" { if title == "" {
utils.Check(fmt.Errorf("Aborting release due to empty release title")) utils.Check(fmt.Errorf("Aborting release due to empty release title"))
} }
...@@ -343,9 +345,7 @@ func createRelease(cmd *Command, args *Args) { ...@@ -343,9 +345,7 @@ func createRelease(cmd *Command, args *Args) {
printBrowseOrCopy(args, release.HtmlUrl, flagReleaseBrowse, flagReleaseCopy) printBrowseOrCopy(args, release.HtmlUrl, flagReleaseBrowse, flagReleaseCopy)
} }
if editor != nil { messageBuilder.Cleanup()
editor.DeleteFile()
}
uploadAssets(gh, release, flagReleaseAssets, args) uploadAssets(gh, release, flagReleaseAssets, args)
} }
...@@ -369,11 +369,9 @@ func editRelease(cmd *Command, args *Args) { ...@@ -369,11 +369,9 @@ func editRelease(cmd *Command, args *Args) {
utils.Check(err) utils.Check(err)
params := map[string]interface{}{} params := map[string]interface{}{}
commitish := release.TargetCommitish
if cmd.FlagPassed("commitish") { if cmd.FlagPassed("commitish") {
params["target_commitish"] = flagReleaseCommitish params["target_commitish"] = flagReleaseCommitish
commitish = flagReleaseCommitish
} }
if cmd.FlagPassed("draft") { if cmd.FlagPassed("draft") {
...@@ -384,34 +382,33 @@ func editRelease(cmd *Command, args *Args) { ...@@ -384,34 +382,33 @@ func editRelease(cmd *Command, args *Args) {
params["prerelease"] = flagReleasePrerelease params["prerelease"] = flagReleasePrerelease
} }
var title string messageBuilder := &github.MessageBuilder{
var body string Filename: "RELEASE_EDITMSG",
var editor *github.Editor Title: "release",
}
messageBuilder.AddCommentedSection(fmt.Sprintf(`Editing release %s for %s
Write a message for this release. The first block of
text is the title and the rest is the description.`, tagName, project))
if cmd.FlagPassed("message") { if cmd.FlagPassed("message") {
title, body = readMsg(flagReleaseMessage) messageBuilder.Message = flagReleaseMessage
messageBuilder.Edit = flagReleaseEdit
} else if cmd.FlagPassed("file") { } else if cmd.FlagPassed("file") {
title, body, editor, err = readMsgFromFile(flagReleaseFile, flagReleaseEdit, "RELEASE", "release") messageBuilder.Message, err = msgFromFile(flagReleaseFile)
utils.Check(err) utils.Check(err)
messageBuilder.Edit = flagReleaseEdit
if title == "" {
utils.Check(fmt.Errorf("Aborting editing due to empty release title"))
}
} else { } else {
cs := git.CommentChar() messageBuilder.Edit = true
message, err := renderReleaseTpl("Editing", cs, tagName, project.String(), commitish) messageBuilder.Message = fmt.Sprintf("%s\n\n%s", release.Name, release.Body)
utils.Check(err) }
message = fmt.Sprintf("%s\n\n%s\n%s", release.Name, release.Body, message)
editor, err := github.NewEditor("RELEASE", "release", message)
utils.Check(err)
title, body, err = editor.EditTitleAndBody() title, body, err := messageBuilder.Extract()
utils.Check(err) utils.Check(err)
if title == "" { if title == "" && !cmd.FlagPassed("message") {
utils.Check(fmt.Errorf("Aborting editing due to empty release title")) utils.Check(fmt.Errorf("Aborting editing due to empty release title"))
}
} }
if title != "" { if title != "" {
...@@ -429,9 +426,7 @@ func editRelease(cmd *Command, args *Args) { ...@@ -429,9 +426,7 @@ func editRelease(cmd *Command, args *Args) {
utils.Check(err) utils.Check(err)
} }
if editor != nil { messageBuilder.Cleanup()
editor.DeleteFile()
}
} }
uploadAssets(gh, release, flagReleaseAssets, args) uploadAssets(gh, release, flagReleaseAssets, args)
......
package commands
import (
"bytes"
"text/template"
)
const releaseTmpl = `
{{.CS}} {{.Operation}} release {{.TagName}} for {{.ProjectName}}{{if .BranchName}} from {{.BranchName}}{{end}}
{{.CS}}
{{.CS}} Write a message for this release. The first block of
{{.CS}} text is the title and the rest is the description.`
type releaseMsg struct {
Operation string
CS string
TagName string
ProjectName string
BranchName string
}
func renderReleaseTpl(operation, cs, tagName, projectName, branchName string) (string, error) {
t, err := template.New("releaseTmpl").Parse(releaseTmpl)
if err != nil {
return "", err
}
msg := &releaseMsg{
Operation: operation,
CS: cs,
TagName: tagName,
ProjectName: projectName,
BranchName: branchName,
}
var b bytes.Buffer
err = t.Execute(&b, msg)
return b.String(), err
}
package commands
import (
"testing"
"github.com/bmizerany/assert"
)
func TestRenderReleaseTpl(t *testing.T) {
msg, err := renderReleaseTpl("Creating", "#", "1.0", "github/hub", "master")
assert.Equal(t, nil, err)
expMsg := `
# Creating release 1.0 for github/hub from master
#
# Write a message for this release. The first block of
# text is the title and the rest is the description.`
assert.Equal(t, expMsg, msg)
}
...@@ -90,25 +90,6 @@ func isEmptyDir(path string) bool { ...@@ -90,25 +90,6 @@ func isEmptyDir(path string) bool {
return match == nil return match == nil
} }
func readMsgFromFile(filename string, edit bool, editorPrefix, editorTopic string) (title, body string, editor *github.Editor, err error) {
message, err := msgFromFile(filename)
if err != nil {
return
}
if edit {
editor, err = github.NewEditor(editorPrefix, editorTopic, message)
if err != nil {
return
}
title, body, err = editor.EditTitleAndBody()
return
} else {
title, body = readMsg(message)
return
}
}
func msgFromFile(filename string) (string, error) { func msgFromFile(filename string) (string, error) {
var content []byte var content []byte
var err error var err error
...@@ -125,16 +106,6 @@ func msgFromFile(filename string) (string, error) { ...@@ -125,16 +106,6 @@ func msgFromFile(filename string) (string, error) {
return strings.Replace(string(content), "\r\n", "\n", -1), nil return strings.Replace(string(content), "\r\n", "\n", -1), nil
} }
func readMsg(message string) (title, body string) {
parts := strings.SplitN(message, "\n\n", 2)
title = strings.TrimSpace(strings.Replace(parts[0], "\n", " ", -1))
if len(parts) > 1 {
body = strings.TrimSpace(parts[1])
}
return
}
func printBrowseOrCopy(args *Args, msg string, openBrowser bool, performCopy bool) { func printBrowseOrCopy(args *Args, msg string, openBrowser bool, performCopy bool) {
if performCopy { if performCopy {
if err := clipboard.WriteAll(msg); err != nil { if err := clipboard.WriteAll(msg); err != nil {
......
...@@ -8,24 +8,6 @@ import ( ...@@ -8,24 +8,6 @@ import (
"github.com/bmizerany/assert" "github.com/bmizerany/assert"
) )
func TestReadMsg(t *testing.T) {
title, body := readMsg("")
assert.Equal(t, "", title)
assert.Equal(t, "", body)
title, body = readMsg("my pull title")
assert.Equal(t, "my pull title", title)
assert.Equal(t, "", body)
title, body = readMsg("my pull title\n\nmy description\n\nanother line")
assert.Equal(t, "my pull title", title)
assert.Equal(t, "my description\n\nanother line", body)
title, body = readMsg("my pull\ntitle\n\nmy description\n\nanother line")
assert.Equal(t, "my pull title", title)
assert.Equal(t, "my description\n\nanother line", body)
}
func TestDirIsNotEmpty(t *testing.T) { func TestDirIsNotEmpty(t *testing.T) {
dir := createTempDir(t) dir := createTempDir(t)
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
......
...@@ -317,6 +317,30 @@ Feature: hub issue ...@@ -317,6 +317,30 @@ Feature: hub issue
https://github.com/github/hub/issues/1337\n https://github.com/github/hub/issues/1337\n
""" """
Scenario: Editing empty issue message
Given the git commit editor is "vim"
And the text editor adds:
"""
hello
my nice issue
"""
Given the GitHub API server:
"""
post('/repos/github/hub/issues') {
assert :title => "hello",
:body => "my nice issue"
status 201
json :html_url => "https://github.com/github/hub/issues/1337"
}
"""
When I successfully run `hub issue create -m '' --edit`
Then the output should contain exactly:
"""
https://github.com/github/hub/issues/1337\n
"""
Scenario: Issue template Scenario: Issue template
Given the git commit editor is "vim" Given the git commit editor is "vim"
And the text editor adds: And the text editor adds:
......
...@@ -101,11 +101,11 @@ Feature: hub pull-request ...@@ -101,11 +101,11 @@ Feature: hub pull-request
halt 400 if request.content_charset != 'utf-8' halt 400 if request.content_charset != 'utf-8'
assert :title => 'Commit title', assert :title => 'Commit title',
:body => <<BODY.chomp :body => <<BODY.chomp
Commit body
This is the pull request template This is the pull request template
Another line of template Another line of template
Commit body
BODY BODY
status 201 status 201
json :html_url => "the://url" json :html_url => "the://url"
......
...@@ -192,13 +192,26 @@ func (r *Range) IsAncestor() bool { ...@@ -192,13 +192,26 @@ func (r *Range) IsAncestor() bool {
return cmd.Success() return cmd.Success()
} }
func CommentChar() string { func CommentChar(text string) (string, error) {
char, err := Config("core.commentchar") char, err := Config("core.commentchar")
if err != nil { if err != nil {
char = "#" return "#", nil
} else if char == "auto" {
lines := strings.Split(text, "\n")
commentCharCandidates := strings.Split("#;@!$%^&|:", "")
candidateLoop:
for _, candidate := range commentCharCandidates {
for _, line := range lines {
if strings.HasPrefix(line, candidate) {
continue candidateLoop
}
}
return candidate, nil
}
return "", fmt.Errorf("unable to select a comment character that is not used in the current message")
} else {
return char, nil
} }
return char
} }
func Show(sha string) (string, error) { func Show(sha string) (string, error) {
......
...@@ -154,3 +154,33 @@ func TestRemotes(t *testing.T) { ...@@ -154,3 +154,33 @@ func TestRemotes(t *testing.T) {
} }
} }
} }
func TestCommentChar(t *testing.T) {
repo := fixtures.SetupTestRepo()
defer repo.TearDown()
char, err := CommentChar("")
assert.Equal(t, nil, err)
assert.Equal(t, "#", char)
SetGlobalConfig("core.commentchar", ";")
char, err = CommentChar("")
assert.Equal(t, nil, err)
assert.Equal(t, ";", char)
SetGlobalConfig("core.commentchar", "auto")
char, err = CommentChar("")
assert.Equal(t, nil, err)
assert.Equal(t, "#", char)
char, err = CommentChar("hello\n#nice\nworld")
assert.Equal(t, nil, err)
assert.Equal(t, ";", char)
char, err = CommentChar("hello\n#nice\n;world")
assert.Equal(t, nil, err)
assert.Equal(t, "@", char)
char, err = CommentChar("#\n;\n@\n!\n$\n%\n^\n&\n|\n:")
assert.Equal(t, "unable to select a comment character that is not used in the current message", err.Error())
}
...@@ -85,15 +85,7 @@ const crashReportTmpl = "Crash report - %v\n\n" + ...@@ -85,15 +85,7 @@ const crashReportTmpl = "Crash report - %v\n\n" +
"Error (%s): `%v`\n\n" + "Error (%s): `%v`\n\n" +
"Stack:\n\n```\n%s\n```\n\n" + "Stack:\n\n```\n%s\n```\n\n" +
"Runtime:\n\n```\n%s\n```\n\n" + "Runtime:\n\n```\n%s\n```\n\n" +
"Version:\n\n```\n%s\n```\n" + "Version:\n\n```\n%s\n```\n"
`
# Creating crash report:
#
# This information will be posted as a new issue under github/hub.
# We're NOT including any information about the command that you were executing,
# but knowing a little bit more about it would really help us to solve this problem.
# Feel free to modify the title and the description for this issue.
`
func reportTitleAndBody(reportedError error, stack string) (title, body string, err error) { func reportTitleAndBody(reportedError error, stack string) (title, body string, err error) {
errType := reflect.TypeOf(reportedError).String() errType := reflect.TypeOf(reportedError).String()
...@@ -108,14 +100,26 @@ func reportTitleAndBody(reportedError error, stack string) (title, body string, ...@@ -108,14 +100,26 @@ func reportTitleAndBody(reportedError error, stack string) (title, body string,
fullVersion, fullVersion,
) )
editor, err := NewEditor("CRASH_REPORT", "crash report", message) messageBuilder := &MessageBuilder{
if err != nil { Filename: "CRASH_REPORT",
return "", "", err Title: "crash report",
Message: message,
Edit: true,
} }
messageBuilder.AddCommentedSection(`Creating crash report:
This information will be posted as a new issue under github/hub.
We're NOT including any information about the command that you were executing,
but knowing a little bit more about it would really help us to solve this problem.
Feel free to modify the title and the description for this issue.`)
defer editor.DeleteFile() title, body, err = messageBuilder.Extract()
if err != nil {
return
}
defer messageBuilder.Cleanup()
return editor.EditTitleAndBody() return
} }
func runtimeInfo() string { func runtimeInfo() string {
......
...@@ -4,7 +4,6 @@ import ( ...@@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
...@@ -15,18 +14,22 @@ import ( ...@@ -15,18 +14,22 @@ import (
"github.com/github/hub/git" "github.com/github/hub/git"
) )
func NewEditor(filePrefix, topic, message string) (editor *Editor, err error) { func NewEditor(filename, topic, message string) (editor *Editor, err error) {
messageFile, err := getMessageFile(filePrefix) gitDir, err := git.Dir()
if err != nil { if err != nil {
return return
} }
messageFile := filepath.Join(gitDir, filename)
program, err := git.Editor() program, err := git.Editor()
if err != nil { if err != nil {
return return
} }
cs := git.CommentChar() cs, err := git.CommentChar(message)
if err != nil {
return
}
editor = &Editor{ editor = &Editor{
Program: program, Program: program,
...@@ -49,24 +52,40 @@ type Editor struct { ...@@ -49,24 +52,40 @@ type Editor struct {
openEditor func(program, file string) error openEditor func(program, file string) error
} }
func (e *Editor) AddCommentedSection(text string) {
startRegexp := regexp.MustCompilePOSIX("^")
endRegexp := regexp.MustCompilePOSIX(" +$")
commentedText := startRegexp.ReplaceAllString(text, e.CS+" ")
commentedText = endRegexp.ReplaceAllString(commentedText, "")
e.Message = e.Message + "\n" + commentedText
}
func (e *Editor) DeleteFile() error { func (e *Editor) DeleteFile() error {
return os.Remove(e.File) return os.Remove(e.File)
} }
func (e *Editor) EditTitleAndBody() (title, body string, err error) { func (e *Editor) EditContent() (content string, err error) {
content, err := e.openAndEdit() b, err := e.openAndEdit()
if err != nil { if err != nil {
return return
} }
content = bytes.TrimSpace(content) b = bytes.TrimSpace(b)
reader := bytes.NewReader(content) reader := bytes.NewReader(b)
title, body, err = readTitleAndBody(reader, e.CS) scanner := bufio.NewScanner(reader)
unquotedLines := []string{}
if err != nil || title == "" { for scanner.Scan() {
defer e.DeleteFile() line := scanner.Text()
if e.CS == "" || !strings.HasPrefix(line, e.CS) {
unquotedLines = append(unquotedLines, line)
}
}
if err = scanner.Err(); err != nil {
return
} }
content = strings.Join(unquotedLines, "\n")
return return
} }
...@@ -89,8 +108,7 @@ func (e *Editor) openAndEdit() (content []byte, err error) { ...@@ -89,8 +108,7 @@ func (e *Editor) openAndEdit() (content []byte, err error) {
} }
func (e *Editor) writeContent() (err error) { func (e *Editor) writeContent() (err error) {
// only write message if file doesn't exist if !e.isFileExist() {
if !e.isFileExist() && e.Message != "" {
err = ioutil.WriteFile(e.File, []byte(e.Message), 0644) err = ioutil.WriteFile(e.File, []byte(e.Message), 0644)
if err != nil { if err != nil {
return return
...@@ -122,43 +140,3 @@ func openTextEditor(program, file string) error { ...@@ -122,43 +140,3 @@ func openTextEditor(program, file string) error {
return editCmd.Spawn() return editCmd.Spawn()
} }
func readTitleAndBody(reader io.Reader, cs string) (title, body string, err error) {
var titleParts, bodyParts []string
r := regexp.MustCompile("\\S")
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, cs) {
continue
}
if len(bodyParts) == 0 && r.MatchString(line) {
titleParts = append(titleParts, line)
} else {
bodyParts = append(bodyParts, line)
}
}
if err = scanner.Err(); err != nil {
return
}
title = strings.Join(titleParts, " ")
title = strings.TrimSpace(title)
body = strings.Join(bodyParts, "\n")
body = strings.TrimSpace(body)
return
}
func getMessageFile(about string) (string, error) {
gitDir, err := git.Dir()
if err != nil {
return "", err
}
return filepath.Join(gitDir, fmt.Sprintf("%s_EDITMSG", about)), nil
}
package github package github
import ( import (
"bufio"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"strings"
"testing" "testing"
"github.com/bmizerany/assert" "github.com/bmizerany/assert"
...@@ -78,90 +76,3 @@ func TestEditor_openAndEdit_writeFileIfNotExist(t *testing.T) { ...@@ -78,90 +76,3 @@ func TestEditor_openAndEdit_writeFileIfNotExist(t *testing.T) {
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, "hello", string(content)) assert.Equal(t, "hello", string(content))
} }
func TestEditor_EditTitleAndBodyEmptyTitle(t *testing.T) {
tempFile, _ := ioutil.TempFile("", "PULLREQ")
tempFile.Close()
editor := Editor{
Program: "memory",
File: tempFile.Name(),
CS: "#",
openEditor: func(program string, file string) error {
assert.Equal(t, "memory", program)
assert.Equal(t, tempFile.Name(), file)
return ioutil.WriteFile(file, []byte(""), 0644)
},
}
title, body, err := editor.EditTitleAndBody()
assert.Equal(t, nil, err)
assert.Equal(t, "", title)
assert.Equal(t, "", body)
_, err = os.Stat(tempFile.Name())
assert.T(t, os.IsNotExist(err))
}
func TestEditor_EditTitleAndBody(t *testing.T) {
tempFile, _ := ioutil.TempFile("", "PULLREQ")
tempFile.Close()
editor := Editor{
Program: "memory",
File: tempFile.Name(),
CS: "#",
openEditor: func(program string, file string) error {
assert.Equal(t, "memory", program)
assert.Equal(t, tempFile.Name(), file)
message := `A title
A title continues
A body
A body continues
# comment
`
return ioutil.WriteFile(file, []byte(message), 0644)
},
}
title, body, err := editor.EditTitleAndBody()
assert.Equal(t, nil, err)
assert.Equal(t, "A title A title continues", title)
assert.Equal(t, "A body\nA body continues", body)
}
func TestReadTitleAndBody(t *testing.T) {
message := `A title
A title continues
A body
A body continues
# comment
`
r := strings.NewReader(message)
reader := bufio.NewReader(r)
title, body, err := readTitleAndBody(reader, "#")
assert.Equal(t, nil, err)
assert.Equal(t, "A title A title continues", title)
assert.Equal(t, "A body\nA body continues", body)
message = `# Dat title
/ This line is commented out.
Dem body.
`
r = strings.NewReader(message)
reader = bufio.NewReader(r)
title, body, err = readTitleAndBody(reader, "/")
assert.Equal(t, nil, err)
assert.Equal(t, "# Dat title", title)
assert.Equal(t, "Dem body.", body)
}
func TestGetMessageFile(t *testing.T) {
gitPullReqMsgFile, _ := getMessageFile("PULLREQ")
assert.T(t, strings.Contains(gitPullReqMsgFile, "PULLREQ_EDITMSG"))
}
package github
import (
"regexp"
"strings"
)
type MessageBuilder struct {
Title string
Filename string
Message string
Edit bool
commentedSections []string
editor *Editor
}
func (b *MessageBuilder) AddCommentedSection(section string) {
b.commentedSections = append(b.commentedSections, section)
}
func (b *MessageBuilder) Extract() (title, body string, err error) {
content := b.Message
if b.Edit {
b.editor, err = NewEditor(b.Filename, b.Title, content)
if err != nil {
return
}
for _, section := range b.commentedSections {
b.editor.AddCommentedSection(section)
}
content, err = b.editor.EditContent()
if err != nil {
return
}
} else {
nl := regexp.MustCompile(`\r?\n`)
content = nl.ReplaceAllString(content, "\n")
}
parts := strings.SplitN(content, "\n\n", 2)
if len(parts) >= 1 {
title = strings.TrimSpace(strings.Replace(parts[0], "\n", " ", -1))
}
if len(parts) >= 2 {
body = strings.TrimSpace(parts[1])
}
if title == "" {
defer b.Cleanup()
}
return
}
func (b *MessageBuilder) Cleanup() {
if b.editor != nil {
b.editor.DeleteFile()
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册