Skip to content

Commit 2af67f6

Browse files
filipnavaralunny
authored andcommitted
Improve listing performance by using go-git (#6478)
* Use go-git for tree reading and commit info lookup. Signed-off-by: Filip Navara <[email protected]> * Use TreeEntry.IsRegular() instead of ObjectType that was removed. Signed-off-by: Filip Navara <[email protected]> * Use the treePath to optimize commit info search. Signed-off-by: Filip Navara <[email protected]> * Extract the latest commit at treePath along with the other commits. Signed-off-by: Filip Navara <[email protected]> * Fix listing commit info for a directory that was created in one commit and never modified after. Signed-off-by: Filip Navara <[email protected]> * Avoid nearly all external 'git' invocations when doing directory listing (.editorconfig code path is still hit). Signed-off-by: Filip Navara <[email protected]> * Use go-git for reading blobs. Signed-off-by: Filip Navara <[email protected]> * Make SHA1 type alias for plumbing.Hash in go-git. Signed-off-by: Filip Navara <[email protected]> * Make Signature type alias for object.Signature in go-git. Signed-off-by: Filip Navara <[email protected]> * Fix GetCommitsInfo for repository with only one commit. Signed-off-by: Filip Navara <[email protected]> * Fix PGP signature verification. Signed-off-by: Filip Navara <[email protected]> * Fix issues with walking commit graph across merges. Signed-off-by: Filip Navara <[email protected]> * Fix typo in condition. Signed-off-by: Filip Navara <[email protected]> * Speed up loading branch list by keeping the repository reference (and thus all the loaded packfile indexes). Signed-off-by: Filip Navara <[email protected]> * Fix lising submodules. Signed-off-by: Filip Navara <[email protected]> * Fix build Signed-off-by: Filip Navara <[email protected]> * Add back commit cache because of name-rev Signed-off-by: Filip Navara <[email protected]> * Fix tests Signed-off-by: Filip Navara <[email protected]> * Fix code style * Fix spelling * Address PR feedback Signed-off-by: Filip Navara <[email protected]> * Update vendor module list Signed-off-by: Filip Navara <[email protected]> * Fix getting trees by commit id Signed-off-by: Filip Navara <[email protected]> * Fix remaining unit test failures * Fix GetTreeBySHA * Avoid running `git name-rev` if not necessary Signed-off-by: Filip Navara <[email protected]> * Move Branch code to git module * Clean up GPG signature verification and fix it for tagged commits * Address PR feedback (import formatting, copyright headers) * Make blob lookup by SHA working * Update tests to use public API * Allow getting content from any type of object through the blob interface * Change test to actually expect the object content that is in the GIT repository * Change one more test to actually expect the object content that is in the GIT repository * Add comments
1 parent 19ec260 commit 2af67f6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+724
-748
lines changed

go.mod

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ require (
3232
github.com/dgrijalva/jwt-go v0.0.0-20161101193935-9ed569b5d1ac
3333
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect
3434
github.com/elazarl/go-bindata-assetfs v0.0.0-20151224045452-57eb5e1fc594 // indirect
35-
github.com/emirpasic/gods v1.12.0 // indirect
35+
github.com/emirpasic/gods v1.12.0
3636
github.com/etcd-io/bbolt v1.3.2 // indirect
3737
github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a
3838
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
@@ -127,7 +127,7 @@ require (
127127
gopkg.in/ldap.v3 v3.0.2
128128
gopkg.in/macaron.v1 v1.3.2
129129
gopkg.in/redis.v2 v2.3.2 // indirect
130-
gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect
130+
gopkg.in/src-d/go-billy.v4 v4.3.0
131131
gopkg.in/src-d/go-git.v4 v4.10.0
132132
gopkg.in/testfixtures.v2 v2.5.0
133133
mvdan.cc/xurls/v2 v2.0.0

integrations/api_repo_git_blobs_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func TestAPIReposGitBlobs(t *testing.T) {
3838
var gitBlobResponse api.GitBlobResponse
3939
DecodeJSON(t, resp, &gitBlobResponse)
4040
assert.NotNil(t, gitBlobResponse)
41-
expectedContent := "Y29tbWl0IDY1ZjFiZjI3YmMzYmY3MGY2NDY1NzY1ODYzNWU2NjA5NGVkYmNiNGQKQXV0aG9yOiB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+CkRhdGU6ICAgU3VuIE1hciAxOSAxNjo0Nzo1OSAyMDE3IC0wNDAwCgogICAgSW5pdGlhbCBjb21taXQKCmRpZmYgLS1naXQgYS9SRUFETUUubWQgYi9SRUFETUUubWQKbmV3IGZpbGUgbW9kZSAxMDA2NDQKaW5kZXggMDAwMDAwMC4uNGI0ODUxYQotLS0gL2Rldi9udWxsCisrKyBiL1JFQURNRS5tZApAQCAtMCwwICsxLDMgQEAKKyMgcmVwbzEKKworRGVzY3JpcHRpb24gZm9yIHJlcG8xClwgTm8gbmV3bGluZSBhdCBlbmQgb2YgZmlsZQo="
41+
expectedContent := "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK"
4242
assert.Equal(t, expectedContent, gitBlobResponse.Content)
4343

4444
// Tests a private repo with no token so will fail

models/error.go

-15
Original file line numberDiff line numberDiff line change
@@ -874,21 +874,6 @@ func (err ErrUserDoesNotHaveAccessToRepo) Error() string {
874874
// |______ / |__| (____ /___| /\___ >___| /
875875
// \/ \/ \/ \/ \/
876876

877-
// ErrBranchNotExist represents a "BranchNotExist" kind of error.
878-
type ErrBranchNotExist struct {
879-
Name string
880-
}
881-
882-
// IsErrBranchNotExist checks if an error is a ErrBranchNotExist.
883-
func IsErrBranchNotExist(err error) bool {
884-
_, ok := err.(ErrBranchNotExist)
885-
return ok
886-
}
887-
888-
func (err ErrBranchNotExist) Error() string {
889-
return fmt.Sprintf("branch does not exist [name: %s]", err.Name)
890-
}
891-
892877
// ErrBranchAlreadyExists represents an error that branch with such name already exists.
893878
type ErrBranchAlreadyExists struct {
894879
BranchName string

models/pull.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright 2015 The Gogs Authors. All rights reserved.
2+
// Copyright 2019 The Gitea Authors. All rights reserved.
23
// Use of this source code is governed by a MIT-style
34
// license that can be found in the LICENSE file.
45

@@ -165,8 +166,8 @@ func (pr *PullRequest) APIFormat() *api.PullRequest {
165166

166167
func (pr *PullRequest) apiFormat(e Engine) *api.PullRequest {
167168
var (
168-
baseBranch *Branch
169-
headBranch *Branch
169+
baseBranch *git.Branch
170+
headBranch *git.Branch
170171
baseCommit *git.Commit
171172
headCommit *git.Commit
172173
err error

models/repo_branch.go

+10-47
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright 2016 The Gogs Authors. All rights reserved.
2+
// Copyright 2019 The Gitea Authors. All rights reserved.
23
// Use of this source code is governed by a MIT-style
34
// license that can be found in the LICENSE file.
45

@@ -86,53 +87,24 @@ func (repo *Repository) DeleteLocalBranch(branchName string) error {
8687
return deleteLocalBranch(repo.LocalCopyPath(), repo.DefaultBranch, branchName)
8788
}
8889

89-
// Branch holds the branch information
90-
type Branch struct {
91-
Path string
92-
Name string
93-
}
94-
95-
// GetBranchesByPath returns a branch by it's path
96-
func GetBranchesByPath(path string) ([]*Branch, error) {
97-
gitRepo, err := git.OpenRepository(path)
98-
if err != nil {
99-
return nil, err
100-
}
101-
102-
brs, err := gitRepo.GetBranches()
103-
if err != nil {
104-
return nil, err
105-
}
106-
107-
branches := make([]*Branch, len(brs))
108-
for i := range brs {
109-
branches[i] = &Branch{
110-
Path: path,
111-
Name: brs[i],
112-
}
113-
}
114-
return branches, nil
115-
}
116-
11790
// CanCreateBranch returns true if repository meets the requirements for creating new branches.
11891
func (repo *Repository) CanCreateBranch() bool {
11992
return !repo.IsMirror
12093
}
12194

122-
// GetBranch returns a branch by it's name
123-
func (repo *Repository) GetBranch(branch string) (*Branch, error) {
124-
if !git.IsBranchExist(repo.RepoPath(), branch) {
125-
return nil, ErrBranchNotExist{branch}
95+
// GetBranch returns a branch by its name
96+
func (repo *Repository) GetBranch(branch string) (*git.Branch, error) {
97+
gitRepo, err := git.OpenRepository(repo.RepoPath())
98+
if err != nil {
99+
return nil, err
126100
}
127-
return &Branch{
128-
Path: repo.RepoPath(),
129-
Name: branch,
130-
}, nil
101+
102+
return gitRepo.GetBranch(branch)
131103
}
132104

133105
// GetBranches returns all the branches of a repository
134-
func (repo *Repository) GetBranches() ([]*Branch, error) {
135-
return GetBranchesByPath(repo.RepoPath())
106+
func (repo *Repository) GetBranches() ([]*git.Branch, error) {
107+
return git.GetBranchesByPath(repo.RepoPath())
136108
}
137109

138110
// CheckBranchName validates branch name with existing repository branches
@@ -257,12 +229,3 @@ func (repo *Repository) CreateNewBranchFromCommit(doer *User, commit, branchName
257229

258230
return nil
259231
}
260-
261-
// GetCommit returns all the commits of a branch
262-
func (branch *Branch) GetCommit() (*git.Commit, error) {
263-
gitRepo, err := git.OpenRepository(branch.Path)
264-
if err != nil {
265-
return nil, err
266-
}
267-
return gitRepo.GetBranchCommit(branch.Name)
268-
}

modules/context/repo.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,11 @@ func (r *Repository) GetEditorconfig() (*editorconfig.Editorconfig, error) {
157157
if treeEntry.Blob().Size() >= setting.UI.MaxDisplayFileSize {
158158
return nil, git.ErrNotExist{ID: "", RelPath: ".editorconfig"}
159159
}
160-
reader, err := treeEntry.Blob().Data()
160+
reader, err := treeEntry.Blob().DataAsync()
161161
if err != nil {
162162
return nil, err
163163
}
164+
defer reader.Close()
164165
data, err := ioutil.ReadAll(reader)
165166
if err != nil {
166167
return nil, err

modules/git/blob.go

+15-51
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,40 @@
11
// Copyright 2015 The Gogs Authors. All rights reserved.
2+
// Copyright 2019 The Gitea Authors. All rights reserved.
23
// Use of this source code is governed by a MIT-style
34
// license that can be found in the LICENSE file.
45

56
package git
67

78
import (
8-
"bytes"
99
"encoding/base64"
10-
"fmt"
1110
"io"
1211
"io/ioutil"
13-
"os"
14-
"os/exec"
12+
13+
"gopkg.in/src-d/go-git.v4/plumbing"
1514
)
1615

1716
// Blob represents a Git object.
1817
type Blob struct {
19-
repo *Repository
20-
*TreeEntry
21-
}
22-
23-
// Data gets content of blob all at once and wrap it as io.Reader.
24-
// This can be very slow and memory consuming for huge content.
25-
func (b *Blob) Data() (io.Reader, error) {
26-
stdout := new(bytes.Buffer)
27-
stderr := new(bytes.Buffer)
28-
29-
// Preallocate memory to save ~50% memory usage on big files.
30-
stdout.Grow(int(b.Size() + 2048))
31-
32-
if err := b.DataPipeline(stdout, stderr); err != nil {
33-
return nil, concatenateError(err, stderr.String())
34-
}
35-
return stdout, nil
36-
}
18+
ID SHA1
3719

38-
// DataPipeline gets content of blob and write the result or error to stdout or stderr
39-
func (b *Blob) DataPipeline(stdout, stderr io.Writer) error {
40-
return NewCommand("show", b.ID.String()).RunInDirPipeline(b.repo.Path, stdout, stderr)
41-
}
42-
43-
type cmdReadCloser struct {
44-
cmd *exec.Cmd
45-
stdout io.Reader
46-
}
47-
48-
func (c cmdReadCloser) Read(p []byte) (int, error) {
49-
return c.stdout.Read(p)
50-
}
51-
52-
func (c cmdReadCloser) Close() error {
53-
io.Copy(ioutil.Discard, c.stdout)
54-
return c.cmd.Wait()
20+
gogitEncodedObj plumbing.EncodedObject
21+
name string
5522
}
5623

5724
// DataAsync gets a ReadCloser for the contents of a blob without reading it all.
5825
// Calling the Close function on the result will discard all unread output.
5926
func (b *Blob) DataAsync() (io.ReadCloser, error) {
60-
cmd := exec.Command("git", "show", b.ID.String())
61-
cmd.Dir = b.repo.Path
62-
cmd.Stderr = os.Stderr
63-
64-
stdout, err := cmd.StdoutPipe()
65-
if err != nil {
66-
return nil, fmt.Errorf("StdoutPipe: %v", err)
67-
}
27+
return b.gogitEncodedObj.Reader()
28+
}
6829

69-
if err = cmd.Start(); err != nil {
70-
return nil, fmt.Errorf("Start: %v", err)
71-
}
30+
// Size returns the uncompressed size of the blob
31+
func (b *Blob) Size() int64 {
32+
return b.gogitEncodedObj.Size()
33+
}
7234

73-
return cmdReadCloser{stdout: stdout, cmd: cmd}, nil
35+
// Name returns name of the tree entry this blob object was created from (or empty string)
36+
func (b *Blob) Name() string {
37+
return b.name
7438
}
7539

7640
// GetBlobContentBase64 Reads the content of the blob with a base64 encode and returns the encoded string

modules/git/blob_test.go

+18-27
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,18 @@
11
// Copyright 2015 The Gogs Authors. All rights reserved.
2+
// Copyright 2019 The Gitea Authors. All rights reserved.
23
// Use of this source code is governed by a MIT-style
34
// license that can be found in the LICENSE file.
45

56
package git
67

78
import (
8-
"bytes"
99
"io/ioutil"
1010
"testing"
1111

1212
"github.com/stretchr/testify/assert"
1313
"github.com/stretchr/testify/require"
1414
)
1515

16-
var repoSelf = &Repository{
17-
Path: "./",
18-
}
19-
20-
var testBlob = &Blob{
21-
repo: repoSelf,
22-
TreeEntry: &TreeEntry{
23-
ID: MustIDFromString("a8d4b49dd073a4a38a7e58385eeff7cc52568697"),
24-
ptree: &Tree{
25-
repo: repoSelf,
26-
},
27-
},
28-
}
29-
3016
func TestBlob_Data(t *testing.T) {
3117
output := `Copyright (c) 2016 The Gitea Authors
3218
Copyright (c) 2015 The Gogs Authors
@@ -49,32 +35,37 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
4935
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
5036
THE SOFTWARE.
5137
`
38+
repo, err := OpenRepository("../../.git")
39+
assert.NoError(t, err)
40+
testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697")
41+
assert.NoError(t, err)
5242

53-
r, err := testBlob.Data()
43+
r, err := testBlob.DataAsync()
5444
assert.NoError(t, err)
5545
require.NotNil(t, r)
46+
defer r.Close()
5647

5748
data, err := ioutil.ReadAll(r)
5849
assert.NoError(t, err)
5950
assert.Equal(t, output, string(data))
6051
}
6152

6253
func Benchmark_Blob_Data(b *testing.B) {
63-
for i := 0; i < b.N; i++ {
64-
r, err := testBlob.Data()
65-
if err != nil {
66-
b.Fatal(err)
67-
}
68-
ioutil.ReadAll(r)
54+
repo, err := OpenRepository("../../.git")
55+
if err != nil {
56+
b.Fatal(err)
57+
}
58+
testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697")
59+
if err != nil {
60+
b.Fatal(err)
6961
}
70-
}
7162

72-
func Benchmark_Blob_DataPipeline(b *testing.B) {
73-
stdout := new(bytes.Buffer)
7463
for i := 0; i < b.N; i++ {
75-
stdout.Reset()
76-
if err := testBlob.DataPipeline(stdout, nil); err != nil {
64+
r, err := testBlob.DataAsync()
65+
if err != nil {
7766
b.Fatal(err)
7867
}
68+
defer r.Close()
69+
ioutil.ReadAll(r)
7970
}
8071
}

0 commit comments

Comments
 (0)