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 }