77 "encoding/base64"
88 "errors"
99 "fmt"
10+ "io"
1011 "io/ioutil"
1112 "strconv"
1213 "strings"
@@ -48,6 +49,9 @@ func NewApplier(client *github.Client, repo Repository, c *github.Commit) *Appli
4849// Apply applies the changes in a file, adds the result to the list of pending
4950// tree entries, and returns the entry. If the application succeeds, Apply
5051// creates a blob in the repository with the modified content.
52+ //
53+ // If the apply fails due to a conflict, Apply returns an error of type
54+ // *Conflict.
5155func (a * Applier ) Apply (ctx context.Context , f * gitdiff.File ) (* github.TreeEntry , error ) {
5256 // TODO(bkeyes): validate file to make sure fields are consistent
5357 // maybe two modes: validate and fix, where fix tries to set
@@ -91,10 +95,10 @@ func (a *Applier) applyCreate(ctx context.Context, f *gitdiff.File) (*github.Tre
9195 return nil , err
9296 }
9397 if exists {
94- return nil , errors . New ( "existing entry for new file" )
98+ return nil , & Conflict { Type : ConflictNewFileExists , File : f . NewName }
9599 }
96100
97- c , err := base64Apply (nil , f )
101+ c , err := base64Apply (nil , f . NewName , f )
98102 if err != nil {
99103 return nil , err
100104 }
@@ -117,17 +121,17 @@ func (a *Applier) applyDelete(ctx context.Context, f *gitdiff.File) (*github.Tre
117121 return nil , err
118122 }
119123 if ! exists {
120- // because the rest of application is strict, return an error if the
124+ // Because the rest of application is strict, return an error if the
121125 // file was already deleted, since it indicates a conflict of some kind
122- return nil , errors . New ( "missing entry for deleted file" )
126+ return nil , & Conflict { Type : ConflictDeletedFileMissing , File : f . OldName }
123127 }
124128
125129 data , _ , err := a .client .Git .GetBlobRaw (ctx , a .owner , a .repo , entry .GetSHA ())
126130 if err != nil {
127131 return nil , fmt .Errorf ("get blob content failed: %w" , err )
128132 }
129133
130- if err := gitdiff . Apply (ioutil .Discard , bytes .NewReader (data ), f ); err != nil {
134+ if err := apply (ioutil .Discard , bytes .NewReader (data ), f . OldName , f ); err != nil {
131135 return nil , err
132136 }
133137
@@ -147,7 +151,7 @@ func (a *Applier) applyModify(ctx context.Context, f *gitdiff.File) (*github.Tre
147151 return nil , err
148152 }
149153 if ! exists {
150- return nil , errors . New ( "no entry for modified file" )
154+ return nil , & Conflict { Type : ConflictModifiedFileMissing , File : f . OldName }
151155 }
152156
153157 path := f .NewName
@@ -163,7 +167,7 @@ func (a *Applier) applyModify(ctx context.Context, f *gitdiff.File) (*github.Tre
163167 return nil , fmt .Errorf ("get blob content failed: %w" , err )
164168 }
165169
166- c , err := base64Apply (data , f )
170+ c , err := base64Apply (data , f . OldName , f )
167171 if err != nil {
168172 return nil , err
169173 }
@@ -341,11 +345,13 @@ func findTreeEntry(t *github.Tree, name, entryType string) (*github.TreeEntry, b
341345 return nil , false
342346}
343347
344- func base64Apply (data []byte , f * gitdiff.File ) (string , error ) {
348+ // base64Apply applies the patch in f to data and returns the result as a
349+ // base64-encoded string.
350+ func base64Apply (data []byte , name string , f * gitdiff.File ) (string , error ) {
345351 var b bytes.Buffer
346352
347353 enc := base64 .NewEncoder (base64 .StdEncoding , & b )
348- if err := gitdiff . Apply (enc , bytes .NewReader (data ), f ); err != nil {
354+ if err := apply (enc , bytes .NewReader (data ), name , f ); err != nil {
349355 return "" , err
350356 }
351357 if err := enc .Close (); err != nil {
@@ -355,6 +361,24 @@ func base64Apply(data []byte, f *gitdiff.File) (string, error) {
355361 return b .String (), nil
356362}
357363
364+ // apply runs gitdiff.Apply, wrapping any conflicts in patch2pr's Conflict type.
365+ func apply (dst io.Writer , src io.ReaderAt , name string , f * gitdiff.File ) error {
366+ if err := gitdiff .Apply (dst , src , f ); err != nil {
367+ var applyErr * gitdiff.ApplyError
368+ var conflict * gitdiff.Conflict
369+ if errors .As (err , & applyErr ) && errors .As (err , & conflict ) {
370+ return & Conflict {
371+ Type : ConflictContent ,
372+ File : name ,
373+ Line : applyErr .Line ,
374+ cause : conflict ,
375+ }
376+ }
377+ return err
378+ }
379+ return nil
380+ }
381+
358382// TODO(bkeyes): extract this to go-gitdiff in some form?
359383func getMode (f * gitdiff.File , existing * github.TreeEntry ) string {
360384 switch {
0 commit comments