@@ -270,63 +270,98 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
270
270
return fmt .Errorf ("OpenRepository: %v" , err )
271
271
}
272
272
273
- // Clone base repo.
274
- tmpBasePath := path .Join (setting .AppDataPath , "tmp/repos" , com .ToStr (time .Now ().Nanosecond ())+ ".git" )
273
+ baseRepoPath := pr .BaseRepo .RepoPath ()
275
274
276
- if err := os .MkdirAll (path .Dir (tmpBasePath ), os .ModePerm ); err != nil {
277
- return fmt .Errorf ("Failed to create dir %s: %v" , tmpBasePath , err )
278
- }
275
+ pullHead := fmt .Sprintf ("refs/pull/%d/head" , pr .Index )
279
276
280
- defer os .RemoveAll (path .Dir (tmpBasePath ))
277
+ // A temporary Git index file we are going to build. The merge commit will be based on it.
278
+ indexTmpPath := filepath .Join (os .TempDir (), "gitea-" + pr .BaseRepo .Name + "-" + strconv .Itoa (time .Now ().Nanosecond ()))
279
+ defer os .Remove (indexTmpPath )
281
280
282
- var stderr string
283
- if _ , stderr , err = process .GetManager ().ExecTimeout (5 * time .Minute ,
284
- fmt .Sprintf ("PullRequest.Merge (git clone): %s" , tmpBasePath ),
285
- "git" , "clone" , baseGitRepo .Path , tmpBasePath ); err != nil {
286
- return fmt .Errorf ("git clone: %s" , stderr )
281
+ // A temporary Git working tree for git-merge-index and git-merge-one-file.
282
+ workTreeTmpPath , err := ioutil .TempDir ("" , "gitea-merge-" )
283
+ if err != nil {
284
+ return fmt .Errorf ("Cannot create temporary directory for git-merge-index" )
287
285
}
286
+ defer os .RemoveAll (workTreeTmpPath )
288
287
289
- // Check out base branch.
290
- if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
291
- fmt .Sprintf ("PullRequest.Merge (git checkout): %s" , tmpBasePath ),
292
- "git" , "checkout" , pr .BaseBranch ); err != nil {
293
- return fmt .Errorf ("git checkout: %s" , stderr )
294
- }
288
+ log .Trace ("BaseRepo:%s Index:%s Ancestor:%s BaseBranch:%s HeadBranch:%s PullHead:%s" ,
289
+ baseRepoPath , indexTmpPath , pr .MergeBase , pr .BaseBranch , pr .HeadBranch , pullHead )
290
+
291
+ var stdout , stderr string
295
292
296
- // Add head repo remote.
297
- if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
298
- fmt .Sprintf ("PullRequest.Merge (git remote add): %s" , tmpBasePath ),
299
- "git" , "remote" , "add" , "head_repo" , headRepoPath ); err != nil {
300
- return fmt .Errorf ("git remote add [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
293
+ // Build the index from the common ancestor, the base-branch head, and the pull head, for 3-way merge.
294
+ if _ , stderr , err = process .GetManager ().ExecDirEnv (- 1 , "" ,
295
+ fmt .Sprintf ("PullRequest.Merge (git read-tree): %s" , baseRepoPath ),
296
+ []string {"GIT_DIR=" + baseRepoPath , "GIT_INDEX_FILE=" + indexTmpPath },
297
+ "git" , "read-tree" , "-im" , pr .MergeBase , pr .BaseBranch , pullHead ); err != nil {
298
+ return fmt .Errorf ("git read-tree -im %s %s %s: %s (%v)" , pr .MergeBase , pr .BaseBranch , pullHead , stderr , err )
301
299
}
302
300
303
- // Merge commits.
304
- if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
305
- fmt .Sprintf ("PullRequest.Merge (git fetch): %s" , tmpBasePath ),
306
- "git" , "fetch" , "head_repo" ); err != nil {
307
- return fmt .Errorf ("git fetch [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
301
+ // Run the merge algorithm throughout the prepared index.
302
+ if stdout , stderr , err = process .GetManager ().ExecDirEnv (- 1 , "" ,
303
+ fmt .Sprintf ("PullRequest.Merge (git merge-index)" ),
304
+ []string {
305
+ "GIT_DIR=" + baseRepoPath ,
306
+ "GIT_INDEX_FILE=" + indexTmpPath ,
307
+ "GIT_WORK_TREE=" + workTreeTmpPath ,
308
+ },
309
+ "git" , "merge-index" , "git-merge-one-file" , "-a" ); err != nil {
310
+ return fmt .Errorf ("git merge-index git merge-one-file -a: %s (%v)" , stderr , err )
308
311
}
309
312
310
- if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
311
- fmt .Sprintf ("PullRequest.Merge (git merge --no-ff --no-commit): %s" , tmpBasePath ),
312
- "git" , "merge" , "--no-ff" , "--no-commit" , "head_repo/" + pr .HeadBranch ); err != nil {
313
- return fmt .Errorf ("git merge --no-ff --no-commit [%s]: %v - %s" , tmpBasePath , err , stderr )
313
+ // Write the tree object back to the Git object store from the index.
314
+ if stdout , stderr , err = process .GetManager ().ExecDirEnv (- 1 , "" ,
315
+ fmt .Sprintf ("PullRequest.Merge (git write-tree): %s" , baseRepoPath ),
316
+ []string {"GIT_DIR=" + baseRepoPath , "GIT_INDEX_FILE=" + indexTmpPath },
317
+ "git" , "write-tree" ); err != nil {
318
+ return fmt .Errorf ("git write-tree: %s (%v)" , stderr , err )
314
319
}
320
+ treeID := strings .TrimSpace (stdout )
315
321
316
- sig := doer . NewGitSig ( )
317
- if _ , stderr , err = process . GetManager (). ExecDir ( - 1 , tmpBasePath ,
318
- fmt . Sprintf ( "PullRequest.Merge (git merge): %s" , tmpBasePath ),
319
- "git" , "commit" , fmt . Sprintf ( "--author='%s <%s>'" , sig . Name , sig . Email ),
320
- "-m" , fmt . Sprintf ( "Merge branch '%s' of %s/%s into %s" , pr . HeadBranch , pr . HeadUserName , pr .HeadRepo .Name , pr .BaseBranch ) ); err != nil {
321
- return fmt .Errorf ("git commit [%s] : %v - %s " , tmpBasePath , err , stderr )
322
+ log . Trace ( "BaseRepo:%s TreeId:%s" , baseRepoPath , treeID )
323
+
324
+ // Create the commit object based on the tree object we just built.
325
+ var headBranch * Branch
326
+ if headBranch , err = pr .HeadRepo .GetBranch ( pr .HeadBranch ); err != nil {
327
+ return fmt .Errorf ("Getting head branch: %s : %s (%v) " , pr . HeadBranch , stderr , err )
322
328
}
323
329
324
- // Push back to upstream.
325
- if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
326
- fmt .Sprintf ("PullRequest.Merge (git push): %s" , tmpBasePath ),
327
- "git" , "push" , baseGitRepo .Path , pr .BaseBranch ); err != nil {
328
- return fmt .Errorf ("git push: %s" , stderr )
330
+ var headCommit * git.Commit
331
+ if headCommit , err = headBranch .GetCommit (); err != nil {
332
+ return fmt .Errorf ("Getting head commit: %s: %s (%v)" , pr .HeadBranch , stderr , err )
329
333
}
334
+ commitMessage := fmt .Sprintf ("Merge branch '%s' of %s/%s into %s" , pr .HeadBranch , pr .HeadUserName , pr .HeadRepo .Name , pr .BaseBranch )
335
+
336
+ sig := doer .NewGitSig ()
337
+ if stdout , stderr , err = process .GetManager ().ExecDirEnv (- 1 , "" ,
338
+ fmt .Sprintf ("PullRequest.Merge (git commit-tree): %s" , baseRepoPath ),
339
+ []string {
340
+ "GIT_DIR=" + baseRepoPath ,
341
+ "GIT_AUTHOR_NAME=" + headCommit .Author .Name ,
342
+ "GIT_AUTHOR_EMAIL=" + headCommit .Author .Email ,
343
+ "GIT_AUTHOR_DATE=" + headCommit .Author .When .Format ("Mon, 02 Jan 2006 15:04:05 -0700" ),
344
+ "GIT_COMMITTER_NAME=" + sig .Name ,
345
+ "GIT_COMMITTER_EMAIL=" + sig .Email ,
346
+ "GIT_COMMITTER_DATE=" + sig .When .Format ("Mon, 02 Jan 2006 15:04:05 -0700" ),
347
+ },
348
+ "git" , "commit-tree" , treeID , "-p" , pr .BaseBranch , "-p" , pullHead , "-m" , commitMessage ); err != nil {
349
+ return fmt .Errorf ("git commit-tree %s -p %s -p %s: %s (%v)" , strings .TrimSpace (treeID ), pr .BaseBranch , pullHead , stderr , err )
350
+ }
351
+ commitID := strings .TrimSpace (stdout )
352
+
353
+ log .Trace ("BaseRepo:%s CommitId:%s" , baseRepoPath , commitID )
354
+
355
+ // Update the ref of the base-branch to point to the new commit object.
356
+ refPath := fmt .Sprintf ("refs/heads/%s" , pr .BaseBranch )
357
+ if _ , stderr , err = process .GetManager ().ExecDirEnv (- 1 , "" ,
358
+ fmt .Sprintf ("PullRequest.Merge (git update-ref): %s" , baseRepoPath ),
359
+ []string {"GIT_DIR=" + baseRepoPath },
360
+ "git" , "update-ref" , refPath , commitID ); err != nil {
361
+ return fmt .Errorf ("git update-ref -m %s %s: %s (%v)" , commitMessage , refPath , stderr , err )
362
+ }
363
+
364
+ log .Trace ("BaseRepo:%s Update-ref: %s->%s. Done." , baseRepoPath , refPath , commitID )
330
365
331
366
pr .MergedCommitID , err = baseGitRepo .GetBranchCommitID (pr .BaseBranch )
332
367
if err != nil {
0 commit comments