Skip to content

Commit 6ad834e

Browse files
typelesslafriks
authored andcommittedJan 23, 2019
Optimize pulls merging (go-gitea#4921)
* Optimize pulls merging By utilizing `git clone -s --no-checkout` rather than cloning the whole repo. * Use sparse-checkout to speedup pulls merge * Use bytes.Buffer instead of strings.Builder for backward compatibility * Fix empty diff-tree output for repos with only the initial commit * Fix missing argument for the format string * Rework diff-tree-list generation * Remove logging code * File list for sparse-checkout must be prefix with / Otherwise, they would match all files with the same name under subdirectories. * Update onto the rebased head * Use referecen repo to avoid fetching objects
1 parent 7d43437 commit 6ad834e

File tree

1 file changed

+86
-16
lines changed

1 file changed

+86
-16
lines changed
 

‎models/pull.go

+86-16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
package models
66

77
import (
8+
"bufio"
9+
"bytes"
810
"fmt"
911
"io/ioutil"
1012
"os"
@@ -328,6 +330,34 @@ func (pr *PullRequest) CheckUserAllowedToMerge(doer *User) (err error) {
328330
return nil
329331
}
330332

333+
func getDiffTree(repoPath, baseBranch, headBranch string) (string, error) {
334+
getDiffTreeFromBranch := func(repoPath, baseBranch, headBranch string) (string, error) {
335+
var stdout, stderr string
336+
// Compute the diff-tree for sparse-checkout
337+
// The branch argument must be enclosed with double-quotes ("") in case it contains slashes (e.g "feature/test")
338+
stdout, stderr, err := process.GetManager().ExecDir(-1, repoPath,
339+
fmt.Sprintf("PullRequest.Merge (git diff-tree): %s", repoPath),
340+
"git", "diff-tree", "--no-commit-id", "--name-only", "-r", "--root", baseBranch, headBranch)
341+
if err != nil {
342+
return "", fmt.Errorf("git diff-tree [%s base:%s head:%s]: %s", repoPath, baseBranch, headBranch, stderr)
343+
}
344+
return stdout, nil
345+
}
346+
347+
list, err := getDiffTreeFromBranch(repoPath, baseBranch, headBranch)
348+
if err != nil {
349+
return "", err
350+
}
351+
352+
// Prefixing '/' for each entry, otherwise all files with the same name in subdirectories would be matched.
353+
out := bytes.Buffer{}
354+
scanner := bufio.NewScanner(strings.NewReader(list))
355+
for scanner.Scan() {
356+
fmt.Fprintf(&out, "/%s\n", scanner.Text())
357+
}
358+
return out.String(), nil
359+
}
360+
331361
// Merge merges pull request to base repository.
332362
// FIXME: add repoWorkingPull make sure two merges does not happen at same time.
333363
func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle MergeStyle, message string) (err error) {
@@ -371,36 +401,76 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
371401
var stderr string
372402
if _, stderr, err = process.GetManager().ExecTimeout(5*time.Minute,
373403
fmt.Sprintf("PullRequest.Merge (git clone): %s", tmpBasePath),
374-
"git", "clone", baseGitRepo.Path, tmpBasePath); err != nil {
404+
"git", "clone", "-s", "--no-checkout", "-b", pr.BaseBranch, baseGitRepo.Path, tmpBasePath); err != nil {
375405
return fmt.Errorf("git clone: %s", stderr)
376406
}
377407

378-
// Check out base branch.
379-
if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
380-
fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath),
381-
"git", "checkout", pr.BaseBranch); err != nil {
382-
return fmt.Errorf("git checkout: %s", stderr)
383-
}
408+
remoteRepoName := "head_repo"
384409

