233 lines
5.9 KiB
Go
233 lines
5.9 KiB
Go
package unlink
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/MakeNowJust/heredoc"
|
|
"github.com/cli/cli/v2/api"
|
|
"github.com/cli/cli/v2/internal/gh"
|
|
"github.com/cli/cli/v2/internal/ghrepo"
|
|
"github.com/cli/cli/v2/pkg/cmd/project/shared/client"
|
|
"github.com/cli/cli/v2/pkg/cmd/project/shared/queries"
|
|
"github.com/cli/cli/v2/pkg/cmdutil"
|
|
"github.com/cli/cli/v2/pkg/iostreams"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
type unlinkOpts struct {
|
|
number int32
|
|
host string
|
|
owner string
|
|
repo string
|
|
team string
|
|
projectID string
|
|
projectTitle string
|
|
repoID string
|
|
teamID string
|
|
}
|
|
|
|
type unlinkConfig struct {
|
|
httpClient func() (*http.Client, error)
|
|
config func() (gh.Config, error)
|
|
client *queries.Client
|
|
opts unlinkOpts
|
|
io *iostreams.IOStreams
|
|
}
|
|
|
|
func NewCmdUnlink(f *cmdutil.Factory, runF func(config unlinkConfig) error) *cobra.Command {
|
|
opts := unlinkOpts{}
|
|
linkCmd := &cobra.Command{
|
|
Short: "Unlink a project from a repository or a team",
|
|
Use: "unlink [<number>]",
|
|
Example: heredoc.Doc(`
|
|
# Unlink monalisa's project 1 from her repository "my_repo"
|
|
$ gh project unlink 1 --owner monalisa --repo my_repo
|
|
|
|
# Unlink monalisa's organization's project 1 from her team "my_team"
|
|
$ gh project unlink 1 --owner my_organization --team my_team
|
|
|
|
# Unlink monalisa's project 1 from the repository of current directory if neither --repo nor --team is specified
|
|
$ gh project unlink 1
|
|
`),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
client, err := client.New(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(args) == 1 {
|
|
num, err := strconv.ParseInt(args[0], 10, 32)
|
|
if err != nil {
|
|
return cmdutil.FlagErrorf("invalid number: %v", args[0])
|
|
}
|
|
opts.number = int32(num)
|
|
}
|
|
|
|
if opts.repo == "" && opts.team == "" {
|
|
repo, err := f.BaseRepo()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
opts.repo = repo.RepoName()
|
|
if opts.owner == "" {
|
|
opts.owner = repo.RepoOwner()
|
|
}
|
|
}
|
|
|
|
if err := cmdutil.MutuallyExclusive("specify only one of `--repo` or `--team`", opts.repo != "", opts.team != ""); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = validateRepoOrTeamFlag(&opts); err != nil {
|
|
return err
|
|
}
|
|
|
|
config := unlinkConfig{
|
|
httpClient: f.HttpClient,
|
|
config: f.Config,
|
|
client: client,
|
|
opts: opts,
|
|
io: f.IOStreams,
|
|
}
|
|
|
|
// allow testing of the command without actually running it
|
|
if runF != nil {
|
|
return runF(config)
|
|
}
|
|
return runUnlink(config)
|
|
},
|
|
}
|
|
|
|
cmdutil.EnableRepoOverride(linkCmd, f)
|
|
linkCmd.Flags().StringVar(&opts.owner, "owner", "", "Login of the owner. Use \"@me\" for the current user.")
|
|
linkCmd.Flags().StringVarP(&opts.repo, "repo", "R", "", "The repository to be unlinked from this project")
|
|
linkCmd.Flags().StringVarP(&opts.team, "team", "T", "", "The team to be unlinked from this project")
|
|
|
|
return linkCmd
|
|
}
|
|
|
|
func validateRepoOrTeamFlag(opts *unlinkOpts) error {
|
|
|
|
unlinkedTarget := ""
|
|
if opts.repo != "" {
|
|
unlinkedTarget = opts.repo
|
|
} else if opts.team != "" {
|
|
unlinkedTarget = opts.team
|
|
}
|
|
|
|
if strings.Contains(unlinkedTarget, "/") {
|
|
nameArgs := strings.Split(unlinkedTarget, "/")
|
|
var host, owner, name string
|
|
|
|
if len(nameArgs) == 2 {
|
|
owner = nameArgs[0]
|
|
name = nameArgs[1]
|
|
} else if len(nameArgs) == 3 {
|
|
host = nameArgs[0]
|
|
owner = nameArgs[1]
|
|
name = nameArgs[2]
|
|
} else {
|
|
if opts.repo != "" {
|
|
return fmt.Errorf("expected the \"[HOST/]OWNER/REPO\" or \"REPO\" format, got \"%s\"", unlinkedTarget)
|
|
} else if opts.team != "" {
|
|
return fmt.Errorf("expected the \"[HOST/]OWNER/TEAM\" or \"TEAM\" format, got \"%s\"", unlinkedTarget)
|
|
}
|
|
}
|
|
|
|
if opts.owner != "" && opts.owner != owner {
|
|
return fmt.Errorf("'%s' has different owner from '%s'", unlinkedTarget, opts.owner)
|
|
}
|
|
|
|
opts.owner = owner
|
|
opts.host = host
|
|
if opts.repo != "" {
|
|
opts.repo = name
|
|
} else if opts.team != "" {
|
|
opts.team = name
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func runUnlink(config unlinkConfig) error {
|
|
canPrompt := config.io.CanPrompt()
|
|
owner, err := config.client.NewOwner(canPrompt, config.opts.owner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
config.opts.owner = owner.Login
|
|
|
|
project, err := config.client.NewProject(canPrompt, owner, config.opts.number, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
config.opts.projectTitle = project.Title
|
|
config.opts.projectID = project.ID
|
|
if config.opts.number == 0 {
|
|
config.opts.number = project.Number
|
|
}
|
|
|
|
httpClient, err := config.httpClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c := api.NewClientFromHTTP(httpClient)
|
|
|
|
if config.opts.host == "" {
|
|
cfg, err := config.config()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
host, _ := cfg.Authentication().DefaultHost()
|
|
config.opts.host = host
|
|
}
|
|
|
|
if config.opts.repo != "" {
|
|
return unlinkRepo(c, config)
|
|
} else if config.opts.team != "" {
|
|
return unlinkTeam(c, config)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func unlinkRepo(c *api.Client, config unlinkConfig) error {
|
|
repo, err := api.GitHubRepo(c, ghrepo.NewWithHost(config.opts.owner, config.opts.repo, config.opts.host))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
config.opts.repoID = repo.ID
|
|
|
|
err = config.client.UnlinkProjectFromRepository(config.opts.projectID, config.opts.repoID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return printResults(config, config.opts.repo)
|
|
}
|
|
|
|
func unlinkTeam(c *api.Client, config unlinkConfig) error {
|
|
team, err := api.OrganizationTeam(c, config.opts.host, config.opts.owner, config.opts.team)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
config.opts.teamID = team.ID
|
|
|
|
err = config.client.UnlinkProjectFromTeam(config.opts.projectID, config.opts.teamID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return printResults(config, config.opts.team)
|
|
}
|
|
|
|
func printResults(config unlinkConfig, linkedTarget string) error {
|
|
if !config.io.IsStdoutTTY() {
|
|
return nil
|
|
}
|
|
|
|
_, err := fmt.Fprintf(config.io.Out, "Unlinked '%s/%s' from project #%d '%s'\n", config.opts.owner, linkedTarget, config.opts.number, config.opts.projectTitle)
|
|
return err
|
|
}
|