Merge remote-tracking branch 'origin/master' into pr-current-branch
This commit is contained in:
commit
db8c2d4e01
22 changed files with 713 additions and 654 deletions
192
command/issue.go
192
command/issue.go
|
|
@ -2,51 +2,43 @@ package command
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/github/gh-cli/api"
|
||||
"github.com/github/gh-cli/utils"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(issueCmd)
|
||||
issueCmd.AddCommand(
|
||||
&cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Show status of relevant issues",
|
||||
RunE: issueStatus,
|
||||
},
|
||||
&cobra.Command{
|
||||
Use: "view <issue-number>",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Short: "View an issue in the browser",
|
||||
RunE: issueView,
|
||||
},
|
||||
)
|
||||
issueCmd.AddCommand(issueCreateCmd)
|
||||
issueCreateCmd.Flags().StringArrayP("message", "m", nil, "set title and body")
|
||||
issueCreateCmd.Flags().BoolP("web", "w", false, "open the web browser to create an issue")
|
||||
issueCmd.AddCommand(issueStatusCmd)
|
||||
issueCmd.AddCommand(issueViewCmd)
|
||||
issueCreateCmd.Flags().StringP("title", "t", "",
|
||||
"Supply a title. Will prompt for one otherwise.")
|
||||
issueCreateCmd.Flags().StringP("body", "b", "",
|
||||
"Supply a body. Will prompt for one otherwise.")
|
||||
issueCreateCmd.Flags().BoolP("web", "w", false, "Open the web browser to create an issue")
|
||||
|
||||
issueListCmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List and filter issues in this repository",
|
||||
RunE: issueList,
|
||||
}
|
||||
issueListCmd.Flags().StringP("assignee", "a", "", "filter by assignee")
|
||||
issueListCmd.Flags().StringSliceP("label", "l", nil, "filter by label")
|
||||
issueListCmd.Flags().StringP("state", "s", "", "filter by state (open|closed|all)")
|
||||
issueListCmd.Flags().IntP("limit", "L", 30, "maximum number of issues to fetch")
|
||||
issueListCmd.Flags().StringP("assignee", "a", "", "Filter by assignee")
|
||||
issueListCmd.Flags().StringSliceP("label", "l", nil, "Filter by label")
|
||||
issueListCmd.Flags().StringP("state", "s", "", "Filter by state (open|closed|all)")
|
||||
issueListCmd.Flags().IntP("limit", "L", 30, "Maximum number of issues to fetch")
|
||||
issueCmd.AddCommand((issueListCmd))
|
||||
}
|
||||
|
||||
var issueCmd = &cobra.Command{
|
||||
Use: "issue",
|
||||
Short: "Work with issues",
|
||||
Short: "Create and view issues",
|
||||
Long: `Work with GitHub issues`,
|
||||
}
|
||||
var issueCreateCmd = &cobra.Command{
|
||||
|
|
@ -54,6 +46,17 @@ var issueCreateCmd = &cobra.Command{
|
|||
Short: "Create a new issue",
|
||||
RunE: issueCreate,
|
||||
}
|
||||
var issueStatusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Show status of relevant issues",
|
||||
RunE: issueStatus,
|
||||
}
|
||||
var issueViewCmd = &cobra.Command{
|
||||
Use: "view <issue-number>",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Short: "View an issue in the browser",
|
||||
RunE: issueView,
|
||||
}
|
||||
|
||||
func issueList(cmd *cobra.Command, args []string) error {
|
||||
ctx := contextForCommand(cmd)
|
||||
|
|
@ -92,12 +95,31 @@ func issueList(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if len(issues) > 0 {
|
||||
printIssues("", issues...)
|
||||
} else {
|
||||
message := fmt.Sprintf("There are no open issues")
|
||||
printMessage(message)
|
||||
out := cmd.OutOrStdout()
|
||||
colorOut := colorableOut(cmd)
|
||||
|
||||
if len(issues) == 0 {
|
||||
printMessage(colorOut, "There are no open issues")
|
||||
return nil
|
||||
}
|
||||
|
||||
table := utils.NewTablePrinter(out)
|
||||
for _, issue := range issues {
|
||||
issueNum := strconv.Itoa(issue.Number)
|
||||
if table.IsTTY() {
|
||||
issueNum = "#" + issueNum
|
||||
}
|
||||
labels := labelList(issue)
|
||||
if labels != "" && table.IsTTY() {
|
||||
labels = fmt.Sprintf("(%s)", labels)
|
||||
}
|
||||
table.AddField(issueNum, nil, colorFuncForState(issue.State))
|
||||
table.AddField(issue.Title, nil, nil)
|
||||
table.AddField(labels, nil, utils.Gray)
|
||||
table.EndRow()
|
||||
}
|
||||
table.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -123,30 +145,32 @@ func issueStatus(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
printHeader("Issues assigned to you")
|
||||
out := colorableOut(cmd)
|
||||
|
||||
printHeader(out, "Issues assigned to you")
|
||||
if issuePayload.Assigned != nil {
|
||||
printIssues(" ", issuePayload.Assigned...)
|
||||
printIssues(out, " ", issuePayload.Assigned...)
|
||||
} else {
|
||||
message := fmt.Sprintf(" There are no issues assgined to you")
|
||||
printMessage(message)
|
||||
printMessage(out, message)
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Fprintln(out)
|
||||
|
||||
printHeader("Issues mentioning you")
|
||||
printHeader(out, "Issues mentioning you")
|
||||
if len(issuePayload.Mentioned) > 0 {
|
||||
printIssues(" ", issuePayload.Mentioned...)
|
||||
printIssues(out, " ", issuePayload.Mentioned...)
|
||||
} else {
|
||||
printMessage(" There are no issues mentioning you")
|
||||
printMessage(out, " There are no issues mentioning you")
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Fprintln(out)
|
||||
|
||||
printHeader("Recent issues")
|
||||
if len(issuePayload.Recent) > 0 {
|
||||
printIssues(" ", issuePayload.Recent...)
|
||||
printHeader(out, "Issues opened by you")
|
||||
if len(issuePayload.Authored) > 0 {
|
||||
printIssues(out, " ", issuePayload.Authored...)
|
||||
} else {
|
||||
printMessage(" There are no recent issues")
|
||||
printMessage(out, " There are no issues opened by you")
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Fprintln(out)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -189,44 +213,39 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
|||
return utils.OpenInBrowser(openURL)
|
||||
}
|
||||
|
||||
var title string
|
||||
var body string
|
||||
|
||||
message, err := cmd.Flags().GetStringArray("message")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
apiClient, err := apiClientForContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(message) > 0 {
|
||||
title = message[0]
|
||||
body = strings.Join(message[1:], "\n\n")
|
||||
} else {
|
||||
// TODO: open the text editor for issue title & body
|
||||
input := os.Stdin
|
||||
if terminal.IsTerminal(int(input.Fd())) {
|
||||
cmd.Println("Enter the issue title and body; press Enter + Ctrl-D when done:")
|
||||
}
|
||||
inputBytes, err := ioutil.ReadAll(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parts := strings.SplitN(string(inputBytes), "\n\n", 2)
|
||||
if len(parts) > 0 {
|
||||
title = parts[0]
|
||||
}
|
||||
if len(parts) > 1 {
|
||||
body = parts[1]
|
||||
}
|
||||
title, err := cmd.Flags().GetString("title")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse title")
|
||||
}
|
||||
body, err := cmd.Flags().GetString("body")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse body")
|
||||
}
|
||||
|
||||
if title == "" {
|
||||
return fmt.Errorf("aborting due to empty title")
|
||||
interactive := title == "" || body == ""
|
||||
|
||||
if interactive {
|
||||
tb, err := titleBodySurvey(cmd, title, body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not collect title and/or body")
|
||||
}
|
||||
|
||||
if tb == nil {
|
||||
// editing was canceled, we can just leave
|
||||
return nil
|
||||
}
|
||||
|
||||
if title == "" {
|
||||
title = tb.Title
|
||||
}
|
||||
if body == "" {
|
||||
body = tb.Body
|
||||
}
|
||||
}
|
||||
params := map[string]interface{}{
|
||||
"title": title,
|
||||
|
|
@ -242,17 +261,30 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func printIssues(prefix string, issues ...api.Issue) {
|
||||
func printIssues(w io.Writer, prefix string, issues ...api.Issue) {
|
||||
for _, issue := range issues {
|
||||
number := utils.Green("#" + strconv.Itoa(issue.Number))
|
||||
var coloredLabels string
|
||||
if len(issue.Labels) > 0 {
|
||||
var ellipse string
|
||||
if issue.TotalLabelCount > len(issue.Labels) {
|
||||
ellipse = "…"
|
||||
}
|
||||
coloredLabels = utils.Gray(fmt.Sprintf(" (%s%s)", strings.Join(issue.Labels, ", "), ellipse))
|
||||
coloredLabels := labelList(issue)
|
||||
if coloredLabels != "" {
|
||||
coloredLabels = utils.Gray(fmt.Sprintf(" (%s)", coloredLabels))
|
||||
}
|
||||
fmt.Printf("%s%s %s %s\n", prefix, number, truncate(70, issue.Title), coloredLabels)
|
||||
fmt.Fprintf(w, "%s%s %s%s\n", prefix, number, truncate(70, issue.Title), coloredLabels)
|
||||
}
|
||||
}
|
||||
|
||||
func labelList(issue api.Issue) string {
|
||||
if len(issue.Labels.Nodes) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
labelNames := []string{}
|
||||
for _, label := range issue.Labels.Nodes {
|
||||
labelNames = append(labelNames, label.Name)
|
||||
}
|
||||
|
||||
list := strings.Join(labelNames, ", ")
|
||||
if issue.Labels.TotalCount > len(issue.Labels.Nodes) {
|
||||
list += ", …"
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,9 +55,9 @@ func TestIssueList(t *testing.T) {
|
|||
}
|
||||
|
||||
expectedIssues := []*regexp.Regexp{
|
||||
regexp.MustCompile(`#1.*won`),
|
||||
regexp.MustCompile(`#2.*too`),
|
||||
regexp.MustCompile(`#4.*fore`),
|
||||
regexp.MustCompile(`(?m)^1\t.*won`),
|
||||
regexp.MustCompile(`(?m)^2\t.*too`),
|
||||
regexp.MustCompile(`(?m)^4\t.*fore`),
|
||||
}
|
||||
|
||||
for _, r := range expectedIssues {
|
||||
|
|
@ -144,7 +144,7 @@ func TestIssueCreate(t *testing.T) {
|
|||
out := bytes.Buffer{}
|
||||
issueCreateCmd.SetOut(&out)
|
||||
|
||||
RootCmd.SetArgs([]string{"issue", "create", "-m", "hello", "-m", "ab", "-m", "cd"})
|
||||
RootCmd.SetArgs([]string{"issue", "create", "-t", "hello", "-b", "cash rules everything around me"})
|
||||
_, err := RootCmd.ExecuteC()
|
||||
if err != nil {
|
||||
t.Errorf("error running command `issue create`: %v", err)
|
||||
|
|
@ -164,7 +164,7 @@ func TestIssueCreate(t *testing.T) {
|
|||
|
||||
eq(t, reqBody.Variables.Input.RepositoryID, "REPOID")
|
||||
eq(t, reqBody.Variables.Input.Title, "hello")
|
||||
eq(t, reqBody.Variables.Input.Body, "ab\n\ncd")
|
||||
eq(t, reqBody.Variables.Input.Body, "cash rules everything around me")
|
||||
|
||||
eq(t, out.String(), "https://github.com/OWNER/REPO/issues/12\n")
|
||||
}
|
||||
|
|
|
|||
146
command/pr.go
146
command/pr.go
|
|
@ -2,6 +2,7 @@ package command
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
|
|
@ -13,7 +14,6 @@ import (
|
|||
"github.com/github/gh-cli/git"
|
||||
"github.com/github/gh-cli/utils"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -24,15 +24,15 @@ func init() {
|
|||
prCmd.AddCommand(prStatusCmd)
|
||||
prCmd.AddCommand(prViewCmd)
|
||||
|
||||
prListCmd.Flags().IntP("limit", "L", 30, "maximum number of items to fetch")
|
||||
prListCmd.Flags().StringP("state", "s", "open", "filter by state")
|
||||
prListCmd.Flags().StringP("base", "b", "", "filter by base branch")
|
||||
prListCmd.Flags().StringArrayP("label", "l", nil, "filter by label")
|
||||
prListCmd.Flags().IntP("limit", "L", 30, "Maximum number of items to fetch")
|
||||
prListCmd.Flags().StringP("state", "s", "open", "Filter by state")
|
||||
prListCmd.Flags().StringP("base", "B", "", "Filter by base branch")
|
||||
prListCmd.Flags().StringArrayP("label", "l", nil, "Filter by label")
|
||||
}
|
||||
|
||||
var prCmd = &cobra.Command{
|
||||
Use: "pr",
|
||||
Short: "Work with pull requests",
|
||||
Short: "Create, view, and checkout pull requests",
|
||||
Long: `Work with GitHub pull requests.`,
|
||||
}
|
||||
var prCheckoutCmd = &cobra.Command{
|
||||
|
|
@ -82,30 +82,32 @@ func prStatus(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
printHeader("Current branch")
|
||||
out := colorableOut(cmd)
|
||||
|
||||
printHeader(out, "Current branch")
|
||||
if prPayload.CurrentPR != nil {
|
||||
printPrs(*prPayload.CurrentPR)
|
||||
printPrs(out, *prPayload.CurrentPR)
|
||||
} else {
|
||||
message := fmt.Sprintf(" There is no pull request associated with %s", utils.Cyan("["+currentBranch+"]"))
|
||||
printMessage(message)
|
||||
printMessage(out, message)
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Fprintln(out)
|
||||
|
||||
printHeader("Created by you")
|
||||
printHeader(out, "Created by you")
|
||||
if len(prPayload.ViewerCreated) > 0 {
|
||||
printPrs(prPayload.ViewerCreated...)
|
||||
printPrs(out, prPayload.ViewerCreated...)
|
||||
} else {
|
||||
printMessage(" You have no open pull requests")
|
||||
printMessage(out, " You have no open pull requests")
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Fprintln(out)
|
||||
|
||||
printHeader("Requesting a code review from you")
|
||||
printHeader(out, "Requesting a code review from you")
|
||||
if len(prPayload.ReviewRequested) > 0 {
|
||||
printPrs(prPayload.ReviewRequested...)
|
||||
printPrs(out, prPayload.ReviewRequested...)
|
||||
} else {
|
||||
printMessage(" You have no pull requests to review")
|
||||
printMessage(out, " You have no pull requests to review")
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Fprintln(out)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -170,57 +172,38 @@ func prList(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
tty := false
|
||||
ttyWidth := 80
|
||||
out := cmd.OutOrStdout()
|
||||
if outFile, isFile := out.(*os.File); isFile {
|
||||
fd := int(outFile.Fd())
|
||||
tty = terminal.IsTerminal(fd)
|
||||
if w, _, err := terminal.GetSize(fd); err == nil {
|
||||
ttyWidth = w
|
||||
}
|
||||
}
|
||||
|
||||
numWidth := 0
|
||||
maxTitleWidth := 0
|
||||
table := utils.NewTablePrinter(cmd.OutOrStdout())
|
||||
for _, pr := range prs {
|
||||
numLen := len(strconv.Itoa(pr.Number)) + 1
|
||||
if numLen > numWidth {
|
||||
numWidth = numLen
|
||||
}
|
||||
if len(pr.Title) > maxTitleWidth {
|
||||
maxTitleWidth = len(pr.Title)
|
||||
prNum := strconv.Itoa(pr.Number)
|
||||
if table.IsTTY() {
|
||||
prNum = "#" + prNum
|
||||
}
|
||||
table.AddField(prNum, nil, colorFuncForState(pr.State))
|
||||
table.AddField(pr.Title, nil, nil)
|
||||
table.AddField(pr.HeadLabel(), nil, utils.Cyan)
|
||||
table.EndRow()
|
||||
}
|
||||
err = table.Render()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
branchWidth := 40
|
||||
titleWidth := ttyWidth - branchWidth - 2 - numWidth - 2
|
||||
|
||||
if maxTitleWidth < titleWidth {
|
||||
branchWidth += titleWidth - maxTitleWidth
|
||||
titleWidth = maxTitleWidth
|
||||
}
|
||||
|
||||
for _, pr := range prs {
|
||||
if tty {
|
||||
prNum := fmt.Sprintf("% *s", numWidth, fmt.Sprintf("#%d", pr.Number))
|
||||
switch pr.State {
|
||||
case "OPEN":
|
||||
prNum = utils.Green(prNum)
|
||||
case "CLOSED":
|
||||
prNum = utils.Red(prNum)
|
||||
case "MERGED":
|
||||
prNum = utils.Magenta(prNum)
|
||||
}
|
||||
prBranch := utils.Cyan(truncate(branchWidth, pr.HeadLabel()))
|
||||
fmt.Fprintf(out, "%s %-*s %s\n", prNum, titleWidth, truncate(titleWidth, pr.Title), prBranch)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%d\t%s\t%s\n", pr.Number, pr.Title, pr.HeadLabel())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func colorFuncForState(state string) func(string) string {
|
||||
switch state {
|
||||
case "OPEN":
|
||||
return utils.Green
|
||||
case "CLOSED":
|
||||
return utils.Red
|
||||
case "MERGED":
|
||||
return utils.Magenta
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func prView(cmd *cobra.Command, args []string) error {
|
||||
ctx := contextForCommand(cmd)
|
||||
baseRepo, err := ctx.BaseRepo()
|
||||
|
|
@ -380,50 +363,51 @@ func prCheckout(cmd *cobra.Command, args []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func printPrs(prs ...api.PullRequest) {
|
||||
func printPrs(w io.Writer, prs ...api.PullRequest) {
|
||||
for _, pr := range prs {
|
||||
prNumber := fmt.Sprintf("#%d", pr.Number)
|
||||
fmt.Printf(" %s %s %s", utils.Yellow(prNumber), truncate(50, pr.Title), utils.Cyan("["+pr.HeadLabel()+"]"))
|
||||
fmt.Fprintf(w, " %s %s %s", utils.Yellow(prNumber), truncate(50, pr.Title), utils.Cyan("["+pr.HeadLabel()+"]"))
|
||||
|
||||
checks := pr.ChecksStatus()
|
||||
reviews := pr.ReviewStatus()
|
||||
if checks.Total > 0 || reviews.ChangesRequested || reviews.Approved {
|
||||
fmt.Printf("\n ")
|
||||
fmt.Fprintf(w, "\n ")
|
||||
}
|
||||
|
||||
if checks.Total > 0 {
|
||||
var ratio string
|
||||
var summary string
|
||||
if checks.Failing > 0 {
|
||||
ratio = fmt.Sprintf("%d/%d", checks.Passing, checks.Total)
|
||||
ratio = utils.Red(ratio)
|
||||
if checks.Failing == checks.Total {
|
||||
summary = utils.Red("All checks failing")
|
||||
} else {
|
||||
summary = utils.Red(fmt.Sprintf("%d/%d checks failing", checks.Failing, checks.Total))
|
||||
}
|
||||
} else if checks.Pending > 0 {
|
||||
ratio = fmt.Sprintf("%d/%d", checks.Passing, checks.Total)
|
||||
ratio = utils.Yellow(ratio)
|
||||
summary = utils.Yellow("Checks pending")
|
||||
} else if checks.Passing == checks.Total {
|
||||
ratio = fmt.Sprintf("%d", checks.Total)
|
||||
ratio = utils.Green(ratio)
|
||||
summary = utils.Green("Checks passing")
|
||||
}
|
||||
fmt.Printf(" - checks: %s", ratio)
|
||||
fmt.Fprintf(w, " - %s", summary)
|
||||
}
|
||||
|
||||
if reviews.ChangesRequested {
|
||||
fmt.Printf(" - %s", utils.Red("changes requested"))
|
||||
fmt.Fprintf(w, " - %s", utils.Red("changes requested"))
|
||||
} else if reviews.ReviewRequired {
|
||||
fmt.Printf(" - %s", utils.Yellow("review required"))
|
||||
fmt.Fprintf(w, " - %s", utils.Yellow("review required"))
|
||||
} else if reviews.Approved {
|
||||
fmt.Printf(" - %s", utils.Green("approved"))
|
||||
fmt.Fprintf(w, " - %s", utils.Green("approved"))
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
fmt.Fprint(w, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
func printHeader(s string) {
|
||||
fmt.Println(utils.Bold(s))
|
||||
func printHeader(w io.Writer, s string) {
|
||||
fmt.Fprintln(w, utils.Bold(s))
|
||||
}
|
||||
|
||||
func printMessage(s string) {
|
||||
fmt.Println(utils.Gray(s))
|
||||
func printMessage(w io.Writer, s string) {
|
||||
fmt.Fprintln(w, utils.Gray(s))
|
||||
}
|
||||
|
||||
func truncate(maxLength int, title string) string {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import (
|
|||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/github/gh-cli/api"
|
||||
"github.com/github/gh-cli/context"
|
||||
"github.com/github/gh-cli/git"
|
||||
|
|
@ -55,86 +54,25 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
|||
|
||||
interactive := title == "" || body == ""
|
||||
|
||||
inProgress := struct {
|
||||
Body string
|
||||
Title string
|
||||
}{}
|
||||
|
||||
if interactive {
|
||||
confirmed := false
|
||||
editor := determineEditor()
|
||||
tb, err := titleBodySurvey(cmd, title, body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not collect title and/or body")
|
||||
}
|
||||
|
||||
for !confirmed {
|
||||
titleQuestion := &survey.Question{
|
||||
Name: "title",
|
||||
Prompt: &survey.Input{
|
||||
Message: "Pull request title",
|
||||
Default: inProgress.Title,
|
||||
},
|
||||
}
|
||||
bodyQuestion := &survey.Question{
|
||||
Name: "body",
|
||||
Prompt: &survey.Editor{
|
||||
Message: fmt.Sprintf("Pull request body (%s)", editor),
|
||||
FileName: "*.md",
|
||||
Default: inProgress.Body,
|
||||
AppendDefault: true,
|
||||
Editor: editor,
|
||||
},
|
||||
}
|
||||
if tb == nil {
|
||||
// editing was canceled, we can just leave
|
||||
return nil
|
||||
}
|
||||
|
||||
qs := []*survey.Question{}
|
||||
if title == "" {
|
||||
qs = append(qs, titleQuestion)
|
||||
}
|
||||
if body == "" {
|
||||
qs = append(qs, bodyQuestion)
|
||||
}
|
||||
|
||||
err := survey.Ask(qs, &inProgress)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not prompt")
|
||||
}
|
||||
confirmAnswers := struct {
|
||||
Confirmation string
|
||||
}{}
|
||||
confirmQs := []*survey.Question{
|
||||
{
|
||||
Name: "confirmation",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Submit?",
|
||||
Options: []string{
|
||||
"Yes",
|
||||
"Edit",
|
||||
"Cancel",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = survey.Ask(confirmQs, &confirmAnswers)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not prompt")
|
||||
}
|
||||
|
||||
switch confirmAnswers.Confirmation {
|
||||
case "Yes":
|
||||
confirmed = true
|
||||
case "Edit":
|
||||
continue
|
||||
case "Cancel":
|
||||
cmd.Println("Discarding pull request")
|
||||
return nil
|
||||
}
|
||||
if title == "" {
|
||||
title = tb.Title
|
||||
}
|
||||
if body == "" {
|
||||
body = tb.Body
|
||||
}
|
||||
}
|
||||
|
||||
if title == "" {
|
||||
title = inProgress.Title
|
||||
}
|
||||
if body == "" {
|
||||
body = inProgress.Body
|
||||
}
|
||||
base, err := cmd.Flags().GetString("base")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse base")
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@ package command
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/github/gh-cli/api"
|
||||
"github.com/github/gh-cli/context"
|
||||
"github.com/github/gh-cli/utils"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
|
@ -17,9 +20,12 @@ var Version = "DEV"
|
|||
var BuildDate = "YYYY-MM-DD"
|
||||
|
||||
func init() {
|
||||
RootCmd.Version = fmt.Sprintf("%s (%s)", Version, BuildDate)
|
||||
RootCmd.PersistentFlags().StringP("repo", "R", "", "current GitHub repository")
|
||||
RootCmd.PersistentFlags().StringP("current-branch", "B", "", "current git branch")
|
||||
RootCmd.Version = fmt.Sprintf("%s (%s)", strings.TrimPrefix(Version, "v"), BuildDate)
|
||||
RootCmd.AddCommand(versionCmd)
|
||||
|
||||
RootCmd.PersistentFlags().StringP("repo", "R", "", "Current GitHub repository")
|
||||
RootCmd.PersistentFlags().Bool("help", false, "Show help for command")
|
||||
RootCmd.Flags().Bool("version", false, "Print gh version")
|
||||
// TODO:
|
||||
// RootCmd.PersistentFlags().BoolP("verbose", "V", false, "enable verbose output")
|
||||
|
||||
|
|
@ -37,12 +43,20 @@ type FlagError struct {
|
|||
var RootCmd = &cobra.Command{
|
||||
Use: "gh",
|
||||
Short: "GitHub CLI",
|
||||
Long: `Work with GitHub from your terminal`,
|
||||
Long: `Work seamlessly with GitHub from the command line`,
|
||||
|
||||
SilenceErrors: true,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Hidden: true,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("gh version %s\n", RootCmd.Version)
|
||||
},
|
||||
}
|
||||
|
||||
// overriden in tests
|
||||
var initContext = func() context.Context {
|
||||
ctx := context.New()
|
||||
|
|
@ -56,9 +70,7 @@ func contextForCommand(cmd *cobra.Command) context.Context {
|
|||
ctx := initContext()
|
||||
if repo, err := cmd.Flags().GetString("repo"); err == nil && repo != "" {
|
||||
ctx.SetBaseRepo(repo)
|
||||
}
|
||||
if branch, err := cmd.Flags().GetString("current-branch"); err == nil && branch != "" {
|
||||
ctx.SetBranch(branch)
|
||||
ctx.SetBranch("master")
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
|
@ -82,3 +94,11 @@ var apiClientForContext = func(ctx context.Context) (*api.Client, error) {
|
|||
}
|
||||
return api.NewClient(opts...), nil
|
||||
}
|
||||
|
||||
func colorableOut(cmd *cobra.Command) io.Writer {
|
||||
out := cmd.OutOrStdout()
|
||||
if outFile, isFile := out.(*os.File); isFile {
|
||||
return utils.NewColorable(outFile)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
104
command/title_body_survey.go
Normal file
104
command/title_body_survey.go
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type titleBody struct {
|
||||
Body string
|
||||
Title string
|
||||
}
|
||||
|
||||
const (
|
||||
_confirmed = iota
|
||||
_unconfirmed = iota
|
||||
_cancel = iota
|
||||
)
|
||||
|
||||
func confirm() (int, error) {
|
||||
confirmAnswers := struct {
|
||||
Confirmation int
|
||||
}{}
|
||||
confirmQs := []*survey.Question{
|
||||
{
|
||||
Name: "confirmation",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Submit?",
|
||||
Options: []string{
|
||||
"Yes",
|
||||
"Edit",
|
||||
"Cancel",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := survey.Ask(confirmQs, &confirmAnswers)
|
||||
if err != nil {
|
||||
return -1, errors.Wrap(err, "could not prompt")
|
||||
}
|
||||
|
||||
return confirmAnswers.Confirmation, nil
|
||||
}
|
||||
|
||||
func titleBodySurvey(cmd *cobra.Command, providedTitle string, providedBody string) (*titleBody, error) {
|
||||
inProgress := titleBody{}
|
||||
|
||||
confirmed := false
|
||||
editor := determineEditor()
|
||||
|
||||
for !confirmed {
|
||||
titleQuestion := &survey.Question{
|
||||
Name: "title",
|
||||
Prompt: &survey.Input{
|
||||
Message: "Title",
|
||||
Default: inProgress.Title,
|
||||
},
|
||||
}
|
||||
bodyQuestion := &survey.Question{
|
||||
Name: "body",
|
||||
Prompt: &survey.Editor{
|
||||
Message: fmt.Sprintf("Body (%s)", editor),
|
||||
FileName: "*.md",
|
||||
Default: inProgress.Body,
|
||||
AppendDefault: true,
|
||||
Editor: editor,
|
||||
},
|
||||
}
|
||||
|
||||
qs := []*survey.Question{}
|
||||
if providedTitle == "" {
|
||||
qs = append(qs, titleQuestion)
|
||||
}
|
||||
if providedBody == "" {
|
||||
qs = append(qs, bodyQuestion)
|
||||
}
|
||||
|
||||
err := survey.Ask(qs, &inProgress)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not prompt")
|
||||
}
|
||||
|
||||
confirmA, err := confirm()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to confirm")
|
||||
}
|
||||
switch confirmA {
|
||||
case _confirmed:
|
||||
confirmed = true
|
||||
case _unconfirmed:
|
||||
continue
|
||||
case _cancel:
|
||||
cmd.Println("Discarding.")
|
||||
return nil, nil
|
||||
default:
|
||||
panic("reached unreachable case")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return &inProgress, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue