pull_request.go 5.9 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 11 12
	"io/ioutil"
	"os"
	"regexp"
13
	"strings"
J
Jingwen Owen Ou 已提交
14 15
)

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

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

45 46 47 48 49 50 51 52 53 54 55 56
/*
  # while on a topic branch called "feature":
  $ gh pull-request
  [ opens text editor to edit title & body for the request ]
  [ opened pull request on GitHub for "YOUR_USER:feature" ]

  # explicit pull base & head:
  $ gh pull-request -b jingweno:master -h jingweno:feature

  $ gh pull-request -i 123
  [ attached pull request to issue #123 ]
*/
57
func pullRequest(cmd *Command, args *Args) {
J
Jingwen Owen Ou 已提交
58
	var title, body string
J
Jingwen Owen Ou 已提交
59
	if args.ParamsSize() == 1 {
J
Jingwen Owen Ou 已提交
60
		title = args.RemoveParam(0)
61 62
	}

J
Jingwen Owen Ou 已提交
63
	gh := github.New()
J
Jingwen Owen Ou 已提交
64
	repo := gh.Project.LocalRepoWith(flagPullRequestBase, flagPullRequestHead)
J
Jingwen Owen Ou 已提交
65
	if title == "" && flagPullRequestIssue == "" {
J
Jingwen Owen Ou 已提交
66 67 68 69
		t, b, err := writePullRequestTitleAndBody(repo)
		utils.Check(err)
		title = t
		body = b
J
Jingwen Owen Ou 已提交
70
	}
71

J
Jingwen Owen Ou 已提交
72 73 74
	if title == "" && flagPullRequestIssue == "" {
		utils.Check(fmt.Errorf("Aborting due to empty pull request title"))
	}
J
Jingwen Owen Ou 已提交
75

76
	var pullRequestURL string
J
Jingwen Owen Ou 已提交
77
	if args.Noop {
78 79
		args.Before(fmt.Sprintf("Would request a pull request to %s from %s", repo.FullBase(), repo.FullHead()), "")
		pullRequestURL = "PULL_REQUEST_URL"
J
Jingwen Owen Ou 已提交
80 81
	} else {
		if title != "" {
J
Jingwen Owen Ou 已提交
82 83 84
			pr, err := gh.CreatePullRequest(repo.Base, repo.Head, title, body)
			utils.Check(err)
			pullRequestURL = pr.HTMLURL
J
Jingwen Owen Ou 已提交
85
		}
86

J
Jingwen Owen Ou 已提交
87
		if flagPullRequestIssue != "" {
J
Jingwen Owen Ou 已提交
88 89 90
			pr, err := gh.CreatePullRequestForIssue(repo.Base, repo.Head, flagPullRequestIssue)
			utils.Check(err)
			pullRequestURL = pr.HTMLURL
J
Jingwen Owen Ou 已提交
91 92
		}
	}
93 94

	args.Replace("echo", "", pullRequestURL)
J
Jingwen Owen Ou 已提交
95
}
J
Jingwen Owen Ou 已提交
96

J
Jingwen Owen Ou 已提交
97 98 99 100
func writePullRequestTitleAndBody(repo *github.Repo) (title, body string, err error) {
	messageFile, err := git.PullReqMsgFile()
	if err != nil {
		return
101
	}
102

J
Jingwen Owen Ou 已提交
103 104 105
	err = writePullRequestChanges(repo, messageFile)
	if err != nil {
		return
106 107
	}

J
Jingwen Owen Ou 已提交
108
	editor, err := git.Editor()
J
Jingwen Owen Ou 已提交
109 110
	if err != nil {
		return
111
	}
J
Jingwen Owen Ou 已提交
112

J
Jingwen Owen Ou 已提交
113
	err = editTitleAndBody(editor, messageFile)
J
Jingwen Owen Ou 已提交
114 115
	if err != nil {
		return
116 117
	}

J
Jingwen Owen Ou 已提交
118 119 120 121
	title, body, err = readTitleAndBody(messageFile)
	if err != nil {
		return
	}
J
Jingwen Owen Ou 已提交
122

J
Jingwen Owen Ou 已提交
123
	err = os.Remove(messageFile)
124

J
Jingwen Owen Ou 已提交
125
	return
126 127
}

