5
5
package models
6
6
7
7
import (
8
+ "bytes"
9
+ "context"
8
10
"fmt"
9
11
"io"
10
12
"io/ioutil"
@@ -13,6 +15,7 @@ import (
13
15
"os/exec"
14
16
"path"
15
17
"path/filepath"
18
+ "strings"
16
19
"time"
17
20
18
21
"github.com/Unknwon/com"
@@ -84,74 +87,286 @@ type UpdateRepoFileOptions struct {
84
87
IsNewFile bool
85
88
}
86
89
87
- // UpdateRepoFile adds or updates a file in repository.
88
- func (repo * Repository ) UpdateRepoFile (doer * User , opts UpdateRepoFileOptions ) (err error ) {
89
- repoWorkingPool .CheckIn (com .ToStr (repo .ID ))
90
- defer repoWorkingPool .CheckOut (com .ToStr (repo .ID ))
90
+ func (repo * Repository ) bareClone (repoPath string , branch string ) (err error ) {
91
+ if _ , stderr , err := process .GetManager ().ExecTimeout (5 * time .Minute ,
92
+ fmt .Sprintf ("bareClone (git clone -s --bare): %s" , repoPath ),
93
+ "git" , "clone" , "-s" , "--bare" , "-b" , branch , repo .RepoPath (), repoPath ); err != nil {
94
+ return fmt .Errorf ("bareClone: %v %s" , err , stderr )
95
+ }
96
+ return nil
97
+ }
91
98
92
- if err = repo .DiscardLocalRepoBranchChanges (opts .OldBranch ); err != nil {
93
- return fmt .Errorf ("DiscardLocalRepoBranchChanges [branch: %s]: %v" , opts .OldBranch , err )
94
- } else if err = repo .UpdateLocalCopyBranch (opts .OldBranch ); err != nil {
95
- return fmt .Errorf ("UpdateLocalCopyBranch [branch: %s]: %v" , opts .OldBranch , err )
99
+ func (repo * Repository ) setDefaultIndex (repoPath string ) (err error ) {
100
+ if _ , stderr , err := process .GetManager ().ExecDir (5 * time .Minute ,
101
+ repoPath ,
102
+ fmt .Sprintf ("setDefaultIndex (git read-tree HEAD): %s" , repoPath ),
103
+ "git" , "read-tree" , "HEAD" ); err != nil {
104
+ return fmt .Errorf ("setDefaultIndex: %v %s" , err , stderr )
96
105
}
106
+ return nil
107
+ }
97
108
98
- if opts .OldBranch != opts .NewBranch {
99
- if err := repo .CheckoutNewBranch (opts .OldBranch , opts .NewBranch ); err != nil {
100
- return fmt .Errorf ("CheckoutNewBranch [old_branch: %s, new_branch: %s]: %v" , opts .OldBranch , opts .NewBranch , err )
109
+ // FIXME: We should probably return the mode too
110
+ func (repo * Repository ) lsFiles (repoPath string , args ... string ) ([]string , error ) {
111
+ stdOut := new (bytes.Buffer )
112
+ stdErr := new (bytes.Buffer )
113
+
114
+ timeout := 5 * time .Minute
115
+ ctx , cancel := context .WithTimeout (context .Background (), timeout )
116
+ defer cancel ()
117
+
118
+ cmdArgs := []string {"ls-files" , "-z" , "--" }
119
+ for _ , arg := range args {
120
+ if arg != "" {
121
+ cmdArgs = append (cmdArgs , arg )
101
122
}
102
123
}
103
124
104
- localPath := repo .LocalCopyPath ()
105
- oldFilePath := path .Join (localPath , opts .OldTreeName )
106
- filePath := path .Join (localPath , opts .NewTreeName )
107
- dir := path .Dir (filePath )
125
+ cmd := exec .CommandContext (ctx , "git" , cmdArgs ... )
126
+ desc := fmt .Sprintf ("lsFiles: (git ls-files) %v" , cmdArgs )
127
+ cmd .Dir = repoPath
128
+ cmd .Stdout = stdOut
129
+ cmd .Stderr = stdErr
108
130
109
- if err := os .MkdirAll (dir , os .ModePerm ); err != nil {
110
- return fmt .Errorf ("Failed to create dir %s: %v" , dir , err )
131
+ if err := cmd .Start (); err != nil {
132
+ return nil , fmt .Errorf ("exec(%s) failed: %v(%v)" , desc , err , ctx .Err ())
133
+ }
134
+
135
+ pid := process .GetManager ().Add (desc , cmd )
136
+ err := cmd .Wait ()
137
+ process .GetManager ().Remove (pid )
138
+
139
+ if err != nil {
140
+ err = fmt .Errorf ("exec(%d:%s) failed: %v(%v) stdout: %v stderr: %v" , pid , desc , err , ctx .Err (), stdOut , stdErr )
141
+ return nil , err
142
+ }
143
+
144
+ filelist := make ([]string , len (args ))
145
+ for _ , line := range bytes .Split (stdOut .Bytes (), []byte {'\000' }) {
146
+ filelist = append (filelist , string (line ))
147
+ }
148
+
149
+ return filelist , err
150
+ }
151
+
152
+ func (repo * Repository ) removeFilesFromIndex (repoPath string , args ... string ) error {
153
+ stdOut := new (bytes.Buffer )
154
+ stdErr := new (bytes.Buffer )
155
+ stdIn := new (bytes.Buffer )
156
+ for _ , file := range args {
157
+ if file != "" {
158
+ stdIn .WriteString ("0 0000000000000000000000000000000000000000\t " )
159
+ stdIn .WriteString (file )
160
+ stdIn .WriteByte ('\000' )
161
+ }
162
+ }
163
+
164
+ timeout := 5 * time .Minute
165
+ ctx , cancel := context .WithTimeout (context .Background (), timeout )
166
+ defer cancel ()
167
+
168
+ cmdArgs := []string {"update-index" , "--remove" , "-z" , "--index-info" }
169
+ cmd := exec .CommandContext (ctx , "git" , cmdArgs ... )
170
+ desc := fmt .Sprintf ("removeFilesFromIndex: (git update-index) %v" , args )
171
+ cmd .Dir = repoPath
172
+ cmd .Stdout = stdOut
173
+ cmd .Stderr = stdErr
174
+ cmd .Stdin = bytes .NewReader (stdIn .Bytes ())
175
+
176
+ if err := cmd .Start (); err != nil {
177
+ return fmt .Errorf ("exec(%s) failed: %v(%v)" , desc , err , ctx .Err ())
178
+ }
179
+
180
+ pid := process .GetManager ().Add (desc , cmd )
181
+ err := cmd .Wait ()
182
+ process .GetManager ().Remove (pid )
183
+
184
+ if err != nil {
185
+ err = fmt .Errorf ("exec(%d:%s) failed: %v(%v) stdout: %v stderr: %v" , pid , desc , err , ctx .Err (), stdOut , stdErr )
186
+ }
187
+
188
+ return err
189
+ }
190
+
191
+ func (repo * Repository ) hashObject (repoPath string , content io.Reader ) (string , error ) {
192
+ timeout := 5 * time .Minute
193
+ ctx , cancel := context .WithTimeout (context .Background (), timeout )
194
+ defer cancel ()
195
+
196
+ hashCmd := exec .CommandContext (ctx , "git" , "hash-object" , "-w" , "--stdin" )
197
+ hashCmd .Dir = repoPath
198
+ hashCmd .Stdin = content
199
+ stdOutBuffer := new (bytes.Buffer )
200
+ stdErrBuffer := new (bytes.Buffer )
201
+ hashCmd .Stdout = stdOutBuffer
202
+ hashCmd .Stderr = stdErrBuffer
203
+ desc := fmt .Sprintf ("hashObject: (git hash-object)" )
204
+ if err := hashCmd .Start (); err != nil {
205
+ return "" , fmt .Errorf ("git hash-object: %s" , err )
206
+ }
207
+
208
+ pid := process .GetManager ().Add (desc , hashCmd )
209
+ err := hashCmd .Wait ()
210
+ process .GetManager ().Remove (pid )
211
+
212
+ if err != nil {
213
+ err = fmt .Errorf ("exec(%d:%s) failed: %v(%v) stdout: %v stderr: %v" , pid , desc , err , ctx .Err (), stdOutBuffer , stdErrBuffer )
214
+ return "" , err
215
+ }
216
+
217
+ return strings .TrimSpace (stdOutBuffer .String ()), nil
218
+ }
219
+
220
+ func (repo * Repository ) addObjectToIndex (repoPath , mode , objectHash , objectPath string ) error {
221
+ if _ , stderr , err := process .GetManager ().ExecDir (5 * time .Minute ,
222
+ repoPath ,
223
+ fmt .Sprintf ("addObjectToIndex (git update-index): %s" , repoPath ),
224
+ "git" , "update-index" , "--add" , "--replace" , "--cacheinfo" , mode , objectHash , objectPath ); err != nil {
225
+ return fmt .Errorf ("git update-index: %s" , stderr )
226
+ }
227
+ return nil
228
+ }
229
+
230
+ func (repo * Repository ) writeTree (repoPath string ) (string , error ) {
231
+
232
+ treeHash , stderr , err := process .GetManager ().ExecDir (5 * time .Minute ,
233
+ repoPath ,
234
+ fmt .Sprintf ("writeTree (git write-tree): %s" , repoPath ),
235
+ "git" , "write-tree" )
236
+ if err != nil {
237
+ return "" , fmt .Errorf ("git write-tree: %s" , stderr )
238
+ }
239
+ return strings .TrimSpace (treeHash ), nil
240
+ }
241
+
242
+ func (repo * Repository ) commitTree (repoPath string , doer * User , treeHash string , message string ) (string , error ) {
243
+ commitTimeStr := time .Now ().Format (time .UnixDate )
244
+
245
+ // FIXME: Should we add SSH_ORIGINAL_COMMAND to this
246
+ // Because this may call hooks we should pass in the environment
247
+ env := append (os .Environ (),
248
+ "GIT_AUTHOR_NAME=" + doer .DisplayName (),
249
+ "GIT_AUTHOR_EMAIL=" + doer .getEmail (),
250
+ "GIT_AUTHOR_DATE=" + commitTimeStr ,
251
+ "GIT_COMMITTER_NAME=" + doer .DisplayName (),
252
+ "GIT_COMMITTER_EMAIL=" + doer .getEmail (),
253
+ "GIT_COMMITTER_DATE=" + commitTimeStr ,
254
+ )
255
+ commitHash , stderr , err := process .GetManager ().ExecDirEnv (5 * time .Minute ,
256
+ repoPath ,
257
+ fmt .Sprintf ("commitTree (git commit-tree): %s" , repoPath ),
258
+ env ,
259
+ "git" , "commit-tree" , treeHash , "-p" , "HEAD" , "-m" , message )
260
+ if err != nil {
261
+ return "" , fmt .Errorf ("git commit-tree: %s" , stderr )
262
+ }
263
+ return strings .TrimSpace (commitHash ), nil
264
+ }
265
+
266
+ func (repo * Repository ) actuallyPush (repoPath string , doer * User , commitHash string , branch string ) error {
267
+ isUncyclo := "false"
268
+ if strings .HasSuffix (repo .Name , ".wiki" ) {
269
+ isUncyclo = "true"
270
+ }
271
+
272
+ // FIXME: Should we add SSH_ORIGINAL_COMMAND to this
273
+ // Because calls hooks we need to pass in the environment
274
+ env := append (os .Environ (),
275
+ "GIT_AUTHOR_NAME=" + doer .DisplayName (),
276
+ "GIT_AUTHOR_EMAIL=" + doer .getEmail (),
277
+ "GIT_COMMITTER_NAME=" + doer .DisplayName (),
278
+ "GIT_COMMITTER_EMAIL=" + doer .getEmail (),
279
+ EnvRepoName + "=" + repo .Name ,
280
+ EnvRepoUsername + "=" + repo .OwnerName ,
281
+ EnvRepoIsUncyclo + "=" + isUncyclo ,
282
+ EnvPusherName + "=" + doer .Name ,
283
+ EnvPusherID + "=" + fmt .Sprintf ("%d" , doer .ID ),
284
+ ProtectedBranchRepoID + "=" + fmt .Sprintf ("%d" , repo .ID ),
285
+ )
286
+
287
+ if _ , stderr , err := process .GetManager ().ExecDirEnv (5 * time .Minute ,
288
+ repoPath ,
289
+ fmt .Sprintf ("actuallyPush (git push): %s" , repoPath ),
290
+ env ,
291
+ "git" , "push" , repo .RepoPath (), strings .TrimSpace (commitHash )+ ":refs/heads/" + strings .TrimSpace (branch )); err != nil {
292
+ return fmt .Errorf ("git push: %s" , stderr )
293
+ }
294
+ return nil
295
+ }
296
+
297
+ // UpdateRepoFile adds or updates a file in the repository.
298
+ func (repo * Repository ) UpdateRepoFile (doer * User , opts UpdateRepoFileOptions ) (err error ) {
299
+ timeStr := com .ToStr (time .Now ().Nanosecond ()) // SHOULD USE SOMETHING UNIQUE
300
+ tmpBasePath := path .Join (LocalCopyPath (), "upload-" + timeStr + ".git" )
301
+ if err := os .MkdirAll (path .Dir (tmpBasePath ), os .ModePerm ); err != nil {
302
+ return fmt .Errorf ("Failed to create dir %s: %v" , tmpBasePath , err )
303
+ }
304
+
305
+ defer os .RemoveAll (path .Dir (tmpBasePath ))
306
+
307
+ // Do a bare shared clone into tmpBasePath and
308
+ // make HEAD to point to the OldBranch tree
309
+ if err := repo .bareClone (tmpBasePath , opts .OldBranch ); err != nil {
310
+ return fmt .Errorf ("UpdateRepoFile: %v" , err )
311
+ }
312
+
313
+ // Set the default index
314
+ if err := repo .setDefaultIndex (tmpBasePath ); err != nil {
315
+ return fmt .Errorf ("UpdateRepoFile: %v" , err )
316
+ }
317
+
318
+ filesInIndex , err := repo .lsFiles (tmpBasePath , opts .NewTreeName , opts .OldTreeName )
319
+
320
+ if err != nil {
321
+ return fmt .Errorf ("UpdateRepoFile: %v" , err )
111
322
}
112
323
113
- // If it's meant to be a new file, make sure it doesn't exist.
114
324
if opts .IsNewFile {
115
- if com .IsExist (filePath ) {
116
- return ErrRepoFileAlreadyExist {filePath }
325
+ for _ , file := range filesInIndex {
326
+ if file == opts .NewTreeName {
327
+ return ErrRepoFileAlreadyExist {opts .NewTreeName }
328
+ }
117
329
}
118
330
}
119
331
120
- // Ignore move step if it's a new file under a directory.
121
- // Otherwise, move the file when name changed.
122
- if com .IsFile (oldFilePath ) && opts .OldTreeName != opts .NewTreeName {
123
- if err = git .MoveFile (localPath , opts .OldTreeName , opts .NewTreeName ); err != nil {
124
- return fmt .Errorf ("git mv %s %s: %v" , opts .OldTreeName , opts .NewTreeName , err )
332
+ //var stdout string
333
+ if opts .OldTreeName != opts .NewTreeName && len (filesInIndex ) > 0 {
334
+ for _ , file := range filesInIndex {
335
+ if file == opts .OldTreeName {
336
+ if err := repo .removeFilesFromIndex (tmpBasePath , opts .OldTreeName ); err != nil {
337
+ return err
338
+ }
339
+ }
125
340
}
341
+
126
342
}
127
343
128
- if err = ioutil .WriteFile (filePath , []byte (opts .Content ), 0666 ); err != nil {
129
- return fmt .Errorf ("WriteFile: %v" , err )
344
+ // Add the object to the database
345
+ objectHash , err := repo .hashObject (tmpBasePath , strings .NewReader (opts .Content ))
346
+ if err != nil {
347
+ return err
130
348
}
131
349
132
- if err = git .AddChanges (localPath , true ); err != nil {
133
- return fmt .Errorf ("git add --all: %v" , err )
134
- } else if err = git .CommitChanges (localPath , git.CommitChangesOptions {
135
- Committer : doer .NewGitSig (),
136
- Message : opts .Message ,
137
- }); err != nil {
138
- return fmt .Errorf ("CommitChanges: %v" , err )
139
- } else if err = git .Push (localPath , git.PushOptions {
140
- Remote : "origin" ,
141
- Branch : opts .NewBranch ,
142
- }); err != nil {
143
- return fmt .Errorf ("git push origin %s: %v" , opts .NewBranch , err )
350
+ // Add the object to the index
351
+ if err := repo .addObjectToIndex (tmpBasePath , "100666" , objectHash , opts .NewTreeName ); err != nil {
352
+ return err
144
353
}
145
354
146
- gitRepo , err := git .OpenRepository (repo .RepoPath ())
355
+ // Now write the tree
356
+ treeHash , err := repo .writeTree (tmpBasePath )
147
357
if err != nil {
148
- log .Error (4 , "OpenRepository: %v" , err )
149
- return nil
358
+ return err
150
359
}
151
- commit , err := gitRepo .GetBranchCommit (opts .NewBranch )
360
+
361
+ // Now commit the tree
362
+ commitHash , err := repo .commitTree (tmpBasePath , doer , treeHash , opts .Message )
152
363
if err != nil {
153
- log .Error (4 , "GetBranchCommit [branch: %s]: %v" , opts .NewBranch , err )
154
- return nil
364
+ return err
365
+ }
366
+
367
+ // Then push this tree to NewBranch
368
+ if err := repo .actuallyPush (tmpBasePath , doer , commitHash , opts .NewBranch ); err != nil {
369
+ return err
155
370
}
156
371
157
372
// Simulate push event.
@@ -172,7 +387,7 @@ func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) (
172
387
RepoName : repo .Name ,
173
388
RefFullName : git .BranchPrefix + opts .NewBranch ,
174
389
OldCommitID : oldCommitID ,
175
- NewCommitID : commit . ID . String () ,
390
+ NewCommitID : commitHash ,
176
391
},
177
392
)
178
393
if err != nil {
0 commit comments