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
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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 {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user