385410
// Add head repo remote.
411+
addCacheRepo := func(staging, cache string) error {
412+
p := filepath.Join(staging, ".git", "objects", "info", "alternates")
413+
f, err := os.OpenFile(p, os.O_APPEND|os.O_WRONLY, 0600)
414+
if err != nil {
415+
return err
416+
}
417+
defer f.Close()
418+
data := filepath.Join(cache, "objects")
419+
if _, err := fmt.Fprintln(f, data); err != nil {
420+
return err
421+
}
422+
return nil
423+
}
424+
425+
if err := addCacheRepo(tmpBasePath, headRepoPath); err != nil {
426+
return fmt.Errorf("addCacheRepo [%s -> %s]: %v", headRepoPath, tmpBasePath, err)
427+
}
386428
if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
387429
fmt.Sprintf("PullRequest.Merge (git remote add): %s", tmpBasePath),
388-
"git", "remote", "add", "head_repo", headRepoPath); err != nil {
430+
"git", "remote", "add", remoteRepoName, headRepoPath); err != nil {
389431
return fmt.Errorf("git remote add [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
390432
}
391433

392-
// Merge commits.
434+
// Fetch head branch
393435
if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
394436
fmt.Sprintf("PullRequest.Merge (git fetch): %s", tmpBasePath),
395-
"git", "fetch", "head_repo"); err != nil {
437+
"git", "fetch", remoteRepoName); err != nil {
396438
return fmt.Errorf("git fetch [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
397439
}
398440

441+
trackingBranch := path.Join(remoteRepoName, pr.HeadBranch)
442+
stagingBranch := fmt.Sprintf("%s_%s", remoteRepoName, pr.HeadBranch)
443+
444+
// Enable sparse-checkout
445+
sparseCheckoutList, err := getDiffTree(tmpBasePath, pr.BaseBranch, trackingBranch)
446+
if err != nil {
447+
return fmt.Errorf("getDiffTree: %v", err)
448+
}
449+
450+
sparseCheckoutListPath := filepath.Join(tmpBasePath, ".git", "info", "sparse-checkout")
451+
if err := ioutil.WriteFile(sparseCheckoutListPath, []byte(sparseCheckoutList), 0600); err != nil {
452+
return fmt.Errorf("Writing sparse-checkout file to %s: %v", sparseCheckoutListPath, err)
453+
}
454+
455+
if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
456+
fmt.Sprintf("PullRequest.Merge (git config): %s", tmpBasePath),
457+
"git", "config", "--local", "core.sparseCheckout", "true"); err != nil {
458+
return fmt.Errorf("git config [core.sparsecheckout -> true]: %v", stderr)
459+
}
460+
461+
// Read base branch index
462+
if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
463+
fmt.Sprintf("PullRequest.Merge (git read-tree): %s", tmpBasePath),
464+
"git", "read-tree", "HEAD"); err != nil {
465+
return fmt.Errorf("git read-tree HEAD: %s", stderr)
466+
}
467+
468+
// Merge commits.
399469
switch mergeStyle {
400470
case MergeStyleMerge:
401471
if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
402472
fmt.Sprintf("PullRequest.Merge (git merge --no-ff --no-commit): %s", tmpBasePath),
403-
"git", "merge", "--no-ff", "--no-commit", "head_repo/"+pr.HeadBranch); err != nil {
473+
"git", "merge", "--no-ff", "--no-commit", trackingBranch); err != nil {
404474
return fmt.Errorf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, stderr)
405475
}
406476

@@ -415,7 +485,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
415485
// Checkout head branch
416486
if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
417487
fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath),
418-
"git", "checkout", "-b", "head_repo_"+pr.HeadBranch, "head_repo/"+pr.HeadBranch); err != nil {
488+
"git", "checkout", "-b", stagingBranch, trackingBranch); err != nil {
419489
return fmt.Errorf("git checkout: %s", stderr)
420490
}
421491
// Rebase before merging
@@ -433,14 +503,14 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
433503
// Merge fast forward
434504
if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
435505
fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath),
436-
"git", "merge", "--ff-only", "-q", "head_repo_"+pr.HeadBranch); err != nil {
506+
"git", "merge", "--ff-only", "-q", stagingBranch); err != nil {
437507
return fmt.Errorf("git merge --ff-only [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
438508
}
439509
case MergeStyleRebaseMerge:
440510
// Checkout head branch
441511
if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
442512
fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath),
443-
"git", "checkout", "-b", "head_repo_"+pr.HeadBranch, "head_repo/"+pr.HeadBranch); err != nil {
513+
"git", "checkout", "-b", stagingBranch, trackingBranch); err != nil {
444514
return fmt.Errorf("git checkout: %s", stderr)
445515
}
446516
// Rebase before merging
@@ -458,7 +528,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
458528
// Prepare merge with commit
459529
if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
460530
fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath),
461-
"git", "merge", "--no-ff", "--no-commit", "-q", "head_repo_"+pr.HeadBranch); err != nil {
531+
"git", "merge", "--no-ff", "--no-commit", "-q", stagingBranch); err != nil {
462532
return fmt.Errorf("git merge --no-ff [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
463533
}
464534

@@ -475,7 +545,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
475545
// Merge with squash
476546
if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
477547
fmt.Sprintf("PullRequest.Merge (git squash): %s", tmpBasePath),
478-
"git", "merge", "-q", "--squash", "head_repo/"+pr.HeadBranch); err != nil {
548+
"git", "merge", "-q", "--squash", trackingBranch); err != nil {
479549
return fmt.Errorf("git merge --squash [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
480550
}
481551
sig := pr.Issue.Poster.NewGitSig()

0 commit comments

Comments
 (0)
Please sign in to comment.