1package git23import (4 "context"5 "fmt"6 "io"7)89// DiffOptions contains optional arguments for parsing diff.10//11// Docs: https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---full-index12type DiffOptions struct {13 // The commit ID to used for computing diff between a range of commits (base,14 // revision]. When not set, only computes diff for a single commit at revision.15 Base string16 CommandOptions17}1819// Diff returns a parsed diff object between given commits of the repository.20func (r *Repository) Diff(ctx context.Context, rev string, maxFiles, maxFileLines, maxLineChars int, opts ...DiffOptions) (*Diff, error) {21 var opt DiffOptions22 if len(opts) > 0 {23 opt = opts[0]24 }2526 commit, err := r.CatFileCommit(ctx, rev)27 if err != nil {28 return nil, err29 }3031 var args []string32 if opt.Base == "" {33 // First commit of repository34 if commit.ParentsCount() == 0 {35 args = []string{"show", "--full-index", "--end-of-options", rev}36 } else {37 c, err := commit.Parent(ctx, 0)38 if err != nil {39 return nil, err40 }41 args = []string{"diff", "--full-index", "-M", c.ID.String(), "--end-of-options", rev}42 }43 } else {44 args = []string{"diff", "--full-index", "-M", opt.Base, "--end-of-options", rev}45 }4647 stdout, w := io.Pipe()48 done := make(chan SteamParseDiffResult)49 go StreamParseDiff(stdout, done, maxFiles, maxFileLines, maxLineChars)5051 err = pipe(ctx, r.path, args, opt.Envs, w)52 _ = w.Close() // Close writer to exit parsing goroutine53 if err != nil {54 return nil, err55 }5657 result := <-done58 return result.Diff, result.Err59}6061// RawDiffFormat is the format of a raw diff.62type RawDiffFormat string6364const (65 RawDiffNormal RawDiffFormat = "diff"66 RawDiffPatch RawDiffFormat = "patch"67)6869// RawDiffOptions contains optional arguments for dumping a raw diff.70//71// Docs: https://git-scm.com/docs/git-format-patch72type RawDiffOptions struct {73 CommandOptions74}7576// RawDiff dumps diff of repository in given revision directly to given77// io.Writer.78func (r *Repository) RawDiff(ctx context.Context, rev string, diffType RawDiffFormat, w io.Writer, opts ...RawDiffOptions) error {79 var opt RawDiffOptions80 if len(opts) > 0 {81 opt = opts[0]82 }8384 commit, err := r.CatFileCommit(ctx, rev) //nolint85 if err != nil {86 return err87 }8889 var args []string90 switch diffType {91 case RawDiffNormal:92 if commit.ParentsCount() == 0 {93 args = []string{"show", "--full-index", "--end-of-options", rev}94 } else {95 c, err := commit.Parent(ctx, 0)96 if err != nil {97 return err98 }99 args = []string{"diff", "--full-index", "-M", c.ID.String(), "--end-of-options", rev}100 }101 case RawDiffPatch:102 if commit.ParentsCount() == 0 {103 args = []string{"format-patch", "--full-index", "--no-signoff", "--no-signature", "--stdout", "--root", "--end-of-options", rev}104 } else {105 c, err := commit.Parent(ctx, 0)106 if err != nil {107 return err108 }109 args = []string{"format-patch", "--full-index", "--no-signoff", "--no-signature", "--stdout", "--end-of-options", rev + "..." + c.ID.String()}110 }111 default:112 return fmt.Errorf("invalid diffType: %s", diffType)113 }114115 if err = pipe(ctx, r.path, args, opt.Envs, w); err != nil {116 return err117 }118 return nil119}120121// DiffBinaryOptions contains optional arguments for producing binary patch.122type DiffBinaryOptions struct {123 CommandOptions124}125126// DiffBinary returns binary patch between base and head revisions that could be127// used for git-apply.128func (r *Repository) DiffBinary(ctx context.Context, base, head string, opts ...DiffBinaryOptions) ([]byte, error) {129 var opt DiffBinaryOptions130 if len(opts) > 0 {131 opt = opts[0]132 }133134 args := []string{"diff", "--full-index", "--binary", "--end-of-options", base, head}135 return exec(ctx, r.path, args, opt.Envs)136}