@@ -28,7 +28,7 @@ type Applier struct {
28
28
commit * github.Commit
29
29
tree string
30
30
treeCache map [string ]* github.Tree
31
- entries [ ]* github.TreeEntry
31
+ entries map [ string ]* github.TreeEntry
32
32
uncommitted bool
33
33
}
34
34
@@ -55,30 +55,20 @@ func (a *Applier) Apply(ctx context.Context, f *gitdiff.File) (*github.TreeEntry
55
55
// in particular, we need IsNew, IsDelete, maybe IsCopy and IsRename to
56
56
// agree with the fragments and NewName/OldName
57
57
58
- // Because of the tree cache, files must be part of a remote (i.e. created)
59
- // tree before they are modifiable. This could be fixed by refactoring the
60
- // apply logic but doesn't seem like an onerous restriction.
61
- for _ , e := range a .entries {
62
- if e .GetPath () == f .NewName {
63
- return nil , errors .New ("cannot apply with pending changes to file" )
64
- }
65
- }
66
-
58
+ var entry * github.TreeEntry
67
59
var err error
68
60
switch {
69
61
case f .IsNew :
70
- err = a .applyCreate (ctx , f )
62
+ entry , err = a .applyCreate (ctx , f )
71
63
case f .IsDelete :
72
- err = a .applyDelete (ctx , f )
64
+ entry , err = a .applyDelete (ctx , f )
73
65
default :
74
- err = a .applyModify (ctx , f )
66
+ entry , err = a .applyModify (ctx , f )
75
67
}
76
68
if err != nil {
77
69
return nil , err
78
70
}
79
71
80
- entry := a .entries [len (a .entries )- 1 ]
81
-
82
72
if entry .Content != nil {
83
73
blob , _ , err := a .client .Git .CreateBlob (ctx , a .owner , a .repo , & github.Blob {
84
74
Content : entry .Content ,
@@ -94,103 +84,115 @@ func (a *Applier) Apply(ctx context.Context, f *gitdiff.File) (*github.TreeEntry
94
84
return entry , nil
95
85
}
96
86
97
- func (a * Applier ) applyCreate (ctx context.Context , f * gitdiff.File ) error {
87
+ func (a * Applier ) applyCreate (ctx context.Context , f * gitdiff.File ) ( * github. TreeEntry , error ) {
98
88
_ , exists , err := a .getEntry (ctx , f .NewName )
99
89
if err != nil {
100
- return err
90
+ return nil , err
101
91
}
102
92
if exists {
103
- return errors .New ("existing entry for new file" )
93
+ return nil , errors .New ("existing entry for new file" )
104
94
}
105
95
106
96
c , err := base64Apply (nil , f )
107
97
if err != nil {
108
- return err
98
+ return nil , err
109
99
}
110
100
111
- a .entries = append (a .entries , & github.TreeEntry {
112
- Path : github .String (f .NewName ),
101
+ path := f .NewName
102
+ newEntry := & github.TreeEntry {
103
+ Path : & path ,
113
104
Mode : github .String (getMode (f , nil )),
114
105
Type : github .String ("blob" ),
115
106
Content : & c ,
116
- })
117
- return nil
107
+ }
108
+ a .entries [path ] = newEntry
109
+
110
+ return newEntry , nil
118
111
}
119
112
120
- func (a * Applier ) applyDelete (ctx context.Context , f * gitdiff.File ) error {
113
+ func (a * Applier ) applyDelete (ctx context.Context , f * gitdiff.File ) ( * github. TreeEntry , error ) {
121
114
entry , exists , err := a .getEntry (ctx , f .OldName )
122
115
if err != nil {
123
- return err
116
+ return nil , err
124
117
}
125
118
if ! exists {
126
119
// because the rest of application is strict, return an error if the
127
120
// file was already deleted, since it indicates a conflict of some kind
128
- return errors .New ("missing entry for deleted file" )
121
+ return nil , errors .New ("missing entry for deleted file" )
129
122
}
130
123
131
124
data , _ , err := a .client .Git .GetBlobRaw (ctx , a .owner , a .repo , entry .GetSHA ())
132
125
if err != nil {
133
- return fmt .Errorf ("get blob content failed: %w" , err )
126
+ return nil , fmt .Errorf ("get blob content failed: %w" , err )
134
127
}
135
128
136
129
if err := gitdiff .Apply (ioutil .Discard , bytes .NewReader (data ), f ); err != nil {
137
- return err
130
+ return nil , err
138
131
}
139
132
140
- a .entries = append (a .entries , & github.TreeEntry {
141
- Path : github .String (f .OldName ),
133
+ path := f .OldName
134
+ newEntry := & github.TreeEntry {
135
+ Path : & path ,
142
136
Mode : entry .Mode ,
143
- })
144
- return nil
137
+ }
138
+ a .entries [path ] = newEntry
139
+
140
+ return newEntry , nil
145
141
}
146
142
147
- func (a * Applier ) applyModify (ctx context.Context , f * gitdiff.File ) error {
143
+ func (a * Applier ) applyModify (ctx context.Context , f * gitdiff.File ) ( * github. TreeEntry , error ) {
148
144
entry , exists , err := a .getEntry (ctx , f .OldName )
149
145
if err != nil {
150
- return err
146
+ return nil , err
151
147
}
152
148
if ! exists {
153
- return errors .New ("no entry for modified file" )
149
+ return nil , errors .New ("no entry for modified file" )
154
150
}
155
151
152
+ path := f .NewName
156
153
newEntry := & github.TreeEntry {
157
- Path : github . String ( f . NewName ) ,
154
+ Path : & path ,
158
155
Mode : github .String (getMode (f , entry )),
159
156
Type : github .String ("blob" ),
160
157
}
161
158
162
159
if len (f .TextFragments ) > 0 || f .BinaryFragment != nil {
163
160
data , _ , err := a .client .Git .GetBlobRaw (ctx , a .owner , a .repo , entry .GetSHA ())
164
161
if err != nil {
165
- return fmt .Errorf ("get blob content failed: %w" , err )
162
+ return nil , fmt .Errorf ("get blob content failed: %w" , err )
166
163
}
167
164
168
165
c , err := base64Apply (data , f )
169
166
if err != nil {
170
- return err
167
+ return nil , err
171
168
}
172
169
newEntry .Content = & c
173
170
}
174
171
175
172
// delete the old file if it was renamed
176
173
if f .OldName != f .NewName {
177
- a .entries = append (a .entries , & github.TreeEntry {
178
- Path : github .String (f .OldName ),
174
+ path := f .OldName
175
+ a .entries [path ] = & github.TreeEntry {
176
+ Path : & path ,
179
177
Mode : entry .Mode ,
180
- })
178
+ }
181
179
}
182
180
183
181
if newEntry .Content == nil {
184
182
newEntry .SHA = entry .SHA
185
183
}
186
- a .entries = append ( a . entries , newEntry )
184
+ a .entries [ path ] = newEntry
187
185
188
- return nil
186
+ return newEntry , nil
189
187
}
190
188
191
189
// Entries returns the list of pending tree entries.
192
190
func (a * Applier ) Entries () []* github.TreeEntry {
193
- return a .entries
191
+ entries := make ([]* github.TreeEntry , 0 , len (a .entries ))
192
+ for _ , e := range a .entries {
193
+ entries = append (entries , e )
194
+ }
195
+ return entries
194
196
}
195
197
196
198
// CreateTree creates a tree from the pending tree entries and clears the entry
@@ -201,16 +203,16 @@ func (a *Applier) CreateTree(ctx context.Context) (*github.Tree, error) {
201
203
return nil , errors .New ("no pending tree entries" )
202
204
}
203
205
204
- tree , _ , err := a .client .Git .CreateTree (ctx , a .owner , a .repo , a .tree , a .entries )
206
+ tree , _ , err := a .client .Git .CreateTree (ctx , a .owner , a .repo , a .tree , a .Entries () )
205
207
if err != nil {
206
208
return nil , err
207
209
}
208
210
209
211
// Clear the tree cache here because we only cache trees that lead to file
210
212
// modifications, and any change creates a new (i.e. uncached) tree SHA
211
- a .treeCache = make (map [string ]* github.Tree )
212
213
a .tree = tree .GetSHA ()
213
- a .entries = nil
214
+ a .treeCache = make (map [string ]* github.Tree )
215
+ a .entries = make (map [string ]* github.TreeEntry )
214
216
a .uncommitted = true
215
217
return tree , nil
216
218
}
@@ -276,13 +278,22 @@ func (a *Applier) Reset(c *github.Commit) {
276
278
a .commit = c
277
279
a .tree = c .GetTree ().GetSHA ()
278
280
a .treeCache = make (map [string ]* github.Tree )
279
- a .entries = nil
281
+ a .entries = make ( map [ string ] * github. TreeEntry )
280
282
a .uncommitted = false
281
283
}
282
284
283
- // getEntry returns the tree entry for path in the base tree. Returns nil and
284
- // false if no entry exists for path.
285
+ // getEntry returns the tree entry for a path. If the path has a pending
286
+ // change, return the entry representing that change, otherwise return an entry
287
+ // from the base tree. Returns nil and false if no entry exists for path.
285
288
func (a * Applier ) getEntry (ctx context.Context , path string ) (* github.TreeEntry , bool , error ) {
289
+ if entry , ok := a .entries [path ]; ok {
290
+ if entry .SHA == nil && entry .Content == nil {
291
+ // The existing entry is a deletion, so pretend it doesn't exist
292
+ return nil , false , nil
293
+ }
294
+ return entry , true , nil
295
+ }
296
+
286
297
parts := strings .Split (path , "/" )
287
298
dir , name := parts [:len (parts )- 1 ], parts [len (parts )- 1 ]
288
299
0 commit comments