pull_request.go 4.7 KB
Newer Older
1
package commands
J
Jingwen Owen Ou 已提交
2 3

import (
4
	"bufio"
J
Jingwen Owen Ou 已提交
5
	"fmt"
6 7 8 9
	"github.com/jingweno/gh/cmd"
	"github.com/jingweno/gh/git"
	"github.com/jingweno/gh/github"
	"github.com/jingweno/gh/utils"
J
Jingwen Owen Ou 已提交
10
	"io/ioutil"
J
Jingwen Owen Ou 已提交
11
	"log"
J
Jingwen Owen Ou 已提交
12 13
	"os"
	"regexp"
14
	"strings"
J
Jingwen Owen Ou 已提交
15 16
)

17 18
var cmdPullRequest = &Command{
	Run:   pullRequest,
J
Jingwen Owen Ou 已提交
19
	Usage: "pull-request [-f] [-i ISSUE] [-b BASE] [-d HEAD] [TITLE]",
J
Jingwen Owen Ou 已提交
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
	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.
`,
}

J
Jingwen Owen Ou 已提交
38
var flagPullRequestBase, flagPullRequestHead, flagPullRequestIssue string
J
Jingwen Owen Ou 已提交
39 40

func init() {
41 42 43
	cmdPullRequest.Flag.StringVar(&flagPullRequestBase, "b", "master", "BASE")
	cmdPullRequest.Flag.StringVar(&flagPullRequestHead, "d", "", "HEAD")
	cmdPullRequest.Flag.StringVar(&flagPullRequestIssue, "i", "", "ISSUE")
J
Jingwen Owen Ou 已提交
44 45
}

46
func pullRequest(cmd *Command, args *Args) {
47
	var title, body string
J
Jingwen Owen Ou 已提交
48 49
	if args.Size() == 1 {
		title = args.First()
50 51
	}

J
Jingwen Owen Ou 已提交
52
	gh := github.New()
J
Jingwen Owen Ou 已提交
53
	repo := gh.Project.LocalRepoWith(flagPullRequestBase, flagPullRequestHead)
J
Jingwen Owen Ou 已提交
54
	if title == "" && flagPullRequestIssue == "" {
55 56
		messageFile, err := git.PullReqMsgFile()
		utils.Check(err)
57

58 59
		err = writePullRequestChanges(repo, messageFile)
		utils.Check(err)
J
Jingwen Owen Ou 已提交
60

61 62
		editorPath, err := git.EditorPath()
		utils.Check(err)
63

64 65 66 67 68
		err = editTitleAndBody(editorPath, messageFile)
		utils.Check(err)

		title, body, err = readTitleAndBody(messageFile)
		utils.Check(err)
J
Jingwen Owen Ou 已提交
69 70 71

		err = os.Remove(messageFile)
		utils.Check(err)
72
	}
73

J
Jingwen Owen Ou 已提交
74
	if title == "" && flagPullRequestIssue == "" {
J
Jingwen Owen Ou 已提交
75
		log.Fatal("Aborting due to empty pull request title")
76 77
	}

J
Jingwen Owen Ou 已提交
78
	var pullRequestURL string
79 80
	var err error
	if title != "" {
J
Jingwen Owen Ou 已提交
81
		pullRequestURL, err = gh.CreatePullRequest(repo.Base, repo.Head, title, body)
82 83
	}
	if flagPullRequestIssue != "" {
J
Jingwen Owen Ou 已提交
84
		pullRequestURL, err = gh.CreatePullRequestForIssue(repo.Base, repo.Head, flagPullRequestIssue)
85 86
	}

87
	utils.Check(err)
J
Jingwen Owen Ou 已提交
88

J
Jingwen Owen Ou 已提交
89
	fmt.Println(pullRequestURL)
90 91

	os.Exit(0)
92 93
}

J
Jingwen Owen Ou 已提交
94
func writePullRequestChanges(repo *github.Repo, messageFile string) error {
J
Jingwen Owen Ou 已提交
95 96 97
	message := `
# Requesting a pull to %s from %s
#
J
Jonathan Roes 已提交
98
# Write a message for this pull request. The first block
99 100 101 102 103
# of the text is the title and the rest is description.%s
`
	startRegexp := regexp.MustCompilePOSIX("^")
	endRegexp := regexp.MustCompilePOSIX(" +$")

J
Jingwen Owen Ou 已提交
104
	commitLogs, _ := git.Log(repo.Base, repo.Head)
105 106 107 108 109 110
	var changesMsg string
	if len(commitLogs) > 0 {
		commitLogs = strings.TrimSpace(commitLogs)
		commitLogs = startRegexp.ReplaceAllString(commitLogs, "# ")
		commitLogs = endRegexp.ReplaceAllString(commitLogs, "")
		changesMsg = `
J
Jingwen Owen Ou 已提交
111 112 113
#
# Changes:
#
J
Jingwen Owen Ou 已提交
114
%s`
115 116
		changesMsg = fmt.Sprintf(changesMsg, commitLogs)
	}
J
Jingwen Owen Ou 已提交
117

118
	message = fmt.Sprintf(message, repo.FullBase(), repo.FullHead(), changesMsg)
J
Jingwen Owen Ou 已提交
119 120 121 122

	return ioutil.WriteFile(messageFile, []byte(message), 0644)
}

J
Jingwen Owen Ou 已提交
123
func editTitleAndBody(editorPath, messageFile string) error {
124
	editCmd := cmd.New(editorPath)
J
Jingwen Owen Ou 已提交
125 126
	r := regexp.MustCompile("[mg]?vi[m]$")
	if r.MatchString(editorPath) {
J
Jingwen Owen Ou 已提交
127
		editCmd.WithArg("-c")
J
Jingwen Owen Ou 已提交
128
		editCmd.WithArg("set ft=gitcommit tw=0 wrap lbr")
J
Jingwen Owen Ou 已提交
129
	}
J
Jingwen Owen Ou 已提交
130
	editCmd.WithArg(messageFile)
J
Jingwen Owen Ou 已提交
131

J
Jingwen Owen Ou 已提交
132
	return editCmd.Exec()
J
Jingwen Owen Ou 已提交
133 134
}

J
Jingwen Owen Ou 已提交
135
func readTitleAndBody(messageFile string) (title, body string, err error) {
136 137
	f, err := os.Open(messageFile)
	defer f.Close()
J
Jingwen Owen Ou 已提交
138
	if err != nil {
139 140 141 142
		return "", "", err
	}

	reader := bufio.NewReader(f)
J
Jingwen Owen Ou 已提交
143

J
Jingwen Owen Ou 已提交
144
	return readTitleAndBodyFrom(reader)
145 146
}

J
Jingwen Owen Ou 已提交
147
func readTitleAndBodyFrom(reader *bufio.Reader) (title, body string, err error) {
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
	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 (
J
Jingwen Owen Ou 已提交
175 176
		isPrefix = true
		err      error
177 178 179 180 181
		line, ln []byte
	)
	for isPrefix && err == nil {
		line, isPrefix, err = r.ReadLine()
		ln = append(ln, line...)
J
Jingwen Owen Ou 已提交
182
	}
J
Jingwen Owen Ou 已提交
183

184
	return string(ln), err
J
Jingwen Owen Ou 已提交
185
}