pull_request.go 4.8 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

J
Jingwen Owen Ou 已提交
58 59
		defer removeFile(messageFile)

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

63 64
		editorPath, err := git.EditorPath()
		utils.Check(err)
65

66 67 68 69 70 71
		err = editTitleAndBody(editorPath, messageFile)
		utils.Check(err)

		title, body, err = readTitleAndBody(messageFile)
		utils.Check(err)
	}
72

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

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

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

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

	os.Exit(0)
91 92
}

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

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

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

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

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

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

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

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

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

J
Jingwen Owen Ou 已提交
146
func readTitleAndBodyFrom(reader *bufio.Reader) (title, body string, err error) {
147 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
	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 已提交
174 175
		isPrefix = true
		err      error
176 177 178 179 180
		line, ln []byte
	)
	for isPrefix && err == nil {
		line, isPrefix, err = r.ReadLine()
		ln = append(ln, line...)
J
Jingwen Owen Ou 已提交
181
	}
J
Jingwen Owen Ou 已提交
182

183
	return string(ln), err
J
Jingwen Owen Ou 已提交
184
}
J
Jingwen Owen Ou 已提交
185 186 187 188 189

func removeFile(file string) {
	err := os.Remove(file)
	utils.Check(err)
}