2013-05-29 22:58:46 +04:00
|
|
|
package commands
|
2013-04-11 19:33:43 +04:00
|
|
|
|
|
|
|
import (
|
2013-04-28 21:45:00 +04:00
|
|
|
"bufio"
|
2013-04-29 00:41:45 +04:00
|
|
|
"fmt"
|
2013-05-29 22:58:46 +04:00
|
|
|
"github.com/jingweno/gh/cmd"
|
|
|
|
"github.com/jingweno/gh/git"
|
|
|
|
"github.com/jingweno/gh/github"
|
|
|
|
"github.com/jingweno/gh/utils"
|
2013-04-24 14:00:45 +04:00
|
|
|
"io/ioutil"
|
2013-04-11 19:33:43 +04:00
|
|
|
"log"
|
2013-04-24 14:00:45 +04:00
|
|
|
"os"
|
|
|
|
"regexp"
|
2013-04-28 21:45:00 +04:00
|
|
|
"strings"
|
2013-04-11 19:33:43 +04:00
|
|
|
)
|
|
|
|
|
|
|
|
var cmdPullRequest = &Command{
|
|
|
|
Run: pullRequest,
|
|
|
|
Usage: "pull-request [-f] [TITLE|-i ISSUE] [-b BASE] [-h HEAD]",
|
|
|
|
Short: "Open a pull request on GitHub",
|
|
|
|
Long: `Opens a pull request on GitHub for the project that the "origin" remote
|
|
|
|
points to. The default head of the pull request is the current branch.
|
|
|
|
Both base and head of the pull request can be explicitly given in one of
|
|
|
|
the following formats: "branch", "owner:branch", "owner/repo:branch".
|
|
|
|
This command will abort operation if it detects that the current topic
|
|
|
|
branch has local commits that are not yet pushed to its upstream branch
|
|
|
|
on the remote. To skip this check, use -f.
|
|
|
|
|
|
|
|
If TITLE is omitted, a text editor will open in which title and body of
|
|
|
|
the pull request can be entered in the same manner as git commit message.
|
|
|
|
|
|
|
|
If instead of normal TITLE an issue number is given with -i, the pull
|
|
|
|
request will be attached to an existing GitHub issue. Alternatively, instead
|
|
|
|
of title you can paste a full URL to an issue on GitHub.
|
|
|
|
`,
|
|
|
|
}
|
|
|
|
|
|
|
|
var flagPullRequestBase, flagPullRequestHead string
|
|
|
|
|
|
|
|
func init() {
|
2013-05-24 09:41:06 +04:00
|
|
|
cmdPullRequest.Flag.StringVar(&flagPullRequestBase, "b", "master", "BASE")
|
2013-05-28 21:30:40 +04:00
|
|
|
cmdPullRequest.Flag.StringVar(&flagPullRequestHead, "h", "", "HEAD")
|
2013-04-11 19:33:43 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
func pullRequest(cmd *Command, args []string) {
|
2013-05-28 21:52:27 +04:00
|
|
|
repo := NewRepo(flagPullRequestBase, flagPullRequestHead)
|
2013-04-28 21:45:00 +04:00
|
|
|
|
2013-05-28 23:42:11 +04:00
|
|
|
messageFile, err := git.PullReqMsgFile()
|
2013-05-29 22:58:46 +04:00
|
|
|
utils.Check(err)
|
2013-05-28 23:42:11 +04:00
|
|
|
err = writePullRequestChanges(repo, messageFile)
|
2013-05-29 22:58:46 +04:00
|
|
|
utils.Check(err)
|
2013-05-28 21:13:55 +04:00
|
|
|
|
2013-05-28 23:42:11 +04:00
|
|
|
editorPath, err := git.EditorPath()
|
2013-05-29 22:58:46 +04:00
|
|
|
utils.Check(err)
|
2013-05-28 23:42:11 +04:00
|
|
|
err = editTitleAndBody(editorPath, messageFile)
|
2013-04-28 21:45:00 +04:00
|
|
|
|
2013-05-29 22:58:46 +04:00
|
|
|
utils.Check(err)
|
2013-05-28 23:42:11 +04:00
|
|
|
title, body, err := readTitleAndBody(messageFile)
|
2013-05-29 22:58:46 +04:00
|
|
|
utils.Check(err)
|
2013-05-24 09:41:06 +04:00
|
|
|
|
2013-05-28 23:42:11 +04:00
|
|
|
if title == "" {
|
2013-04-28 21:45:00 +04:00
|
|
|
log.Fatal("Aborting due to empty pull request title")
|
|
|
|
}
|
|
|
|
|
2013-05-29 22:58:46 +04:00
|
|
|
params := github.PullRequestParams{title, body, repo.Base, repo.Head}
|
2013-05-30 01:44:28 +04:00
|
|
|
gh := github.New()
|
2013-05-30 05:18:01 +04:00
|
|
|
pullRequestResponse, err := gh.CreatePullRequest(params)
|
2013-05-29 22:58:46 +04:00
|
|
|
utils.Check(err)
|
2013-04-29 06:21:16 +04:00
|
|
|
|
|
|
|
fmt.Println(pullRequestResponse.HtmlUrl)
|
2013-04-28 21:45:00 +04:00
|
|
|
}
|
|
|
|
|
2013-05-24 09:41:06 +04:00
|
|
|
func writePullRequestChanges(repo *Repo, messageFile string) error {
|
2013-04-29 00:41:45 +04:00
|
|
|
message := `
|
|
|
|
# Requesting a pull to %s from %s
|
|
|
|
#
|
|
|
|
# Write a message for this pull reuqest. The first block
|
2013-05-24 22:54:01 +04:00
|
|
|
# of the text is the title and the rest is description.%s
|
|
|
|
`
|
|
|
|
startRegexp := regexp.MustCompilePOSIX("^")
|
|
|
|
endRegexp := regexp.MustCompilePOSIX(" +$")
|
|
|
|
|
2013-05-28 22:05:10 +04:00
|
|
|
commitLogs, _ := git.Log(repo.Base, repo.Head)
|
2013-05-24 22:54:01 +04:00
|
|
|
var changesMsg string
|
|
|
|
if len(commitLogs) > 0 {
|
|
|
|
commitLogs = strings.TrimSpace(commitLogs)
|
|
|
|
commitLogs = startRegexp.ReplaceAllString(commitLogs, "# ")
|
|
|
|
commitLogs = endRegexp.ReplaceAllString(commitLogs, "")
|
|
|
|
changesMsg = `
|
2013-04-29 00:41:45 +04:00
|
|
|
#
|
|
|
|
# Changes:
|
|
|
|
#
|
2013-05-30 05:19:14 +04:00
|
|
|
%s`
|
2013-05-24 22:54:01 +04:00
|
|
|
changesMsg = fmt.Sprintf(changesMsg, commitLogs)
|
|
|
|
}
|
2013-04-29 00:41:45 +04:00
|
|
|
|
2013-05-24 22:54:01 +04:00
|
|
|
message = fmt.Sprintf(message, repo.FullBase(), repo.FullHead(), changesMsg)
|
2013-04-29 05:32:01 +04:00
|
|
|
|
|
|
|
return ioutil.WriteFile(messageFile, []byte(message), 0644)
|
|
|
|
}
|
|
|
|
|
2013-05-28 23:42:11 +04:00
|
|
|
func editTitleAndBody(editorPath, messageFile string) error {
|
2013-05-29 22:58:46 +04:00
|
|
|
editCmd := cmd.New(editorPath)
|
2013-05-28 21:13:55 +04:00
|
|
|
r := regexp.MustCompile("[mg]?vi[m]$")
|
|
|
|
if r.MatchString(editorPath) {
|
2013-04-29 05:47:08 +04:00
|
|
|
editCmd.WithArg("-c")
|
2013-05-28 22:07:08 +04:00
|
|
|
editCmd.WithArg("set ft=gitcommit tw=0 wrap lbr")
|
2013-04-24 14:00:45 +04:00
|
|
|
}
|
2013-04-29 05:47:08 +04:00
|
|
|
editCmd.WithArg(messageFile)
|
2013-04-24 14:00:45 +04:00
|
|
|
|
2013-05-28 23:42:11 +04:00
|
|
|
return editCmd.Exec()
|
2013-04-24 14:00:45 +04:00
|
|
|
}
|
|
|
|
|
2013-05-28 23:42:11 +04:00
|
|
|
func readTitleAndBody(messageFile string) (title, body string, err error) {
|
2013-04-28 21:45:00 +04:00
|
|
|
f, err := os.Open(messageFile)
|
|
|
|
defer f.Close()
|
2013-04-22 07:36:21 +04:00
|
|
|
if err != nil {
|
2013-04-28 21:45:00 +04:00
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
reader := bufio.NewReader(f)
|
2013-04-29 05:47:08 +04:00
|
|
|
|
2013-05-28 23:42:11 +04:00
|
|
|
return readTitleAndBodyFrom(reader)
|
2013-04-28 21:45:00 +04:00
|
|
|
}
|
|
|
|
|
2013-05-28 23:42:11 +04:00
|
|
|
func readTitleAndBodyFrom(reader *bufio.Reader) (title, body string, err error) {
|
2013-04-28 21:45:00 +04:00
|
|
|
r := regexp.MustCompile("\\S")
|
|
|
|
var titleParts, bodyParts []string
|
|
|
|
|
|
|
|
line, err := readln(reader)
|
|
|
|
for err == nil {
|
|
|
|
if strings.HasPrefix(line, "#") {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if len(bodyParts) == 0 && r.MatchString(line) {
|
|
|
|
titleParts = append(titleParts, line)
|
|
|
|
} else {
|
|
|
|
bodyParts = append(bodyParts, line)
|
|
|
|
}
|
|
|
|
line, err = readln(reader)
|
|
|
|
}
|
|
|
|
|
|
|
|
title = strings.Join(titleParts, " ")
|
|
|
|
title = strings.TrimSpace(title)
|
|
|
|
|
|
|
|
body = strings.Join(bodyParts, "\n")
|
|
|
|
body = strings.TrimSpace(body)
|
|
|
|
|
|
|
|
return title, body, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func readln(r *bufio.Reader) (string, error) {
|
|
|
|
var (
|
|
|
|
isPrefix bool = true
|
|
|
|
err error = nil
|
|
|
|
line, ln []byte
|
|
|
|
)
|
|
|
|
for isPrefix && err == nil {
|
|
|
|
line, isPrefix, err = r.ReadLine()
|
|
|
|
ln = append(ln, line...)
|
2013-04-22 07:36:21 +04:00
|
|
|
}
|
2013-04-29 05:47:08 +04:00
|
|
|
|
2013-04-28 21:45:00 +04:00
|
|
|
return string(ln), err
|
2013-04-11 19:33:43 +04:00
|
|
|
}
|