зеркало из https://github.com/mislav/hub.git
Add `hub pr list` command to list pull requests
This commit is contained in:
Родитель
9c2d6c2b00
Коммит
674fe7d9d8
|
@ -260,7 +260,7 @@ func listIssues(cmd *Command, args *Args) {
|
|||
args.NoForward()
|
||||
}
|
||||
|
||||
func formatIssue(issue github.Issue, format string, colorize bool) string {
|
||||
func formatIssuePlaceholders(issue github.Issue, colorize bool) map[string]string {
|
||||
var stateColorSwitch string
|
||||
if colorize {
|
||||
issueColor := 32
|
||||
|
@ -323,7 +323,7 @@ func formatIssue(issue github.Issue, format string, colorize bool) string {
|
|||
updatedAtRelative = utils.TimeAgo(issue.UpdatedAt)
|
||||
}
|
||||
|
||||
placeholders := map[string]string{
|
||||
return map[string]string{
|
||||
"I": fmt.Sprintf("%d", issue.Number),
|
||||
"i": fmt.Sprintf("#%d", issue.Number),
|
||||
"U": issue.HtmlUrl,
|
||||
|
@ -348,7 +348,10 @@ func formatIssue(issue github.Issue, format string, colorize bool) string {
|
|||
"ut": updatedAtUnix,
|
||||
"ur": updatedAtRelative,
|
||||
}
|
||||
}
|
||||
|
||||
func formatIssue(issue github.Issue, format string, colorize bool) string {
|
||||
placeholders := formatIssuePlaceholders(issue, colorize)
|
||||
return ui.Expand(format, placeholders, colorize)
|
||||
}
|
||||
|
||||
|
|
167
commands/pr.go
167
commands/pr.go
|
@ -6,33 +6,128 @@ import (
|
|||
"strconv"
|
||||
|
||||
"github.com/github/hub/github"
|
||||
"github.com/github/hub/ui"
|
||||
"github.com/github/hub/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
cmdPr = &Command{
|
||||
Run: printHelp,
|
||||
Usage: "pr checkout <PULLREQ-NUMBER> [<BRANCH>]",
|
||||
Long: `Check out the head of a pull request as a local branch.
|
||||
Run: printHelp,
|
||||
Usage: `
|
||||
pr list [-s <STATE>] [-h <HEAD>] [-b <BASE>] [-o <SORT_KEY> [-^]] [-L <LIMIT>]
|
||||
pr checkout <PR-NUMBER> [<BRANCH>]
|
||||
`,
|
||||
Long: `Manage GitHub pull requests for the current project.
|
||||
|
||||
## Examples:
|
||||
$ hub pr checkout 73
|
||||
> git fetch origin pull/73/head:jingweno-feature
|
||||
> git checkout jingweno-feature
|
||||
## Commands:
|
||||
|
||||
* _list_:
|
||||
List pull requests in the current project.
|
||||
|
||||
* _checkout_:
|
||||
Check out the head of a pull request in a new branch.
|
||||
|
||||
## Options:
|
||||
|
||||
-s, --state <STATE>
|
||||
Display pull requests with state <STATE> (default: "open").
|
||||
|
||||
-f, --format <FORMAT>
|
||||
Pretty print the list of pull requests using format <FORMAT> (default:
|
||||
"%sC%>(8)%i%Creset %t% l%n"). See the "PRETTY FORMATS" section of the
|
||||
git-log manual for some additional details on how placeholders are used in
|
||||
format. The available placeholders are:
|
||||
|
||||
%I: pull request number
|
||||
|
||||
%i: pull request number prefixed with "#"
|
||||
|
||||
%U: the URL of this pull request
|
||||
|
||||
%S: state (i.e. "open", "closed")
|
||||
|
||||
%sC: set color to red or green, depending on pull request state.
|
||||
|
||||
%t: title
|
||||
|
||||
%l: colored labels
|
||||
|
||||
%L: raw, comma-separated labels
|
||||
|
||||
%b: body
|
||||
|
||||
%au: login name of author
|
||||
|
||||
%as: comma-separated list of assignees
|
||||
|
||||
%Mn: milestone number
|
||||
|
||||
%Mt: milestone title
|
||||
|
||||
%NC: number of comments
|
||||
|
||||
%Nc: number of comments wrapped in parentheses, or blank string if zero.
|
||||
|
||||
%cD: created date-only (no time of day)
|
||||
|
||||
%cr: created date, relative
|
||||
|
||||
%ct: created date, UNIX timestamp
|
||||
|
||||
%cI: created date, ISO 8601 format
|
||||
|
||||
%uD: updated date-only (no time of day)
|
||||
|
||||
%ur: updated date, relative
|
||||
|
||||
%ut: updated date, UNIX timestamp
|
||||
|
||||
%uI: updated date, ISO 8601 format
|
||||
|
||||
-o, --sort <SORT_KEY>
|
||||
Sort displayed issues by "created" (default), "updated", "popularity", or "long-running".
|
||||
|
||||
-^ --sort-ascending
|
||||
Sort by ascending dates instead of descending.
|
||||
|
||||
-L, --limit <LIMIT>
|
||||
Display only the first <LIMIT> issues.
|
||||
|
||||
## See also:
|
||||
|
||||
hub-merge(1), hub(1), hub-checkout(1)
|
||||
`,
|
||||
hub-issue(1), hub-pull-request(1), hub(1)
|
||||
`,
|
||||
}
|
||||
|
||||
cmdCheckoutPr = &Command{
|
||||
Key: "checkout",
|
||||
Run: checkoutPr,
|
||||
}
|
||||
|
||||
cmdListPulls = &Command{
|
||||
Key: "list",
|
||||
Run: listPulls,
|
||||
}
|
||||
|
||||
flagPullRequestState,
|
||||
flagPullRequestFormat,
|
||||
flagPullRequestSort string
|
||||
|
||||
flagPullRequestSortAscending bool
|
||||
|
||||
flagPullRequestLimit int
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmdListPulls.Flag.StringVarP(&flagPullRequestState, "state", "s", "", "STATE")
|
||||
cmdListPulls.Flag.StringVarP(&flagPullRequestBase, "base", "b", "", "BASE")
|
||||
cmdListPulls.Flag.StringVarP(&flagPullRequestHead, "head", "h", "", "HEAD")
|
||||
cmdListPulls.Flag.StringVarP(&flagPullRequestFormat, "format", "f", "%sC%>(8)%i%Creset %t% l%n", "FORMAT")
|
||||
cmdListPulls.Flag.StringVarP(&flagPullRequestSort, "sort", "o", "created", "SORT_KEY")
|
||||
cmdListPulls.Flag.BoolVarP(&flagPullRequestSortAscending, "sort-ascending", "^", false, "SORT_KEY")
|
||||
cmdListPulls.Flag.IntVarP(&flagPullRequestLimit, "limit", "L", -1, "LIMIT")
|
||||
|
||||
cmdPr.Use(cmdListPulls)
|
||||
cmdPr.Use(cmdCheckoutPr)
|
||||
CmdRunner.Use(cmdPr)
|
||||
}
|
||||
|
@ -42,6 +137,46 @@ func printHelp(command *Command, args *Args) {
|
|||
os.Exit(0)
|
||||
}
|
||||
|
||||
func listPulls(cmd *Command, args *Args) {
|
||||
localRepo, err := github.LocalRepo()
|
||||
utils.Check(err)
|
||||
|
||||
project, err := localRepo.MainProject()
|
||||
utils.Check(err)
|
||||
|
||||
gh := github.NewClient(project.Host)
|
||||
|
||||
args.NoForward()
|
||||
if args.Noop {
|
||||
ui.Printf("Would request list of pull requests for %s\n", project)
|
||||
return
|
||||
}
|
||||
|
||||
flagFilters := map[string]string{
|
||||
"state": flagPullRequestState,
|
||||
"head": flagPullRequestHead,
|
||||
"base": flagPullRequestBase,
|
||||
"sort": flagPullRequestSort,
|
||||
}
|
||||
filters := map[string]interface{}{}
|
||||
for flag, filter := range flagFilters {
|
||||
if cmd.FlagPassed(flag) {
|
||||
filters[flag] = filter
|
||||
}
|
||||
}
|
||||
if flagPullRequestSortAscending {
|
||||
filters["direction"] = "asc"
|
||||
}
|
||||
|
||||
pulls, err := gh.FetchPullRequests(project, filters, flagPullRequestLimit, nil)
|
||||
utils.Check(err)
|
||||
|
||||
colorize := ui.IsTerminal(os.Stdout)
|
||||
for _, pr := range pulls {
|
||||
ui.Printf(formatPullRequest(pr, flagPullRequestFormat, colorize))
|
||||
}
|
||||
}
|
||||
|
||||
func checkoutPr(command *Command, args *Args) {
|
||||
words := args.Words()
|
||||
var newBranchName string
|
||||
|
@ -72,3 +207,17 @@ func checkoutPr(command *Command, args *Args) {
|
|||
|
||||
args.Replace(args.Executable, "checkout", newArgs...)
|
||||
}
|
||||
|
||||
func formatPullRequest(pr github.PullRequest, format string, colorize bool) string {
|
||||
base := pr.Base.Ref
|
||||
head := pr.Head.Label
|
||||
if pr.IsSameRepo() {
|
||||
head = pr.Head.Ref
|
||||
}
|
||||
|
||||
placeholders := formatIssuePlaceholders(github.Issue(pr), colorize)
|
||||
placeholders["B"] = base
|
||||
placeholders["H"] = head
|
||||
|
||||
return ui.Expand(format, placeholders, colorize)
|
||||
}
|
||||
|
|
118
github/client.go
118
github/client.go
|
@ -34,6 +34,52 @@ type Client struct {
|
|||
Host *Host
|
||||
}
|
||||
|
||||
func (client *Client) FetchPullRequests(project *Project, filterParams map[string]interface{}, limit int, filter func(*PullRequest) bool) (pulls []PullRequest, err error) {
|
||||
api, err := client.simpleApi()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("repos/%s/%s/pulls?per_page=%d", project.Owner, project.Name, perPage(limit, 100))
|
||||
if filterParams != nil {
|
||||
query := url.Values{}
|
||||
for key, value := range filterParams {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
query.Add(key, v)
|
||||
}
|
||||
}
|
||||
path += "&" + query.Encode()
|
||||
}
|
||||
|
||||
pulls = []PullRequest{}
|
||||
var res *simpleResponse
|
||||
|
||||
for path != "" {
|
||||
res, err = api.Get(path)
|
||||
if err = checkStatus(200, "fetching pull requests", res, err); err != nil {
|
||||
return
|
||||
}
|
||||
path = res.Link("next")
|
||||
|
||||
pullsPage := []PullRequest{}
|
||||
if err = res.Unmarshal(&pullsPage); err != nil {
|
||||
return
|
||||
}
|
||||
for _, pr := range pullsPage {
|
||||
if filter == nil || filter(&pr) {
|
||||
pulls = append(pulls, pr)
|
||||
if limit > 0 && len(pulls) == limit {
|
||||
path = ""
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (client *Client) PullRequest(project *Project, id string) (pr *PullRequest, err error) {
|
||||
api, err := client.simpleApi()
|
||||
if err != nil {
|
||||
|
@ -65,29 +111,6 @@ func (client *Client) PullRequestPatch(project *Project, id string) (patch io.Re
|
|||
return res.Body, nil
|
||||
}
|
||||
|
||||
type PullRequest struct {
|
||||
ApiUrl string `json:"url"`
|
||||
Number int `json:"number"`
|
||||
HtmlUrl string `json:"html_url"`
|
||||
Title string `json:"title"`
|
||||
MaintainerCanModify bool `json:"maintainer_can_modify"`
|
||||
Head *PullRequestSpec `json:"head"`
|
||||
Base *PullRequestSpec `json:"base"`
|
||||
}
|
||||
|
||||
type PullRequestSpec struct {
|
||||
Label string `json:"label"`
|
||||
Ref string `json:"ref"`
|
||||
Sha string `json:"sha"`
|
||||
Repo *Repository `json:"repo"`
|
||||
}
|
||||
|
||||
func (pr *PullRequest) IsSameRepo() bool {
|
||||
return pr.Head.Repo != nil &&
|
||||
pr.Head.Repo.Name == pr.Base.Repo.Name &&
|
||||
pr.Head.Repo.Owner.Login == pr.Base.Repo.Owner.Login
|
||||
}
|
||||
|
||||
func (client *Client) CreatePullRequest(project *Project, params map[string]interface{}) (pr *PullRequest, err error) {
|
||||
api, err := client.simpleApi()
|
||||
if err != nil {
|
||||
|
@ -455,19 +478,42 @@ func (client *Client) ForkRepository(project *Project, params map[string]interfa
|
|||
}
|
||||
|
||||
type Issue struct {
|
||||
Number int `json:"number"`
|
||||
State string `json:"state"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
User *User `json:"user"`
|
||||
Assignees []User `json:"assignees"`
|
||||
Labels []IssueLabel `json:"labels"`
|
||||
PullRequest *PullRequest `json:"pull_request"`
|
||||
HtmlUrl string `json:"html_url"`
|
||||
Comments int `json:"comments"`
|
||||
Milestone *Milestone `json:"milestone"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Number int `json:"number"`
|
||||
State string `json:"state"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
User *User `json:"user"`
|
||||
|
||||
PullRequest *PullRequest `json:"pull_request"`
|
||||
Head *PullRequestSpec `json:"head"`
|
||||
Base *PullRequestSpec `json:"base"`
|
||||
|
||||
MaintainerCanModify bool `json:"maintainer_can_modify"`
|
||||
|
||||
Comments int `json:"comments"`
|
||||
Labels []IssueLabel `json:"labels"`
|
||||
Assignees []User `json:"assignees"`
|
||||
Milestone *Milestone `json:"milestone"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
ApiUrl string `json:"url"`
|
||||
HtmlUrl string `json:"html_url"`
|
||||
}
|
||||
|
||||
type PullRequest Issue
|
||||
|
||||
type PullRequestSpec struct {
|
||||
Label string `json:"label"`
|
||||
Ref string `json:"ref"`
|
||||
Sha string `json:"sha"`
|
||||
Repo *Repository `json:"repo"`
|
||||
}
|
||||
|
||||
func (pr *PullRequest) IsSameRepo() bool {
|
||||
return pr.Head != nil && pr.Head.Repo != nil &&
|
||||
pr.Head.Repo.Name == pr.Base.Repo.Name &&
|
||||
pr.Head.Repo.Owner.Login == pr.Base.Repo.Owner.Login
|
||||
}
|
||||
|
||||
type IssueLabel struct {
|
||||
|
|
Загрузка…
Ссылка в новой задаче