replace flag globals, require context for commands #291
63
FEATURE-COMPARISON.md
Normal file
63
FEATURE-COMPARISON.md
Normal file
|
@ -0,0 +1,63 @@
|
|||
# comparing git forge commandline interfaces
|
||||
|
||||
[tea]: https://gitea.com/gitea/tea
|
||||
[sip]: https://gitea.com/jolheiser/sip
|
||||
[gitlab]: https://github.com/makkes/gitlab-cli
|
||||
[glab]: https://github.com/profclems/glab
|
||||
[gh]: https://cli.github.com
|
||||
|
||||
last update: 2020-12-11
|
||||
|
||||
## general
|
||||
/ | [tea][tea] | [sip][sip] | [gitlab][gitlab] | [gh][gh]
|
||||
-----------------------|:-----:|:-----:|:-----:|:-----:
|
||||
forge|gitea|gitea|gitlab|github
|
||||
official forge support|✓|✘|✘|✓
|
||||
dev status|adding features|maintenance||
|
||||
platform|any|any|any|any
|
||||
|
||||
## philosophy
|
||||
/ | [tea][tea] | [sip][sip] | [gitlab][gitlab] | [gh][gh]
|
||||
-----------------------|:-----:|:-----:|:-----:|:-----:
|
||||
aims to replace git cli|✘|||✓
|
||||
works with decentralization in mind|✓|✓|✓|✘
|
||||
per-repo setup needed|✘||✓|✘
|
||||
workflow helpers|✓|||
|
||||
interactive mode |[(✓)](https://gitea.com/gitea/tea/issues?type=all&state=open&labels=&milestone=0&assignee=0&q=interactive)|✘| |✓
|
||||
programmatic mode|✓|||✓
|
||||
machine readable output|✓|||
|
||||
follows XDG spec|✓|||
|
||||
|
||||
## features
|
||||
/ | [tea][tea] | [sip][sip] | [gitlab][gitlab] | [gh][gh]
|
||||
-----------------------|:-----:|:-----:|:-----:|:-----:
|
||||
open web UI|✓|||
|
||||
search repos|✓|||
|
||||
search issues|✘|✓||
|
||||
textual item search filter syntax|✘|✓||
|
||||
CRUD repos|[(✓)](https://gitea.com/gitea/tea/issues/239)|||
|
||||
CRUD issues|[(✓)](https://gitea.com/gitea/tea/issues/229)|||
|
||||
CRUD milestones|[(✓)](https://gitea.com/gitea/tea/issues/246)|||
|
||||
CRUD releases|✓|||
|
||||
CRUD labels|✓|||
|
||||
CRUD PRs|✓|||
|
||||
CRUD time tracking|✓|||x
|
||||
CRUD orgs|[(✓)](https://gitea.com/gitea/tea/issues/287)|||
|
||||
create PRs from local repo|✓|||
|
||||
create PRs from remote repo|✓|||
|
||||
code review|[u](https://gitea.com/gitea/tea/issues/131)|||
|
||||
merge PRs||||
|
||||
read comments|[u](https://gitea.com/gitea/tea/issues/172)|||
|
||||
post comments||||
|
||||
manage CI|✘|✘|✓|
|
||||
manage notifications|[(✓)]()|||
|
||||
administration|[u](https://gitea.com/gitea/tea/issues/161)|✘||✘
|
||||
markdown rendering|✓|||✓
|
||||
issue import/export|[u](https://gitea.com/gitea/tea/issues/132)|||
|
||||
checkout PRs|✓|||
|
||||
|
||||
- ✓: supported
|
||||
- (✓): partial support
|
||||
- u: upcoming
|
||||
- ✘: not supported
|
||||
- ?: unknown
|
|
@ -9,6 +9,8 @@ It uses [code.gitea.io/sdk](https://code.gitea.io/sdk) and interacts with the Gi
|
|||
|
||||
![demo gif](https://dl.gitea.io/screenshots/tea_demo.gif)
|
||||
|
||||
For a feature comparison with other git forge CLIs see [here](./FEATURE-COMPARISON.md).
|
||||
|
||||
## Installation
|
||||
|
||||
You can use the prebuilt binaries from [dl.gitea.io](https://dl.gitea.io/tea/)
|
||||
|
|
|
@ -5,14 +5,11 @@
|
|||
package issues
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/context"
|
||||
"code.gitea.io/tea/modules/print"
|
||||
"code.gitea.io/tea/modules/interact"
|
||||
"code.gitea.io/tea/modules/task"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
@ -40,23 +37,15 @@ func runIssuesCreate(cmd *cli.Context) error {
|
|||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
|
||||
issue, _, err := ctx.Login.Client().CreateIssue(ctx.Owner, ctx.Repo, gitea.CreateIssueOption{
|
||||
Title: ctx.String("title"),
|
||||
Body: ctx.String("body"),
|
||||
// TODO:
|
||||
//Assignee string `json:"assignee"`
|
||||
//Assignees []string `json:"assignees"`
|
||||
//Deadline *time.Time `json:"due_date"`
|
||||
//Milestone int64 `json:"milestone"`
|
||||
//Labels []int64 `json:"labels"`
|
||||
//Closed bool `json:"closed"`
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
if ctx.NumFlags() == 0 {
|
||||
return interact.CreateIssue(ctx.Login, ctx.Owner, ctx.Repo)
|
||||
}
|
||||
|
||||
print.IssueDetails(issue)
|
||||
fmt.Println(issue.HTMLURL)
|
||||
return nil
|
||||
return task.CreateIssue(
|
||||
ctx.Login,
|
||||
ctx.Owner,
|
||||
ctx.Repo,
|
||||
ctx.String("title"),
|
||||
ctx.String("body"),
|
||||
)
|
||||
}
|
||||
|
|
43
modules/interact/issue_create.go
Normal file
43
modules/interact/issue_create.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
// 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 interact
|
||||
|
||||
import (
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/task"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
)
|
||||
|
||||
// CreateIssue interactively creates a PR
|
||||
func CreateIssue(login *config.Login, owner, repo string) error {
|
||||
var title, description string
|
||||
|
||||
// owner, repo
|
||||
owner, repo, err := promptRepoSlug(owner, repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// title
|
||||
promptOpts := survey.WithValidator(survey.Required)
|
||||
promptI := &survey.Input{Message: "Issue title:"}
|
||||
if err := survey.AskOne(promptI, &title, promptOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// description
|
||||
promptM := &survey.Multiline{Message: "Issue description:"}
|
||||
if err := survey.AskOne(promptM, &description); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return task.CreateIssue(
|
||||
login,
|
||||
owner,
|
||||
repo,
|
||||
title,
|
||||
description)
|
||||
}
|
|
@ -5,6 +5,9 @@
|
|||
package interact
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
)
|
||||
|
||||
|
@ -14,3 +17,43 @@ func PromptPassword(name string) (pass string, err error) {
|
|||
err = survey.AskOne(promptPW, &pass, survey.WithValidator(survey.Required))
|
||||
return
|
||||
}
|
||||
|
||||
// promptRepoSlug interactively prompts for a Gitea repository or returns the current one
|
||||
func promptRepoSlug(defaultOwner, defaultRepo string) (owner, repo string, err error) {
|
||||
prompt := "Target repo:"
|
||||
required := true
|
||||
if len(defaultOwner) != 0 && len(defaultRepo) != 0 {
|
||||
prompt = fmt.Sprintf("Target repo [%s/%s]:", defaultOwner, defaultRepo)
|
||||
required = false
|
||||
}
|
||||
var repoSlug string
|
||||
|
||||
owner = defaultOwner
|
||||
repo = defaultRepo
|
||||
|
||||
err = survey.AskOne(
|
||||
&survey.Input{Message: prompt},
|
||||
&repoSlug,
|
||||
survey.WithValidator(func(input interface{}) error {
|
||||
if str, ok := input.(string); ok {
|
||||
if !required && len(str) == 0 {
|
||||
return nil
|
||||
}
|
||||
split := strings.Split(str, "/")
|
||||
if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 {
|
||||
return fmt.Errorf("must follow the <owner>/<repo> syntax")
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("invalid result type")
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
|
||||
if err == nil && len(repoSlug) != 0 {
|
||||
repoSlugSplit := strings.Split(repoSlug, "/")
|
||||
owner = repoSlugSplit[0]
|
||||
repo = repoSlugSplit[1]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
package interact
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/git"
|
||||
"code.gitea.io/tea/modules/task"
|
||||
|
@ -92,42 +89,3 @@ func CreatePull(login *config.Login, owner, repo string) error {
|
|||
title,
|
||||
description)
|
||||
}
|
||||
|
||||
func promptRepoSlug(defaultOwner, defaultRepo string) (owner, repo string, err error) {
|
||||
prompt := "Target repo:"
|
||||
required := true
|
||||
if len(defaultOwner) != 0 && len(defaultRepo) != 0 {
|
||||
prompt = fmt.Sprintf("Target repo [%s/%s]:", defaultOwner, defaultRepo)
|
||||
required = false
|
||||
}
|
||||
var repoSlug string
|
||||
|
||||
owner = defaultOwner
|
||||
repo = defaultRepo
|
||||
|
||||
err = survey.AskOne(
|
||||
&survey.Input{Message: prompt},
|
||||
&repoSlug,
|
||||
survey.WithValidator(func(input interface{}) error {
|
||||
if str, ok := input.(string); ok {
|
||||
if !required && len(str) == 0 {
|
||||
return nil
|
||||
}
|
||||
split := strings.Split(str, "/")
|
||||
if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 {
|
||||
return fmt.Errorf("must follow the <owner>/<repo> syntax")
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("invalid result type")
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
|
||||
if err == nil && len(repoSlug) != 0 {
|
||||
repoSlugSplit := strings.Split(repoSlug, "/")
|
||||
owner = repoSlugSplit[0]
|
||||
repo = repoSlugSplit[1]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
45
modules/task/issue_create.go
Normal file
45
modules/task/issue_create.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
// 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 task
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/print"
|
||||
)
|
||||
|
||||
// CreateIssue creates a PR in the given repo and prints the result
|
||||
func CreateIssue(login *config.Login, repoOwner, repoName, title, description string) error {
|
||||
|
||||
// title is required
|
||||
if len(title) == 0 {
|
||||
return fmt.Errorf("Title is required")
|
||||
}
|
||||
|
||||
issue, _, err := login.Client().CreateIssue(repoOwner, repoName, gitea.CreateIssueOption{
|
||||
Title: title,
|
||||
Body: description,
|
||||
// TODO:
|
||||
//Assignee string `json:"assignee"`
|
||||
//Assignees []string `json:"assignees"`
|
||||
//Deadline *time.Time `json:"due_date"`
|
||||
//Milestone int64 `json:"milestone"`
|
||||
//Labels []int64 `json:"labels"`
|
||||
//Closed bool `json:"closed"`
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("could not create issue: %s", err)
|
||||
}
|
||||
|
||||
print.IssueDetails(issue)
|
||||
|
||||
fmt.Println(issue.HTMLURL)
|
||||
|
||||
return err
|
||||
}
|
Loading…
Reference in New Issue
Block a user