From 7c30579900a2f8724c8416a2802c33377fa32103 Mon Sep 17 00:00:00 2001 From: 6543 <6543@noreply.gitea.io> Date: Thu, 17 Sep 2020 19:35:24 +0000 Subject: [PATCH] Add milestones subcomand (#149) issues/pulls show milestones add mile issues subsubcomand Add milestones subcomand Co-authored-by: 6543 <6543@obermui.de> Reviewed-on: https://gitea.com/gitea/tea/pulls/149 Reviewed-by: Lunny Xiao Reviewed-by: Andrew Thornton --- cmd/issues.go | 20 ++-- cmd/milestone_issues.go | 199 +++++++++++++++++++++++++++++++ cmd/milestones.go | 252 ++++++++++++++++++++++++++++++++++++++++ cmd/pulls.go | 20 ++-- main.go | 1 + 5 files changed, 478 insertions(+), 14 deletions(-) create mode 100644 cmd/milestone_issues.go create mode 100644 cmd/milestones.go diff --git a/cmd/issues.go b/cmd/issues.go index f2cb4fa..b8a70d7 100644 --- a/cmd/issues.go +++ b/cmd/issues.go @@ -97,10 +97,11 @@ func runIssuesList(ctx *cli.Context) error { headers := []string{ "Index", + "Title", "State", "Author", + "Milestone", "Updated", - "Title", } var values [][]string @@ -111,18 +112,23 @@ func runIssuesList(ctx *cli.Context) error { } for _, issue := range issues { - name := issue.Poster.FullName - if len(name) == 0 { - name = issue.Poster.UserName + author := issue.Poster.FullName + if len(author) == 0 { + author = issue.Poster.UserName + } + mile := "" + if issue.Milestone != nil { + mile = issue.Milestone.Title } values = append( values, []string{ strconv.FormatInt(issue.Index, 10), - string(issue.State), - name, - issue.Updated.Format("2006-01-02 15:04:05"), issue.Title, + string(issue.State), + author, + mile, + issue.Updated.Format("2006-01-02 15:04:05"), }, ) } diff --git a/cmd/milestone_issues.go b/cmd/milestone_issues.go new file mode 100644 index 0000000..419c85c --- /dev/null +++ b/cmd/milestone_issues.go @@ -0,0 +1,199 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "strconv" + + "code.gitea.io/sdk/gitea" + "github.com/urfave/cli/v2" +) + +// CmdMilestonesIssues represents a sub command of milestones to manage issue/pull of an milestone +var CmdMilestonesIssues = cli.Command{ + Name: "issues", + Aliases: []string{"i"}, + Usage: "manage issue/pull of an milestone", + Description: "manage issue/pull of an milestone", + ArgsUsage: "", + Action: runMilestoneIssueList, + Subcommands: []*cli.Command{ + &CmdMilestoneAddIssue, + &CmdMilestoneRemoveIssue, + }, + Flags: append([]cli.Flag{ + &cli.StringFlag{ + Name: "state", + Usage: "Filter by issue state (all|open|closed)", + DefaultText: "open", + }, + &cli.StringFlag{ + Name: "kind", + Usage: "Filter by kind (issue|pull)", + }, + }, AllDefaultFlags...), +} + +// CmdMilestoneAddIssue represents a sub command of milestone issues to add an issue/pull to an milestone +var CmdMilestoneAddIssue = cli.Command{ + Name: "add", + Aliases: []string{"a"}, + Usage: "Add an issue/pull to an milestone", + Description: "Add an issue/pull to an milestone", + ArgsUsage: " ", + Action: runMilestoneIssueAdd, + Flags: AllDefaultFlags, +} + +// CmdMilestoneRemoveIssue represents a sub command of milestones to remove an issue/pull from an milestone +var CmdMilestoneRemoveIssue = cli.Command{ + Name: "remove", + Aliases: []string{"r"}, + Usage: "Remove an issue/pull to an milestone", + Description: "Remove an issue/pull to an milestone", + ArgsUsage: " ", + Action: runMilestoneIssueRemove, + Flags: AllDefaultFlags, +} + +func runMilestoneIssueList(ctx *cli.Context) error { + login, owner, repo := initCommand() + client := login.Client() + + state := gitea.StateOpen + switch ctx.String("state") { + case "all": + state = gitea.StateAll + case "closed": + state = gitea.StateClosed + } + + kind := gitea.IssueTypeAll + switch ctx.String("kind") { + case "issue": + kind = gitea.IssueTypeIssue + case "pull": + kind = gitea.IssueTypePull + } + + fmt.Println(state) + + milestone := ctx.Args().First() + // make sure milestone exist + _, _, err := client.GetMilestoneByName(owner, repo, milestone) + if err != nil { + return err + } + + issues, _, err := client.ListRepoIssues(owner, repo, gitea.ListIssueOption{ + Milestones: []string{milestone}, + Type: kind, + State: state, + }) + if err != nil { + return err + } + + headers := []string{ + "Index", + "State", + "Kind", + "Author", + "Updated", + "Title", + } + + var values [][]string + + if len(issues) == 0 { + Output(outputValue, headers, values) + return nil + } + + for _, issue := range issues { + name := issue.Poster.FullName + if len(name) == 0 { + name = issue.Poster.UserName + } + kind := "Issue" + if issue.PullRequest != nil { + kind = "Pull" + } + values = append( + values, + []string{ + strconv.FormatInt(issue.Index, 10), + string(issue.State), + kind, + name, + issue.Updated.Format("2006-01-02 15:04:05"), + issue.Title, + }, + ) + } + Output(outputValue, headers, values) + return nil +} + +func runMilestoneIssueAdd(ctx *cli.Context) error { + login, owner, repo := initCommand() + client := login.Client() + if ctx.Args().Len() == 0 { + return fmt.Errorf("need two arguments") + } + + mileName := ctx.Args().Get(0) + issueIndex := ctx.Args().Get(1) + idx, err := argToIndex(issueIndex) + if err != nil { + return err + } + + // make sure milestone exist + mile, _, err := client.GetMilestoneByName(owner, repo, mileName) + if err != nil { + return err + } + + _, _, err = client.EditIssue(owner, repo, idx, gitea.EditIssueOption{ + Milestone: &mile.ID, + }) + return err +} + +func runMilestoneIssueRemove(ctx *cli.Context) error { + login, owner, repo := initCommand() + client := login.Client() + if ctx.Args().Len() == 0 { + return fmt.Errorf("need two arguments") + } + + mileName := ctx.Args().Get(0) + issueIndex := ctx.Args().Get(1) + idx, err := argToIndex(issueIndex) + if err != nil { + return err + } + + issue, _, err := client.GetIssue(owner, repo, idx) + if err != nil { + return err + } + + if issue.Milestone == nil { + return fmt.Errorf("issue is not assigned to a milestone") + } + + if issue.Milestone.Title != mileName { + return fmt.Errorf("issue is not assigned to this milestone") + } + + zero := int64(0) + _, _, err = client.EditIssue(owner, repo, idx, gitea.EditIssueOption{ + Milestone: &zero, + }) + return err +} diff --git a/cmd/milestones.go b/cmd/milestones.go new file mode 100644 index 0000000..82858b1 --- /dev/null +++ b/cmd/milestones.go @@ -0,0 +1,252 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "log" + + "code.gitea.io/sdk/gitea" + "github.com/urfave/cli/v2" +) + +// CmdMilestones represents to operate repositories milestones. +var CmdMilestones = cli.Command{ + Name: "milestones", + Aliases: []string{"ms", "mile"}, + Usage: "List and create milestones", + Description: `List and create milestones`, + ArgsUsage: "[]", + Action: runMilestones, + Subcommands: []*cli.Command{ + &CmdMilestonesList, + &CmdMilestonesCreate, + &CmdMilestonesClose, + &CmdMilestonesDelete, + &CmdMilestonesReopen, + &CmdMilestonesIssues, + }, + Flags: AllDefaultFlags, +} + +// CmdMilestonesList represents a sub command of milestones to list milestones +var CmdMilestonesList = cli.Command{ + Name: "ls", + Usage: "List milestones of the repository", + Description: `List milestones of the repository`, + Action: runMilestonesList, + Flags: append([]cli.Flag{ + &cli.StringFlag{ + Name: "state", + Usage: "Filter by milestone state (all|open|closed)", + DefaultText: "open", + }, + }, AllDefaultFlags...), +} + +func runMilestones(ctx *cli.Context) error { + if ctx.Args().Len() == 1 { + return runMilestoneDetail(ctx, ctx.Args().First()) + } + return runMilestonesList(ctx) +} + +func runMilestoneDetail(ctx *cli.Context, name string) error { + login, owner, repo := initCommand() + client := login.Client() + + milestone, _, err := client.GetMilestoneByName(owner, repo, name) + if err != nil { + return err + } + + fmt.Printf("%s\n", + milestone.Title, + ) + if len(milestone.Description) != 0 { + fmt.Printf("\n%s\n", milestone.Description) + } + if milestone.Deadline != nil && !milestone.Deadline.IsZero() { + fmt.Printf("\nDeadline: %s\n", milestone.Deadline.Format("2006-01-02 15:04:05")) + } + return nil +} + +func runMilestonesList(ctx *cli.Context) error { + login, owner, repo := initCommand() + + state := gitea.StateOpen + switch ctx.String("state") { + case "all": + state = gitea.StateAll + case "closed": + state = gitea.StateClosed + } + + milestones, _, err := login.Client().ListRepoMilestones(owner, repo, gitea.ListMilestoneOption{ + State: state, + }) + + if err != nil { + log.Fatal(err) + } + + headers := []string{ + "Title", + } + if state == gitea.StateAll { + headers = append(headers, "State") + } + headers = append(headers, + "Open/Closed Issues", + "DueDate", + ) + + var values [][]string + + for _, m := range milestones { + var deadline = "" + + if m.Deadline != nil && !m.Deadline.IsZero() { + deadline = m.Deadline.Format("2006-01-02 15:04:05") + } + + item := []string{ + m.Title, + } + if state == gitea.StateAll { + item = append(item, string(m.State)) + } + item = append(item, + fmt.Sprintf("%d/%d", m.OpenIssues, m.ClosedIssues), + deadline, + ) + + values = append(values, item) + } + Output(outputValue, headers, values) + + return nil +} + +// CmdMilestonesCreate represents a sub command of milestones to create milestone +var CmdMilestonesCreate = cli.Command{ + Name: "create", + Usage: "Create an milestone on repository", + Description: `Create an milestone on repository`, + Action: runMilestonesCreate, + Flags: append([]cli.Flag{ + &cli.StringFlag{ + Name: "title", + Aliases: []string{"t"}, + Usage: "milestone title to create", + }, + &cli.StringFlag{ + Name: "description", + Aliases: []string{"d"}, + Usage: "milestone description to create", + }, + &cli.StringFlag{ + Name: "state", + Usage: "set milestone state (default is open)", + DefaultText: "open", + }, + }, AllDefaultFlags...), +} + +func runMilestonesCreate(ctx *cli.Context) error { + login, owner, repo := initCommand() + + title := ctx.String("title") + if len(title) == 0 { + fmt.Printf("Title is required\n") + return nil + } + + state := gitea.StateOpen + if ctx.String("state") == "closed" { + state = gitea.StateClosed + } + + mile, _, err := login.Client().CreateMilestone(owner, repo, gitea.CreateMilestoneOption{ + Title: title, + Description: ctx.String("description"), + State: state, + }) + if err != nil { + log.Fatal(err) + } + + return runMilestoneDetail(ctx, mile.Title) +} + +// CmdMilestonesClose represents a sub command of milestones to close an milestone +var CmdMilestonesClose = cli.Command{ + Name: "close", + Usage: "Change state of an milestone to 'closed'", + Description: `Change state of an milestone to 'closed'`, + ArgsUsage: "", + Action: func(ctx *cli.Context) error { + if ctx.Bool("force") { + return deleteMilestone(ctx) + } + return editMilestoneStatus(ctx, true) + }, + Flags: append([]cli.Flag{ + &cli.BoolFlag{ + Name: "force", + Aliases: []string{"f"}, + Usage: "delete milestone", + }, + }, AllDefaultFlags...), +} + +func editMilestoneStatus(ctx *cli.Context, close bool) error { + login, owner, repo := initCommand() + client := login.Client() + + state := gitea.StateOpen + if close { + state = gitea.StateClosed + } + _, _, err := client.EditMilestoneByName(owner, repo, ctx.Args().First(), gitea.EditMilestoneOption{ + State: &state, + Title: ctx.Args().First(), + }) + + return err +} + +// CmdMilestonesDelete represents a sub command of milestones to delete an milestone +var CmdMilestonesDelete = cli.Command{ + Name: "delete", + Aliases: []string{"rm"}, + Usage: "delete a milestone", + Description: "delete a milestone", + ArgsUsage: "", + Action: deleteMilestone, + Flags: AllDefaultFlags, +} + +func deleteMilestone(ctx *cli.Context) error { + login, owner, repo := initCommand() + client := login.Client() + + _, err := client.DeleteMilestoneByName(owner, repo, ctx.Args().First()) + return err +} + +// CmdMilestonesReopen represents a sub command of milestones to open an milestone +var CmdMilestonesReopen = cli.Command{ + Name: "reopen", + Aliases: []string{"open"}, + Usage: "Change state of an milestone to 'open'", + Description: `Change state of an milestone to 'open'`, + ArgsUsage: "", + Action: func(ctx *cli.Context) error { + return editMilestoneStatus(ctx, false) + }, + Flags: AllDefaultFlags, +} diff --git a/cmd/pulls.go b/cmd/pulls.go index 5692219..121d379 100644 --- a/cmd/pulls.go +++ b/cmd/pulls.go @@ -62,10 +62,11 @@ func runPulls(ctx *cli.Context) error { headers := []string{ "Index", + "Title", "State", "Author", + "Milestone", "Updated", - "Title", } var values [][]string @@ -79,18 +80,23 @@ func runPulls(ctx *cli.Context) error { if pr == nil { continue } - name := pr.Poster.FullName - if len(name) == 0 { - name = pr.Poster.UserName + author := pr.Poster.FullName + if len(author) == 0 { + author = pr.Poster.UserName + } + mile := "" + if pr.Milestone != nil { + mile = pr.Milestone.Title } values = append( values, []string{ strconv.FormatInt(pr.Index, 10), - string(pr.State), - name, - pr.Updated.Format("2006-01-02 15:04:05"), pr.Title, + string(pr.State), + author, + mile, + pr.Updated.Format("2006-01-02 15:04:05"), }, ) } diff --git a/main.go b/main.go index cdfa16a..d382016 100644 --- a/main.go +++ b/main.go @@ -44,6 +44,7 @@ func main() { &cmd.CmdTrackedTimes, &cmd.CmdOpen, &cmd.CmdNotifications, + &cmd.CmdMilestones, } app.EnableBashCompletion = true err := app.Run(os.Args)