mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Improve listing performance by using go-git (#6478)
* Use go-git for tree reading and commit info lookup. Signed-off-by: Filip Navara <navara@emclient.com> * Use TreeEntry.IsRegular() instead of ObjectType that was removed. Signed-off-by: Filip Navara <navara@emclient.com> * Use the treePath to optimize commit info search. Signed-off-by: Filip Navara <navara@emclient.com> * Extract the latest commit at treePath along with the other commits. Signed-off-by: Filip Navara <navara@emclient.com> * Fix listing commit info for a directory that was created in one commit and never modified after. Signed-off-by: Filip Navara <navara@emclient.com> * Avoid nearly all external 'git' invocations when doing directory listing (.editorconfig code path is still hit). Signed-off-by: Filip Navara <navara@emclient.com> * Use go-git for reading blobs. Signed-off-by: Filip Navara <navara@emclient.com> * Make SHA1 type alias for plumbing.Hash in go-git. Signed-off-by: Filip Navara <navara@emclient.com> * Make Signature type alias for object.Signature in go-git. Signed-off-by: Filip Navara <navara@emclient.com> * Fix GetCommitsInfo for repository with only one commit. Signed-off-by: Filip Navara <navara@emclient.com> * Fix PGP signature verification. Signed-off-by: Filip Navara <navara@emclient.com> * Fix issues with walking commit graph across merges. Signed-off-by: Filip Navara <navara@emclient.com> * Fix typo in condition. Signed-off-by: Filip Navara <navara@emclient.com> * Speed up loading branch list by keeping the repository reference (and thus all the loaded packfile indexes). Signed-off-by: Filip Navara <navara@emclient.com> * Fix lising submodules. Signed-off-by: Filip Navara <navara@emclient.com> * Fix build Signed-off-by: Filip Navara <navara@emclient.com> * Add back commit cache because of name-rev Signed-off-by: Filip Navara <navara@emclient.com> * Fix tests Signed-off-by: Filip Navara <navara@emclient.com> * Fix code style * Fix spelling * Address PR feedback Signed-off-by: Filip Navara <navara@emclient.com> * Update vendor module list Signed-off-by: Filip Navara <navara@emclient.com> * Fix getting trees by commit id Signed-off-by: Filip Navara <navara@emclient.com> * Fix remaining unit test failures * Fix GetTreeBySHA * Avoid running `git name-rev` if not necessary Signed-off-by: Filip Navara <navara@emclient.com> * 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
This commit is contained in:
		
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							@@ -32,7 +32,7 @@ require (
 | 
				
			|||||||
	github.com/dgrijalva/jwt-go v0.0.0-20161101193935-9ed569b5d1ac
 | 
						github.com/dgrijalva/jwt-go v0.0.0-20161101193935-9ed569b5d1ac
 | 
				
			||||||
	github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect
 | 
						github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect
 | 
				
			||||||
	github.com/elazarl/go-bindata-assetfs v0.0.0-20151224045452-57eb5e1fc594 // indirect
 | 
						github.com/elazarl/go-bindata-assetfs v0.0.0-20151224045452-57eb5e1fc594 // indirect
 | 
				
			||||||
	github.com/emirpasic/gods v1.12.0 // indirect
 | 
						github.com/emirpasic/gods v1.12.0
 | 
				
			||||||
	github.com/etcd-io/bbolt v1.3.2 // indirect
 | 
						github.com/etcd-io/bbolt v1.3.2 // indirect
 | 
				
			||||||
	github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a
 | 
						github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a
 | 
				
			||||||
	github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
 | 
						github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
 | 
				
			||||||
@@ -127,7 +127,7 @@ require (
 | 
				
			|||||||
	gopkg.in/ldap.v3 v3.0.2
 | 
						gopkg.in/ldap.v3 v3.0.2
 | 
				
			||||||
	gopkg.in/macaron.v1 v1.3.2
 | 
						gopkg.in/macaron.v1 v1.3.2
 | 
				
			||||||
	gopkg.in/redis.v2 v2.3.2 // indirect
 | 
						gopkg.in/redis.v2 v2.3.2 // indirect
 | 
				
			||||||
	gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect
 | 
						gopkg.in/src-d/go-billy.v4 v4.3.0
 | 
				
			||||||
	gopkg.in/src-d/go-git.v4 v4.10.0
 | 
						gopkg.in/src-d/go-git.v4 v4.10.0
 | 
				
			||||||
	gopkg.in/testfixtures.v2 v2.5.0
 | 
						gopkg.in/testfixtures.v2 v2.5.0
 | 
				
			||||||
	mvdan.cc/xurls/v2 v2.0.0
 | 
						mvdan.cc/xurls/v2 v2.0.0
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,7 +38,7 @@ func TestAPIReposGitBlobs(t *testing.T) {
 | 
				
			|||||||
	var gitBlobResponse api.GitBlobResponse
 | 
						var gitBlobResponse api.GitBlobResponse
 | 
				
			||||||
	DecodeJSON(t, resp, &gitBlobResponse)
 | 
						DecodeJSON(t, resp, &gitBlobResponse)
 | 
				
			||||||
	assert.NotNil(t, gitBlobResponse)
 | 
						assert.NotNil(t, gitBlobResponse)
 | 
				
			||||||
	expectedContent := "Y29tbWl0IDY1ZjFiZjI3YmMzYmY3MGY2NDY1NzY1ODYzNWU2NjA5NGVkYmNiNGQKQXV0aG9yOiB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+CkRhdGU6ICAgU3VuIE1hciAxOSAxNjo0Nzo1OSAyMDE3IC0wNDAwCgogICAgSW5pdGlhbCBjb21taXQKCmRpZmYgLS1naXQgYS9SRUFETUUubWQgYi9SRUFETUUubWQKbmV3IGZpbGUgbW9kZSAxMDA2NDQKaW5kZXggMDAwMDAwMC4uNGI0ODUxYQotLS0gL2Rldi9udWxsCisrKyBiL1JFQURNRS5tZApAQCAtMCwwICsxLDMgQEAKKyMgcmVwbzEKKworRGVzY3JpcHRpb24gZm9yIHJlcG8xClwgTm8gbmV3bGluZSBhdCBlbmQgb2YgZmlsZQo="
 | 
						expectedContent := "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK"
 | 
				
			||||||
	assert.Equal(t, expectedContent, gitBlobResponse.Content)
 | 
						assert.Equal(t, expectedContent, gitBlobResponse.Content)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Tests a private repo with no token so will fail
 | 
						// Tests a private repo with no token so will fail
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -874,21 +874,6 @@ func (err ErrUserDoesNotHaveAccessToRepo) Error() string {
 | 
				
			|||||||
//  |______  / |__|  (____  /___|  /\___  >___|  /
 | 
					//  |______  / |__|  (____  /___|  /\___  >___|  /
 | 
				
			||||||
//         \/             \/     \/     \/     \/
 | 
					//         \/             \/     \/     \/     \/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ErrBranchNotExist represents a "BranchNotExist" kind of error.
 | 
					 | 
				
			||||||
type ErrBranchNotExist struct {
 | 
					 | 
				
			||||||
	Name string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// IsErrBranchNotExist checks if an error is a ErrBranchNotExist.
 | 
					 | 
				
			||||||
func IsErrBranchNotExist(err error) bool {
 | 
					 | 
				
			||||||
	_, ok := err.(ErrBranchNotExist)
 | 
					 | 
				
			||||||
	return ok
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (err ErrBranchNotExist) Error() string {
 | 
					 | 
				
			||||||
	return fmt.Sprintf("branch does not exist [name: %s]", err.Name)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ErrBranchAlreadyExists represents an error that branch with such name already exists.
 | 
					// ErrBranchAlreadyExists represents an error that branch with such name already exists.
 | 
				
			||||||
type ErrBranchAlreadyExists struct {
 | 
					type ErrBranchAlreadyExists struct {
 | 
				
			||||||
	BranchName string
 | 
						BranchName string
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -165,8 +166,8 @@ func (pr *PullRequest) APIFormat() *api.PullRequest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (pr *PullRequest) apiFormat(e Engine) *api.PullRequest {
 | 
					func (pr *PullRequest) apiFormat(e Engine) *api.PullRequest {
 | 
				
			||||||
	var (
 | 
						var (
 | 
				
			||||||
		baseBranch *Branch
 | 
							baseBranch *git.Branch
 | 
				
			||||||
		headBranch *Branch
 | 
							headBranch *git.Branch
 | 
				
			||||||
		baseCommit *git.Commit
 | 
							baseCommit *git.Commit
 | 
				
			||||||
		headCommit *git.Commit
 | 
							headCommit *git.Commit
 | 
				
			||||||
		err        error
 | 
							err        error
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
// Copyright 2016 The Gogs Authors. All rights reserved.
 | 
					// Copyright 2016 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -86,53 +87,24 @@ func (repo *Repository) DeleteLocalBranch(branchName string) error {
 | 
				
			|||||||
	return deleteLocalBranch(repo.LocalCopyPath(), repo.DefaultBranch, branchName)
 | 
						return deleteLocalBranch(repo.LocalCopyPath(), repo.DefaultBranch, branchName)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Branch holds the branch information
 | 
					 | 
				
			||||||
type Branch struct {
 | 
					 | 
				
			||||||
	Path string
 | 
					 | 
				
			||||||
	Name string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetBranchesByPath returns a branch by it's path
 | 
					 | 
				
			||||||
func GetBranchesByPath(path string) ([]*Branch, error) {
 | 
					 | 
				
			||||||
	gitRepo, err := git.OpenRepository(path)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	brs, err := gitRepo.GetBranches()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	branches := make([]*Branch, len(brs))
 | 
					 | 
				
			||||||
	for i := range brs {
 | 
					 | 
				
			||||||
		branches[i] = &Branch{
 | 
					 | 
				
			||||||
			Path: path,
 | 
					 | 
				
			||||||
			Name: brs[i],
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return branches, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// CanCreateBranch returns true if repository meets the requirements for creating new branches.
 | 
					// CanCreateBranch returns true if repository meets the requirements for creating new branches.
 | 
				
			||||||
func (repo *Repository) CanCreateBranch() bool {
 | 
					func (repo *Repository) CanCreateBranch() bool {
 | 
				
			||||||
	return !repo.IsMirror
 | 
						return !repo.IsMirror
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetBranch returns a branch by it's name
 | 
					// GetBranch returns a branch by its name
 | 
				
			||||||
func (repo *Repository) GetBranch(branch string) (*Branch, error) {
 | 
					func (repo *Repository) GetBranch(branch string) (*git.Branch, error) {
 | 
				
			||||||
	if !git.IsBranchExist(repo.RepoPath(), branch) {
 | 
						gitRepo, err := git.OpenRepository(repo.RepoPath())
 | 
				
			||||||
		return nil, ErrBranchNotExist{branch}
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return &Branch{
 | 
					
 | 
				
			||||||
		Path: repo.RepoPath(),
 | 
						return gitRepo.GetBranch(branch)
 | 
				
			||||||
		Name: branch,
 | 
					 | 
				
			||||||
	}, nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetBranches returns all the branches of a repository
 | 
					// GetBranches returns all the branches of a repository
 | 
				
			||||||
func (repo *Repository) GetBranches() ([]*Branch, error) {
 | 
					func (repo *Repository) GetBranches() ([]*git.Branch, error) {
 | 
				
			||||||
	return GetBranchesByPath(repo.RepoPath())
 | 
						return git.GetBranchesByPath(repo.RepoPath())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CheckBranchName validates branch name with existing repository branches
 | 
					// CheckBranchName validates branch name with existing repository branches
 | 
				
			||||||
@@ -257,12 +229,3 @@ func (repo *Repository) CreateNewBranchFromCommit(doer *User, commit, branchName
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetCommit returns all the commits of a branch
 | 
					 | 
				
			||||||
func (branch *Branch) GetCommit() (*git.Commit, error) {
 | 
					 | 
				
			||||||
	gitRepo, err := git.OpenRepository(branch.Path)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return gitRepo.GetBranchCommit(branch.Name)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -157,10 +157,11 @@ func (r *Repository) GetEditorconfig() (*editorconfig.Editorconfig, error) {
 | 
				
			|||||||
	if treeEntry.Blob().Size() >= setting.UI.MaxDisplayFileSize {
 | 
						if treeEntry.Blob().Size() >= setting.UI.MaxDisplayFileSize {
 | 
				
			||||||
		return nil, git.ErrNotExist{ID: "", RelPath: ".editorconfig"}
 | 
							return nil, git.ErrNotExist{ID: "", RelPath: ".editorconfig"}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	reader, err := treeEntry.Blob().Data()
 | 
						reader, err := treeEntry.Blob().DataAsync()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						defer reader.Close()
 | 
				
			||||||
	data, err := ioutil.ReadAll(reader)
 | 
						data, err := ioutil.ReadAll(reader)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,76 +1,40 @@
 | 
				
			|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"encoding/base64"
 | 
						"encoding/base64"
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"os"
 | 
					
 | 
				
			||||||
	"os/exec"
 | 
						"gopkg.in/src-d/go-git.v4/plumbing"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Blob represents a Git object.
 | 
					// Blob represents a Git object.
 | 
				
			||||||
type Blob struct {
 | 
					type Blob struct {
 | 
				
			||||||
	repo *Repository
 | 
						ID SHA1
 | 
				
			||||||
	*TreeEntry
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Data gets content of blob all at once and wrap it as io.Reader.
 | 
						gogitEncodedObj plumbing.EncodedObject
 | 
				
			||||||
// This can be very slow and memory consuming for huge content.
 | 
						name            string
 | 
				
			||||||
func (b *Blob) Data() (io.Reader, error) {
 | 
					 | 
				
			||||||
	stdout := new(bytes.Buffer)
 | 
					 | 
				
			||||||
	stderr := new(bytes.Buffer)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Preallocate memory to save ~50% memory usage on big files.
 | 
					 | 
				
			||||||
	stdout.Grow(int(b.Size() + 2048))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := b.DataPipeline(stdout, stderr); err != nil {
 | 
					 | 
				
			||||||
		return nil, concatenateError(err, stderr.String())
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return stdout, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// DataPipeline gets content of blob and write the result or error to stdout or stderr
 | 
					 | 
				
			||||||
func (b *Blob) DataPipeline(stdout, stderr io.Writer) error {
 | 
					 | 
				
			||||||
	return NewCommand("show", b.ID.String()).RunInDirPipeline(b.repo.Path, stdout, stderr)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type cmdReadCloser struct {
 | 
					 | 
				
			||||||
	cmd    *exec.Cmd
 | 
					 | 
				
			||||||
	stdout io.Reader
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (c cmdReadCloser) Read(p []byte) (int, error) {
 | 
					 | 
				
			||||||
	return c.stdout.Read(p)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (c cmdReadCloser) Close() error {
 | 
					 | 
				
			||||||
	io.Copy(ioutil.Discard, c.stdout)
 | 
					 | 
				
			||||||
	return c.cmd.Wait()
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DataAsync gets a ReadCloser for the contents of a blob without reading it all.
 | 
					// DataAsync gets a ReadCloser for the contents of a blob without reading it all.
 | 
				
			||||||
// Calling the Close function on the result will discard all unread output.
 | 
					// Calling the Close function on the result will discard all unread output.
 | 
				
			||||||
func (b *Blob) DataAsync() (io.ReadCloser, error) {
 | 
					func (b *Blob) DataAsync() (io.ReadCloser, error) {
 | 
				
			||||||
	cmd := exec.Command("git", "show", b.ID.String())
 | 
						return b.gogitEncodedObj.Reader()
 | 
				
			||||||
	cmd.Dir = b.repo.Path
 | 
					}
 | 
				
			||||||
	cmd.Stderr = os.Stderr
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	stdout, err := cmd.StdoutPipe()
 | 
					// Size returns the uncompressed size of the blob
 | 
				
			||||||
	if err != nil {
 | 
					func (b *Blob) Size() int64 {
 | 
				
			||||||
		return nil, fmt.Errorf("StdoutPipe: %v", err)
 | 
						return b.gogitEncodedObj.Size()
 | 
				
			||||||
	}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err = cmd.Start(); err != nil {
 | 
					// Name returns name of the tree entry this blob object was created from (or empty string)
 | 
				
			||||||
		return nil, fmt.Errorf("Start: %v", err)
 | 
					func (b *Blob) Name() string {
 | 
				
			||||||
	}
 | 
						return b.name
 | 
				
			||||||
 | 
					 | 
				
			||||||
	return cmdReadCloser{stdout: stdout, cmd: cmd}, nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetBlobContentBase64 Reads the content of the blob with a base64 encode and returns the encoded string
 | 
					// GetBlobContentBase64 Reads the content of the blob with a base64 encode and returns the encoded string
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -13,20 +13,6 @@ import (
 | 
				
			|||||||
	"github.com/stretchr/testify/require"
 | 
						"github.com/stretchr/testify/require"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var repoSelf = &Repository{
 | 
					 | 
				
			||||||
	Path: "./",
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var testBlob = &Blob{
 | 
					 | 
				
			||||||
	repo: repoSelf,
 | 
					 | 
				
			||||||
	TreeEntry: &TreeEntry{
 | 
					 | 
				
			||||||
		ID: MustIDFromString("a8d4b49dd073a4a38a7e58385eeff7cc52568697"),
 | 
					 | 
				
			||||||
		ptree: &Tree{
 | 
					 | 
				
			||||||
			repo: repoSelf,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestBlob_Data(t *testing.T) {
 | 
					func TestBlob_Data(t *testing.T) {
 | 
				
			||||||
	output := `Copyright (c) 2016 The Gitea Authors
 | 
						output := `Copyright (c) 2016 The Gitea Authors
 | 
				
			||||||
Copyright (c) 2015 The Gogs Authors
 | 
					Copyright (c) 2015 The Gogs Authors
 | 
				
			||||||
@@ -49,10 +35,15 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
					OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
				
			||||||
THE SOFTWARE.
 | 
					THE SOFTWARE.
 | 
				
			||||||
`
 | 
					`
 | 
				
			||||||
 | 
						repo, err := OpenRepository("../../.git")
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697")
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	r, err := testBlob.Data()
 | 
						r, err := testBlob.DataAsync()
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	require.NotNil(t, r)
 | 
						require.NotNil(t, r)
 | 
				
			||||||
 | 
						defer r.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data, err := ioutil.ReadAll(r)
 | 
						data, err := ioutil.ReadAll(r)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
@@ -60,21 +51,21 @@ THE SOFTWARE.
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func Benchmark_Blob_Data(b *testing.B) {
 | 
					func Benchmark_Blob_Data(b *testing.B) {
 | 
				
			||||||
 | 
						repo, err := OpenRepository("../../.git")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							b.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							b.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i := 0; i < b.N; i++ {
 | 
						for i := 0; i < b.N; i++ {
 | 
				
			||||||
		r, err := testBlob.Data()
 | 
							r, err := testBlob.DataAsync()
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			b.Fatal(err)
 | 
								b.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							defer r.Close()
 | 
				
			||||||
		ioutil.ReadAll(r)
 | 
							ioutil.ReadAll(r)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func Benchmark_Blob_DataPipeline(b *testing.B) {
 | 
					 | 
				
			||||||
	stdout := new(bytes.Buffer)
 | 
					 | 
				
			||||||
	for i := 0; i < b.N; i++ {
 | 
					 | 
				
			||||||
		stdout.Reset()
 | 
					 | 
				
			||||||
		if err := testBlob.DataPipeline(stdout, nil); err != nil {
 | 
					 | 
				
			||||||
			b.Fatal(err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,8 @@ import (
 | 
				
			|||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing/object"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Commit represents a git commit.
 | 
					// Commit represents a git commit.
 | 
				
			||||||
@@ -36,20 +38,59 @@ type CommitGPGSignature struct {
 | 
				
			|||||||
	Payload   string //TODO check if can be reconstruct from the rest of commit information to not have duplicate data
 | 
						Payload   string //TODO check if can be reconstruct from the rest of commit information to not have duplicate data
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// similar to https://github.com/git/git/blob/3bc53220cb2dcf709f7a027a3f526befd021d858/commit.c#L1128
 | 
					func convertPGPSignature(c *object.Commit) *CommitGPGSignature {
 | 
				
			||||||
func newGPGSignatureFromCommitline(data []byte, signatureStart int, tag bool) (*CommitGPGSignature, error) {
 | 
						if c.PGPSignature == "" {
 | 
				
			||||||
	sig := new(CommitGPGSignature)
 | 
							return nil
 | 
				
			||||||
	signatureEnd := bytes.LastIndex(data, []byte("-----END PGP SIGNATURE-----"))
 | 
					 | 
				
			||||||
	if signatureEnd == -1 {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("end of commit signature not found")
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	sig.Signature = strings.Replace(string(data[signatureStart:signatureEnd+27]), "\n ", "\n", -1)
 | 
					
 | 
				
			||||||
	if tag {
 | 
						var w strings.Builder
 | 
				
			||||||
		sig.Payload = string(data[:signatureStart-1])
 | 
						var err error
 | 
				
			||||||
	} else {
 | 
					
 | 
				
			||||||
		sig.Payload = string(data[:signatureStart-8]) + string(data[signatureEnd+27:])
 | 
						if _, err = fmt.Fprintf(&w, "tree %s\n", c.TreeHash.String()); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, parent := range c.ParentHashes {
 | 
				
			||||||
 | 
							if _, err = fmt.Fprintf(&w, "parent %s\n", parent.String()); err != nil {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err = fmt.Fprint(&w, "author "); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = c.Author.Encode(&w); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err = fmt.Fprint(&w, "\ncommitter "); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = c.Committer.Encode(&w); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &CommitGPGSignature{
 | 
				
			||||||
 | 
							Signature: c.PGPSignature,
 | 
				
			||||||
 | 
							Payload:   w.String(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func convertCommit(c *object.Commit) *Commit {
 | 
				
			||||||
 | 
						return &Commit{
 | 
				
			||||||
 | 
							ID:            c.Hash,
 | 
				
			||||||
 | 
							CommitMessage: c.Message,
 | 
				
			||||||
 | 
							Committer:     &c.Committer,
 | 
				
			||||||
 | 
							Author:        &c.Author,
 | 
				
			||||||
 | 
							Signature:     convertPGPSignature(c),
 | 
				
			||||||
 | 
							parents:       c.ParentHashes,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return sig, nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Message returns the commit message. Same as retrieving CommitMessage directly.
 | 
					// Message returns the commit message. Same as retrieving CommitMessage directly.
 | 
				
			||||||
@@ -281,11 +322,13 @@ func (c *Commit) GetSubModules() (*ObjectCache, error) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	rd, err := entry.Blob().Data()
 | 
					
 | 
				
			||||||
 | 
						rd, err := entry.Blob().DataAsync()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer rd.Close()
 | 
				
			||||||
	scanner := bufio.NewScanner(rd)
 | 
						scanner := bufio.NewScanner(rd)
 | 
				
			||||||
	c.submoduleCache = newObjectCache()
 | 
						c.submoduleCache = newObjectCache()
 | 
				
			||||||
	var ismodule bool
 | 
						var ismodule bool
 | 
				
			||||||
@@ -326,6 +369,17 @@ func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
 | 
				
			|||||||
	return nil, nil
 | 
						return nil, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetBranchName gets the closes branch name (as returned by 'git name-rev')
 | 
				
			||||||
 | 
					func (c *Commit) GetBranchName() (string, error) {
 | 
				
			||||||
 | 
						data, err := NewCommand("name-rev", c.ID.String()).RunInDirBytes(c.repo.Path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// name-rev commitID output will be "COMMIT_ID master" or "COMMIT_ID master~12"
 | 
				
			||||||
 | 
						return strings.Split(strings.Split(string(data), " ")[1], "~")[0], nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CommitFileStatus represents status of files in a commit.
 | 
					// CommitFileStatus represents status of files in a commit.
 | 
				
			||||||
type CommitFileStatus struct {
 | 
					type CommitFileStatus struct {
 | 
				
			||||||
	Added    []string
 | 
						Added    []string
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,325 +5,237 @@
 | 
				
			|||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bufio"
 | 
						"github.com/emirpasic/gods/trees/binaryheap"
 | 
				
			||||||
	"context"
 | 
						"gopkg.in/src-d/go-git.v4/plumbing"
 | 
				
			||||||
	"fmt"
 | 
						"gopkg.in/src-d/go-git.v4/plumbing/object"
 | 
				
			||||||
	"os/exec"
 | 
					 | 
				
			||||||
	"path"
 | 
					 | 
				
			||||||
	"runtime"
 | 
					 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
	"sync"
 | 
					 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					 | 
				
			||||||
	// parameters for searching for commit infos. If the untargeted search has
 | 
					 | 
				
			||||||
	// not found any entries in the past 5 commits, and 12 or fewer entries
 | 
					 | 
				
			||||||
	// remain, then we'll just let the targeted-searching threads finish off,
 | 
					 | 
				
			||||||
	// and stop the untargeted search to not interfere.
 | 
					 | 
				
			||||||
	deferToTargetedSearchColdStreak          = 5
 | 
					 | 
				
			||||||
	deferToTargetedSearchNumRemainingEntries = 12
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// getCommitsInfoState shared state while getting commit info for entries
 | 
					 | 
				
			||||||
type getCommitsInfoState struct {
 | 
					 | 
				
			||||||
	lock sync.Mutex
 | 
					 | 
				
			||||||
	/* read-only fields, can be read without the mutex */
 | 
					 | 
				
			||||||
	// entries and entryPaths are read-only after initialization, so they can
 | 
					 | 
				
			||||||
	// safely be read without the mutex
 | 
					 | 
				
			||||||
	entries []*TreeEntry
 | 
					 | 
				
			||||||
	// set of filepaths to get info for
 | 
					 | 
				
			||||||
	entryPaths map[string]struct{}
 | 
					 | 
				
			||||||
	treePath   string
 | 
					 | 
				
			||||||
	headCommit *Commit
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/* mutable fields, must hold mutex to read or write */
 | 
					 | 
				
			||||||
	// map from filepath to commit
 | 
					 | 
				
			||||||
	commits map[string]*Commit
 | 
					 | 
				
			||||||
	// set of filepaths that have been or are being searched for in a target search
 | 
					 | 
				
			||||||
	targetedPaths map[string]struct{}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (state *getCommitsInfoState) numRemainingEntries() int {
 | 
					 | 
				
			||||||
	state.lock.Lock()
 | 
					 | 
				
			||||||
	defer state.lock.Unlock()
 | 
					 | 
				
			||||||
	return len(state.entries) - len(state.commits)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// getTargetEntryPath Returns the next path for a targeted-searching thread to
 | 
					 | 
				
			||||||
// search for, or returns the empty string if nothing left to search for
 | 
					 | 
				
			||||||
func (state *getCommitsInfoState) getTargetedEntryPath() string {
 | 
					 | 
				
			||||||
	var targetedEntryPath string
 | 
					 | 
				
			||||||
	state.lock.Lock()
 | 
					 | 
				
			||||||
	defer state.lock.Unlock()
 | 
					 | 
				
			||||||
	for _, entry := range state.entries {
 | 
					 | 
				
			||||||
		entryPath := path.Join(state.treePath, entry.Name())
 | 
					 | 
				
			||||||
		if _, ok := state.commits[entryPath]; ok {
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		} else if _, ok = state.targetedPaths[entryPath]; ok {
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		targetedEntryPath = entryPath
 | 
					 | 
				
			||||||
		state.targetedPaths[entryPath] = struct{}{}
 | 
					 | 
				
			||||||
		break
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return targetedEntryPath
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// repeatedly perform targeted searches for unpopulated entries
 | 
					 | 
				
			||||||
func targetedSearch(state *getCommitsInfoState, done chan error, cache LastCommitCache) {
 | 
					 | 
				
			||||||
	for {
 | 
					 | 
				
			||||||
		entryPath := state.getTargetedEntryPath()
 | 
					 | 
				
			||||||
		if len(entryPath) == 0 {
 | 
					 | 
				
			||||||
			done <- nil
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if cache != nil {
 | 
					 | 
				
			||||||
			commit, err := cache.Get(state.headCommit.repo.Path, state.headCommit.ID.String(), entryPath)
 | 
					 | 
				
			||||||
			if err == nil && commit != nil {
 | 
					 | 
				
			||||||
				state.update(entryPath, commit)
 | 
					 | 
				
			||||||
				continue
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		command := NewCommand("rev-list", "-1", state.headCommit.ID.String(), "--", entryPath)
 | 
					 | 
				
			||||||
		output, err := command.RunInDir(state.headCommit.repo.Path)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			done <- err
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		id, err := NewIDFromString(strings.TrimSpace(output))
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			done <- err
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		commit, err := state.headCommit.repo.getCommit(id)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			done <- err
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		state.update(entryPath, commit)
 | 
					 | 
				
			||||||
		if cache != nil {
 | 
					 | 
				
			||||||
			cache.Put(state.headCommit.repo.Path, state.headCommit.ID.String(), entryPath, commit)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func initGetCommitInfoState(entries Entries, headCommit *Commit, treePath string) *getCommitsInfoState {
 | 
					 | 
				
			||||||
	entryPaths := make(map[string]struct{}, len(entries))
 | 
					 | 
				
			||||||
	for _, entry := range entries {
 | 
					 | 
				
			||||||
		entryPaths[path.Join(treePath, entry.Name())] = struct{}{}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if treePath = path.Clean(treePath); treePath == "." {
 | 
					 | 
				
			||||||
		treePath = ""
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return &getCommitsInfoState{
 | 
					 | 
				
			||||||
		entries:       entries,
 | 
					 | 
				
			||||||
		entryPaths:    entryPaths,
 | 
					 | 
				
			||||||
		commits:       make(map[string]*Commit, len(entries)),
 | 
					 | 
				
			||||||
		targetedPaths: make(map[string]struct{}, len(entries)),
 | 
					 | 
				
			||||||
		treePath:      treePath,
 | 
					 | 
				
			||||||
		headCommit:    headCommit,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetCommitsInfo gets information of all commits that are corresponding to these entries
 | 
					// GetCommitsInfo gets information of all commits that are corresponding to these entries
 | 
				
			||||||
func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCommitCache) ([][]interface{}, error) {
 | 
					func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCommitCache) ([][]interface{}, *Commit, error) {
 | 
				
			||||||
	state := initGetCommitInfoState(tes, commit, treePath)
 | 
						entryPaths := make([]string, len(tes)+1)
 | 
				
			||||||
	if err := getCommitsInfo(state, cache); err != nil {
 | 
						// Get the commit for the treePath itself
 | 
				
			||||||
		return nil, err
 | 
						entryPaths[0] = ""
 | 
				
			||||||
 | 
						for i, entry := range tes {
 | 
				
			||||||
 | 
							entryPaths[i+1] = entry.Name()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if len(state.commits) < len(state.entryPaths) {
 | 
					
 | 
				
			||||||
		return nil, fmt.Errorf("could not find commits for all entries")
 | 
						c, err := commit.repo.gogitRepo.CommitObject(plumbing.Hash(commit.ID))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						revs, err := getLastCommitForPaths(c, treePath, entryPaths)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commit.repo.gogitStorage.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	commitsInfo := make([][]interface{}, len(tes))
 | 
						commitsInfo := make([][]interface{}, len(tes))
 | 
				
			||||||
	for i, entry := range tes {
 | 
						for i, entry := range tes {
 | 
				
			||||||
		commit, ok := state.commits[path.Join(treePath, entry.Name())]
 | 
							if rev, ok := revs[entry.Name()]; ok {
 | 
				
			||||||
		if !ok {
 | 
								entryCommit := convertCommit(rev)
 | 
				
			||||||
			return nil, fmt.Errorf("could not find commit for %s", entry.Name())
 | 
								if entry.IsSubModule() {
 | 
				
			||||||
		}
 | 
									subModuleURL := ""
 | 
				
			||||||
		switch entry.Type {
 | 
									if subModule, err := commit.GetSubModule(entry.Name()); err != nil {
 | 
				
			||||||
		case ObjectCommit:
 | 
										return nil, nil, err
 | 
				
			||||||
			subModuleURL := ""
 | 
									} else if subModule != nil {
 | 
				
			||||||
			if subModule, err := state.headCommit.GetSubModule(entry.Name()); err != nil {
 | 
										subModuleURL = subModule.URL
 | 
				
			||||||
				return nil, err
 | 
					 | 
				
			||||||
			} else if subModule != nil {
 | 
					 | 
				
			||||||
				subModuleURL = subModule.URL
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			subModuleFile := NewSubModuleFile(commit, subModuleURL, entry.ID.String())
 | 
					 | 
				
			||||||
			commitsInfo[i] = []interface{}{entry, subModuleFile}
 | 
					 | 
				
			||||||
		default:
 | 
					 | 
				
			||||||
			commitsInfo[i] = []interface{}{entry, commit}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return commitsInfo, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (state *getCommitsInfoState) cleanEntryPath(rawEntryPath string) (string, error) {
 | 
					 | 
				
			||||||
	if rawEntryPath[0] == '"' {
 | 
					 | 
				
			||||||
		var err error
 | 
					 | 
				
			||||||
		rawEntryPath, err = strconv.Unquote(rawEntryPath)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return rawEntryPath, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	var entryNameStartIndex int
 | 
					 | 
				
			||||||
	if len(state.treePath) > 0 {
 | 
					 | 
				
			||||||
		entryNameStartIndex = len(state.treePath) + 1
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if index := strings.IndexByte(rawEntryPath[entryNameStartIndex:], '/'); index >= 0 {
 | 
					 | 
				
			||||||
		return rawEntryPath[:entryNameStartIndex+index], nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return rawEntryPath, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// update report that the given path was last modified by the given commit.
 | 
					 | 
				
			||||||
// Returns whether state.commits was updated
 | 
					 | 
				
			||||||
func (state *getCommitsInfoState) update(entryPath string, commit *Commit) bool {
 | 
					 | 
				
			||||||
	if _, ok := state.entryPaths[entryPath]; !ok {
 | 
					 | 
				
			||||||
		return false
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var updated bool
 | 
					 | 
				
			||||||
	state.lock.Lock()
 | 
					 | 
				
			||||||
	defer state.lock.Unlock()
 | 
					 | 
				
			||||||
	if _, ok := state.commits[entryPath]; !ok {
 | 
					 | 
				
			||||||
		state.commits[entryPath] = commit
 | 
					 | 
				
			||||||
		updated = true
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return updated
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getCommitsInfoPretty = "--pretty=format:%H %ct %s"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func getCommitsInfo(state *getCommitsInfoState, cache LastCommitCache) error {
 | 
					 | 
				
			||||||
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
 | 
					 | 
				
			||||||
	defer cancel()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	args := []string{"log", state.headCommit.ID.String(), getCommitsInfoPretty, "--name-status", "-c"}
 | 
					 | 
				
			||||||
	if len(state.treePath) > 0 {
 | 
					 | 
				
			||||||
		args = append(args, "--", state.treePath)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	cmd := exec.CommandContext(ctx, "git", args...)
 | 
					 | 
				
			||||||
	cmd.Dir = state.headCommit.repo.Path
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	readCloser, err := cmd.StdoutPipe()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := cmd.Start(); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	// it's okay to ignore the error returned by cmd.Wait(); we expect the
 | 
					 | 
				
			||||||
	// subprocess to sometimes have a non-zero exit status, since we may
 | 
					 | 
				
			||||||
	// prematurely close stdout, resulting in a broken pipe.
 | 
					 | 
				
			||||||
	defer cmd.Wait()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	numThreads := runtime.NumCPU()
 | 
					 | 
				
			||||||
	done := make(chan error, numThreads)
 | 
					 | 
				
			||||||
	for i := 0; i < numThreads; i++ {
 | 
					 | 
				
			||||||
		go targetedSearch(state, done, cache)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	scanner := bufio.NewScanner(readCloser)
 | 
					 | 
				
			||||||
	err = state.processGitLogOutput(scanner)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// it is important that we close stdout here; if we do not close
 | 
					 | 
				
			||||||
	// stdout, the subprocess will keep running, and the deffered call
 | 
					 | 
				
			||||||
	// cmd.Wait() may block for a long time.
 | 
					 | 
				
			||||||
	if closeErr := readCloser.Close(); closeErr != nil && err == nil {
 | 
					 | 
				
			||||||
		err = closeErr
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for i := 0; i < numThreads; i++ {
 | 
					 | 
				
			||||||
		doneErr := <-done
 | 
					 | 
				
			||||||
		if doneErr != nil && err == nil {
 | 
					 | 
				
			||||||
			err = doneErr
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return err
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (state *getCommitsInfoState) processGitLogOutput(scanner *bufio.Scanner) error {
 | 
					 | 
				
			||||||
	// keep a local cache of seen paths to avoid acquiring a lock for paths
 | 
					 | 
				
			||||||
	// we've already seen
 | 
					 | 
				
			||||||
	seenPaths := make(map[string]struct{}, len(state.entryPaths))
 | 
					 | 
				
			||||||
	// number of consecutive commits without any finds
 | 
					 | 
				
			||||||
	coldStreak := 0
 | 
					 | 
				
			||||||
	var commit *Commit
 | 
					 | 
				
			||||||
	var err error
 | 
					 | 
				
			||||||
	for scanner.Scan() {
 | 
					 | 
				
			||||||
		line := scanner.Text()
 | 
					 | 
				
			||||||
		if len(line) == 0 { // in-between commits
 | 
					 | 
				
			||||||
			numRemainingEntries := state.numRemainingEntries()
 | 
					 | 
				
			||||||
			if numRemainingEntries == 0 {
 | 
					 | 
				
			||||||
				break
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if coldStreak >= deferToTargetedSearchColdStreak &&
 | 
					 | 
				
			||||||
				numRemainingEntries <= deferToTargetedSearchNumRemainingEntries {
 | 
					 | 
				
			||||||
				// stop this untargeted search, and let the targeted-search threads
 | 
					 | 
				
			||||||
				// finish the work
 | 
					 | 
				
			||||||
				break
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if line[0] >= 'A' && line[0] <= 'X' { // a file was changed by the current commit
 | 
					 | 
				
			||||||
			// look for the last tab, since for copies (C) and renames (R) two
 | 
					 | 
				
			||||||
			// filenames are printed: src, then dest
 | 
					 | 
				
			||||||
			tabIndex := strings.LastIndexByte(line, '\t')
 | 
					 | 
				
			||||||
			if tabIndex < 1 {
 | 
					 | 
				
			||||||
				return fmt.Errorf("misformatted line: %s", line)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			entryPath, err := state.cleanEntryPath(line[tabIndex+1:])
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if _, ok := seenPaths[entryPath]; !ok {
 | 
					 | 
				
			||||||
				if state.update(entryPath, commit) {
 | 
					 | 
				
			||||||
					coldStreak = 0
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				seenPaths[entryPath] = struct{}{}
 | 
									subModuleFile := NewSubModuleFile(entryCommit, subModuleURL, entry.ID.String())
 | 
				
			||||||
 | 
									commitsInfo[i] = []interface{}{entry, subModuleFile}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									commitsInfo[i] = []interface{}{entry, entryCommit}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								commitsInfo[i] = []interface{}{entry, nil}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Retrieve the commit for the treePath itself (see above). We basically
 | 
				
			||||||
 | 
						// get it for free during the tree traversal and it's used for listing
 | 
				
			||||||
 | 
						// pages to display information about newest commit for a given path.
 | 
				
			||||||
 | 
						var treeCommit *Commit
 | 
				
			||||||
 | 
						if rev, ok := revs[""]; ok {
 | 
				
			||||||
 | 
							treeCommit = convertCommit(rev)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return commitsInfo, treeCommit, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type commitAndPaths struct {
 | 
				
			||||||
 | 
						commit *object.Commit
 | 
				
			||||||
 | 
						// Paths that are still on the branch represented by commit
 | 
				
			||||||
 | 
						paths []string
 | 
				
			||||||
 | 
						// Set of hashes for the paths
 | 
				
			||||||
 | 
						hashes map[string]plumbing.Hash
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getCommitTree(c *object.Commit, treePath string) (*object.Tree, error) {
 | 
				
			||||||
 | 
						tree, err := c.Tree()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Optimize deep traversals by focusing only on the specific tree
 | 
				
			||||||
 | 
						if treePath != "" {
 | 
				
			||||||
 | 
							tree, err = tree.Tree(treePath)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return tree, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getFullPath(treePath, path string) string {
 | 
				
			||||||
 | 
						if treePath != "" {
 | 
				
			||||||
 | 
							if path != "" {
 | 
				
			||||||
 | 
								return treePath + "/" + path
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return treePath
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return path
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getFileHashes(c *object.Commit, treePath string, paths []string) (map[string]plumbing.Hash, error) {
 | 
				
			||||||
 | 
						tree, err := getCommitTree(c, treePath)
 | 
				
			||||||
 | 
						if err == object.ErrDirectoryNotFound {
 | 
				
			||||||
 | 
							// The whole tree didn't exist, so return empty map
 | 
				
			||||||
 | 
							return make(map[string]plumbing.Hash), nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hashes := make(map[string]plumbing.Hash)
 | 
				
			||||||
 | 
						for _, path := range paths {
 | 
				
			||||||
 | 
							if path != "" {
 | 
				
			||||||
 | 
								entry, err := tree.FindEntry(path)
 | 
				
			||||||
 | 
								if err == nil {
 | 
				
			||||||
 | 
									hashes[path] = entry.Hash
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								hashes[path] = tree.Hash
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return hashes, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (map[string]*object.Commit, error) {
 | 
				
			||||||
 | 
						// We do a tree traversal with nodes sorted by commit time
 | 
				
			||||||
 | 
						seen := make(map[plumbing.Hash]bool)
 | 
				
			||||||
 | 
						heap := binaryheap.NewWith(func(a, b interface{}) int {
 | 
				
			||||||
 | 
							if a.(*commitAndPaths).commit.Committer.When.Before(b.(*commitAndPaths).commit.Committer.When) {
 | 
				
			||||||
 | 
								return 1
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return -1
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result := make(map[string]*object.Commit)
 | 
				
			||||||
 | 
						initialHashes, err := getFileHashes(c, treePath, paths)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Start search from the root commit and with full set of paths
 | 
				
			||||||
 | 
						heap.Push(&commitAndPaths{c, paths, initialHashes})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							cIn, ok := heap.Pop()
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							current := cIn.(*commitAndPaths)
 | 
				
			||||||
 | 
							currentID := current.commit.ID()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if seen[currentID] {
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							seen[currentID] = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// a new commit
 | 
							// Load the parent commits for the one we are currently examining
 | 
				
			||||||
		commit, err = parseCommitInfo(line)
 | 
							numParents := current.commit.NumParents()
 | 
				
			||||||
		if err != nil {
 | 
							var parents []*object.Commit
 | 
				
			||||||
			return err
 | 
							for i := 0; i < numParents; i++ {
 | 
				
			||||||
 | 
								parent, err := current.commit.Parent(i)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								parents = append(parents, parent)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		coldStreak++
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return scanner.Err()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// parseCommitInfo parse a commit from a line of `git log` output. Expects the
 | 
							// Examine the current commit and set of interesting paths
 | 
				
			||||||
// line to be formatted according to getCommitsInfoPretty.
 | 
							numOfParentsWithPath := make([]int, len(current.paths))
 | 
				
			||||||
func parseCommitInfo(line string) (*Commit, error) {
 | 
							pathChanged := make([]bool, len(current.paths))
 | 
				
			||||||
	if len(line) < 43 {
 | 
							parentHashes := make([]map[string]plumbing.Hash, len(parents))
 | 
				
			||||||
		return nil, fmt.Errorf("invalid git output: %s", line)
 | 
							for j, parent := range parents {
 | 
				
			||||||
 | 
								parentHashes[j], err = getFileHashes(parent, treePath, current.paths)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for i, path := range current.paths {
 | 
				
			||||||
 | 
									if parentHashes[j][path] != plumbing.ZeroHash {
 | 
				
			||||||
 | 
										numOfParentsWithPath[i]++
 | 
				
			||||||
 | 
										if parentHashes[j][path] != current.hashes[path] {
 | 
				
			||||||
 | 
											pathChanged[i] = true
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var remainingPaths []string
 | 
				
			||||||
 | 
							for i, path := range current.paths {
 | 
				
			||||||
 | 
								switch numOfParentsWithPath[i] {
 | 
				
			||||||
 | 
								case 0:
 | 
				
			||||||
 | 
									// The path didn't exist in any parent, so it must have been created by
 | 
				
			||||||
 | 
									// this commit. The results could already contain some newer change from
 | 
				
			||||||
 | 
									// different path, so don't override that.
 | 
				
			||||||
 | 
									if result[path] == nil {
 | 
				
			||||||
 | 
										result[path] = current.commit
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								case 1:
 | 
				
			||||||
 | 
									// The file is present on exactly one parent, so check if it was changed
 | 
				
			||||||
 | 
									// and save the revision if it did.
 | 
				
			||||||
 | 
									if pathChanged[i] {
 | 
				
			||||||
 | 
										if result[path] == nil {
 | 
				
			||||||
 | 
											result[path] = current.commit
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										remainingPaths = append(remainingPaths, path)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									// The file is present on more than one of the parent paths, so this is
 | 
				
			||||||
 | 
									// a merge. We have to examine all the parent trees to find out where
 | 
				
			||||||
 | 
									// the change occurred. pathChanged[i] would tell us that the file was
 | 
				
			||||||
 | 
									// changed during the merge, but it wouldn't tell us the relevant commit
 | 
				
			||||||
 | 
									// that introduced it.
 | 
				
			||||||
 | 
									remainingPaths = append(remainingPaths, path)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(remainingPaths) > 0 {
 | 
				
			||||||
 | 
								// Add the parent nodes along with remaining paths to the heap for further
 | 
				
			||||||
 | 
								// processing.
 | 
				
			||||||
 | 
								for j, parent := range parents {
 | 
				
			||||||
 | 
									if seen[parent.ID()] {
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Combine remainingPath with paths available on the parent branch
 | 
				
			||||||
 | 
									// and make union of them
 | 
				
			||||||
 | 
									var remainingPathsForParent []string
 | 
				
			||||||
 | 
									for _, path := range remainingPaths {
 | 
				
			||||||
 | 
										if parentHashes[j][path] != plumbing.ZeroHash {
 | 
				
			||||||
 | 
											remainingPathsForParent = append(remainingPathsForParent, path)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									heap.Push(&commitAndPaths{parent, remainingPathsForParent, parentHashes[j]})
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ref, err := NewIDFromString(line[:40])
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						return result, nil
 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	spaceIndex := strings.IndexByte(line[41:], ' ')
 | 
					 | 
				
			||||||
	if spaceIndex < 0 {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("invalid git output: %s", line)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	unixSeconds, err := strconv.Atoi(line[41 : 41+spaceIndex])
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	message := line[spaceIndex+42:]
 | 
					 | 
				
			||||||
	return &Commit{
 | 
					 | 
				
			||||||
		ID:            ref,
 | 
					 | 
				
			||||||
		CommitMessage: message,
 | 
					 | 
				
			||||||
		Committer: &Signature{
 | 
					 | 
				
			||||||
			When: time.Unix(int64(unixSeconds), 0),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}, nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,7 +51,7 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
 | 
				
			|||||||
		assert.NoError(t, err)
 | 
							assert.NoError(t, err)
 | 
				
			||||||
		entries, err := tree.ListEntries()
 | 
							entries, err := tree.ListEntries()
 | 
				
			||||||
		assert.NoError(t, err)
 | 
							assert.NoError(t, err)
 | 
				
			||||||
		commitsInfo, err := entries.GetCommitsInfo(commit, testCase.Path, nil)
 | 
							commitsInfo, _, err := entries.GetCommitsInfo(commit, testCase.Path, nil)
 | 
				
			||||||
		assert.NoError(t, err)
 | 
							assert.NoError(t, err)
 | 
				
			||||||
		assert.Len(t, commitsInfo, len(testCase.ExpectedIDs))
 | 
							assert.Len(t, commitsInfo, len(testCase.ExpectedIDs))
 | 
				
			||||||
		for _, commitInfo := range commitsInfo {
 | 
							for _, commitInfo := range commitsInfo {
 | 
				
			||||||
@@ -107,7 +107,7 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
 | 
				
			|||||||
		b.ResetTimer()
 | 
							b.ResetTimer()
 | 
				
			||||||
		b.Run(benchmark.name, func(b *testing.B) {
 | 
							b.Run(benchmark.name, func(b *testing.B) {
 | 
				
			||||||
			for i := 0; i < b.N; i++ {
 | 
								for i := 0; i < b.N; i++ {
 | 
				
			||||||
				_, err := entries.GetCommitsInfo(commit, "", nil)
 | 
									_, _, err := entries.GetCommitsInfo(commit, "", nil)
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					b.Fatal(err)
 | 
										b.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -64,3 +64,18 @@ func IsErrUnsupportedVersion(err error) bool {
 | 
				
			|||||||
func (err ErrUnsupportedVersion) Error() string {
 | 
					func (err ErrUnsupportedVersion) Error() string {
 | 
				
			||||||
	return fmt.Sprintf("Operation requires higher version [required: %s]", err.Required)
 | 
						return fmt.Sprintf("Operation requires higher version [required: %s]", err.Required)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrBranchNotExist represents a "BranchNotExist" kind of error.
 | 
				
			||||||
 | 
					type ErrBranchNotExist struct {
 | 
				
			||||||
 | 
						Name string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsErrBranchNotExist checks if an error is a ErrBranchNotExist.
 | 
				
			||||||
 | 
					func IsErrBranchNotExist(err error) bool {
 | 
				
			||||||
 | 
						_, ok := err.(ErrBranchNotExist)
 | 
				
			||||||
 | 
						return ok
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (err ErrBranchNotExist) Error() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("branch does not exist [name: %s]", err.Name)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,10 @@ import (
 | 
				
			|||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing/filemode"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing/object"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ParseTreeEntries parses the output of a `git ls-tree` command.
 | 
					// ParseTreeEntries parses the output of a `git ls-tree` command.
 | 
				
			||||||
@@ -20,30 +24,26 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
 | 
				
			|||||||
	for pos := 0; pos < len(data); {
 | 
						for pos := 0; pos < len(data); {
 | 
				
			||||||
		// expect line to be of the form "<mode> <type> <sha>\t<filename>"
 | 
							// expect line to be of the form "<mode> <type> <sha>\t<filename>"
 | 
				
			||||||
		entry := new(TreeEntry)
 | 
							entry := new(TreeEntry)
 | 
				
			||||||
 | 
							entry.gogitTreeEntry = &object.TreeEntry{}
 | 
				
			||||||
		entry.ptree = ptree
 | 
							entry.ptree = ptree
 | 
				
			||||||
		if pos+6 > len(data) {
 | 
							if pos+6 > len(data) {
 | 
				
			||||||
			return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
 | 
								return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		switch string(data[pos : pos+6]) {
 | 
							switch string(data[pos : pos+6]) {
 | 
				
			||||||
		case "100644":
 | 
							case "100644":
 | 
				
			||||||
			entry.mode = EntryModeBlob
 | 
								entry.gogitTreeEntry.Mode = filemode.Regular
 | 
				
			||||||
			entry.Type = ObjectBlob
 | 
					 | 
				
			||||||
			pos += 12 // skip over "100644 blob "
 | 
								pos += 12 // skip over "100644 blob "
 | 
				
			||||||
		case "100755":
 | 
							case "100755":
 | 
				
			||||||
			entry.mode = EntryModeExec
 | 
								entry.gogitTreeEntry.Mode = filemode.Executable
 | 
				
			||||||
			entry.Type = ObjectBlob
 | 
					 | 
				
			||||||
			pos += 12 // skip over "100755 blob "
 | 
								pos += 12 // skip over "100755 blob "
 | 
				
			||||||
		case "120000":
 | 
							case "120000":
 | 
				
			||||||
			entry.mode = EntryModeSymlink
 | 
								entry.gogitTreeEntry.Mode = filemode.Symlink
 | 
				
			||||||
			entry.Type = ObjectBlob
 | 
					 | 
				
			||||||
			pos += 12 // skip over "120000 blob "
 | 
								pos += 12 // skip over "120000 blob "
 | 
				
			||||||
		case "160000":
 | 
							case "160000":
 | 
				
			||||||
			entry.mode = EntryModeCommit
 | 
								entry.gogitTreeEntry.Mode = filemode.Submodule
 | 
				
			||||||
			entry.Type = ObjectCommit
 | 
					 | 
				
			||||||
			pos += 14 // skip over "160000 object "
 | 
								pos += 14 // skip over "160000 object "
 | 
				
			||||||
		case "040000":
 | 
							case "040000":
 | 
				
			||||||
			entry.mode = EntryModeTree
 | 
								entry.gogitTreeEntry.Mode = filemode.Dir
 | 
				
			||||||
			entry.Type = ObjectTree
 | 
					 | 
				
			||||||
			pos += 12 // skip over "040000 tree "
 | 
								pos += 12 // skip over "040000 tree "
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+6]))
 | 
								return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+6]))
 | 
				
			||||||
@@ -57,6 +57,7 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
 | 
				
			|||||||
			return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
 | 
								return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		entry.ID = id
 | 
							entry.ID = id
 | 
				
			||||||
 | 
							entry.gogitTreeEntry.Hash = plumbing.Hash(id)
 | 
				
			||||||
		pos += 41 // skip over sha and trailing space
 | 
							pos += 41 // skip over sha and trailing space
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		end := pos + bytes.IndexByte(data[pos:], '\n')
 | 
							end := pos + bytes.IndexByte(data[pos:], '\n')
 | 
				
			||||||
@@ -66,12 +67,12 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// In case entry name is surrounded by double quotes(it happens only in git-shell).
 | 
							// In case entry name is surrounded by double quotes(it happens only in git-shell).
 | 
				
			||||||
		if data[pos] == '"' {
 | 
							if data[pos] == '"' {
 | 
				
			||||||
			entry.name, err = strconv.Unquote(string(data[pos:end]))
 | 
								entry.gogitTreeEntry.Name, err = strconv.Unquote(string(data[pos:end]))
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
 | 
									return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			entry.name = string(data[pos:end])
 | 
								entry.gogitTreeEntry.Name = string(data[pos:end])
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pos = end + 1
 | 
							pos = end + 1
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,8 @@ import (
 | 
				
			|||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing/filemode"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing/object"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestParseTreeEntries(t *testing.T) {
 | 
					func TestParseTreeEntries(t *testing.T) {
 | 
				
			||||||
@@ -23,10 +25,12 @@ func TestParseTreeEntries(t *testing.T) {
 | 
				
			|||||||
			Input: "100644 blob 61ab7345a1a3bbc590068ccae37b8515cfc5843c\texample/file2.txt\n",
 | 
								Input: "100644 blob 61ab7345a1a3bbc590068ccae37b8515cfc5843c\texample/file2.txt\n",
 | 
				
			||||||
			Expected: []*TreeEntry{
 | 
								Expected: []*TreeEntry{
 | 
				
			||||||
				{
 | 
									{
 | 
				
			||||||
					mode: EntryModeBlob,
 | 
										ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
 | 
				
			||||||
					Type: ObjectBlob,
 | 
										gogitTreeEntry: &object.TreeEntry{
 | 
				
			||||||
					ID:   MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
 | 
											Hash: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
 | 
				
			||||||
					name: "example/file2.txt",
 | 
											Name: "example/file2.txt",
 | 
				
			||||||
 | 
											Mode: filemode.Regular,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
@@ -35,16 +39,20 @@ func TestParseTreeEntries(t *testing.T) {
 | 
				
			|||||||
				"040000 tree 1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8\texample\n",
 | 
									"040000 tree 1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8\texample\n",
 | 
				
			||||||
			Expected: []*TreeEntry{
 | 
								Expected: []*TreeEntry{
 | 
				
			||||||
				{
 | 
									{
 | 
				
			||||||
					ID:   MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
 | 
										ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
 | 
				
			||||||
					Type: ObjectBlob,
 | 
										gogitTreeEntry: &object.TreeEntry{
 | 
				
			||||||
					mode: EntryModeSymlink,
 | 
											Hash: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
 | 
				
			||||||
					name: "example/\n.txt",
 | 
											Name: "example/\n.txt",
 | 
				
			||||||
 | 
											Mode: filemode.Symlink,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
				{
 | 
									{
 | 
				
			||||||
					ID:   MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"),
 | 
										ID: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"),
 | 
				
			||||||
					Type: ObjectTree,
 | 
										gogitTreeEntry: &object.TreeEntry{
 | 
				
			||||||
					mode: EntryModeTree,
 | 
											Hash: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"),
 | 
				
			||||||
					name: "example",
 | 
											Name: "example",
 | 
				
			||||||
 | 
											Mode: filemode.Dir,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,14 +16,20 @@ import (
 | 
				
			|||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/Unknwon/com"
 | 
						"github.com/Unknwon/com"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-billy.v4/osfs"
 | 
				
			||||||
 | 
						gogit "gopkg.in/src-d/go-git.v4"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing/cache"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/storage/filesystem"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Repository represents a Git repository.
 | 
					// Repository represents a Git repository.
 | 
				
			||||||
type Repository struct {
 | 
					type Repository struct {
 | 
				
			||||||
	Path string
 | 
						Path string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	commitCache *ObjectCache
 | 
						tagCache *ObjectCache
 | 
				
			||||||
	tagCache    *ObjectCache
 | 
					
 | 
				
			||||||
 | 
						gogitRepo    *gogit.Repository
 | 
				
			||||||
 | 
						gogitStorage *filesystem.Storage
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const prettyLogFormat = `--pretty=format:%H`
 | 
					const prettyLogFormat = `--pretty=format:%H`
 | 
				
			||||||
@@ -77,10 +83,25 @@ func OpenRepository(repoPath string) (*Repository, error) {
 | 
				
			|||||||
		return nil, errors.New("no such file or directory")
 | 
							return nil, errors.New("no such file or directory")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fs := osfs.New(repoPath)
 | 
				
			||||||
 | 
						_, err = fs.Stat(".git")
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							fs, err = fs.Chroot(".git")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						storage := filesystem.NewStorageWithOptions(fs, cache.NewObjectLRUDefault(), filesystem.Options{KeepDescriptors: true})
 | 
				
			||||||
 | 
						gogitRepo, err := gogit.Open(storage, fs)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &Repository{
 | 
						return &Repository{
 | 
				
			||||||
		Path:        repoPath,
 | 
							Path:         repoPath,
 | 
				
			||||||
		commitCache: newObjectCache(),
 | 
							gogitRepo:    gogitRepo,
 | 
				
			||||||
		tagCache:    newObjectCache(),
 | 
							gogitStorage: storage,
 | 
				
			||||||
 | 
							tagCache:     newObjectCache(),
 | 
				
			||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,19 +4,19 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (repo *Repository) getBlob(id SHA1) (*Blob, error) {
 | 
					func (repo *Repository) getBlob(id SHA1) (*Blob, error) {
 | 
				
			||||||
	if _, err := NewCommand("cat-file", "-p", id.String()).RunInDir(repo.Path); err != nil {
 | 
						encodedObj, err := repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, plumbing.Hash(id))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
		return nil, ErrNotExist{id.String(), ""}
 | 
							return nil, ErrNotExist{id.String(), ""}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &Blob{
 | 
						return &Blob{
 | 
				
			||||||
		repo: repo,
 | 
							ID:              id,
 | 
				
			||||||
		TreeEntry: &TreeEntry{
 | 
							gogitEncodedObj: encodedObj,
 | 
				
			||||||
			ID: id,
 | 
					 | 
				
			||||||
			ptree: &Tree{
 | 
					 | 
				
			||||||
				repo: repo,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,8 +30,9 @@ func TestRepository_GetBlob_Found(t *testing.T) {
 | 
				
			|||||||
		blob, err := r.GetBlob(testCase.OID)
 | 
							blob, err := r.GetBlob(testCase.OID)
 | 
				
			||||||
		assert.NoError(t, err)
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		dataReader, err := blob.Data()
 | 
							dataReader, err := blob.DataAsync()
 | 
				
			||||||
		assert.NoError(t, err)
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							defer dataReader.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		data, err := ioutil.ReadAll(dataReader)
 | 
							data, err := ioutil.ReadAll(dataReader)
 | 
				
			||||||
		assert.NoError(t, err)
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,6 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"gopkg.in/src-d/go-git.v4"
 | 
					 | 
				
			||||||
	"gopkg.in/src-d/go-git.v4/plumbing"
 | 
						"gopkg.in/src-d/go-git.v4/plumbing"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -29,13 +28,19 @@ func IsBranchExist(repoPath, name string) bool {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// IsBranchExist returns true if given branch exists in current repository.
 | 
					// IsBranchExist returns true if given branch exists in current repository.
 | 
				
			||||||
func (repo *Repository) IsBranchExist(name string) bool {
 | 
					func (repo *Repository) IsBranchExist(name string) bool {
 | 
				
			||||||
	return IsBranchExist(repo.Path, name)
 | 
						_, err := repo.gogitRepo.Reference(plumbing.ReferenceName(BranchPrefix+name), true)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Branch represents a Git branch.
 | 
					// Branch represents a Git branch.
 | 
				
			||||||
type Branch struct {
 | 
					type Branch struct {
 | 
				
			||||||
	Name string
 | 
						Name string
 | 
				
			||||||
	Path string
 | 
						Path string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gitRepo *Repository
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetHEADBranch returns corresponding branch of HEAD.
 | 
					// GetHEADBranch returns corresponding branch of HEAD.
 | 
				
			||||||
@@ -51,8 +56,9 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &Branch{
 | 
						return &Branch{
 | 
				
			||||||
		Name: stdout[len(BranchPrefix):],
 | 
							Name:    stdout[len(BranchPrefix):],
 | 
				
			||||||
		Path: stdout,
 | 
							Path:    stdout,
 | 
				
			||||||
 | 
							gitRepo: repo,
 | 
				
			||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -64,23 +70,56 @@ func (repo *Repository) SetDefaultBranch(name string) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// GetBranches returns all branches of the repository.
 | 
					// GetBranches returns all branches of the repository.
 | 
				
			||||||
func (repo *Repository) GetBranches() ([]string, error) {
 | 
					func (repo *Repository) GetBranches() ([]string, error) {
 | 
				
			||||||
	r, err := git.PlainOpen(repo.Path)
 | 
						var branchNames []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						branches, err := repo.gogitRepo.Branches()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	branchIter, err := r.Branches()
 | 
						branches.ForEach(func(branch *plumbing.Reference) error {
 | 
				
			||||||
 | 
							branchNames = append(branchNames, strings.TrimPrefix(branch.Name().String(), BranchPrefix))
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: Sort?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return branchNames, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetBranch returns a branch by it's name
 | 
				
			||||||
 | 
					func (repo *Repository) GetBranch(branch string) (*Branch, error) {
 | 
				
			||||||
 | 
						if !repo.IsBranchExist(branch) {
 | 
				
			||||||
 | 
							return nil, ErrBranchNotExist{branch}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &Branch{
 | 
				
			||||||
 | 
							Path:    repo.Path,
 | 
				
			||||||
 | 
							Name:    branch,
 | 
				
			||||||
 | 
							gitRepo: repo,
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetBranchesByPath returns a branch by it's path
 | 
				
			||||||
 | 
					func GetBranchesByPath(path string) ([]*Branch, error) {
 | 
				
			||||||
 | 
						gitRepo, err := OpenRepository(path)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	branches := make([]string, 0)
 | 
					
 | 
				
			||||||
	if err = branchIter.ForEach(func(branch *plumbing.Reference) error {
 | 
						brs, err := gitRepo.GetBranches()
 | 
				
			||||||
		branches = append(branches, branch.Name().Short())
 | 
						if err != nil {
 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}); err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						branches := make([]*Branch, len(brs))
 | 
				
			||||||
 | 
						for i := range brs {
 | 
				
			||||||
 | 
							branches[i] = &Branch{
 | 
				
			||||||
 | 
								Path:    path,
 | 
				
			||||||
 | 
								Name:    brs[i],
 | 
				
			||||||
 | 
								gitRepo: gitRepo,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return branches, nil
 | 
						return branches, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -132,3 +171,8 @@ func (repo *Repository) RemoveRemote(name string) error {
 | 
				
			|||||||
	_, err := NewCommand("remote", "remove", name).RunInDir(repo.Path)
 | 
						_, err := NewCommand("remote", "remove", name).RunInDir(repo.Path)
 | 
				
			||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetCommit returns the head commit of a branch
 | 
				
			||||||
 | 
					func (branch *Branch) GetCommit() (*Commit, error) {
 | 
				
			||||||
 | 
						return branch.gitRepo.GetBranchCommit(branch.Name)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -7,22 +8,23 @@ package git
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"container/list"
 | 
						"container/list"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/mcuadros/go-version"
 | 
						"github.com/mcuadros/go-version"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing/object"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetRefCommitID returns the last commit ID string of given reference (branch or tag).
 | 
					// GetRefCommitID returns the last commit ID string of given reference (branch or tag).
 | 
				
			||||||
func (repo *Repository) GetRefCommitID(name string) (string, error) {
 | 
					func (repo *Repository) GetRefCommitID(name string) (string, error) {
 | 
				
			||||||
	stdout, err := NewCommand("show-ref", "--verify", name).RunInDir(repo.Path)
 | 
						ref, err := repo.gogitRepo.Reference(plumbing.ReferenceName(name), true)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if strings.Contains(err.Error(), "not a valid ref") {
 | 
					 | 
				
			||||||
			return "", ErrNotExist{name, ""}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return "", err
 | 
							return "", err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return strings.Split(stdout, " ")[0], nil
 | 
					
 | 
				
			||||||
 | 
						return ref.Hash().String(), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetBranchCommitID returns last commit ID string of given branch.
 | 
					// GetBranchCommitID returns last commit ID string of given branch.
 | 
				
			||||||
@@ -42,114 +44,69 @@ func (repo *Repository) GetTagCommitID(name string) (string, error) {
 | 
				
			|||||||
	return strings.TrimSpace(stdout), nil
 | 
						return strings.TrimSpace(stdout), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// parseCommitData parses commit information from the (uncompressed) raw
 | 
					func convertPGPSignatureForTag(t *object.Tag) *CommitGPGSignature {
 | 
				
			||||||
// data from the commit object.
 | 
						if t.PGPSignature == "" {
 | 
				
			||||||
// \n\n separate headers from message
 | 
							return nil
 | 
				
			||||||
func parseCommitData(data []byte) (*Commit, error) {
 | 
						}
 | 
				
			||||||
	commit := new(Commit)
 | 
					
 | 
				
			||||||
	commit.parents = make([]SHA1, 0, 1)
 | 
						var w strings.Builder
 | 
				
			||||||
	// we now have the contents of the commit object. Let's investigate...
 | 
						var err error
 | 
				
			||||||
	nextline := 0
 | 
					
 | 
				
			||||||
l:
 | 
						if _, err = fmt.Fprintf(&w,
 | 
				
			||||||
	for {
 | 
							"object %s\ntype %s\ntag %s\ntagger ",
 | 
				
			||||||
		eol := bytes.IndexByte(data[nextline:], '\n')
 | 
							t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil {
 | 
				
			||||||
		switch {
 | 
							return nil
 | 
				
			||||||
		case eol > 0:
 | 
						}
 | 
				
			||||||
			line := data[nextline : nextline+eol]
 | 
					
 | 
				
			||||||
			spacepos := bytes.IndexByte(line, ' ')
 | 
						if err = t.Tagger.Encode(&w); err != nil {
 | 
				
			||||||
			reftype := line[:spacepos]
 | 
							return nil
 | 
				
			||||||
			switch string(reftype) {
 | 
						}
 | 
				
			||||||
			case "tree", "object":
 | 
					
 | 
				
			||||||
				id, err := NewIDFromString(string(line[spacepos+1:]))
 | 
						if _, err = fmt.Fprintf(&w, "\n\n"); err != nil {
 | 
				
			||||||
				if err != nil {
 | 
							return nil
 | 
				
			||||||
					return nil, err
 | 
						}
 | 
				
			||||||
				}
 | 
					
 | 
				
			||||||
				commit.Tree.ID = id
 | 
						if _, err = fmt.Fprintf(&w, t.Message); err != nil {
 | 
				
			||||||
			case "parent":
 | 
							return nil
 | 
				
			||||||
				// A commit can have one or more parents
 | 
						}
 | 
				
			||||||
				oid, err := NewIDFromString(string(line[spacepos+1:]))
 | 
					
 | 
				
			||||||
				if err != nil {
 | 
						return &CommitGPGSignature{
 | 
				
			||||||
					return nil, err
 | 
							Signature: t.PGPSignature,
 | 
				
			||||||
				}
 | 
							Payload:   strings.TrimSpace(w.String()) + "\n",
 | 
				
			||||||
				commit.parents = append(commit.parents, oid)
 | 
					 | 
				
			||||||
			case "author", "tagger":
 | 
					 | 
				
			||||||
				sig, err := newSignatureFromCommitline(line[spacepos+1:])
 | 
					 | 
				
			||||||
				if err != nil {
 | 
					 | 
				
			||||||
					return nil, err
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				commit.Author = sig
 | 
					 | 
				
			||||||
			case "committer":
 | 
					 | 
				
			||||||
				sig, err := newSignatureFromCommitline(line[spacepos+1:])
 | 
					 | 
				
			||||||
				if err != nil {
 | 
					 | 
				
			||||||
					return nil, err
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				commit.Committer = sig
 | 
					 | 
				
			||||||
			case "gpgsig":
 | 
					 | 
				
			||||||
				sig, err := newGPGSignatureFromCommitline(data, nextline+spacepos+1, false)
 | 
					 | 
				
			||||||
				if err != nil {
 | 
					 | 
				
			||||||
					return nil, err
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				commit.Signature = sig
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			nextline += eol + 1
 | 
					 | 
				
			||||||
		case eol == 0:
 | 
					 | 
				
			||||||
			cm := string(data[nextline+1:])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Tag GPG signatures are stored below the commit message
 | 
					 | 
				
			||||||
			sigindex := strings.Index(cm, "-----BEGIN PGP SIGNATURE-----")
 | 
					 | 
				
			||||||
			if sigindex != -1 {
 | 
					 | 
				
			||||||
				sig, err := newGPGSignatureFromCommitline(data, (nextline+1)+sigindex, true)
 | 
					 | 
				
			||||||
				if err == nil && sig != nil {
 | 
					 | 
				
			||||||
					// remove signature from commit message
 | 
					 | 
				
			||||||
					if sigindex == 0 {
 | 
					 | 
				
			||||||
						cm = ""
 | 
					 | 
				
			||||||
					} else {
 | 
					 | 
				
			||||||
						cm = cm[:sigindex-1]
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
					commit.Signature = sig
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			commit.CommitMessage = cm
 | 
					 | 
				
			||||||
			break l
 | 
					 | 
				
			||||||
		default:
 | 
					 | 
				
			||||||
			break l
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return commit, nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
 | 
					func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
 | 
				
			||||||
	c, ok := repo.commitCache.Get(id.String())
 | 
						var tagObject *object.Tag
 | 
				
			||||||
	if ok {
 | 
					 | 
				
			||||||
		log("Hit cache: %s", id)
 | 
					 | 
				
			||||||
		return c.(*Commit), nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data, err := NewCommand("cat-file", "-p", id.String()).RunInDirBytes(repo.Path)
 | 
						gogitCommit, err := repo.gogitRepo.CommitObject(plumbing.Hash(id))
 | 
				
			||||||
	if err != nil {
 | 
						if err == plumbing.ErrObjectNotFound {
 | 
				
			||||||
		if strings.Contains(err.Error(), "fatal: Not a valid object name") {
 | 
							tagObject, err = repo.gogitRepo.TagObject(plumbing.Hash(id))
 | 
				
			||||||
			return nil, ErrNotExist{id.String(), ""}
 | 
							if err == nil {
 | 
				
			||||||
 | 
								gogitCommit, err = repo.gogitRepo.CommitObject(tagObject.Target)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	commit, err := parseCommitData(data)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commit := convertCommit(gogitCommit)
 | 
				
			||||||
	commit.repo = repo
 | 
						commit.repo = repo
 | 
				
			||||||
	commit.ID = id
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data, err = NewCommand("name-rev", id.String()).RunInDirBytes(repo.Path)
 | 
						if tagObject != nil {
 | 
				
			||||||
 | 
							commit.CommitMessage = strings.TrimSpace(tagObject.Message)
 | 
				
			||||||
 | 
							commit.Author = &tagObject.Tagger
 | 
				
			||||||
 | 
							commit.Signature = convertPGPSignatureForTag(tagObject)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tree, err := gogitCommit.Tree()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// name-rev commitID output will be "COMMIT_ID master" or "COMMIT_ID master~12"
 | 
						commit.Tree.ID = tree.Hash
 | 
				
			||||||
	commit.Branch = strings.Split(strings.Split(string(data), " ")[1], "~")[0]
 | 
						commit.Tree.gogitTree = tree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	repo.commitCache.Set(id.String(), commit)
 | 
					 | 
				
			||||||
	return commit, nil
 | 
						return commit, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -8,6 +9,7 @@ import (
 | 
				
			|||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/mcuadros/go-version"
 | 
						"github.com/mcuadros/go-version"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TagPrefix tags prefix path on the repository
 | 
					// TagPrefix tags prefix path on the repository
 | 
				
			||||||
@@ -20,7 +22,11 @@ func IsTagExist(repoPath, name string) bool {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// IsTagExist returns true if given tag exists in the repository.
 | 
					// IsTagExist returns true if given tag exists in the repository.
 | 
				
			||||||
func (repo *Repository) IsTagExist(name string) bool {
 | 
					func (repo *Repository) IsTagExist(name string) bool {
 | 
				
			||||||
	return IsTagExist(repo.Path, name)
 | 
						_, err := repo.gogitRepo.Reference(plumbing.ReferenceName(TagPrefix+name), true)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CreateTag create one tag in the repository
 | 
					// CreateTag create one tag in the repository
 | 
				
			||||||
@@ -122,28 +128,25 @@ func (repo *Repository) GetTagInfos() ([]*Tag, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// GetTags returns all tags of the repository.
 | 
					// GetTags returns all tags of the repository.
 | 
				
			||||||
func (repo *Repository) GetTags() ([]string, error) {
 | 
					func (repo *Repository) GetTags() ([]string, error) {
 | 
				
			||||||
	cmd := NewCommand("tag", "-l")
 | 
						var tagNames []string
 | 
				
			||||||
	if version.Compare(gitVersion, "2.0.0", ">=") {
 | 
					 | 
				
			||||||
		cmd.AddArguments("--sort=-v:refname")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	stdout, err := cmd.RunInDir(repo.Path)
 | 
						tags, err := repo.gogitRepo.Tags()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tags := strings.Split(stdout, "\n")
 | 
						tags.ForEach(func(tag *plumbing.Reference) error {
 | 
				
			||||||
	tags = tags[:len(tags)-1]
 | 
							tagNames = append(tagNames, strings.TrimPrefix(tag.Name().String(), TagPrefix))
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if version.Compare(gitVersion, "2.0.0", "<") {
 | 
						version.Sort(tagNames)
 | 
				
			||||||
		version.Sort(tags)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Reverse order
 | 
						// Reverse order
 | 
				
			||||||
		for i := 0; i < len(tags)/2; i++ {
 | 
						for i := 0; i < len(tagNames)/2; i++ {
 | 
				
			||||||
			j := len(tags) - i - 1
 | 
							j := len(tagNames) - i - 1
 | 
				
			||||||
			tags[i], tags[j] = tags[j], tags[i]
 | 
							tagNames[i], tagNames[j] = tagNames[j], tagNames[i]
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return tags, nil
 | 
						return tagNames, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,23 @@
 | 
				
			|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (repo *Repository) getTree(id SHA1) (*Tree, error) {
 | 
					func (repo *Repository) getTree(id SHA1) (*Tree, error) {
 | 
				
			||||||
	treePath := filepathFromSHA1(repo.Path, id.String())
 | 
						gogitTree, err := repo.gogitRepo.TreeObject(plumbing.Hash(id))
 | 
				
			||||||
	if isFile(treePath) {
 | 
						if err != nil {
 | 
				
			||||||
		_, err := NewCommand("ls-tree", id.String()).RunInDir(repo.Path)
 | 
							return nil, err
 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, ErrNotExist{id.String(), ""}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return NewTree(repo, id), nil
 | 
						tree := NewTree(repo, id)
 | 
				
			||||||
 | 
						tree.gogitTree = gogitTree
 | 
				
			||||||
 | 
						return tree, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetTree find the tree object in the repository.
 | 
					// GetTree find the tree object in the repository.
 | 
				
			||||||
@@ -31,5 +35,14 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) {
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return repo.getTree(id)
 | 
						commitObject, err := repo.gogitRepo.CommitObject(plumbing.Hash(id))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						treeObject, err := repo.getTree(SHA1(commitObject.TreeHash))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						treeObject.CommitID = id
 | 
				
			||||||
 | 
						return treeObject, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,44 +1,23 @@
 | 
				
			|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"encoding/hex"
 | 
						"encoding/hex"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// EmptySHA defines empty git SHA
 | 
					// EmptySHA defines empty git SHA
 | 
				
			||||||
const EmptySHA = "0000000000000000000000000000000000000000"
 | 
					const EmptySHA = "0000000000000000000000000000000000000000"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SHA1 a git commit name
 | 
					// SHA1 a git commit name
 | 
				
			||||||
type SHA1 [20]byte
 | 
					type SHA1 = plumbing.Hash
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Equal returns true if s has the same SHA1 as caller.
 | 
					 | 
				
			||||||
// Support 40-length-string, []byte, SHA1.
 | 
					 | 
				
			||||||
func (id SHA1) Equal(s2 interface{}) bool {
 | 
					 | 
				
			||||||
	switch v := s2.(type) {
 | 
					 | 
				
			||||||
	case string:
 | 
					 | 
				
			||||||
		if len(v) != 40 {
 | 
					 | 
				
			||||||
			return false
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return v == id.String()
 | 
					 | 
				
			||||||
	case []byte:
 | 
					 | 
				
			||||||
		return bytes.Equal(v, id[:])
 | 
					 | 
				
			||||||
	case SHA1:
 | 
					 | 
				
			||||||
		return v == id
 | 
					 | 
				
			||||||
	default:
 | 
					 | 
				
			||||||
		return false
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// String returns string (hex) representation of the Oid.
 | 
					 | 
				
			||||||
func (id SHA1) String() string {
 | 
					 | 
				
			||||||
	return hex.EncodeToString(id[:])
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MustID always creates a new SHA1 from a [20]byte array with no validation of input.
 | 
					// MustID always creates a new SHA1 from a [20]byte array with no validation of input.
 | 
				
			||||||
func MustID(b []byte) SHA1 {
 | 
					func MustID(b []byte) SHA1 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -8,14 +9,12 @@ import (
 | 
				
			|||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing/object"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Signature represents the Author or Committer information.
 | 
					// Signature represents the Author or Committer information.
 | 
				
			||||||
type Signature struct {
 | 
					type Signature = object.Signature
 | 
				
			||||||
	Email string
 | 
					 | 
				
			||||||
	Name  string
 | 
					 | 
				
			||||||
	When  time.Time
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	// GitTimeLayout is the (default) time layout used by git.
 | 
						// GitTimeLayout is the (default) time layout used by git.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,29 +1,31 @@
 | 
				
			|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing/object"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Tree represents a flat directory listing.
 | 
					// Tree represents a flat directory listing.
 | 
				
			||||||
type Tree struct {
 | 
					type Tree struct {
 | 
				
			||||||
	ID   SHA1
 | 
						ID       SHA1
 | 
				
			||||||
	repo *Repository
 | 
						CommitID SHA1
 | 
				
			||||||
 | 
						repo     *Repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gogitTree *object.Tree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// parent tree
 | 
						// parent tree
 | 
				
			||||||
	ptree *Tree
 | 
						ptree *Tree
 | 
				
			||||||
 | 
					 | 
				
			||||||
	entries       Entries
 | 
					 | 
				
			||||||
	entriesParsed bool
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	entriesRecursive       Entries
 | 
					 | 
				
			||||||
	entriesRecursiveParsed bool
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewTree create a new tree according the repository and commit id
 | 
					// NewTree create a new tree according the repository and tree id
 | 
				
			||||||
func NewTree(repo *Repository, id SHA1) *Tree {
 | 
					func NewTree(repo *Repository, id SHA1) *Tree {
 | 
				
			||||||
	return &Tree{
 | 
						return &Tree{
 | 
				
			||||||
		ID:   id,
 | 
							ID:   id,
 | 
				
			||||||
@@ -60,39 +62,68 @@ func (t *Tree) SubTree(rpath string) (*Tree, error) {
 | 
				
			|||||||
	return g, nil
 | 
						return g, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *Tree) loadTreeObject() error {
 | 
				
			||||||
 | 
						gogitTree, err := t.repo.gogitRepo.TreeObject(plumbing.Hash(t.ID))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.gogitTree = gogitTree
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ListEntries returns all entries of current tree.
 | 
					// ListEntries returns all entries of current tree.
 | 
				
			||||||
func (t *Tree) ListEntries() (Entries, error) {
 | 
					func (t *Tree) ListEntries() (Entries, error) {
 | 
				
			||||||
	if t.entriesParsed {
 | 
						if t.gogitTree == nil {
 | 
				
			||||||
		return t.entries, nil
 | 
							err := t.loadTreeObject()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	stdout, err := NewCommand("ls-tree", t.ID.String()).RunInDirBytes(t.repo.Path)
 | 
						entries := make([]*TreeEntry, len(t.gogitTree.Entries))
 | 
				
			||||||
	if err != nil {
 | 
						for i, entry := range t.gogitTree.Entries {
 | 
				
			||||||
		return nil, err
 | 
							entries[i] = &TreeEntry{
 | 
				
			||||||
 | 
								ID:             entry.Hash,
 | 
				
			||||||
 | 
								gogitTreeEntry: &t.gogitTree.Entries[i],
 | 
				
			||||||
 | 
								ptree:          t,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.entries, err = parseTreeEntries(stdout, t)
 | 
						return entries, nil
 | 
				
			||||||
	if err == nil {
 | 
					 | 
				
			||||||
		t.entriesParsed = true
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return t.entries, err
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ListEntriesRecursive returns all entries of current tree recursively including all subtrees
 | 
					// ListEntriesRecursive returns all entries of current tree recursively including all subtrees
 | 
				
			||||||
func (t *Tree) ListEntriesRecursive() (Entries, error) {
 | 
					func (t *Tree) ListEntriesRecursive() (Entries, error) {
 | 
				
			||||||
	if t.entriesRecursiveParsed {
 | 
						if t.gogitTree == nil {
 | 
				
			||||||
		return t.entriesRecursive, nil
 | 
							err := t.loadTreeObject()
 | 
				
			||||||
	}
 | 
							if err != nil {
 | 
				
			||||||
	stdout, err := NewCommand("ls-tree", "-t", "-r", t.ID.String()).RunInDirBytes(t.repo.Path)
 | 
								return nil, err
 | 
				
			||||||
	if err != nil {
 | 
							}
 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.entriesRecursive, err = parseTreeEntries(stdout, t)
 | 
						var entries []*TreeEntry
 | 
				
			||||||
	if err == nil {
 | 
						seen := map[plumbing.Hash]bool{}
 | 
				
			||||||
		t.entriesRecursiveParsed = true
 | 
						walker := object.NewTreeWalker(t.gogitTree, true, seen)
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							_, entry, err := walker.Next()
 | 
				
			||||||
 | 
							if err == io.EOF {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if seen[entry.Hash] {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							convertedEntry := &TreeEntry{
 | 
				
			||||||
 | 
								ID:             entry.Hash,
 | 
				
			||||||
 | 
								gogitTreeEntry: &entry,
 | 
				
			||||||
 | 
								ptree:          t,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							entries = append(entries, convertedEntry)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return t.entriesRecursive, err
 | 
						return entries, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -7,15 +8,23 @@ package git
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing/filemode"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing/object"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetTreeEntryByPath get the tree entries according the sub dir
 | 
					// GetTreeEntryByPath get the tree entries according the sub dir
 | 
				
			||||||
func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
 | 
					func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
 | 
				
			||||||
	if len(relpath) == 0 {
 | 
						if len(relpath) == 0 {
 | 
				
			||||||
		return &TreeEntry{
 | 
							return &TreeEntry{
 | 
				
			||||||
			ID:   t.ID,
 | 
								ID: t.ID,
 | 
				
			||||||
			Type: ObjectTree,
 | 
								//Type: ObjectTree,
 | 
				
			||||||
			mode: EntryModeTree,
 | 
								gogitTreeEntry: &object.TreeEntry{
 | 
				
			||||||
 | 
									Name: "",
 | 
				
			||||||
 | 
									Mode: filemode.Dir,
 | 
				
			||||||
 | 
									Hash: plumbing.Hash(t.ID),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
		}, nil
 | 
							}, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -30,7 +39,7 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
 | 
				
			|||||||
				return nil, err
 | 
									return nil, err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			for _, v := range entries {
 | 
								for _, v := range entries {
 | 
				
			||||||
				if v.name == name {
 | 
									if v.Name() == name {
 | 
				
			||||||
					return v, nil
 | 
										return v, nil
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -7,8 +8,11 @@ package git
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing/filemode"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing/object"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// EntryMode the type of the object in the git tree
 | 
					// EntryMode the type of the object in the git tree
 | 
				
			||||||
@@ -18,28 +22,23 @@ type EntryMode int
 | 
				
			|||||||
// one of these.
 | 
					// one of these.
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	// EntryModeBlob
 | 
						// EntryModeBlob
 | 
				
			||||||
	EntryModeBlob EntryMode = 0x0100644
 | 
						EntryModeBlob EntryMode = 0100644
 | 
				
			||||||
	// EntryModeExec
 | 
						// EntryModeExec
 | 
				
			||||||
	EntryModeExec EntryMode = 0x0100755
 | 
						EntryModeExec EntryMode = 0100755
 | 
				
			||||||
	// EntryModeSymlink
 | 
						// EntryModeSymlink
 | 
				
			||||||
	EntryModeSymlink EntryMode = 0x0120000
 | 
						EntryModeSymlink EntryMode = 0120000
 | 
				
			||||||
	// EntryModeCommit
 | 
						// EntryModeCommit
 | 
				
			||||||
	EntryModeCommit EntryMode = 0x0160000
 | 
						EntryModeCommit EntryMode = 0160000
 | 
				
			||||||
	// EntryModeTree
 | 
						// EntryModeTree
 | 
				
			||||||
	EntryModeTree EntryMode = 0x0040000
 | 
						EntryModeTree EntryMode = 0040000
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TreeEntry the leaf in the git tree
 | 
					// TreeEntry the leaf in the git tree
 | 
				
			||||||
type TreeEntry struct {
 | 
					type TreeEntry struct {
 | 
				
			||||||
	ID   SHA1
 | 
						ID SHA1
 | 
				
			||||||
	Type ObjectType
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mode EntryMode
 | 
						gogitTreeEntry *object.TreeEntry
 | 
				
			||||||
	name string
 | 
						ptree          *Tree
 | 
				
			||||||
 | 
					 | 
				
			||||||
	ptree *Tree
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	committed bool
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	size  int64
 | 
						size  int64
 | 
				
			||||||
	sized bool
 | 
						sized bool
 | 
				
			||||||
@@ -47,12 +46,24 @@ type TreeEntry struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Name returns the name of the entry
 | 
					// Name returns the name of the entry
 | 
				
			||||||
func (te *TreeEntry) Name() string {
 | 
					func (te *TreeEntry) Name() string {
 | 
				
			||||||
	return te.name
 | 
						return te.gogitTreeEntry.Name
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Mode returns the mode of the entry
 | 
					// Mode returns the mode of the entry
 | 
				
			||||||
func (te *TreeEntry) Mode() EntryMode {
 | 
					func (te *TreeEntry) Mode() EntryMode {
 | 
				
			||||||
	return te.mode
 | 
						return EntryMode(te.gogitTreeEntry.Mode)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Type returns the type of the entry (commit, tree, blob)
 | 
				
			||||||
 | 
					func (te *TreeEntry) Type() string {
 | 
				
			||||||
 | 
						switch te.Mode() {
 | 
				
			||||||
 | 
						case EntryModeCommit:
 | 
				
			||||||
 | 
							return "commit"
 | 
				
			||||||
 | 
						case EntryModeTree:
 | 
				
			||||||
 | 
							return "tree"
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return "blob"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Size returns the size of the entry
 | 
					// Size returns the size of the entry
 | 
				
			||||||
@@ -63,36 +74,47 @@ func (te *TreeEntry) Size() int64 {
 | 
				
			|||||||
		return te.size
 | 
							return te.size
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	stdout, err := NewCommand("cat-file", "-s", te.ID.String()).RunInDir(te.ptree.repo.Path)
 | 
						file, err := te.ptree.gogitTree.TreeEntryFile(te.gogitTreeEntry)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return 0
 | 
							return 0
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	te.sized = true
 | 
						te.sized = true
 | 
				
			||||||
	te.size, _ = strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
 | 
						te.size = file.Size
 | 
				
			||||||
	return te.size
 | 
						return te.size
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IsSubModule if the entry is a sub module
 | 
					// IsSubModule if the entry is a sub module
 | 
				
			||||||
func (te *TreeEntry) IsSubModule() bool {
 | 
					func (te *TreeEntry) IsSubModule() bool {
 | 
				
			||||||
	return te.mode == EntryModeCommit
 | 
						return te.gogitTreeEntry.Mode == filemode.Submodule
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IsDir if the entry is a sub dir
 | 
					// IsDir if the entry is a sub dir
 | 
				
			||||||
func (te *TreeEntry) IsDir() bool {
 | 
					func (te *TreeEntry) IsDir() bool {
 | 
				
			||||||
	return te.mode == EntryModeTree
 | 
						return te.gogitTreeEntry.Mode == filemode.Dir
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IsLink if the entry is a symlink
 | 
					// IsLink if the entry is a symlink
 | 
				
			||||||
func (te *TreeEntry) IsLink() bool {
 | 
					func (te *TreeEntry) IsLink() bool {
 | 
				
			||||||
	return te.mode == EntryModeSymlink
 | 
						return te.gogitTreeEntry.Mode == filemode.Symlink
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Blob retrun the blob object the entry
 | 
					// IsRegular if the entry is a regular file
 | 
				
			||||||
 | 
					func (te *TreeEntry) IsRegular() bool {
 | 
				
			||||||
 | 
						return te.gogitTreeEntry.Mode == filemode.Regular
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Blob returns the blob object the entry
 | 
				
			||||||
func (te *TreeEntry) Blob() *Blob {
 | 
					func (te *TreeEntry) Blob() *Blob {
 | 
				
			||||||
 | 
						encodedObj, err := te.ptree.repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, te.gogitTreeEntry.Hash)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &Blob{
 | 
						return &Blob{
 | 
				
			||||||
		repo:      te.ptree.repo,
 | 
							ID:              te.gogitTreeEntry.Hash,
 | 
				
			||||||
		TreeEntry: te,
 | 
							gogitEncodedObj: encodedObj,
 | 
				
			||||||
 | 
							name:            te.Name(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -103,10 +125,11 @@ func (te *TreeEntry) FollowLink() (*TreeEntry, error) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// read the link
 | 
						// read the link
 | 
				
			||||||
	r, err := te.Blob().Data()
 | 
						r, err := te.Blob().DataAsync()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						defer r.Close()
 | 
				
			||||||
	buf := make([]byte, te.Size())
 | 
						buf := make([]byte, te.Size())
 | 
				
			||||||
	_, err = io.ReadFull(r, buf)
 | 
						_, err = io.ReadFull(r, buf)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -140,18 +163,18 @@ func (te *TreeEntry) GetSubJumpablePathName() string {
 | 
				
			|||||||
	if te.IsSubModule() || !te.IsDir() {
 | 
						if te.IsSubModule() || !te.IsDir() {
 | 
				
			||||||
		return ""
 | 
							return ""
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	tree, err := te.ptree.SubTree(te.name)
 | 
						tree, err := te.ptree.SubTree(te.Name())
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return te.name
 | 
							return te.Name()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	entries, _ := tree.ListEntries()
 | 
						entries, _ := tree.ListEntries()
 | 
				
			||||||
	if len(entries) == 1 && entries[0].IsDir() {
 | 
						if len(entries) == 1 && entries[0].IsDir() {
 | 
				
			||||||
		name := entries[0].GetSubJumpablePathName()
 | 
							name := entries[0].GetSubJumpablePathName()
 | 
				
			||||||
		if name != "" {
 | 
							if name != "" {
 | 
				
			||||||
			return te.name + "/" + name
 | 
								return te.Name() + "/" + name
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return te.name
 | 
						return te.Name()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Entries a list of entry
 | 
					// Entries a list of entry
 | 
				
			||||||
@@ -167,7 +190,7 @@ var sorter = []func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool{
 | 
				
			|||||||
		return (t1.IsDir() || t1.IsSubModule()) && !t2.IsDir() && !t2.IsSubModule()
 | 
							return (t1.IsDir() || t1.IsSubModule()) && !t2.IsDir() && !t2.IsSubModule()
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool {
 | 
						func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool {
 | 
				
			||||||
		return cmp(t1.name, t2.name)
 | 
							return cmp(t1.Name(), t2.Name())
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,18 +8,20 @@ import (
 | 
				
			|||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing/filemode"
 | 
				
			||||||
 | 
						"gopkg.in/src-d/go-git.v4/plumbing/object"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getTestEntries() Entries {
 | 
					func getTestEntries() Entries {
 | 
				
			||||||
	return Entries{
 | 
						return Entries{
 | 
				
			||||||
		&TreeEntry{name: "v1.0", mode: EntryModeTree},
 | 
							&TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v1.0", Mode: filemode.Dir}},
 | 
				
			||||||
		&TreeEntry{name: "v2.0", mode: EntryModeTree},
 | 
							&TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.0", Mode: filemode.Dir}},
 | 
				
			||||||
		&TreeEntry{name: "v2.1", mode: EntryModeTree},
 | 
							&TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.1", Mode: filemode.Dir}},
 | 
				
			||||||
		&TreeEntry{name: "v2.12", mode: EntryModeTree},
 | 
							&TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.12", Mode: filemode.Dir}},
 | 
				
			||||||
		&TreeEntry{name: "v2.2", mode: EntryModeTree},
 | 
							&TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.2", Mode: filemode.Dir}},
 | 
				
			||||||
		&TreeEntry{name: "v12.0", mode: EntryModeTree},
 | 
							&TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v12.0", Mode: filemode.Dir}},
 | 
				
			||||||
		&TreeEntry{name: "abc", mode: EntryModeBlob},
 | 
							&TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "abc", Mode: filemode.Regular}},
 | 
				
			||||||
		&TreeEntry{name: "bcd", mode: EntryModeBlob},
 | 
							&TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "bcd", Mode: filemode.Regular}},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,7 @@ func TestGetBlobBySHA(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	gbr, err := GetBlobBySHA(ctx.Repo.Repository, ctx.Params(":sha"))
 | 
						gbr, err := GetBlobBySHA(ctx.Repo.Repository, ctx.Params(":sha"))
 | 
				
			||||||
	expectedGBR := &api.GitBlobResponse{
 | 
						expectedGBR := &api.GitBlobResponse{
 | 
				
			||||||
		Content:  "Y29tbWl0IDY1ZjFiZjI3YmMzYmY3MGY2NDY1NzY1ODYzNWU2NjA5NGVkYmNiNGQKQXV0aG9yOiB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+CkRhdGU6ICAgU3VuIE1hciAxOSAxNjo0Nzo1OSAyMDE3IC0wNDAwCgogICAgSW5pdGlhbCBjb21taXQKCmRpZmYgLS1naXQgYS9SRUFETUUubWQgYi9SRUFETUUubWQKbmV3IGZpbGUgbW9kZSAxMDA2NDQKaW5kZXggMDAwMDAwMC4uNGI0ODUxYQotLS0gL2Rldi9udWxsCisrKyBiL1JFQURNRS5tZApAQCAtMCwwICsxLDMgQEAKKyMgcmVwbzEKKworRGVzY3JpcHRpb24gZm9yIHJlcG8xClwgTm8gbmV3bGluZSBhdCBlbmQgb2YgZmlsZQo=",
 | 
							Content:  "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK",
 | 
				
			||||||
		Encoding: "base64",
 | 
							Encoding: "base64",
 | 
				
			||||||
		URL:      "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/65f1bf27bc3bf70f64657658635e66094edbcb4d",
 | 
							URL:      "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/65f1bf27bc3bf70f64657658635e66094edbcb4d",
 | 
				
			||||||
		SHA:      "65f1bf27bc3bf70f64657658635e66094edbcb4d",
 | 
							SHA:      "65f1bf27bc3bf70f64657658635e66094edbcb4d",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,7 +61,7 @@ func GetFileContents(repo *models.Repository, treePath, ref string) (*api.FileCo
 | 
				
			|||||||
		HTMLURL:     htmlURL.String(),
 | 
							HTMLURL:     htmlURL.String(),
 | 
				
			||||||
		GitURL:      gitURL.String(),
 | 
							GitURL:      gitURL.String(),
 | 
				
			||||||
		DownloadURL: downloadURL.String(),
 | 
							DownloadURL: downloadURL.String(),
 | 
				
			||||||
		Type:        string(entry.Type),
 | 
							Type:        entry.Type(),
 | 
				
			||||||
		Links: &api.FileLinksResponse{
 | 
							Links: &api.FileLinksResponse{
 | 
				
			||||||
			Self:    selfURL.String(),
 | 
								Self:    selfURL.String(),
 | 
				
			||||||
			GitURL:  gitURL.String(),
 | 
								GitURL:  gitURL.String(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -58,7 +58,7 @@ func (t *TemporaryUploadRepository) Clone(branch string) error {
 | 
				
			|||||||
		fmt.Sprintf("Clone (git clone -s --bare): %s", t.basePath),
 | 
							fmt.Sprintf("Clone (git clone -s --bare): %s", t.basePath),
 | 
				
			||||||
		"git", "clone", "-s", "--bare", "-b", branch, t.repo.RepoPath(), t.basePath); err != nil {
 | 
							"git", "clone", "-s", "--bare", "-b", branch, t.repo.RepoPath(), t.basePath); err != nil {
 | 
				
			||||||
		if matched, _ := regexp.MatchString(".*Remote branch .* not found in upstream origin.*", stderr); matched {
 | 
							if matched, _ := regexp.MatchString(".*Remote branch .* not found in upstream origin.*", stderr); matched {
 | 
				
			||||||
			return models.ErrBranchNotExist{
 | 
								return git.ErrBranchNotExist{
 | 
				
			||||||
				Name: branch,
 | 
									Name: branch,
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else if matched, _ := regexp.MatchString(".* repository .* does not exist.*", stderr); matched {
 | 
							} else if matched, _ := regexp.MatchString(".* repository .* does not exist.*", stderr); matched {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,7 @@ func GetTreeBySHA(repo *models.Repository, sha string, page, perPage int, recurs
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	tree := new(api.GitTreeResponse)
 | 
						tree := new(api.GitTreeResponse)
 | 
				
			||||||
	tree.SHA = gitTree.ID.String()
 | 
						tree.SHA = gitTree.CommitID.String()
 | 
				
			||||||
	tree.URL = repo.APIURL() + "/git/trees/" + tree.SHA
 | 
						tree.URL = repo.APIURL() + "/git/trees/" + tree.SHA
 | 
				
			||||||
	var entries git.Entries
 | 
						var entries git.Entries
 | 
				
			||||||
	if recursive {
 | 
						if recursive {
 | 
				
			||||||
@@ -74,11 +74,12 @@ func GetTreeBySHA(repo *models.Repository, sha string, page, perPage int, recurs
 | 
				
			|||||||
	tree.Entries = make([]api.GitEntry, rangeEnd-rangeStart)
 | 
						tree.Entries = make([]api.GitEntry, rangeEnd-rangeStart)
 | 
				
			||||||
	for e := rangeStart; e < rangeEnd; e++ {
 | 
						for e := rangeStart; e < rangeEnd; e++ {
 | 
				
			||||||
		i := e - rangeStart
 | 
							i := e - rangeStart
 | 
				
			||||||
		tree.Entries[i].Path = entries[e].Name()
 | 
					
 | 
				
			||||||
		tree.Entries[i].Mode = fmt.Sprintf("%06x", entries[e].Mode())
 | 
							tree.Entries[e].Path = entries[e].Name()
 | 
				
			||||||
		tree.Entries[i].Type = string(entries[e].Type)
 | 
							tree.Entries[e].Mode = fmt.Sprintf("%06o", entries[e].Mode())
 | 
				
			||||||
		tree.Entries[i].Size = entries[e].Size()
 | 
							tree.Entries[e].Type = entries[e].Type()
 | 
				
			||||||
		tree.Entries[i].SHA = entries[e].ID.String()
 | 
							tree.Entries[e].Size = entries[e].Size()
 | 
				
			||||||
 | 
							tree.Entries[e].SHA = entries[e].ID.String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if entries[e].IsDir() {
 | 
							if entries[e].IsDir() {
 | 
				
			||||||
			copy(treeURL[copyPos:], entries[e].ID.String())
 | 
								copy(treeURL[copyPos:], entries[e].ID.String())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,5 +46,6 @@ func TestGetTreeBySHA(t *testing.T) {
 | 
				
			|||||||
		Page:       1,
 | 
							Page:       1,
 | 
				
			||||||
		TotalCount: 1,
 | 
							TotalCount: 1,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	assert.EqualValues(t, tree, expectedTree)
 | 
					
 | 
				
			||||||
 | 
						assert.EqualValues(t, expectedTree, tree)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,7 +62,7 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up
 | 
				
			|||||||
				BranchName: opts.NewBranch,
 | 
									BranchName: opts.NewBranch,
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if err != nil && !models.IsErrBranchNotExist(err) {
 | 
							if err != nil && !git.IsErrBranchNotExist(err) {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,7 @@ func ToEmail(email *models.EmailAddress) *api.Email {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ToBranch convert a commit and branch to an api.Branch
 | 
					// ToBranch convert a commit and branch to an api.Branch
 | 
				
			||||||
func ToBranch(repo *models.Repository, b *models.Branch, c *git.Commit) *api.Branch {
 | 
					func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit) *api.Branch {
 | 
				
			||||||
	return &api.Branch{
 | 
						return &api.Branch{
 | 
				
			||||||
		Name:   b.Name,
 | 
							Name:   b.Name,
 | 
				
			||||||
		Commit: ToCommit(repo, c),
 | 
							Commit: ToCommit(repo, c),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,13 @@
 | 
				
			|||||||
// Copyright 2016 The Gogs Authors. All rights reserved.
 | 
					// Copyright 2016 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package repo
 | 
					package repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/v1/convert"
 | 
						"code.gitea.io/gitea/routers/api/v1/convert"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	api "code.gitea.io/sdk/gitea"
 | 
						api "code.gitea.io/sdk/gitea"
 | 
				
			||||||
@@ -47,7 +48,7 @@ func GetBranch(ctx *context.APIContext) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	branch, err := ctx.Repo.Repository.GetBranch(ctx.Repo.BranchName)
 | 
						branch, err := ctx.Repo.Repository.GetBranch(ctx.Repo.BranchName)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if models.IsErrBranchNotExist(err) {
 | 
							if git.IsErrBranchNotExist(err) {
 | 
				
			||||||
			ctx.NotFound(err)
 | 
								ctx.NotFound(err)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ctx.Error(500, "GetBranch", err)
 | 
								ctx.Error(500, "GetBranch", err)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -243,6 +243,7 @@ func Diff(ctx *context.Context) {
 | 
				
			|||||||
		ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", "commit", parents[0])
 | 
							ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", "commit", parents[0])
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", "commit", commitID)
 | 
						ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", "commit", commitID)
 | 
				
			||||||
 | 
						ctx.Data["BranchName"], err = commit.GetBranchName()
 | 
				
			||||||
	ctx.HTML(200, tplDiff)
 | 
						ctx.HTML(200, tplDiff)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -95,11 +95,12 @@ func editFile(ctx *context.Context, isNewFile bool) {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		dataRc, err := blob.Data()
 | 
							dataRc, err := blob.DataAsync()
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			ctx.NotFound("blob.Data", err)
 | 
								ctx.NotFound("blob.Data", err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							defer dataRc.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ctx.Data["FileSize"] = blob.Size()
 | 
							ctx.Data["FileSize"] = blob.Size()
 | 
				
			||||||
		ctx.Data["FileName"] = blob.Name()
 | 
							ctx.Data["FileName"] = blob.Name()
 | 
				
			||||||
@@ -251,9 +252,9 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo
 | 
				
			|||||||
		} else if models.IsErrRepoFileAlreadyExists(err) {
 | 
							} else if models.IsErrRepoFileAlreadyExists(err) {
 | 
				
			||||||
			ctx.Data["Err_TreePath"] = true
 | 
								ctx.Data["Err_TreePath"] = true
 | 
				
			||||||
			ctx.RenderWithErr(ctx.Tr("repo.editor.file_already_exists", form.TreePath), tplEditFile, &form)
 | 
								ctx.RenderWithErr(ctx.Tr("repo.editor.file_already_exists", form.TreePath), tplEditFile, &form)
 | 
				
			||||||
		} else if models.IsErrBranchNotExist(err) {
 | 
							} else if git.IsErrBranchNotExist(err) {
 | 
				
			||||||
			// For when a user adds/updates a file to a branch that no longer exists
 | 
								// For when a user adds/updates a file to a branch that no longer exists
 | 
				
			||||||
			if branchErr, ok := err.(models.ErrBranchNotExist); ok {
 | 
								if branchErr, ok := err.(git.ErrBranchNotExist); ok {
 | 
				
			||||||
				ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplEditFile, &form)
 | 
									ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplEditFile, &form)
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				ctx.Error(500, err.Error())
 | 
									ctx.Error(500, err.Error())
 | 
				
			||||||
@@ -417,9 +418,9 @@ func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
 | 
				
			|||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				ctx.ServerError("DeleteRepoFile", err)
 | 
									ctx.ServerError("DeleteRepoFile", err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else if models.IsErrBranchNotExist(err) {
 | 
							} else if git.IsErrBranchNotExist(err) {
 | 
				
			||||||
			// For when a user deletes a file to a branch that no longer exists
 | 
								// For when a user deletes a file to a branch that no longer exists
 | 
				
			||||||
			if branchErr, ok := err.(models.ErrBranchNotExist); ok {
 | 
								if branchErr, ok := err.(git.ErrBranchNotExist); ok {
 | 
				
			||||||
				ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplEditFile, &form)
 | 
									ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplEditFile, &form)
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				ctx.Error(500, err.Error())
 | 
									ctx.Error(500, err.Error())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,6 @@ import (
 | 
				
			|||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
					 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
@@ -363,7 +362,6 @@ func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getFileContentFromDefaultBranch(ctx *context.Context, filename string) (string, bool) {
 | 
					func getFileContentFromDefaultBranch(ctx *context.Context, filename string) (string, bool) {
 | 
				
			||||||
	var r io.Reader
 | 
					 | 
				
			||||||
	var bytes []byte
 | 
						var bytes []byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if ctx.Repo.Commit == nil {
 | 
						if ctx.Repo.Commit == nil {
 | 
				
			||||||
@@ -381,10 +379,11 @@ func getFileContentFromDefaultBranch(ctx *context.Context, filename string) (str
 | 
				
			|||||||
	if entry.Blob().Size() >= setting.UI.MaxDisplayFileSize {
 | 
						if entry.Blob().Size() >= setting.UI.MaxDisplayFileSize {
 | 
				
			||||||
		return "", false
 | 
							return "", false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	r, err = entry.Blob().Data()
 | 
						r, err := entry.Blob().DataAsync()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return "", false
 | 
							return "", false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						defer r.Close()
 | 
				
			||||||
	bytes, err = ioutil.ReadAll(r)
 | 
						bytes, err = ioutil.ReadAll(r)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return "", false
 | 
							return "", false
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -103,7 +103,7 @@ func SettingsProtectedBranch(c *context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	protectBranch, err := models.GetProtectedBranchBy(c.Repo.Repository.ID, branch)
 | 
						protectBranch, err := models.GetProtectedBranchBy(c.Repo.Repository.ID, branch)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if !models.IsErrBranchNotExist(err) {
 | 
							if !git.IsErrBranchNotExist(err) {
 | 
				
			||||||
			c.ServerError("GetProtectBranchOfRepoByName", err)
 | 
								c.ServerError("GetProtectBranchOfRepoByName", err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -152,7 +152,7 @@ func SettingsProtectedBranchPost(ctx *context.Context, f auth.ProtectBranchForm)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	protectBranch, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, branch)
 | 
						protectBranch, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, branch)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if !models.IsErrBranchNotExist(err) {
 | 
							if !git.IsErrBranchNotExist(err) {
 | 
				
			||||||
			ctx.ServerError("GetProtectBranchOfRepoByName", err)
 | 
								ctx.ServerError("GetProtectBranchOfRepoByName", err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,7 +49,8 @@ func renderDirectory(ctx *context.Context, treeLink string) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	entries.CustomSort(base.NaturalSortLess)
 | 
						entries.CustomSort(base.NaturalSortLess)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Data["Files"], err = entries.GetCommitsInfo(ctx.Repo.Commit, ctx.Repo.TreePath, nil)
 | 
						var latestCommit *git.Commit
 | 
				
			||||||
 | 
						ctx.Data["Files"], latestCommit, err = entries.GetCommitsInfo(ctx.Repo.Commit, ctx.Repo.TreePath, nil)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("GetCommitsInfo", err)
 | 
							ctx.ServerError("GetCommitsInfo", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@@ -178,14 +179,6 @@ func renderDirectory(ctx *context.Context, treeLink string) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Show latest commit info of repository in table header,
 | 
						// Show latest commit info of repository in table header,
 | 
				
			||||||
	// or of directory if not in root directory.
 | 
						// or of directory if not in root directory.
 | 
				
			||||||
	latestCommit := ctx.Repo.Commit
 | 
					 | 
				
			||||||
	if len(ctx.Repo.TreePath) > 0 {
 | 
					 | 
				
			||||||
		latestCommit, err = ctx.Repo.Commit.GetCommitByPath(ctx.Repo.TreePath)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			ctx.ServerError("GetCommitByPath", err)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	ctx.Data["LatestCommit"] = latestCommit
 | 
						ctx.Data["LatestCommit"] = latestCommit
 | 
				
			||||||
	ctx.Data["LatestCommitVerification"] = models.ParseCommitWithSignature(latestCommit)
 | 
						ctx.Data["LatestCommitVerification"] = models.ParseCommitWithSignature(latestCommit)
 | 
				
			||||||
	ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit)
 | 
						ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,7 +57,7 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error)
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, entry := range entries {
 | 
						for _, entry := range entries {
 | 
				
			||||||
		if entry.Type == git.ObjectBlob && entry.Name() == target {
 | 
							if entry.IsRegular() && entry.Name() == target {
 | 
				
			||||||
			return entry, nil
 | 
								return entry, nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -81,11 +81,12 @@ func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, err
 | 
				
			|||||||
// wikiContentsByEntry returns the contents of the wiki page referenced by the
 | 
					// wikiContentsByEntry returns the contents of the wiki page referenced by the
 | 
				
			||||||
// given tree entry. Writes to ctx if an error occurs.
 | 
					// given tree entry. Writes to ctx if an error occurs.
 | 
				
			||||||
func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
 | 
					func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
 | 
				
			||||||
	reader, err := entry.Blob().Data()
 | 
						reader, err := entry.Blob().DataAsync()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("Blob.Data", err)
 | 
							ctx.ServerError("Blob.Data", err)
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						defer reader.Close()
 | 
				
			||||||
	content, err := ioutil.ReadAll(reader)
 | 
						content, err := ioutil.ReadAll(reader)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("ReadAll", err)
 | 
							ctx.ServerError("ReadAll", err)
 | 
				
			||||||
@@ -125,7 +126,7 @@ func renderWikiPage(ctx *context.Context, isViewPage bool) (*git.Repository, *gi
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		pages := make([]PageMeta, 0, len(entries))
 | 
							pages := make([]PageMeta, 0, len(entries))
 | 
				
			||||||
		for _, entry := range entries {
 | 
							for _, entry := range entries {
 | 
				
			||||||
			if entry.Type != git.ObjectBlob {
 | 
								if !entry.IsRegular() {
 | 
				
			||||||
				continue
 | 
									continue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			wikiName, err := models.WikiFilenameToName(entry.Name())
 | 
								wikiName, err := models.WikiFilenameToName(entry.Name())
 | 
				
			||||||
@@ -259,7 +260,7 @@ func WikiPages(ctx *context.Context) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	pages := make([]PageMeta, 0, len(entries))
 | 
						pages := make([]PageMeta, 0, len(entries))
 | 
				
			||||||
	for _, entry := range entries {
 | 
						for _, entry := range entries {
 | 
				
			||||||
		if entry.Type != git.ObjectBlob {
 | 
							if !entry.IsRegular() {
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		c, err := wikiRepo.GetCommitByPath(entry.Name())
 | 
							c, err := wikiRepo.GetCommitByPath(entry.Name())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,8 +40,9 @@ func wikiContent(t *testing.T, repo *models.Repository, wikiName string) string
 | 
				
			|||||||
	if !assert.NotNil(t, entry) {
 | 
						if !assert.NotNil(t, entry) {
 | 
				
			||||||
		return ""
 | 
							return ""
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	reader, err := entry.Blob().Data()
 | 
						reader, err := entry.Blob().DataAsync()
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						defer reader.Close()
 | 
				
			||||||
	bytes, err := ioutil.ReadAll(reader)
 | 
						bytes, err := ioutil.ReadAll(reader)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	return string(bytes)
 | 
						return string(bytes)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@
 | 
				
			|||||||
				{{if IsMultilineCommitMessage .Commit.Message}}
 | 
									{{if IsMultilineCommitMessage .Commit.Message}}
 | 
				
			||||||
					<pre class="commit-body">{{RenderCommitBody .Commit.Message $.RepoLink $.Repository.ComposeMetas}}</pre>
 | 
										<pre class="commit-body">{{RenderCommitBody .Commit.Message $.RepoLink $.Repository.ComposeMetas}}</pre>
 | 
				
			||||||
				{{end}}
 | 
									{{end}}
 | 
				
			||||||
				<span class="text grey"><i class="octicon octicon-git-branch"></i>{{.Commit.Branch}}</span>
 | 
									<span class="text grey"><i class="octicon octicon-git-branch"></i>{{.BranchName}}</span>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			<div class="ui attached info segment {{if .Commit.Signature}} isSigned {{if .Verification.Verified }} isVerified {{end}}{{end}}">
 | 
								<div class="ui attached info segment {{if .Commit.Signature}} isSigned {{if .Verification.Verified }} isVerified {{end}}{{end}}">
 | 
				
			||||||
				<div class="ui stackable grid">
 | 
									<div class="ui stackable grid">
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										14
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							@@ -410,8 +410,8 @@ gopkg.in/macaron.v1
 | 
				
			|||||||
# gopkg.in/redis.v2 v2.3.2
 | 
					# gopkg.in/redis.v2 v2.3.2
 | 
				
			||||||
gopkg.in/redis.v2
 | 
					gopkg.in/redis.v2
 | 
				
			||||||
# gopkg.in/src-d/go-billy.v4 v4.3.0
 | 
					# gopkg.in/src-d/go-billy.v4 v4.3.0
 | 
				
			||||||
gopkg.in/src-d/go-billy.v4
 | 
					 | 
				
			||||||
gopkg.in/src-d/go-billy.v4/osfs
 | 
					gopkg.in/src-d/go-billy.v4/osfs
 | 
				
			||||||
 | 
					gopkg.in/src-d/go-billy.v4
 | 
				
			||||||
gopkg.in/src-d/go-billy.v4/util
 | 
					gopkg.in/src-d/go-billy.v4/util
 | 
				
			||||||
gopkg.in/src-d/go-billy.v4/helper/chroot
 | 
					gopkg.in/src-d/go-billy.v4/helper/chroot
 | 
				
			||||||
gopkg.in/src-d/go-billy.v4/helper/polyfill
 | 
					gopkg.in/src-d/go-billy.v4/helper/polyfill
 | 
				
			||||||
@@ -419,13 +419,14 @@ gopkg.in/src-d/go-billy.v4/helper/polyfill
 | 
				
			|||||||
gopkg.in/src-d/go-git.v4
 | 
					gopkg.in/src-d/go-git.v4
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/config
 | 
					gopkg.in/src-d/go-git.v4/config
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing
 | 
					gopkg.in/src-d/go-git.v4/plumbing
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/internal/revision
 | 
					 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/cache
 | 
					gopkg.in/src-d/go-git.v4/plumbing/cache
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/filemode
 | 
					gopkg.in/src-d/go-git.v4/plumbing/filemode
 | 
				
			||||||
 | 
					gopkg.in/src-d/go-git.v4/plumbing/object
 | 
				
			||||||
 | 
					gopkg.in/src-d/go-git.v4/storage/filesystem
 | 
				
			||||||
 | 
					gopkg.in/src-d/go-git.v4/internal/revision
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/format/gitignore
 | 
					gopkg.in/src-d/go-git.v4/plumbing/format/gitignore
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/format/index
 | 
					gopkg.in/src-d/go-git.v4/plumbing/format/index
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/format/packfile
 | 
					gopkg.in/src-d/go-git.v4/plumbing/format/packfile
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/object
 | 
					 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/protocol/packp
 | 
					gopkg.in/src-d/go-git.v4/plumbing/protocol/packp
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability
 | 
					gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/sideband
 | 
					gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/sideband
 | 
				
			||||||
@@ -434,7 +435,6 @@ gopkg.in/src-d/go-git.v4/plumbing/storer
 | 
				
			|||||||
gopkg.in/src-d/go-git.v4/plumbing/transport
 | 
					gopkg.in/src-d/go-git.v4/plumbing/transport
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/transport/client
 | 
					gopkg.in/src-d/go-git.v4/plumbing/transport/client
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/storage
 | 
					gopkg.in/src-d/go-git.v4/storage
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/storage/filesystem
 | 
					 | 
				
			||||||
gopkg.in/src-d/go-git.v4/storage/memory
 | 
					gopkg.in/src-d/go-git.v4/storage/memory
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/utils/diff
 | 
					gopkg.in/src-d/go-git.v4/utils/diff
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/utils/ioutil
 | 
					gopkg.in/src-d/go-git.v4/utils/ioutil
 | 
				
			||||||
@@ -444,16 +444,16 @@ gopkg.in/src-d/go-git.v4/utils/merkletrie/index
 | 
				
			|||||||
gopkg.in/src-d/go-git.v4/utils/merkletrie/noder
 | 
					gopkg.in/src-d/go-git.v4/utils/merkletrie/noder
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/internal/url
 | 
					gopkg.in/src-d/go-git.v4/internal/url
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/format/config
 | 
					gopkg.in/src-d/go-git.v4/plumbing/format/config
 | 
				
			||||||
 | 
					gopkg.in/src-d/go-git.v4/plumbing/format/diff
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/utils/binary
 | 
					gopkg.in/src-d/go-git.v4/utils/binary
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/format/idxfile
 | 
					gopkg.in/src-d/go-git.v4/plumbing/format/idxfile
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/format/diff
 | 
					gopkg.in/src-d/go-git.v4/plumbing/format/objfile
 | 
				
			||||||
 | 
					gopkg.in/src-d/go-git.v4/storage/filesystem/dotgit
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/format/pktline
 | 
					gopkg.in/src-d/go-git.v4/plumbing/format/pktline
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/transport/file
 | 
					gopkg.in/src-d/go-git.v4/plumbing/transport/file
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/transport/git
 | 
					gopkg.in/src-d/go-git.v4/plumbing/transport/git
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/transport/http
 | 
					gopkg.in/src-d/go-git.v4/plumbing/transport/http
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/transport/ssh
 | 
					gopkg.in/src-d/go-git.v4/plumbing/transport/ssh
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/format/objfile
 | 
					 | 
				
			||||||
gopkg.in/src-d/go-git.v4/storage/filesystem/dotgit
 | 
					 | 
				
			||||||
gopkg.in/src-d/go-git.v4/utils/merkletrie/internal/frame
 | 
					gopkg.in/src-d/go-git.v4/utils/merkletrie/internal/frame
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/transport/internal/common
 | 
					gopkg.in/src-d/go-git.v4/plumbing/transport/internal/common
 | 
				
			||||||
gopkg.in/src-d/go-git.v4/plumbing/transport/server
 | 
					gopkg.in/src-d/go-git.v4/plumbing/transport/server
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user