J
Jingwen Owen Ou 已提交
128
func writePullRequestChanges(repo *github.Repo, messageFile string) error {
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
	commits, err := git.RefList(repo.Base, repo.Head)
	if err != nil {
		return err
	}

	var defaultMsg, commitSummary string
	if len(commits) == 1 {
		defaultMsg, err = git.Show(commits[0])
		if err != nil {
			return err
		}
		defaultMsg = fmt.Sprintf("%s\n", defaultMsg)
	} else if len(commits) > 1 {
		commitLogs, err := git.Log(repo.Base, repo.Head)
		if err != nil {
			return err
		}

		if len(commitLogs) > 0 {
			startRegexp := regexp.MustCompilePOSIX("^")
			endRegexp := regexp.MustCompilePOSIX(" +$")

			commitLogs = strings.TrimSpace(commitLogs)
			commitLogs = startRegexp.ReplaceAllString(commitLogs, "# ")
			commitLogs = endRegexp.ReplaceAllString(commitLogs, "")
			commitSummary = `
J
Jingwen Owen Ou 已提交
155 156 157
#
# Changes:
#
J
Jingwen Owen Ou 已提交
158
%s`
159 160
			commitSummary = fmt.Sprintf(commitSummary, commitLogs)
		}
161
	}
J
Jingwen Owen Ou 已提交
162

163 164 165 166 167 168 169 170
	message := `%s
# Requesting a pull to %s from %s
#
# Write a message for this pull request. The first block
# of the text is the title and the rest is description.%s
`

	message = fmt.Sprintf(message, defaultMsg, repo.FullBase(), repo.FullHead(), commitSummary)
J
Jingwen Owen Ou 已提交
171 172 173 174

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

J
Jingwen Owen Ou 已提交
175 176
func editTitleAndBody(editor, messageFile string) error {
	editCmd := cmd.New(editor)
J
Jingwen Owen Ou 已提交
177
	r := regexp.MustCompile("[mg]?vi[m]$")
J
Jingwen Owen Ou 已提交
178
	if r.MatchString(editor) {
J
Jingwen Owen Ou 已提交
179
		editCmd.WithArg("-c")
J
Jingwen Owen Ou 已提交
180
		editCmd.WithArg("set ft=gitcommit tw=0 wrap lbr")
J
Jingwen Owen Ou 已提交
181
	}
J
Jingwen Owen Ou 已提交
182
	editCmd.WithArg(messageFile)
J
Jingwen Owen Ou 已提交
183

J
Jingwen Owen Ou 已提交
184
	return editCmd.Exec()
J
Jingwen Owen Ou 已提交
185 186
}

J
Jingwen Owen Ou 已提交
187
func readTitleAndBody(messageFile string) (title, body string, err error) {
188 189
	f, err := os.Open(messageFile)
	defer f.Close()
J
Jingwen Owen Ou 已提交
190
	if err != nil {
191 192 193 194
		return "", "", err
	}

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

J
Jingwen Owen Ou 已提交
196
	return readTitleAndBodyFrom(reader)
197 198
}

J
Jingwen Owen Ou 已提交
199
func readTitleAndBodyFrom(reader *bufio.Reader) (title, body string, err error) {
200 201 202
	r := regexp.MustCompile("\\S")
	var titleParts, bodyParts []string

J
Jingwen Owen Ou 已提交
203
	line, err := readLine(reader)
204 205 206 207
	for err == nil {
		if strings.HasPrefix(line, "#") {
			break
		}
J
Jingwen Owen Ou 已提交
208

209 210 211 212 213
		if len(bodyParts) == 0 && r.MatchString(line) {
			titleParts = append(titleParts, line)
		} else {
			bodyParts = append(bodyParts, line)
		}
J
Jingwen Owen Ou 已提交
214 215

		line, err = readLine(reader)
216 217 218 219 220 221 222 223 224 225 226
	}

	title = strings.Join(titleParts, " ")
	title = strings.TrimSpace(title)

	body = strings.Join(bodyParts, "\n")
	body = strings.TrimSpace(body)

	return title, body, nil
}

J
Jingwen Owen Ou 已提交
227
func readLine(r *bufio.Reader) (string, error) {
228
	var (
J
Jingwen Owen Ou 已提交
229 230
		isPrefix = true
		err      error
231 232
		line, ln []byte
	)
J
Jingwen Owen Ou 已提交
233

234 235 236
	for isPrefix && err == nil {
		line, isPrefix, err = r.ReadLine()
		ln = append(ln, line...)
J
Jingwen Owen Ou 已提交
237
	}
J
Jingwen Owen Ou 已提交
238

239
	return string(ln), err
J
Jingwen Owen Ou 已提交
240
}