diff --git a/commands/mr/approvers/mr_approvers.go b/commands/mr/approvers/mr_approvers.go index 20e4a9f2..d903a851 100644 --- a/commands/mr/approvers/mr_approvers.go +++ b/commands/mr/approvers/mr_approvers.go @@ -16,7 +16,7 @@ import ( func NewCmdApprovers(f *cmdutils.Factory) *cobra.Command { var mrApproversCmd = &cobra.Command{ - Use: "approvers [flags]", + Use: "approvers [ | ] [flags]", Short: `List merge request eligible approvers`, Long: ``, Aliases: []string{}, diff --git a/commands/mr/checkout/mr_checkout.go b/commands/mr/checkout/mr_checkout.go index 00b659d3..0ee87c8c 100644 --- a/commands/mr/checkout/mr_checkout.go +++ b/commands/mr/checkout/mr_checkout.go @@ -24,7 +24,7 @@ var ( func NewCmdCheckout(f *cmdutils.Factory) *cobra.Command { var mrCheckoutCmd = &cobra.Command{ - Use: "checkout ", + Use: "checkout ", Short: "Checkout to an open merge request", Long: ``, Args: cobra.ExactArgs(1), diff --git a/commands/mr/close/mr_close.go b/commands/mr/close/mr_close.go index b6edd621..4e626a37 100644 --- a/commands/mr/close/mr_close.go +++ b/commands/mr/close/mr_close.go @@ -15,7 +15,7 @@ import ( func NewCmdClose(f *cmdutils.Factory) *cobra.Command { var mrCloseCmd = &cobra.Command{ - Use: "close ", + Use: "close [ | ]", Short: `Close merge requests`, Long: ``, Args: cobra.MaximumNArgs(1), diff --git a/commands/mr/delete/mr_delete.go b/commands/mr/delete/mr_delete.go index 0e1f0ca8..a453522c 100644 --- a/commands/mr/delete/mr_delete.go +++ b/commands/mr/delete/mr_delete.go @@ -13,7 +13,7 @@ import ( func NewCmdDelete(f *cmdutils.Factory) *cobra.Command { var mrDeleteCmd = &cobra.Command{ - Use: "delete ", + Use: "delete [ | ]", Short: `Delete merge requests`, Long: ``, Aliases: []string{"del"}, diff --git a/commands/mr/delete/mr_delete_test.go b/commands/mr/delete/mr_delete_test.go index 0370acf9..069b996e 100644 --- a/commands/mr/delete/mr_delete_test.go +++ b/commands/mr/delete/mr_delete_test.go @@ -103,7 +103,7 @@ hosts: name: "delete no args", wantErr: true, assertFunc: func(t *testing.T, out string) { - assert.Contains(t, out, "no open merge request availabe for \"master\"") + assert.Contains(t, out, "no open merge request available for \"master\"") }, }, } diff --git a/commands/mr/diff/diff.go b/commands/mr/diff/diff.go new file mode 100644 index 00000000..d6b48111 --- /dev/null +++ b/commands/mr/diff/diff.go @@ -0,0 +1,164 @@ +// adapted from https://github.com/cli/cli/blob/trunk/pkg/cmd/pr/diff/diff.go +package diff + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "strings" + "syscall" + + "github.com/profclems/glab/commands/cmdutils" + "github.com/profclems/glab/commands/mr/mrutils" + "github.com/profclems/glab/internal/utils" + "github.com/xanzy/go-gitlab" + + "github.com/spf13/cobra" +) + +type DiffOptions struct { + factory *cmdutils.Factory + IO *utils.IOStreams + + Args []string + UseColor string +} + +func NewCmdDiff(f *cmdutils.Factory, runF func(*DiffOptions) error) *cobra.Command { + opts := &DiffOptions{ + factory: f, + IO: f.IO, + } + + cmd := &cobra.Command{ + Use: "diff [ | ]", + Short: "View changes in a merge request", + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + + if repoOverride, _ := cmd.Flags().GetString("repo"); repoOverride != "" && len(args) == 0 { + return &cmdutils.FlagError{Err: errors.New("argument required when using the --repo flag")} + } + + if len(args) > 0 { + opts.Args = args + } + + if !validColorFlag(opts.UseColor) { + return &cmdutils.FlagError{Err: fmt.Errorf("did not understand color: %q. Expected one of always, never, or auto", opts.UseColor)} + } + + if opts.UseColor == "auto" && !opts.IO.IsaTTY { + opts.UseColor = "never" + } + + if runF != nil { + return runF(opts) + } + return diffRun(opts) + }, + } + + cmd.Flags().StringVar(&opts.UseColor, "color", "auto", "Use color in diff output: {always|never|auto}") + + return cmd +} + +func diffRun(opts *DiffOptions) error { + apiClient, err := opts.factory.HttpClient() + if err != nil { + return err + } + mr, baseRepo, err := mrutils.MRFromArgs(opts.factory, opts.Args) + if err != nil { + return err + } + + diffs, _, err := apiClient.MergeRequests.GetMergeRequestDiffVersions(baseRepo.FullName(), mr.IID, &gitlab.GetMergeRequestDiffVersionsOptions{}) + if err != nil { + return fmt.Errorf("could not find merge request diffs: %w", err) + } + + diffOut := &bytes.Buffer{} + for _, diff := range diffs { + // the diffs are not included in the GetMergeRequestDiffVersions so we query for each diff version + diffVersion, _, err := apiClient.MergeRequests.GetSingleMergeRequestDiffVersion(baseRepo.FullName(), mr.IID, diff.ID) + if err != nil { + return fmt.Errorf("could not find merge request diff: %w", err) + } + for _, diffLine := range diffVersion.Diffs { + if diffLine.RenamedFile { + diffOut.WriteString("-" + diffLine.OldPath + "\n") + } + if diffLine.NewFile || diffLine.RenamedFile { + diffOut.WriteString("+" + diffLine.NewPath + "\n") + } else { + diffOut.WriteString(diffLine.OldPath + "\n") + } + + diffOut.WriteString(diffLine.Diff) + } + } + + defer diffOut.Reset() + + err = opts.IO.StartPager() + if err != nil { + return err + } + defer opts.IO.StopPager() + + if opts.UseColor == "never" { + _, err = io.Copy(opts.IO.StdOut, diffOut) + if errors.Is(err, syscall.EPIPE) { + return nil + } + return err + } + + diffLines := bufio.NewScanner(diffOut) + for diffLines.Scan() { + diffLine := diffLines.Text() + switch { + case isHeaderLine(diffLine): + fmt.Fprintf(opts.IO.StdOut, "\x1b[1;38m%s\x1b[m\n", diffLine) + case isAdditionLine(diffLine): + fmt.Fprintf(opts.IO.StdOut, "\x1b[32m%s\x1b[m\n", diffLine) + case isRemovalLine(diffLine): + fmt.Fprintf(opts.IO.StdOut, "\x1b[31m%s\x1b[m\n", diffLine) + default: + fmt.Fprintln(opts.IO.StdOut, diffLine) + } + } + + if err := diffLines.Err(); err != nil { + return fmt.Errorf("error reading merge request diff: %w", err) + } + + return nil +} + +var diffHeaderPrefixes = []string{"+++", "---", "diff", "index"} + +func isHeaderLine(dl string) bool { + for _, p := range diffHeaderPrefixes { + if strings.HasPrefix(dl, p) { + return true + } + } + return false +} + +func isAdditionLine(dl string) bool { + return strings.HasPrefix(dl, "+") +} + +func isRemovalLine(dl string) bool { + return strings.HasPrefix(dl, "-") +} + +func validColorFlag(c string) bool { + return c == "auto" || c == "always" || c == "never" +} diff --git a/commands/mr/diff/diff_test.go b/commands/mr/diff/diff_test.go new file mode 100644 index 00000000..1b5eef99 --- /dev/null +++ b/commands/mr/diff/diff_test.go @@ -0,0 +1,336 @@ +package diff + +import ( + "bytes" + "errors" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/profclems/glab/pkg/api" + "github.com/xanzy/go-gitlab" + + "github.com/google/shlex" + "github.com/jarcoal/httpmock" + "github.com/profclems/glab/commands/cmdutils" + "github.com/profclems/glab/internal/config" + "github.com/profclems/glab/internal/git" + "github.com/profclems/glab/internal/glrepo" + "github.com/profclems/glab/internal/utils" + "github.com/profclems/glab/test" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_NewCmdDiff(t *testing.T) { + tests := []struct { + name string + args string + isTTY bool + want DiffOptions + wantErr string + }{ + { + name: "number argument", + args: "123", + isTTY: true, + want: DiffOptions{ + Args: []string{"123"}, + UseColor: "auto", + }, + }, + { + name: "no argument", + args: "", + isTTY: true, + want: DiffOptions{ + UseColor: "auto", + }, + }, + { + name: "no color when redirected", + args: "", + isTTY: false, + want: DiffOptions{ + UseColor: "never", + }, + }, + { + name: "no argument with --repo override", + args: "-R owner/repo", + isTTY: true, + wantErr: "argument required when using the --repo flag", + }, + { + name: "invalid --color argument", + args: "--color doublerainbow", + isTTY: true, + wantErr: `did not understand color: "doublerainbow". Expected one of always, never, or auto`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + io, _, _, _ := utils.IOTest() + io.IsaTTY = tt.isTTY + io.IsInTTY = tt.isTTY + io.IsErrTTY = tt.isTTY + + f := &cmdutils.Factory{ + IO: io, + } + + var opts *DiffOptions + cmd := NewCmdDiff(f, func(o *DiffOptions) error { + opts = o + return nil + }) + cmd.PersistentFlags().StringP("repo", "R", "", "") + + argv, err := shlex.Split(tt.args) + require.NoError(t, err) + cmd.SetArgs(argv) + + cmd.SetIn(&bytes.Buffer{}) + cmd.SetOut(ioutil.Discard) + cmd.SetErr(ioutil.Discard) + + _, err = cmd.ExecuteC() + if tt.wantErr != "" { + require.EqualError(t, err, tt.wantErr) + return + } else { + require.NoError(t, err) + } + + assert.Equal(t, tt.want.Args, opts.Args) + assert.Equal(t, tt.want.UseColor, opts.UseColor) + }) + } +} + +func runCommand(remotes glrepo.Remotes, isTTY bool, cli string) (*test.CmdOut, error) { + io, _, stdout, stderr := utils.IOTest() + io.IsaTTY = isTTY + io.IsInTTY = isTTY + io.IsErrTTY = isTTY + + factory := &cmdutils.Factory{ + IO: io, + Config: func() (config.Config, error) { + return config.NewBlankConfig(), nil + }, + HttpClient: func() (*gitlab.Client, error) { + return api.TestClient(&http.Client{}, "xxxx", "gitlab.com") + }, + BaseRepo: func() (glrepo.Interface, error) { + return glrepo.New("OWNER", "REPO"), nil + }, + Remotes: func() (glrepo.Remotes, error) { + if remotes == nil { + return glrepo.Remotes{ + { + Remote: &git.Remote{Name: "origin"}, + Repo: glrepo.New("OWNER", "REPO"), + }, + }, nil + } + + return remotes, nil + }, + Branch: func() (string, error) { + return "feature", nil + }, + } + + cmd := NewCmdDiff(factory, nil) + + argv, err := shlex.Split(cli) + if err != nil { + return nil, err + } + cmd.SetArgs(argv) + + cmd.SetIn(&bytes.Buffer{}) + cmd.SetOut(ioutil.Discard) + cmd.SetErr(ioutil.Discard) + + _, err = cmd.ExecuteC() + return &test.CmdOut{ + OutBuf: stdout, + ErrBuf: stderr, + }, err +} + +func TestPRDiff_no_current_mr(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + httpmock.RegisterResponder("GET", `https://gitlab.com/api/v4/projects/OWNER%2FREPO/merge_requests`, + func(req *http.Request) (*http.Response, error) { + return httpmock.NewStringResponse(200, `[]`), nil + }, + ) + _, err := runCommand(nil, false, "") + if err == nil { + t.Fatal("expected error") + } + assert.Equal(t, `no open merge request available for "feature"`, err.Error()) +} + +func TestMRDiff_argument_not_found(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + httpmock.RegisterResponder("GET", `https://gitlab.com/api/v4/projects/OWNER%2FREPO/merge_requests/123`, + func(req *http.Request) (*http.Response, error) { + return httpmock.NewStringResponse(200, `{ + "id": 123, + "iid": 123, + "project_id": 3, + "title": "test1", + "description": "fixed login page css paddings", + "state": "merged"}`), nil + }, + ) + + httpmock.RegisterResponder("GET", `https://gitlab.com/api/v4/projects/OWNER%2FREPO/merge_requests/123/versions`, + func(req *http.Request) (*http.Response, error) { + return httpmock.NewStringResponse(404, ""), errors.New("404 not found") + }, + ) + + _, err := runCommand(nil, false, "123") + if err == nil { + t.Fatal("expected error", err) + } + assert.Equal(t, `could not find merge request diffs: Get "https://gitlab.com/api/v4/projects/OWNER%2FREPO/merge_requests/123/versions": 404 not found`, err.Error()) +} + +func TestMRDiff_notty(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + httpmock.RegisterResponder("GET", `https://gitlab.com/api/v4/projects/OWNER%2FREPO/merge_requests`, + func(req *http.Request) (*http.Response, error) { + return httpmock.NewStringResponse(200, `[{ + "id": 123, + "iid": 123, + "project_id": 3, + "title": "test1", + "description": "fixed login page css paddings", + "state": "merged"}]`), nil + }, + ) + testDiff := DiffTest() + output, err := runCommand(nil, false, "") + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if diff := strings.Contains(testDiff, output.String()); diff { + t.Errorf("command output did not match:\n%v", diff) + } +} + +func TestMRDiff_tty(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + httpmock.RegisterResponder("GET", `https://gitlab.com/api/v4/projects/OWNER%2FREPO/merge_requests`, + func(req *http.Request) (*http.Response, error) { + return httpmock.NewStringResponse(200, `[{ + "id": 123, + "iid": 123, + "project_id": 3, + "title": "test1", + "description": "fixed login page css paddings", + "state": "merged"}]`), nil + }, + ) + + httpmock.RegisterResponder("GET", `https://gitlab.com/api/v4/projects/OWNER%2FREPO/merge_requests/123`, + func(req *http.Request) (*http.Response, error) { + return httpmock.NewStringResponse(200, `{ + "id": 123, + "iid": 123, + "project_id": 3, + "title": "test1", + "description": "fixed login page css paddings", + "state": "merged"}`), nil + }, + ) + + DiffTest() + output, err := runCommand(nil, true, "") + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + assert.Contains(t, output.String(), "\x1b[m\n\x1b[32m+FITNESS") +} + +func DiffTest() string { + httpmock.RegisterResponder("GET", `https://gitlab.com/api/v4/projects/OWNER%2FREPO/merge_requests/123/versions`, + func(req *http.Request) (*http.Response, error) { + return httpmock.NewStringResponse(200, `[{ + "id": 110, + "head_commit_sha": "33e2ee8579fda5bc36accc9c6fbd0b4fefda9e30", + "base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "start_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "created_at": "2016-07-26T14:44:48.926Z", + "merge_request_id": 105, + "state": "collected", + "real_size": "1" +}]`), nil + }, + ) + httpmock.RegisterResponder("GET", `https://gitlab.com/api/v4/projects/OWNER%2FREPO/merge_requests/123/versions/110`, + func(req *http.Request) (*http.Response, error) { + return httpmock.NewStringResponse(200, `{ + "id": 110, + "head_commit_sha": "33e2ee8579fda5bc36accc9c6fbd0b4fefda9e30", + "base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "start_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "created_at": "2016-07-26T14:44:48.926Z", + "merge_request_id": 105, + "state": "collected", + "real_size": "1", + "commits": [{ + "id": "33e2ee8579fda5bc36accc9c6fbd0b4fefda9e30", + "short_id": "33e2ee85", + "title": "Change year to 2018", + "author_name": "Administrator", + "author_email": "admin@example.com", + "created_at": "2016-07-26T17:44:29.000+03:00", + "message": "Change year to 2018" + }, { + "id": "aa24655de48b36335556ac8a3cd8bb521f977cbd", + "short_id": "aa24655d", + "title": "Update LICENSE", + "author_name": "Administrator", + "author_email": "admin@example.com", + "created_at": "2016-07-25T17:21:53.000+03:00", + "message": "Update LICENSE" + }, { + "id": "3eed087b29835c48015768f839d76e5ea8f07a24", + "short_id": "3eed087b", + "title": "Add license", + "author_name": "Administrator", + "author_email": "admin@example.com", + "created_at": "2016-07-25T17:21:20.000+03:00", + "message": "Add license" + }], + "diffs": [{ + "old_path": "LICENSE.md", + "new_path": "LICENSE", + "a_mode": "0", + "b_mode": "100644", + "diff": "--- /dev/null\n+++ b/LICENSE\n@@ -0,0 +1,21 @@\n+The MIT License (MIT)\n+\n+Copyright (c) 2018 Administrator\n+\n+Permission is hereby granted, free of charge, to any person obtaining a copy\n+of this software and associated documentation files (the \"Software\"), to deal\n+in the Software without restriction, including without limitation the rights\n+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+copies of the Software, and to permit persons to whom the Software is\n+furnished to do so, subject to the following conditions:\n+\n+The above copyright notice and this permission notice shall be included in all\n+copies or substantial portions of the Software.\n+\n+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n+SOFTWARE.\n", + "new_file": true, + "renamed_file": true, + "deleted_file": false + }] +}`), nil + }, + ) + return "--- /dev/null\n+++ b/LICENSE\n@@ -0,0 +1,21 @@\n+The MIT License (MIT)\n+\n+Copyright (c) 2018 Administrator\n+\n+Permission is hereby granted, free of charge, to any person obtaining a copy\n+of this software and associated documentation files (the \"Software\"), to deal\n+in the Software without restriction, including without limitation the rights\n+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+copies of the Software, and to permit persons to whom the Software is\n+furnished to do so, subject to the following conditions:\n+\n+The above copyright notice and this permission notice shall be included in all\n+copies or substantial portions of the Software.\n+\n+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n+SOFTWARE.\n" +} diff --git a/commands/mr/issues/mr_issues.go b/commands/mr/issues/mr_issues.go index 93ce688a..23b5a876 100644 --- a/commands/mr/issues/mr_issues.go +++ b/commands/mr/issues/mr_issues.go @@ -15,7 +15,7 @@ import ( func NewCmdIssues(f *cmdutils.Factory) *cobra.Command { var mrIssuesCmd = &cobra.Command{ - Use: "issues ", + Use: "issues [ | ]", Short: `Get issues related to a particular merge request.`, Long: ``, Aliases: []string{"issue"}, diff --git a/commands/mr/mr.go b/commands/mr/mr.go index d4ce90b7..3851bbd5 100644 --- a/commands/mr/mr.go +++ b/commands/mr/mr.go @@ -8,6 +8,7 @@ import ( mrCloseCmd "github.com/profclems/glab/commands/mr/close" mrCreateCmd "github.com/profclems/glab/commands/mr/create" mrDeleteCmd "github.com/profclems/glab/commands/mr/delete" + mrDiffCmd "github.com/profclems/glab/commands/mr/diff" mrForCmd "github.com/profclems/glab/commands/mr/for" mrIssuesCmd "github.com/profclems/glab/commands/mr/issues" mrListCmd "github.com/profclems/glab/commands/mr/list" @@ -40,6 +41,7 @@ func NewCmdMR(f *cmdutils.Factory) *cobra.Command { mrCmd.AddCommand(mrCloseCmd.NewCmdClose(f)) mrCmd.AddCommand(mrCreateCmd.NewCmdCreate(f)) mrCmd.AddCommand(mrDeleteCmd.NewCmdDelete(f)) + mrCmd.AddCommand(mrDiffCmd.NewCmdDiff(f, nil)) mrCmd.AddCommand(mrForCmd.NewCmdFor(f)) mrCmd.AddCommand(mrIssuesCmd.NewCmdIssues(f)) mrCmd.AddCommand(mrListCmd.NewCmdList(f)) diff --git a/commands/mr/mrutils/mrutils.go b/commands/mr/mrutils/mrutils.go index 42a12f82..155307c4 100644 --- a/commands/mr/mrutils/mrutils.go +++ b/commands/mr/mrutils/mrutils.go @@ -160,7 +160,7 @@ func GetOpenMRForBranch(apiClient *gitlab.Client, baseRepo glrepo.Interface, cur return nil, fmt.Errorf("failed to get open merge request for %q: %w", currentBranch, err) } if len(mrs) == 0 { - return nil, fmt.Errorf("no open merge request availabe for %q", currentBranch) + return nil, fmt.Errorf("no open merge request available for %q", currentBranch) } // A single result is expected since gitlab does not allow multiple merge requests for a single source branch return mrs[0], nil diff --git a/commands/mr/note/mr_note_create.go b/commands/mr/note/mr_note_create.go index 4a5f9c80..6964aad0 100644 --- a/commands/mr/note/mr_note_create.go +++ b/commands/mr/note/mr_note_create.go @@ -15,7 +15,7 @@ import ( func NewCmdNote(f *cmdutils.Factory) *cobra.Command { var mrCreateNoteCmd = &cobra.Command{ - Use: "note ", + Use: "note [ | ]", Aliases: []string{"comment"}, Short: "Add a comment or note to merge request", Long: ``, diff --git a/commands/mr/rebase/mr_rebase.go b/commands/mr/rebase/mr_rebase.go index 6a65d242..dc03e8fc 100644 --- a/commands/mr/rebase/mr_rebase.go +++ b/commands/mr/rebase/mr_rebase.go @@ -14,7 +14,7 @@ import ( func NewCmdRebase(f *cmdutils.Factory) *cobra.Command { var mrRebaseCmd = &cobra.Command{ - Use: "rebase [flags]", + Use: "rebase [ | ] [flags]", Short: `Automatically rebase the source_branch of the merge request against its target_branch.`, Long: `If you don’t have permissions to push to the merge request’s source branch - you’ll get a 403 Forbidden response.`, Aliases: []string{"accept"}, diff --git a/commands/mr/reopen/mr_reopen.go b/commands/mr/reopen/mr_reopen.go index c0c21174..5b127ca7 100644 --- a/commands/mr/reopen/mr_reopen.go +++ b/commands/mr/reopen/mr_reopen.go @@ -14,7 +14,7 @@ import ( func NewCmdReopen(f *cmdutils.Factory) *cobra.Command { var mrReopenCmd = &cobra.Command{ - Use: "reopen ", + Use: "reopen [ | ]", Short: `Reopen merge requests`, Long: ``, Aliases: []string{"open"}, diff --git a/commands/mr/revoke/mr_revoke.go b/commands/mr/revoke/mr_revoke.go index ee4961ad..d98ce46d 100644 --- a/commands/mr/revoke/mr_revoke.go +++ b/commands/mr/revoke/mr_revoke.go @@ -13,7 +13,7 @@ import ( func NewCmdRevoke(f *cmdutils.Factory) *cobra.Command { var mrRevokeCmd = &cobra.Command{ - Use: "revoke ", + Use: "revoke [ | ]", Short: `Revoke approval on a merge request `, Long: ``, Aliases: []string{"unapprove"}, diff --git a/commands/mr/subscribe/mr_subscribe.go b/commands/mr/subscribe/mr_subscribe.go index 7524dfd9..986488fc 100644 --- a/commands/mr/subscribe/mr_subscribe.go +++ b/commands/mr/subscribe/mr_subscribe.go @@ -13,7 +13,7 @@ import ( func NewCmdSubscribe(f *cmdutils.Factory) *cobra.Command { var mrSubscribeCmd = &cobra.Command{ - Use: "subscribe ", + Use: "subscribe [ | ]", Short: `Subscribe to merge requests`, Long: ``, Aliases: []string{"sub"}, diff --git a/commands/mr/todo/mr_todo.go b/commands/mr/todo/mr_todo.go index f529134c..ae4e89a2 100644 --- a/commands/mr/todo/mr_todo.go +++ b/commands/mr/todo/mr_todo.go @@ -14,7 +14,7 @@ import ( func NewCmdTodo(f *cmdutils.Factory) *cobra.Command { var mrToDoCmd = &cobra.Command{ - Use: "todo ", + Use: "todo [ | ]", Aliases: []string{"add-todo"}, Short: "Add a ToDo to merge request", Long: ``, diff --git a/commands/mr/unsubscribe/mr_unsubscribe.go b/commands/mr/unsubscribe/mr_unsubscribe.go index 78b9a02f..efd12152 100644 --- a/commands/mr/unsubscribe/mr_unsubscribe.go +++ b/commands/mr/unsubscribe/mr_unsubscribe.go @@ -13,7 +13,7 @@ import ( func NewCmdUnsubscribe(f *cmdutils.Factory) *cobra.Command { var mrUnsubscribeCmd = &cobra.Command{ - Use: "unsubscribe ", + Use: "unsubscribe [ | ]", Short: `Unsubscribe from merge requests`, Long: ``, Aliases: []string{"unsub"}, diff --git a/commands/mr/update/mr_update.go b/commands/mr/update/mr_update.go index 339ae13d..077c0c56 100644 --- a/commands/mr/update/mr_update.go +++ b/commands/mr/update/mr_update.go @@ -16,7 +16,7 @@ import ( func NewCmdUpdate(f *cmdutils.Factory) *cobra.Command { var mrUpdateCmd = &cobra.Command{ - Use: "update ", + Use: "update [ | ]", Short: `Update merge requests`, Long: ``, Example: heredoc.Doc(` diff --git a/go.mod b/go.mod index 41e34231..4ce7fc3a 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/gosuri/uilive v0.0.4 github.com/gosuri/uitable v0.0.4 github.com/hashicorp/go-version v1.2.1 + github.com/jarcoal/httpmock v1.0.6 github.com/lunixbochs/vtclean v1.0.0 github.com/mattn/go-colorable v0.1.8 github.com/mattn/go-isatty v0.0.12 diff --git a/go.sum b/go.sum index b73f309e..da7ee010 100644 --- a/go.sum +++ b/go.sum @@ -114,6 +114,8 @@ github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jarcoal/httpmock v1.0.6 h1:e81vOSexXU3mJuJ4l//geOmKIt+Vkxerk1feQBC8D0g= +github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= diff --git a/pkg/api/client.go b/pkg/api/client.go index 3e6f7768..6519cc16 100644 --- a/pkg/api/client.go +++ b/pkg/api/client.go @@ -81,7 +81,6 @@ func gitlabClient(httpClient *http.Client, token, host string) (*gitlab.Client, return apiClient, nil } -// -//func TestClient(httpClient *http.Client, token, host string) (*gitlab.Client, error) { -// return gitlabClient(httpClient, token, host) -//} +func TestClient(httpClient *http.Client, token, host string) (*gitlab.Client, error) { + return gitlabClient(httpClient, token, host) +}