Skip to content
This repository has been archived by the owner on Nov 22, 2022. It is now read-only.

Commit

Permalink
feat: add mr diff command (#281)
Browse files Browse the repository at this point in the history
* feat: add mr diff command

* fix mr docs

* add tests

* linting

* fix test
  • Loading branch information
profclems committed Oct 26, 2020
1 parent 13789fe commit 69faba7
Show file tree
Hide file tree
Showing 21 changed files with 523 additions and 19 deletions.
2 changes: 1 addition & 1 deletion commands/mr/approvers/mr_approvers.go
Expand Up @@ -16,7 +16,7 @@ import (

func NewCmdApprovers(f *cmdutils.Factory) *cobra.Command {
var mrApproversCmd = &cobra.Command{
Use: "approvers <id> [flags]",
Use: "approvers [<id> | <branch>] [flags]",
Short: `List merge request eligible approvers`,
Long: ``,
Aliases: []string{},
Expand Down
2 changes: 1 addition & 1 deletion commands/mr/checkout/mr_checkout.go
Expand Up @@ -24,7 +24,7 @@ var (

func NewCmdCheckout(f *cmdutils.Factory) *cobra.Command {
var mrCheckoutCmd = &cobra.Command{
Use: "checkout <mr-id>",
Use: "checkout <id>",
Short: "Checkout to an open merge request",
Long: ``,
Args: cobra.ExactArgs(1),
Expand Down
2 changes: 1 addition & 1 deletion commands/mr/close/mr_close.go
Expand Up @@ -15,7 +15,7 @@ import (

func NewCmdClose(f *cmdutils.Factory) *cobra.Command {
var mrCloseCmd = &cobra.Command{
Use: "close <id>",
Use: "close [<id> | <branch>]",
Short: `Close merge requests`,
Long: ``,
Args: cobra.MaximumNArgs(1),
Expand Down
2 changes: 1 addition & 1 deletion commands/mr/delete/mr_delete.go
Expand Up @@ -13,7 +13,7 @@ import (

func NewCmdDelete(f *cmdutils.Factory) *cobra.Command {
var mrDeleteCmd = &cobra.Command{
Use: "delete <id>",
Use: "delete [<id> | <branch>]",
Short: `Delete merge requests`,
Long: ``,
Aliases: []string{"del"},
Expand Down
2 changes: 1 addition & 1 deletion commands/mr/delete/mr_delete_test.go
Expand Up @@ -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\"")
},
},
}
Expand Down
164 changes: 164 additions & 0 deletions 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 [<id> | <branch>]",
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"
}

0 comments on commit 69faba7

Please sign in to comment.