From e209d1451638cce1a5bc8380c53ff088aca20db4 Mon Sep 17 00:00:00 2001 From: benebsiny Date: Fri, 19 Jan 2024 23:35:18 +0800 Subject: [PATCH] Support for unlink --- pkg/cmd/project/link/link.go | 2 +- pkg/cmd/project/project.go | 2 + pkg/cmd/project/shared/queries/queries.go | 12 +- pkg/cmd/project/unlink/unlink.go | 143 ++++++++++++++++++++++ 4 files changed, 149 insertions(+), 10 deletions(-) create mode 100644 pkg/cmd/project/unlink/unlink.go diff --git a/pkg/cmd/project/link/link.go b/pkg/cmd/project/link/link.go index e59330958..47b26492c 100644 --- a/pkg/cmd/project/link/link.go +++ b/pkg/cmd/project/link/link.go @@ -138,6 +138,6 @@ func printResults(config linkConfig, repo queries.Repository) error { return nil } - _, err := fmt.Fprintf(config.io.Out, "%s\n", repo.Project.URL) + _, err := fmt.Fprintf(config.io.Out, "%s\n", repo.URL) return err } diff --git a/pkg/cmd/project/project.go b/pkg/cmd/project/project.go index 42d5df7c0..6dae4a897 100644 --- a/pkg/cmd/project/project.go +++ b/pkg/cmd/project/project.go @@ -19,6 +19,7 @@ import ( cmdLink "github.com/cli/cli/v2/pkg/cmd/project/link" cmdList "github.com/cli/cli/v2/pkg/cmd/project/list" cmdTemplate "github.com/cli/cli/v2/pkg/cmd/project/mark-template" + cmdUnlink "github.com/cli/cli/v2/pkg/cmd/project/unlink" cmdView "github.com/cli/cli/v2/pkg/cmd/project/view" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/spf13/cobra" @@ -47,6 +48,7 @@ func NewCmdProject(f *cmdutil.Factory) *cobra.Command { cmd.AddCommand(cmdLink.NewCmdLink(f, nil)) cmd.AddCommand(cmdView.NewCmdView(f, nil)) cmd.AddCommand(cmdTemplate.NewCmdMarkTemplate(f, nil)) + cmd.AddCommand(cmdUnlink.NewCmdUnlink(f, nil)) // items cmd.AddCommand(cmdItemList.NewCmdList(f, nil)) diff --git a/pkg/cmd/project/shared/queries/queries.go b/pkg/cmd/project/shared/queries/queries.go index 02b4ceb4e..8bfde6bfa 100644 --- a/pkg/cmd/project/shared/queries/queries.go +++ b/pkg/cmd/project/shared/queries/queries.go @@ -104,15 +104,9 @@ type PageInfo struct { // Repository is a Repository GraphQL object https://docs.github.com/en/graphql/reference/objects#repository type Repository struct { - Name string - ID string - URL string - Project struct { - Title string - ID string - Number int32 - URL string - } `graphql:"projectV2(number: $projectNumber)"` + Name string + ID string + URL string } // Project is a ProjectV2 GraphQL object https://docs.github.com/en/graphql/reference/objects#projectv2. diff --git a/pkg/cmd/project/unlink/unlink.go b/pkg/cmd/project/unlink/unlink.go new file mode 100644 index 000000000..bf014fec2 --- /dev/null +++ b/pkg/cmd/project/unlink/unlink.go @@ -0,0 +1,143 @@ +package unlink + +import ( + "fmt" + "github.com/MakeNowJust/heredoc" + "github.com/cli/cli/v2/api" + "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/shurcooL/githubv4" + "github.com/spf13/cobra" + "net/http" + "strconv" +) + +type unlinkOpts struct { + number int32 + owner string + repo string + projectID string + repoID string + format string + exporter cmdutil.Exporter +} + +type unlinkConfig struct { + httpClient func() (*http.Client, error) + client *queries.Client + opts unlinkOpts + io *iostreams.IOStreams +} + +type unlinkProjectFromRepoMutation struct { + UnlinkProjectV2FromRepository struct { + Repository queries.Repository `graphql:"repository"` + } `graphql:"unlinkProjectV2FromRepository(input:$input)"` +} + +func NewCmdUnlink(f *cmdutil.Factory, runF func(config unlinkConfig) error) *cobra.Command { + opts := unlinkOpts{} + linkCmd := &cobra.Command{ + Short: "Unlink a project from a repository", + Use: "unlink [] [flag]", + Example: heredoc.Doc(` + # unlink monalisa's project "1" from her repository "my_repo" + gh project unlink 1 --repo my_repo + `), + 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) + } + + config := unlinkConfig{ + httpClient: f.HttpClient, + client: client, + opts: opts, + io: f.IOStreams, + } + + if config.opts.repo == "" { + return fmt.Errorf("required flag: repo") + } + + // allow testing of the command without actually running it + if runF != nil { + return runF(config) + } + return runUnlink(config) + }, + } + + 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") + cmdutil.AddFormatFlags(linkCmd, &opts.exporter) + + return linkCmd +} + +func runUnlink(config unlinkConfig) error { + canPrompt := config.io.CanPrompt() + owner, err := config.client.NewOwner(canPrompt, config.opts.owner) + if err != nil { + return err + } + + project, err := config.client.NewProject(canPrompt, owner, config.opts.number, false) + if err != nil { + return err + } + config.opts.projectID = project.ID + + httpClient, err := config.httpClient() + if err != nil { + return err + } + c := api.NewClientFromHTTP(httpClient) + + repo, err := api.GitHubRepo(c, ghrepo.New(owner.Login, config.opts.repo)) + if err != nil { + return err + } + config.opts.repoID = repo.ID + + query, variable := unlinkRepoArgs(config) + err = config.client.Mutate("UnlinkProjectV2FromRepository", query, variable) + if err != nil { + return err + } + + if config.opts.exporter != nil { + return config.opts.exporter.Write(config.io, query.UnlinkProjectV2FromRepository.Repository) + } + return printResults(config, query.UnlinkProjectV2FromRepository.Repository) +} + +func unlinkRepoArgs(config unlinkConfig) (*unlinkProjectFromRepoMutation, map[string]interface{}) { + return &unlinkProjectFromRepoMutation{}, map[string]interface{}{ + "input": githubv4.UnlinkProjectV2FromRepositoryInput{ + ProjectID: githubv4.ID(config.opts.projectID), + RepositoryID: githubv4.ID(config.opts.repoID), + }, + } +} + +func printResults(config unlinkConfig, repo queries.Repository) error { + if !config.io.IsStdoutTTY() { + return nil + } + + _, err := fmt.Fprintf(config.io.Out, "%s\n", repo.URL) + return err +}