Skip to content

Commit

Permalink
git-open-pull: add command line arguments for description file path, …
Browse files Browse the repository at this point in the history
…title, labels and interactive mode
  • Loading branch information
dianaabishop committed Apr 16, 2020
1 parent 76ceb21 commit 55f0301
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 47 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ Create a pull request from the command line, or attach a branch to an open GitHu

$ git open-pull

git-open-pull takes the following optional arguments:
--interactive - boolean flag to turn off interactive mode; this is default set to true
--description-file - path to a file that contains your PR description
--title - string title for your PR
--labels - comma separated list of labels to be added to you PR
--version - print version of git-open-pull and Go

$ git open-pull --interactive=false --description-file="description.txt" --labels="label1, label2" --title="My PR Title"

### Installing

Expand Down Expand Up @@ -43,6 +51,18 @@ Hooks. git-open-pull provides the ability to modify an issue template (preProces
postProcess = /path/to/exe
callback = /path/to/exe
git-open-pull will also look for the following environment variables, which will take precendence over values found in the config file.
GITOPENPULL_TOKEN
GITOPENPULL_USER
GITOPENPULL_BASE_ACCOUNT
GITOPENPULL_BASE_REPO
GITOPENPULL_PRE_PROCESS
GITOPENPULL_POST_PROCESS
GITOPENPULL_CALLBACK
GITOPENPULL_BASE_BRANCH
GITOPENPULL_MAINTAINERS_CAN_MODIFY
GITOPENPULL_EDITOR
### ABOUT
Because the ideal workflow is `issue -> branch -> pull request` this script
Expand Down
98 changes: 73 additions & 25 deletions git-open-pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,31 @@ package main

import (
"context"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"time"

"github.com/google/go-github/github"
"github.com/jehiah/git-open-pull/internal/input"
"golang.org/x/oauth2"
)

func RenameBranch(ctx context.Context, branch string, issueNumber int) (string, error) {
branch = fmt.Sprintf("%s_%d", branch, issueNumber)
_, err := RunGit(ctx, "branch", "-m", branch)
if err != nil {
return "", err
}
return branch, nil
}

func SetupClient(ctx context.Context, s *Settings) *github.Client {
if s == nil {
panic("missing settings")
Expand All @@ -30,16 +41,20 @@ func SetupClient(ctx context.Context, s *Settings) *github.Client {
}

// GetIssueNumber prompts to create a new issue, or confirmation of auto-detected issue number
func GetIssueNumber(ctx context.Context, client *github.Client, settings *Settings, detected int) (int, error) {
func GetIssueNumber(ctx context.Context, client *github.Client, settings *Settings, detected int, interactive bool, title, description string, labels []string) (int, error) {
var issue int
if detected == 0 {
n, err := input.Ask("enter issue number (or 'c' to create)", "")
if err != nil {
return issue, err
var err error
n := "c"
if interactive {
n, err = input.Ask("enter issue number (or 'c' to create)", "")
if err != nil {
return issue, err
}
}
switch n {
case "", "c", "C":
return NewIssue(ctx, client, settings)
return NewIssue(ctx, client, settings, interactive, title, description, labels)
default:
return strconv.Atoi(n)
}
Expand All @@ -55,7 +70,15 @@ func GetIssueNumber(ctx context.Context, client *github.Client, settings *Settin
}

func main() {
if len(os.Args) > 1 {
description := flag.String("description-file", "", "Path to PR description file")
labels := flag.String("labels", "", "Comma separated PR Labels")
title := flag.String("title", "", "PR Title")
interactive := flag.Bool("interactive", true, "Toggles interactive mode")
version := flag.Bool("version", false, "Prints current version")

flag.Parse()

if *version {
fmt.Printf("git-open-pull v%s %s\n", Version, runtime.Version())
os.Exit(0)
}
Expand All @@ -68,6 +91,23 @@ func main() {
log.Fatal(err)
}

var labelSlice []string
if *labels != "" {
labelSlice = strings.Split(*labels, ",")
for idx := range labelSlice {
labelSlice[idx] = strings.TrimSpace(labelSlice[idx])
}
}

var descriptionString string
if *description != "" {
fileContent, err := ioutil.ReadFile(*description)
if err != nil {
log.Fatal(err)
}
descriptionString = string(fileContent)
}

branch, err := GitFeatureBranch(ctx)
if err != nil {
log.Fatal(err)
Expand All @@ -87,7 +127,7 @@ func main() {
client := SetupClient(ctx, settings)

// create issue if needed
issueNumber, err := GetIssueNumber(ctx, client, settings, detectedIssueNumber)
issueNumber, err := GetIssueNumber(ctx, client, settings, detectedIssueNumber, *interactive, *title, descriptionString, labelSlice)
if err != nil {
log.Fatal(err)
}
Expand All @@ -97,22 +137,28 @@ func main() {

// Do we need/want to rename the branch?
if issueNumber != detectedIssueNumber {
yn, err := input.Ask(fmt.Sprintf("rename branch to %s_%d [Y/n]", branch, issueNumber), "")
if err != nil {
log.Fatal(err)
}
switch yn {
case "", "y", "Y":
fmt.Printf("renaming local branch %s to %s_%d\n", branch, branch, issueNumber)
branch = fmt.Sprintf("%s_%d", branch, issueNumber)
_, err = RunGit(ctx, "branch", "-m", branch)
if *interactive {
yn, err := input.Ask(fmt.Sprintf("rename branch to %s_%d [Y/n]", branch, issueNumber), "")
if err != nil {
log.Fatal(err)
}
switch yn {
case "", "y", "Y":
branch, err = RenameBranch(ctx, branch, issueNumber)
if err != nil {
log.Fatal(err)
}
case "n", "N":
default:
log.Fatalf("unknown response %q", yn)
}
} else {
branch, err = RenameBranch(ctx, branch, issueNumber)
if err != nil {
log.Fatal(err)
}
case "n", "N":
default:
log.Fatalf("unknown response %q", yn)
}

}

// confirm issue number is valid and issue is open
Expand Down Expand Up @@ -162,12 +208,14 @@ func main() {
fmt.Printf("Issue: %d (%s)\n", issueNumber, *issue.Title)
head := fmt.Sprintf("%s:%s", settings.User, branch)
fmt.Printf("pulling from %s into %s/%s branch %s\n", head, settings.BaseAccount, settings.BaseRepo, settings.BaseBranch)
yn, err := input.Ask("confirm [y/n]", "")
if err != nil {
log.Fatal(err)
}
if yn != "y" {
log.Fatal("exiting")
if *interactive {
yn, err := input.Ask("confirm [y/n]", "")
if err != nil {
log.Fatal(err)
}
if strings.ToLower(yn) != "y" {
log.Fatal("exiting")
}
}

// convert Issue to PR
Expand Down
95 changes: 73 additions & 22 deletions issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,68 @@ func DetectIssueNumber(branch string) int {
return 0
}

// NewIssue creates a template, parses the template and returns the Issue number
func NewIssue(ctx context.Context, client *github.Client, settings *Settings) (issueNumber int, err error) {
labels, err := Labels(ctx, client, settings)
func NewIssue(ctx context.Context, client *github.Client, settings *Settings, interactive bool, title, description string, labels []string) (issueNumber int, err error) {
var gir *github.IssueRequest
if interactive {
gir, err = PopulateIssueInteractive(ctx, client, settings, title, description, labels)
if err != nil {
log.Fatalf("Interactive issue creation failed: %v", err)
}

} else {
if title == "" {
log.Fatal("title cannot be empty")
}

gir = &github.IssueRequest{
Title: &title,
Body: &description,
Assignee: &settings.User,
}

if labels != nil {
gir.Labels = &labels
}
}

i, _, err := client.Issues.Create(ctx, settings.BaseAccount, settings.BaseRepo, gir)
if err != nil {
return 0, err
}

if interactive {
fmt.Printf("Created issue %d (%s)\n", *i.Number, *i.Title)
}

return *i.Number, nil
}

// PopulateIssueInteractive creates a template, parses the template and returns the Issue number if the user is in interactive mode
func PopulateIssueInteractive(ctx context.Context, client *github.Client, settings *Settings, inputTitle, inputDescription string, labelSlice []string) (ir *github.IssueRequest, err error) {
labels, err := Labels(ctx, client, settings)
if err != nil {
return nil, err
}

labelSet := make(map[string]bool)
for _, l := range labelSlice {
labelSet[l] = true
}

tempFile, err := ioutil.TempFile("", "git-open-pull")
if err != nil {
return 0, err
return nil, err
}
// fmt.Printf("drafting %s\n", tempFile.Name())
defer os.Remove(tempFile.Name())

if inputTitle != "" {
fmt.Fprintf(tempFile, "%s\n", inputTitle)
}
if inputDescription != "" {
fmt.Fprintf(tempFile, " * %s\n", inputDescription)
}

// seed template with commit history
mergeBase, err := MergeBase(ctx, settings)
if err != nil {
Expand All @@ -60,7 +108,7 @@ func NewIssue(ctx context.Context, client *github.Client, settings *Settings) (i
// log.Printf("[%d] commit %s", i, c)
t, b, err := CommitDetails(ctx, c)
if err != nil {
return 0, err
return nil, err
}
if t == "" {
continue
Expand All @@ -81,6 +129,11 @@ func NewIssue(ctx context.Context, client *github.Client, settings *Settings) (i
}
io.WriteString(tempFile, "\n# Uncomment to assign labels\n")
for _, l := range labels {
// if labels are passed as command line input, uncomment them
if labelSet[l] {
fmt.Fprintf(tempFile, "Label: %s\n", l)
continue
}
fmt.Fprintf(tempFile, "# Label: %s\n", l)
}

Expand All @@ -100,7 +153,7 @@ func NewIssue(ctx context.Context, client *github.Client, settings *Settings) (i
out, err := cmd.CombinedOutput()
if err != nil {
log.Printf("error running pre process template: %s:\n %s", settings.PreProcess, out)
return 0, err
return nil, err
}
}

Expand All @@ -112,10 +165,10 @@ func NewIssue(ctx context.Context, client *github.Client, settings *Settings) (i
if err != nil {
tempFile.Close()
// os.Remove(tempFile.Name())
return 0, err
return nil, err
}
if cmd.ProcessState != nil && !cmd.ProcessState.Success() {
return 0, fmt.Errorf("non-zero exit code from editor")
return nil, fmt.Errorf("non-zero exit code from editor")
}

// post process template
Expand All @@ -124,14 +177,14 @@ func NewIssue(ctx context.Context, client *github.Client, settings *Settings) (i
out, err := cmd.CombinedOutput()
if err != nil {
log.Printf("error running post process template: %s:\n %s", settings.PostProcess, out)
return 0, err
return nil, err
}
}

// re-open the temp file
tempFile, err = os.Open(tempFile.Name())
if err != nil {
return 0, err
return nil, err
}

var title string
Expand All @@ -154,13 +207,19 @@ func NewIssue(ctx context.Context, client *github.Client, settings *Settings) (i
}

if err := scanner.Err(); err != nil {
return 0, err
return nil, err
}
}
description := strings.TrimSpace(strings.Join(descriptions, "\n"))

var description string
if inputDescription != "" {
description = inputDescription
} else {
description = strings.TrimSpace(strings.Join(descriptions, "\n"))
}

if title == "" {
return 0, fmt.Errorf("missing title")
return nil, fmt.Errorf("missing title")
}

issue := &github.IssueRequest{
Expand All @@ -174,15 +233,7 @@ func NewIssue(ctx context.Context, client *github.Client, settings *Settings) (i
issue.Labels = &selectedLabels
}

i, _, err := client.Issues.Create(ctx, settings.BaseAccount, settings.BaseRepo, issue)
if err != nil {
return 0, err
}
fmt.Printf("Created issue %d (%s)\n", *i.Number, *i.Title)
if len(selectedLabels) > 0 {
fmt.Printf("\tLabels: %s\n", strings.Join(selectedLabels, ", "))
}
return *i.Number, nil
return issue, nil
}

// Labels returns all of the labels for a given repo
Expand Down
Loading

0 comments on commit 55f0301

Please sign in to comment.