github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/pr/diff/diff.go (about)

     1  package diff
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"strings"
    10  	"syscall"
    11  
    12  	"github.com/MakeNowJust/heredoc"
    13  	"github.com/cli/cli/api"
    14  	"github.com/cli/cli/pkg/cmd/pr/shared"
    15  	"github.com/cli/cli/pkg/cmdutil"
    16  	"github.com/cli/cli/pkg/iostreams"
    17  	"github.com/spf13/cobra"
    18  )
    19  
    20  type DiffOptions struct {
    21  	HttpClient func() (*http.Client, error)
    22  	IO         *iostreams.IOStreams
    23  
    24  	Finder shared.PRFinder
    25  
    26  	SelectorArg string
    27  	UseColor    string
    28  }
    29  
    30  func NewCmdDiff(f *cmdutil.Factory, runF func(*DiffOptions) error) *cobra.Command {
    31  	opts := &DiffOptions{
    32  		IO:         f.IOStreams,
    33  		HttpClient: f.HttpClient,
    34  	}
    35  
    36  	cmd := &cobra.Command{
    37  		Use:   "diff [<number> | <url> | <branch>]",
    38  		Short: "View changes in a pull request",
    39  		Long: heredoc.Doc(`
    40  			View changes in a pull request. 
    41  
    42  			Without an argument, the pull request that belongs to the current branch
    43  			is selected.			
    44  		`),
    45  		Args: cobra.MaximumNArgs(1),
    46  		RunE: func(cmd *cobra.Command, args []string) error {
    47  			opts.Finder = shared.NewFinder(f)
    48  
    49  			if repoOverride, _ := cmd.Flags().GetString("repo"); repoOverride != "" && len(args) == 0 {
    50  				return &cmdutil.FlagError{Err: errors.New("argument required when using the --repo flag")}
    51  			}
    52  
    53  			if len(args) > 0 {
    54  				opts.SelectorArg = args[0]
    55  			}
    56  
    57  			if !validColorFlag(opts.UseColor) {
    58  				return &cmdutil.FlagError{Err: fmt.Errorf("did not understand color: %q. Expected one of always, never, or auto", opts.UseColor)}
    59  			}
    60  
    61  			if opts.UseColor == "auto" && !opts.IO.IsStdoutTTY() {
    62  				opts.UseColor = "never"
    63  			}
    64  
    65  			if runF != nil {
    66  				return runF(opts)
    67  			}
    68  			return diffRun(opts)
    69  		},
    70  	}
    71  
    72  	cmd.Flags().StringVar(&opts.UseColor, "color", "auto", "Use color in diff output: {always|never|auto}")
    73  
    74  	return cmd
    75  }
    76  
    77  func diffRun(opts *DiffOptions) error {
    78  	findOptions := shared.FindOptions{
    79  		Selector: opts.SelectorArg,
    80  		Fields:   []string{"number"},
    81  	}
    82  	pr, baseRepo, err := opts.Finder.Find(findOptions)
    83  	if err != nil {
    84  		return err
    85  	}
    86  
    87  	httpClient, err := opts.HttpClient()
    88  	if err != nil {
    89  		return err
    90  	}
    91  	apiClient := api.NewClientFromHTTP(httpClient)
    92  
    93  	diff, err := apiClient.PullRequestDiff(baseRepo, pr.Number)
    94  	if err != nil {
    95  		return fmt.Errorf("could not find pull request diff: %w", err)
    96  	}
    97  	defer diff.Close()
    98  
    99  	err = opts.IO.StartPager()
   100  	if err != nil {
   101  		return err
   102  	}
   103  	defer opts.IO.StopPager()
   104  
   105  	if opts.UseColor == "never" {
   106  		_, err = io.Copy(opts.IO.Out, diff)
   107  		if errors.Is(err, syscall.EPIPE) {
   108  			return nil
   109  		}
   110  		return err
   111  	}
   112  
   113  	diffLines := bufio.NewScanner(diff)
   114  	for diffLines.Scan() {
   115  		diffLine := diffLines.Text()
   116  		switch {
   117  		case isHeaderLine(diffLine):
   118  			fmt.Fprintf(opts.IO.Out, "\x1b[1;38m%s\x1b[m\n", diffLine)
   119  		case isAdditionLine(diffLine):
   120  			fmt.Fprintf(opts.IO.Out, "\x1b[32m%s\x1b[m\n", diffLine)
   121  		case isRemovalLine(diffLine):
   122  			fmt.Fprintf(opts.IO.Out, "\x1b[31m%s\x1b[m\n", diffLine)
   123  		default:
   124  			fmt.Fprintln(opts.IO.Out, diffLine)
   125  		}
   126  	}
   127  
   128  	if err := diffLines.Err(); err != nil {
   129  		return fmt.Errorf("error reading pull request diff: %w", err)
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  var diffHeaderPrefixes = []string{"+++", "---", "diff", "index"}
   136  
   137  func isHeaderLine(dl string) bool {
   138  	for _, p := range diffHeaderPrefixes {
   139  		if strings.HasPrefix(dl, p) {
   140  			return true
   141  		}
   142  	}
   143  	return false
   144  }
   145  
   146  func isAdditionLine(dl string) bool {
   147  	return strings.HasPrefix(dl, "+")
   148  }
   149  
   150  func isRemovalLine(dl string) bool {
   151  	return strings.HasPrefix(dl, "-")
   152  }
   153  
   154  func validColorFlag(c string) bool {
   155  	return c == "auto" || c == "always" || c == "never"
   156  }