aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--go.mod4
-rw-r--r--integrations/api_repo_git_blobs_test.go2
-rw-r--r--models/error.go15
-rw-r--r--models/pull.go5
-rw-r--r--models/repo_branch.go57
-rw-r--r--modules/context/repo.go3
-rw-r--r--modules/git/blob.go66
-rw-r--r--modules/git/blob_test.go45
-rw-r--r--modules/git/commit.go80
-rw-r--r--modules/git/commit_info.go454
-rw-r--r--modules/git/commit_info_test.go4
-rw-r--r--modules/git/error.go15
-rw-r--r--modules/git/parse.go25
-rw-r--r--modules/git/parse_test.go32
-rw-r--r--modules/git/repo.go31
-rw-r--r--modules/git/repo_blob.go16
-rw-r--r--modules/git/repo_blob_test.go3
-rw-r--r--modules/git/repo_branch.go66
-rw-r--r--modules/git/repo_commit.go147
-rw-r--r--modules/git/repo_tag.go35
-rw-r--r--modules/git/repo_tree.go29
-rw-r--r--modules/git/sha1.go29
-rw-r--r--modules/git/signature.go9
-rw-r--r--modules/git/tree.go91
-rw-r--r--modules/git/tree_blob.go17
-rw-r--r--modules/git/tree_entry.go83
-rw-r--r--modules/git/tree_entry_test.go18
-rw-r--r--modules/repofiles/blob_test.go2
-rw-r--r--modules/repofiles/content.go2
-rw-r--r--modules/repofiles/temp_repo.go2
-rw-r--r--modules/repofiles/tree.go13
-rw-r--r--modules/repofiles/tree_test.go3
-rw-r--r--modules/repofiles/update.go2
-rw-r--r--routers/api/v1/convert/convert.go2
-rw-r--r--routers/api/v1/repo/branch.go5
-rw-r--r--routers/repo/commit.go1
-rw-r--r--routers/repo/editor.go11
-rw-r--r--routers/repo/issue.go5
-rw-r--r--routers/repo/setting_protected_branch.go4
-rw-r--r--routers/repo/view.go11
-rw-r--r--routers/repo/wiki.go9
-rw-r--r--routers/repo/wiki_test.go3
-rw-r--r--templates/repo/diff/page.tmpl2
-rw-r--r--vendor/modules.txt14
44 files changed, 724 insertions, 748 deletions
diff --git a/go.mod b/go.mod
index 60a722ab97..419a50014b 100644
--- a/go.mod
+++ b/go.mod
@@ -32,7 +32,7 @@ require (
github.com/dgrijalva/jwt-go v0.0.0-20161101193935-9ed569b5d1ac
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/emirpasic/gods v1.12.0 // indirect
+ github.com/emirpasic/gods v1.12.0
github.com/etcd-io/bbolt v1.3.2 // indirect
github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
@@ -127,7 +127,7 @@ require (
gopkg.in/ldap.v3 v3.0.2
gopkg.in/macaron.v1 v1.3.2
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/testfixtures.v2 v2.5.0
mvdan.cc/xurls/v2 v2.0.0
diff --git a/integrations/api_repo_git_blobs_test.go b/integrations/api_repo_git_blobs_test.go
index 560f108fca..69bb6cde23 100644
--- a/integrations/api_repo_git_blobs_test.go
+++ b/integrations/api_repo_git_blobs_test.go
@@ -38,7 +38,7 @@ func TestAPIReposGitBlobs(t *testing.T) {
var gitBlobResponse api.GitBlobResponse
DecodeJSON(t, resp, &gitBlobResponse)
assert.NotNil(t, gitBlobResponse)
- expectedContent := "Y29tbWl0IDY1ZjFiZjI3YmMzYmY3MGY2NDY1NzY1ODYzNWU2NjA5NGVkYmNiNGQKQXV0aG9yOiB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+CkRhdGU6ICAgU3VuIE1hciAxOSAxNjo0Nzo1OSAyMDE3IC0wNDAwCgogICAgSW5pdGlhbCBjb21taXQKCmRpZmYgLS1naXQgYS9SRUFETUUubWQgYi9SRUFETUUubWQKbmV3IGZpbGUgbW9kZSAxMDA2NDQKaW5kZXggMDAwMDAwMC4uNGI0ODUxYQotLS0gL2Rldi9udWxsCisrKyBiL1JFQURNRS5tZApAQCAtMCwwICsxLDMgQEAKKyMgcmVwbzEKKworRGVzY3JpcHRpb24gZm9yIHJlcG8xClwgTm8gbmV3bGluZSBhdCBlbmQgb2YgZmlsZQo="
+ expectedContent := "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK"
assert.Equal(t, expectedContent, gitBlobResponse.Content)
// Tests a private repo with no token so will fail
diff --git a/models/error.go b/models/error.go
index 3dd2c79e85..6458594a03 100644
--- a/models/error.go
+++ b/models/error.go
@@ -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.
type ErrBranchAlreadyExists struct {
BranchName string
diff --git a/models/pull.go b/models/pull.go
index 88f9b1f6e8..d059081a44 100644
--- a/models/pull.go
+++ b/models/pull.go
@@ -1,4 +1,5 @@
// 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
// 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 {
var (
- baseBranch *Branch
- headBranch *Branch
+ baseBranch *git.Branch
+ headBranch *git.Branch
baseCommit *git.Commit
headCommit *git.Commit
err error
diff --git a/models/repo_branch.go b/models/repo_branch.go
index 1c62a3d67d..0958e23974 100644
--- a/models/repo_branch.go
+++ b/models/repo_branch.go
@@ -1,4 +1,5 @@
// 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
// 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)
}
-// 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.
func (repo *Repository) CanCreateBranch() bool {
return !repo.IsMirror
}
-// GetBranch returns a branch by it's name
-func (repo *Repository) GetBranch(branch string) (*Branch, error) {
- if !git.IsBranchExist(repo.RepoPath(), branch) {
- return nil, ErrBranchNotExist{branch}
+// GetBranch returns a branch by its name
+func (repo *Repository) GetBranch(branch string) (*git.Branch, error) {
+ gitRepo, err := git.OpenRepository(repo.RepoPath())
+ if err != nil {
+ return nil, err
}
- return &Branch{
- Path: repo.RepoPath(),
- Name: branch,
- }, nil
+
+ return gitRepo.GetBranch(branch)
}
// GetBranches returns all the branches of a repository
-func (repo *Repository) GetBranches() ([]*Branch, error) {
- return GetBranchesByPath(repo.RepoPath())
+func (repo *Repository) GetBranches() ([]*git.Branch, error) {
+ return git.GetBranchesByPath(repo.RepoPath())
}
// CheckBranchName validates branch name with existing repository branches
@@ -257,12 +229,3 @@ func (repo *Repository) CreateNewBranchFromCommit(doer *User, commit, branchName
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)
-}
diff --git a/modules/context/repo.go b/modules/context/repo.go
index 9d3fb7cfec..4670e01564 100644
--- a/modules/context/repo.go
+++ b/modules/context/repo.go
@@ -157,10 +157,11 @@ func (r *Repository) GetEditorconfig() (*editorconfig.Editorconfig, error) {
if treeEntry.Blob().Size() >= setting.UI.MaxDisplayFileSize {
return nil, git.ErrNotExist{ID: "", RelPath: ".editorconfig"}
}
- reader, err := treeEntry.Blob().Data()
+ reader, err := treeEntry.Blob().DataAsync()
if err != nil {
return nil, err
}
+ defer reader.Close()
data, err := ioutil.ReadAll(reader)
if err != nil {
return nil, err
diff --git a/modules/git/blob.go b/modules/git/blob.go
index e194b973db..171b4a1010 100644
--- a/modules/git/blob.go
+++ b/modules/git/blob.go
@@ -1,76 +1,40 @@
// 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
// license that can be found in the LICENSE file.
package git
import (
- "bytes"
"encoding/base64"
- "fmt"
"io"
"io/ioutil"
- "os"
- "os/exec"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
)
// Blob represents a Git object.
type Blob struct {
- repo *Repository
- *TreeEntry
-}
-
-// Data gets content of blob all at once and wrap it as io.Reader.
-// This can be very slow and memory consuming for huge content.
-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
-}
+ ID SHA1
-// 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()
+ gogitEncodedObj plumbing.EncodedObject
+ name string
}
// 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.
func (b *Blob) DataAsync() (io.ReadCloser, error) {
- cmd := exec.Command("git", "show", b.ID.String())
- cmd.Dir = b.repo.Path
- cmd.Stderr = os.Stderr
-
- stdout, err := cmd.StdoutPipe()
- if err != nil {
- return nil, fmt.Errorf("StdoutPipe: %v", err)
- }
+ return b.gogitEncodedObj.Reader()
+}
- if err = cmd.Start(); err != nil {
- return nil, fmt.Errorf("Start: %v", err)
- }
+// Size returns the uncompressed size of the blob
+func (b *Blob) Size() int64 {
+ return b.gogitEncodedObj.Size()
+}
- return cmdReadCloser{stdout: stdout, cmd: cmd}, nil
+// Name returns name of the tree entry this blob object was created from (or empty string)
+func (b *Blob) Name() string {
+ return b.name
}
// GetBlobContentBase64 Reads the content of the blob with a base64 encode and returns the encoded string
diff --git a/modules/git/blob_test.go b/modules/git/blob_test.go
index 39516c422c..66c046ecc8 100644
--- a/modules/git/blob_test.go
+++ b/modules/git/blob_test.go
@@ -1,11 +1,11 @@
// 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
// license that can be found in the LICENSE file.
package git
import (
- "bytes"
"io/ioutil"
"testing"
@@ -13,20 +13,6 @@ import (
"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) {
output := `Copyright (c) 2016 The Gitea 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
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)
require.NotNil(t, r)
+ defer r.Close()
data, err := ioutil.ReadAll(r)
assert.NoError(t, err)
@@ -60,21 +51,21 @@ THE SOFTWARE.
}
func Benchmark_Blob_Data(b *testing.B) {
- for i := 0; i < b.N; i++ {
- r, err := testBlob.Data()
- if err != nil {
- b.Fatal(err)
- }
- ioutil.ReadAll(r)
+ repo, err := OpenRepository("../../.git")
+ if err != nil {
+ b.Fatal(err)
+ }
+ testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697")
+ if err != nil {
+ b.Fatal(err)
}
-}
-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 {
+ r, err := testBlob.DataAsync()
+ if err != nil {
b.Fatal(err)
}
+ defer r.Close()
+ ioutil.ReadAll(r)
}
}
diff --git a/modules/git/commit.go b/modules/git/commit.go
index dad67dada6..7b64a300ab 100644
--- a/modules/git/commit.go
+++ b/modules/git/commit.go
@@ -14,6 +14,8 @@ import (
"net/http"
"strconv"
"strings"
+
+ "gopkg.in/src-d/go-git.v4/plumbing/object"
)
// 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
}
-// similar to https://github.com/git/git/blob/3bc53220cb2dcf709f7a027a3f526befd021d858/commit.c#L1128
-func newGPGSignatureFromCommitline(data []byte, signatureStart int, tag bool) (*CommitGPGSignature, error) {
- sig := new(CommitGPGSignature)
- signatureEnd := bytes.LastIndex(data, []byte("-----END PGP SIGNATURE-----"))
- if signatureEnd == -1 {
- return nil, fmt.Errorf("end of commit signature not found")
+func convertPGPSignature(c *object.Commit) *CommitGPGSignature {
+ if c.PGPSignature == "" {
+ return nil
+ }
+
+ var w strings.Builder
+ var err error
+
+ 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
}
- sig.Signature = strings.Replace(string(data[signatureStart:signatureEnd+27]), "\n ", "\n", -1)
- if tag {
- sig.Payload = string(data[:signatureStart-1])
- } else {
- sig.Payload = string(data[:signatureStart-8]) + string(data[signatureEnd+27:])
+
+ 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.
@@ -281,11 +322,13 @@ func (c *Commit) GetSubModules() (*ObjectCache, error) {
}
return nil, err
}
- rd, err := entry.Blob().Data()
+
+ rd, err := entry.Blob().DataAsync()
if err != nil {
return nil, err
}
+ defer rd.Close()
scanner := bufio.NewScanner(rd)
c.submoduleCache = newObjectCache()
var ismodule bool
@@ -326,6 +369,17 @@ func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
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.
type CommitFileStatus struct {
Added []string
diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go
index 971082be1f..02c6f710ad 100644
--- a/modules/git/commit_info.go
+++ b/modules/git/commit_info.go
@@ -5,325 +5,237 @@
package git
import (
- "bufio"
- "context"
- "fmt"
- "os/exec"
- "path"
- "runtime"
- "strconv"
- "strings"
- "sync"
- "time"
+ "github.com/emirpasic/gods/trees/binaryheap"
+ "gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/object"
)
-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
-)
+// GetCommitsInfo gets information of all commits that are corresponding to these entries
+func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCommitCache) ([][]interface{}, *Commit, error) {
+ entryPaths := make([]string, len(tes)+1)
+ // Get the commit for the treePath itself
+ entryPaths[0] = ""
+ for i, entry := range tes {
+ entryPaths[i+1] = entry.Name()
+ }
-// 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
+ c, err := commit.repo.gogitRepo.CommitObject(plumbing.Hash(commit.ID))
+ if err != nil {
+ return nil, nil, err
+ }
- /* 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{}
-}
+ revs, err := getLastCommitForPaths(c, treePath, entryPaths)
+ if err != nil {
+ return nil, nil, err
+ }
-func (state *getCommitsInfoState) numRemainingEntries() int {
- state.lock.Lock()
- defer state.lock.Unlock()
- return len(state.entries) - len(state.commits)
-}
+ commit.repo.gogitStorage.Close()
-// 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
+ commitsInfo := make([][]interface{}, len(tes))
+ for i, entry := range tes {
+ if rev, ok := revs[entry.Name()]; ok {
+ entryCommit := convertCommit(rev)
+ if entry.IsSubModule() {
+ subModuleURL := ""
+ if subModule, err := commit.GetSubModule(entry.Name()); err != nil {
+ return nil, nil, err
+ } else if subModule != nil {
+ subModuleURL = subModule.URL
+ }
+ subModuleFile := NewSubModuleFile(entryCommit, subModuleURL, entry.ID.String())
+ commitsInfo[i] = []interface{}{entry, subModuleFile}
+ } else {
+ commitsInfo[i] = []interface{}{entry, entryCommit}
+ }
+ } else {
+ commitsInfo[i] = []interface{}{entry, nil}
}
- 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)
- }
+ // 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
}
-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,
- }
+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
}
-// GetCommitsInfo gets information of all commits that are corresponding to these entries
-func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCommitCache) ([][]interface{}, error) {
- state := initGetCommitInfoState(tes, commit, treePath)
- if err := getCommitsInfo(state, cache); err != nil {
+func getCommitTree(c *object.Commit, treePath string) (*object.Tree, error) {
+ tree, err := c.Tree()
+ if err != nil {
return nil, err
}
- if len(state.commits) < len(state.entryPaths) {
- return nil, fmt.Errorf("could not find commits for all entries")
- }
-
- commitsInfo := make([][]interface{}, len(tes))
- for i, entry := range tes {
- commit, ok := state.commits[path.Join(treePath, entry.Name())]
- if !ok {
- return nil, fmt.Errorf("could not find commit for %s", entry.Name())
- }
- switch entry.Type {
- case ObjectCommit:
- subModuleURL := ""
- if subModule, err := state.headCommit.GetSubModule(entry.Name()); err != nil {
- 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)
+ // Optimize deep traversals by focusing only on the specific tree
+ if treePath != "" {
+ tree, err = tree.Tree(treePath)
if err != nil {
- return rawEntryPath, err
+ return nil, 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
+ return tree, 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
+func getFullPath(treePath, path string) string {
+ if treePath != "" {
+ if path != "" {
+ return treePath + "/" + path
+ }
+ return treePath
}
- return updated
+ return path
}
-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)
+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
}
- cmd := exec.CommandContext(ctx, "git", args...)
- cmd.Dir = state.headCommit.repo.Path
-
- readCloser, err := cmd.StdoutPipe()
if err != nil {
- return err
+ return nil, err
}
- if err := cmd.Start(); err != nil {
- return 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
+ }
}
- // 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)
- }
+ return hashes, nil
+}
- scanner := bufio.NewScanner(readCloser)
- err = state.processGitLogOutput(scanner)
+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
+ })
- // 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
+ result := make(map[string]*object.Commit)
+ initialHashes, err := getFileHashes(c, treePath, paths)
+ if err != nil {
+ return nil, err
}
- for i := 0; i < numThreads; i++ {
- doneErr := <-done
- if doneErr != nil && err == nil {
- err = doneErr
+ // 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
}
- }
- return err
-}
+ current := cIn.(*commitAndPaths)
+ currentID := current.commit.ID()
-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
- }
+ if seen[currentID] {
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)
+ seen[currentID] = true
+
+ // Load the parent commits for the one we are currently examining
+ numParents := current.commit.NumParents()
+ var parents []*object.Commit
+ for i := 0; i < numParents; i++ {
+ parent, err := current.commit.Parent(i)
+ if err != nil {
+ break
}
- entryPath, err := state.cleanEntryPath(line[tabIndex+1:])
+ parents = append(parents, parent)
+ }
+
+ // Examine the current commit and set of interesting paths
+ numOfParentsWithPath := make([]int, len(current.paths))
+ pathChanged := make([]bool, len(current.paths))
+ parentHashes := make([]map[string]plumbing.Hash, len(parents))
+ for j, parent := range parents {
+ parentHashes[j], err = getFileHashes(parent, treePath, current.paths)
if err != nil {
- return err
+ break
}
- if _, ok := seenPaths[entryPath]; !ok {
- if state.update(entryPath, commit) {
- coldStreak = 0
+
+ for i, path := range current.paths {
+ if parentHashes[j][path] != plumbing.ZeroHash {
+ numOfParentsWithPath[i]++
+ if parentHashes[j][path] != current.hashes[path] {
+ pathChanged[i] = true
+ }
}
- seenPaths[entryPath] = struct{}{}
}
- continue
}
- // a new commit
- commit, err = parseCommitInfo(line)
- if err != nil {
- return err
+ 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)
+ }
}
- coldStreak++
- }
- return scanner.Err()
-}
-// parseCommitInfo parse a commit from a line of `git log` output. Expects the
-// line to be formatted according to getCommitsInfoPretty.
-func parseCommitInfo(line string) (*Commit, error) {
- if len(line) < 43 {
- return nil, fmt.Errorf("invalid git output: %s", line)
- }
- ref, err := NewIDFromString(line[:40])
- if err != 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
+ 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]})
+ }
+ }
}
- message := line[spaceIndex+42:]
- return &Commit{
- ID: ref,
- CommitMessage: message,
- Committer: &Signature{
- When: time.Unix(int64(unixSeconds), 0),
- },
- }, nil
+
+ return result, nil
}
diff --git a/modules/git/commit_info_test.go b/modules/git/commit_info_test.go
index 120a9a737c..d7d863b032 100644
--- a/modules/git/commit_info_test.go
+++ b/modules/git/commit_info_test.go
@@ -51,7 +51,7 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
assert.NoError(t, err)
entries, err := tree.ListEntries()
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.Len(t, commitsInfo, len(testCase.ExpectedIDs))
for _, commitInfo := range commitsInfo {
@@ -107,7 +107,7 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
b.ResetTimer()
b.Run(benchmark.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
- _, err := entries.GetCommitsInfo(commit, "", nil)
+ _, _, err := entries.GetCommitsInfo(commit, "", nil)
if err != nil {
b.Fatal(err)
}
diff --git a/modules/git/error.go b/modules/git/error.go
index 1aae5a37a2..6e4f26de13 100644
--- a/modules/git/error.go
+++ b/modules/git/error.go
@@ -64,3 +64,18 @@ func IsErrUnsupportedVersion(err error) bool {
func (err ErrUnsupportedVersion) Error() string {
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)
+}
diff --git a/modules/git/parse.go b/modules/git/parse.go
index 5c964f16ee..22861b1d2c 100644
--- a/modules/git/parse.go
+++ b/modules/git/parse.go
@@ -8,6 +8,10 @@ import (
"bytes"
"fmt"
"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.
@@ -20,30 +24,26 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
for pos := 0; pos < len(data); {
// expect line to be of the form "<mode> <type> <sha>\t<filename>"
entry := new(TreeEntry)
+ entry.gogitTreeEntry = &object.TreeEntry{}
entry.ptree = ptree
if pos+6 > len(data) {
return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
}
switch string(data[pos : pos+6]) {
case "100644":
- entry.mode = EntryModeBlob
- entry.Type = ObjectBlob
+ entry.gogitTreeEntry.Mode = filemode.Regular
pos += 12 // skip over "100644 blob "
case "100755":
- entry.mode = EntryModeExec
- entry.Type = ObjectBlob
+ entry.gogitTreeEntry.Mode = filemode.Executable
pos += 12 // skip over "100755 blob "
case "120000":
- entry.mode = EntryModeSymlink
- entry.Type = ObjectBlob
+ entry.gogitTreeEntry.Mode = filemode.Symlink
pos += 12 // skip over "120000 blob "
case "160000":
- entry.mode = EntryModeCommit
- entry.Type = ObjectCommit
+ entry.gogitTreeEntry.Mode = filemode.Submodule
pos += 14 // skip over "160000 object "
case "040000":
- entry.mode = EntryModeTree
- entry.Type = ObjectTree
+ entry.gogitTreeEntry.Mode = filemode.Dir
pos += 12 // skip over "040000 tree "
default:
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)
}
entry.ID = id
+ entry.gogitTreeEntry.Hash = plumbing.Hash(id)
pos += 41 // skip over sha and trailing space
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).
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 {
return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
}
} else {
- entry.name = string(data[pos:end])
+ entry.gogitTreeEntry.Name = string(data[pos:end])
}
pos = end + 1
diff --git a/modules/git/parse_test.go b/modules/git/parse_test.go
index 66936cbdf0..d249623949 100644
--- a/modules/git/parse_test.go
+++ b/modules/git/parse_test.go
@@ -8,6 +8,8 @@ import (
"testing"
"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) {
@@ -23,10 +25,12 @@ func TestParseTreeEntries(t *testing.T) {
Input: "100644 blob 61ab7345a1a3bbc590068ccae37b8515cfc5843c\texample/file2.txt\n",
Expected: []*TreeEntry{
{
- mode: EntryModeBlob,
- Type: ObjectBlob,
- ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
- name: "example/file2.txt",
+ ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
+ gogitTreeEntry: &object.TreeEntry{
+ Hash: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
+ Name: "example/file2.txt",
+ Mode: filemode.Regular,
+ },
},
},
},
@@ -35,16 +39,20 @@ func TestParseTreeEntries(t *testing.T) {
"040000 tree 1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8\texample\n",
Expected: []*TreeEntry{
{
- ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
- Type: ObjectBlob,
- mode: EntryModeSymlink,
- name: "example/\n.txt",
+ ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
+ gogitTreeEntry: &object.TreeEntry{
+ Hash: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
+ Name: "example/\n.txt",
+ Mode: filemode.Symlink,
+ },
},
{
- ID: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"),
- Type: ObjectTree,
- mode: EntryModeTree,
- name: "example",
+ ID: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"),
+ gogitTreeEntry: &object.TreeEntry{
+ Hash: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"),
+ Name: "example",
+ Mode: filemode.Dir,
+ },
},
},
},
diff --git a/modules/git/repo.go b/modules/git/repo.go
index 4306730920..f86c4aae5c 100644
--- a/modules/git/repo.go
+++ b/modules/git/repo.go
@@ -16,14 +16,20 @@ import (
"time"
"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.
type Repository struct {
Path string
- commitCache *ObjectCache
- tagCache *ObjectCache
+ tagCache *ObjectCache
+
+ gogitRepo *gogit.Repository
+ gogitStorage *filesystem.Storage
}
const prettyLogFormat = `--pretty=format:%H`
@@ -77,10 +83,25 @@ func OpenRepository(repoPath string) (*Repository, error) {
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{
- Path: repoPath,
- commitCache: newObjectCache(),
- tagCache: newObjectCache(),
+ Path: repoPath,
+ gogitRepo: gogitRepo,
+ gogitStorage: storage,
+ tagCache: newObjectCache(),
}, nil
}
diff --git a/modules/git/repo_blob.go b/modules/git/repo_blob.go
index a9445a1f7a..db63491ce4 100644
--- a/modules/git/repo_blob.go
+++ b/modules/git/repo_blob.go
@@ -4,19 +4,19 @@
package git
+import (
+ "gopkg.in/src-d/go-git.v4/plumbing"
+)
+
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 &Blob{
- repo: repo,
- TreeEntry: &TreeEntry{
- ID: id,
- ptree: &Tree{
- repo: repo,
- },
- },
+ ID: id,
+ gogitEncodedObj: encodedObj,
}, nil
}
diff --git a/modules/git/repo_blob_test.go b/modules/git/repo_blob_test.go
index 074365f164..128a227829 100644
--- a/modules/git/repo_blob_test.go
+++ b/modules/git/repo_blob_test.go
@@ -30,8 +30,9 @@ func TestRepository_GetBlob_Found(t *testing.T) {
blob, err := r.GetBlob(testCase.OID)
assert.NoError(t, err)
- dataReader, err := blob.Data()
+ dataReader, err := blob.DataAsync()
assert.NoError(t, err)
+ defer dataReader.Close()
data, err := ioutil.ReadAll(dataReader)
assert.NoError(t, err)
diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go
index 6414abbec5..83689ee9dc 100644
--- a/modules/git/repo_branch.go
+++ b/modules/git/repo_branch.go
@@ -9,7 +9,6 @@ import (
"fmt"
"strings"
- "gopkg.in/src-d/go-git.v4"
"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.
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.
type Branch struct {
Name string
Path string
+
+ gitRepo *Repository
}
// GetHEADBranch returns corresponding branch of HEAD.
@@ -51,8 +56,9 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) {
}
return &Branch{
- Name: stdout[len(BranchPrefix):],
- Path: stdout,
+ Name: stdout[len(BranchPrefix):],
+ Path: stdout,
+ gitRepo: repo,
}, nil
}
@@ -64,23 +70,56 @@ func (repo *Repository) SetDefaultBranch(name string) error {
// GetBranches returns all branches of the repository.
func (repo *Repository) GetBranches() ([]string, error) {
- r, err := git.PlainOpen(repo.Path)
+ var branchNames []string
+
+ branches, err := repo.gogitRepo.Branches()
if err != nil {
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 {
return nil, err
}
- branches := make([]string, 0)
- if err = branchIter.ForEach(func(branch *plumbing.Reference) error {
- branches = append(branches, branch.Name().Short())
- return nil
- }); err != nil {
+
+ 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],
+ gitRepo: gitRepo,
+ }
+ }
+
return branches, nil
}
@@ -132,3 +171,8 @@ func (repo *Repository) RemoveRemote(name string) error {
_, err := NewCommand("remote", "remove", name).RunInDir(repo.Path)
return err
}
+
+// GetCommit returns the head commit of a branch
+func (branch *Branch) GetCommit() (*Commit, error) {
+ return branch.gitRepo.GetBranchCommit(branch.Name)
+}
diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go
index 7c65d6e921..b631f9341e 100644
--- a/modules/git/repo_commit.go
+++ b/modules/git/repo_commit.go
@@ -1,4 +1,5 @@
// 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
// license that can be found in the LICENSE file.
@@ -7,22 +8,23 @@ package git
import (
"bytes"
"container/list"
+ "fmt"
"strconv"
"strings"
"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).
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 strings.Contains(err.Error(), "not a valid ref") {
- return "", ErrNotExist{name, ""}
- }
return "", err
}
- return strings.Split(stdout, " ")[0], nil
+
+ return ref.Hash().String(), nil
}
// 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
}
-// parseCommitData parses commit information from the (uncompressed) raw
-// data from the commit object.
-// \n\n separate headers from message
-func parseCommitData(data []byte) (*Commit, error) {
- commit := new(Commit)
- commit.parents = make([]SHA1, 0, 1)
- // we now have the contents of the commit object. Let's investigate...
- nextline := 0
-l:
- for {
- eol := bytes.IndexByte(data[nextline:], '\n')
- switch {
- case eol > 0:
- line := data[nextline : nextline+eol]
- spacepos := bytes.IndexByte(line, ' ')
- reftype := line[:spacepos]
- switch string(reftype) {
- case "tree", "object":
- id, err := NewIDFromString(string(line[spacepos+1:]))
- if err != nil {
- return nil, err
- }
- commit.Tree.ID = id
- case "parent":
- // A commit can have one or more parents
- oid, err := NewIDFromString(string(line[spacepos+1:]))
- if err != nil {
- return nil, err
- }
- 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
- }
- }
+func convertPGPSignatureForTag(t *object.Tag) *CommitGPGSignature {
+ if t.PGPSignature == "" {
+ return nil
+ }
- commit.CommitMessage = cm
- break l
- default:
- break l
- }
+ var w strings.Builder
+ var err error
+
+ if _, err = fmt.Fprintf(&w,
+ "object %s\ntype %s\ntag %s\ntagger ",
+ t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil {
+ return nil
+ }
+
+ if err = t.Tagger.Encode(&w); err != nil {
+ return nil
+ }
+
+ if _, err = fmt.Fprintf(&w, "\n\n"); err != nil {
+ return nil
+ }
+
+ if _, err = fmt.Fprintf(&w, t.Message); err != nil {
+ return nil
+ }
+
+ return &CommitGPGSignature{
+ Signature: t.PGPSignature,
+ Payload: strings.TrimSpace(w.String()) + "\n",
}
- return commit, nil
}
func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
- c, ok := repo.commitCache.Get(id.String())
- if ok {
- log("Hit cache: %s", id)
- return c.(*Commit), nil
- }
+ var tagObject *object.Tag
- data, err := NewCommand("cat-file", "-p", id.String()).RunInDirBytes(repo.Path)
- if err != nil {
- if strings.Contains(err.Error(), "fatal: Not a valid object name") {
- return nil, ErrNotExist{id.String(), ""}
+ gogitCommit, err := repo.gogitRepo.CommitObject(plumbing.Hash(id))
+ if err == plumbing.ErrObjectNotFound {
+ tagObject, err = repo.gogitRepo.TagObject(plumbing.Hash(id))
+ if err == nil {
+ gogitCommit, err = repo.gogitRepo.CommitObject(tagObject.Target)
}
- return nil, err
}
-
- commit, err := parseCommitData(data)
if err != nil {
return nil, err
}
+
+ commit := convertCommit(gogitCommit)
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 {
return nil, err
}
- // name-rev commitID output will be "COMMIT_ID master" or "COMMIT_ID master~12"
- commit.Branch = strings.Split(strings.Split(string(data), " ")[1], "~")[0]
+ commit.Tree.ID = tree.Hash
+ commit.Tree.gogitTree = tree
- repo.commitCache.Set(id.String(), commit)
return commit, nil
}
diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go
index 84825d7dc3..8c72528933 100644
--- a/modules/git/repo_tag.go
+++ b/modules/git/repo_tag.go
@@ -1,4 +1,5 @@
// 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
// license that can be found in the LICENSE file.
@@ -8,6 +9,7 @@ import (
"strings"
"github.com/mcuadros/go-version"
+ "gopkg.in/src-d/go-git.v4/plumbing"
)
// 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.
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
@@ -122,28 +128,25 @@ func (repo *Repository) GetTagInfos() ([]*Tag, error) {
// GetTags returns all tags of the repository.
func (repo *Repository) GetTags() ([]string, error) {
- cmd := NewCommand("tag", "-l")
- if version.Compare(gitVersion, "2.0.0", ">=") {
- cmd.AddArguments("--sort=-v:refname")
- }
+ var tagNames []string
- stdout, err := cmd.RunInDir(repo.Path)
+ tags, err := repo.gogitRepo.Tags()
if err != nil {
return nil, err
}
- tags := strings.Split(stdout, "\n")
- tags = tags[:len(tags)-1]
+ tags.ForEach(func(tag *plumbing.Reference) error {
+ tagNames = append(tagNames, strings.TrimPrefix(tag.Name().String(), TagPrefix))
+ return nil
+ })
- if version.Compare(gitVersion, "2.0.0", "<") {
- version.Sort(tags)
+ version.Sort(tagNames)
- // Reverse order
- for i := 0; i < len(tags)/2; i++ {
- j := len(tags) - i - 1
- tags[i], tags[j] = tags[j], tags[i]
- }
+ // Reverse order
+ for i := 0; i < len(tagNames)/2; i++ {
+ j := len(tagNames) - i - 1
+ tagNames[i], tagNames[j] = tagNames[j], tagNames[i]
}
- return tags, nil
+ return tagNames, nil
}
diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go
index 3fa491d529..8a024fe6ac 100644
--- a/modules/git/repo_tree.go
+++ b/modules/git/repo_tree.go
@@ -1,19 +1,23 @@
// 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
// license that can be found in the LICENSE file.
package git
+import (
+ "gopkg.in/src-d/go-git.v4/plumbing"
+)
+
func (repo *Repository) getTree(id SHA1) (*Tree, error) {
- treePath := filepathFromSHA1(repo.Path, id.String())
- if isFile(treePath) {
- _, err := NewCommand("ls-tree", id.String()).RunInDir(repo.Path)
- if err != nil {
- return nil, ErrNotExist{id.String(), ""}
- }
+ gogitTree, err := repo.gogitRepo.TreeObject(plumbing.Hash(id))
+ if err != nil {
+ return nil, err
}
- return NewTree(repo, id), nil
+ tree := NewTree(repo, id)
+ tree.gogitTree = gogitTree
+ return tree, nil
}
// GetTree find the tree object in the repository.
@@ -31,5 +35,14 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) {
if err != nil {
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
}
diff --git a/modules/git/sha1.go b/modules/git/sha1.go
index 6c9d53949d..57b06fe738 100644
--- a/modules/git/sha1.go
+++ b/modules/git/sha1.go
@@ -1,44 +1,23 @@
// 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
// license that can be found in the LICENSE file.
package git
import (
- "bytes"
"encoding/hex"
"fmt"
"strings"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
)
// EmptySHA defines empty git SHA
const EmptySHA = "0000000000000000000000000000000000000000"
// SHA1 a git commit name
-type SHA1 [20]byte
-
-// 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[:])
-}
+type SHA1 = plumbing.Hash
// MustID always creates a new SHA1 from a [20]byte array with no validation of input.
func MustID(b []byte) SHA1 {
diff --git a/modules/git/signature.go b/modules/git/signature.go
index e6ab247fd7..3f67bceb09 100644
--- a/modules/git/signature.go
+++ b/modules/git/signature.go
@@ -1,4 +1,5 @@
// 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
// license that can be found in the LICENSE file.
@@ -8,14 +9,12 @@ import (
"bytes"
"strconv"
"time"
+
+ "gopkg.in/src-d/go-git.v4/plumbing/object"
)
// Signature represents the Author or Committer information.
-type Signature struct {
- Email string
- Name string
- When time.Time
-}
+type Signature = object.Signature
const (
// GitTimeLayout is the (default) time layout used by git.
diff --git a/modules/git/tree.go b/modules/git/tree.go
index 5ec22a3a6f..8f55d7a8c5 100644
--- a/modules/git/tree.go
+++ b/modules/git/tree.go
@@ -1,29 +1,31 @@
// 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
// license that can be found in the LICENSE file.
package git
import (
+ "io"
"strings"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/object"
)
// Tree represents a flat directory listing.
type Tree struct {
- ID SHA1
- repo *Repository
+ ID SHA1
+ CommitID SHA1
+ repo *Repository
+
+ gogitTree *object.Tree
// parent 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 {
return &Tree{
ID: id,
@@ -60,39 +62,68 @@ func (t *Tree) SubTree(rpath string) (*Tree, error) {
return g, nil
}
-// ListEntries returns all entries of current tree.
-func (t *Tree) ListEntries() (Entries, error) {
- if t.entriesParsed {
- return t.entries, nil
+func (t *Tree) loadTreeObject() error {
+ gogitTree, err := t.repo.gogitRepo.TreeObject(plumbing.Hash(t.ID))
+ if err != nil {
+ return err
}
- stdout, err := NewCommand("ls-tree", t.ID.String()).RunInDirBytes(t.repo.Path)
- if err != nil {
- return nil, err
+ t.gogitTree = gogitTree
+ return nil
+}
+
+// ListEntries returns all entries of current tree.
+func (t *Tree) ListEntries() (Entries, error) {
+ if t.gogitTree == nil {
+ err := t.loadTreeObject()
+ if err != nil {
+ return nil, err
+ }
}
- t.entries, err = parseTreeEntries(stdout, t)
- if err == nil {
- t.entriesParsed = true
+ entries := make([]*TreeEntry, len(t.gogitTree.Entries))
+ for i, entry := range t.gogitTree.Entries {
+ entries[i] = &TreeEntry{
+ ID: entry.Hash,
+ gogitTreeEntry: &t.gogitTree.Entries[i],
+ ptree: t,
+ }
}
- return t.entries, err
+ return entries, nil
}
// ListEntriesRecursive returns all entries of current tree recursively including all subtrees
func (t *Tree) ListEntriesRecursive() (Entries, error) {
- if t.entriesRecursiveParsed {
- return t.entriesRecursive, nil
- }
- stdout, err := NewCommand("ls-tree", "-t", "-r", t.ID.String()).RunInDirBytes(t.repo.Path)
- if err != nil {
- return nil, err
+ if t.gogitTree == nil {
+ err := t.loadTreeObject()
+ if err != nil {
+ return nil, err
+ }
}
- t.entriesRecursive, err = parseTreeEntries(stdout, t)
- if err == nil {
- t.entriesRecursiveParsed = true
+ var entries []*TreeEntry
+ seen := map[plumbing.Hash]bool{}
+ 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
}
diff --git a/modules/git/tree_blob.go b/modules/git/tree_blob.go
index a37f6b2279..14237df6e8 100644
--- a/modules/git/tree_blob.go
+++ b/modules/git/tree_blob.go
@@ -1,4 +1,5 @@
// 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
// license that can be found in the LICENSE file.
@@ -7,15 +8,23 @@ package git
import (
"path"
"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
func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
if len(relpath) == 0 {
return &TreeEntry{
- ID: t.ID,
- Type: ObjectTree,
- mode: EntryModeTree,
+ ID: t.ID,
+ //Type: ObjectTree,
+ gogitTreeEntry: &object.TreeEntry{
+ Name: "",
+ Mode: filemode.Dir,
+ Hash: plumbing.Hash(t.ID),
+ },
}, nil
}
@@ -30,7 +39,7 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
return nil, err
}
for _, v := range entries {
- if v.name == name {
+ if v.Name() == name {
return v, nil
}
}
diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go
index 5b74e9a695..fe2fd14f97 100644
--- a/modules/git/tree_entry.go
+++ b/modules/git/tree_entry.go
@@ -1,4 +1,5 @@
// 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
// license that can be found in the LICENSE file.
@@ -7,8 +8,11 @@ package git
import (
"io"
"sort"
- "strconv"
"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
@@ -18,28 +22,23 @@ type EntryMode int
// one of these.
const (
// EntryModeBlob
- EntryModeBlob EntryMode = 0x0100644
+ EntryModeBlob EntryMode = 0100644
// EntryModeExec
- EntryModeExec EntryMode = 0x0100755
+ EntryModeExec EntryMode = 0100755
// EntryModeSymlink
- EntryModeSymlink EntryMode = 0x0120000
+ EntryModeSymlink EntryMode = 0120000
// EntryModeCommit
- EntryModeCommit EntryMode = 0x0160000
+ EntryModeCommit EntryMode = 0160000
// EntryModeTree
- EntryModeTree EntryMode = 0x0040000
+ EntryModeTree EntryMode = 0040000
)
// TreeEntry the leaf in the git tree
type TreeEntry struct {
- ID SHA1
- Type ObjectType
-
- mode EntryMode
- name string
-
- ptree *Tree
+ ID SHA1
- committed bool
+ gogitTreeEntry *object.TreeEntry
+ ptree *Tree
size int64
sized bool
@@ -47,12 +46,24 @@ type TreeEntry struct {
// Name returns the name of the entry
func (te *TreeEntry) Name() string {
- return te.name
+ return te.gogitTreeEntry.Name
}
// Mode returns the mode of the entry
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
@@ -63,36 +74,47 @@ func (te *TreeEntry) Size() int64 {
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 {
return 0
}
te.sized = true
- te.size, _ = strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
+ te.size = file.Size
return te.size
}
// IsSubModule if the entry is a sub module
func (te *TreeEntry) IsSubModule() bool {
- return te.mode == EntryModeCommit
+ return te.gogitTreeEntry.Mode == filemode.Submodule
}
// IsDir if the entry is a sub dir
func (te *TreeEntry) IsDir() bool {
- return te.mode == EntryModeTree
+ return te.gogitTreeEntry.Mode == filemode.Dir
}
// IsLink if the entry is a symlink
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 {
+ encodedObj, err := te.ptree.repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, te.gogitTreeEntry.Hash)
+ if err != nil {
+ return nil
+ }
+
return &Blob{
- repo: te.ptree.repo,
- TreeEntry: te,
+ ID: te.gogitTreeEntry.Hash,
+ gogitEncodedObj: encodedObj,
+ name: te.Name(),
}
}
@@ -103,10 +125,11 @@ func (te *TreeEntry) FollowLink() (*TreeEntry, error) {
}
// read the link
- r, err := te.Blob().Data()
+ r, err := te.Blob().DataAsync()
if err != nil {
return nil, err
}
+ defer r.Close()
buf := make([]byte, te.Size())
_, err = io.ReadFull(r, buf)
if err != nil {
@@ -140,18 +163,18 @@ func (te *TreeEntry) GetSubJumpablePathName() string {
if te.IsSubModule() || !te.IsDir() {
return ""
}
- tree, err := te.ptree.SubTree(te.name)
+ tree, err := te.ptree.SubTree(te.Name())
if err != nil {
- return te.name
+ return te.Name()
}
entries, _ := tree.ListEntries()
if len(entries) == 1 && entries[0].IsDir() {
name := entries[0].GetSubJumpablePathName()
if name != "" {
- return te.name + "/" + name
+ return te.Name() + "/" + name
}
}
- return te.name
+ return te.Name()
}
// 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()
},
func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool {
- return cmp(t1.name, t2.name)
+ return cmp(t1.Name(), t2.Name())
},
}
diff --git a/modules/git/tree_entry_test.go b/modules/git/tree_entry_test.go
index 52920bc00e..c65a691ecf 100644
--- a/modules/git/tree_entry_test.go
+++ b/modules/git/tree_entry_test.go
@@ -8,18 +8,20 @@ import (
"testing"
"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 {
return Entries{
- &TreeEntry{name: "v1.0", mode: EntryModeTree},
- &TreeEntry{name: "v2.0", mode: EntryModeTree},
- &TreeEntry{name: "v2.1", mode: EntryModeTree},
- &TreeEntry{name: "v2.12", mode: EntryModeTree},
- &TreeEntry{name: "v2.2", mode: EntryModeTree},
- &TreeEntry{name: "v12.0", mode: EntryModeTree},
- &TreeEntry{name: "abc", mode: EntryModeBlob},
- &TreeEntry{name: "bcd", mode: EntryModeBlob},
+ &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v1.0", Mode: filemode.Dir}},
+ &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.0", Mode: filemode.Dir}},
+ &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.1", Mode: filemode.Dir}},
+ &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.12", Mode: filemode.Dir}},
+ &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.2", Mode: filemode.Dir}},
+ &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v12.0", Mode: filemode.Dir}},
+ &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "abc", Mode: filemode.Regular}},
+ &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "bcd", Mode: filemode.Regular}},
}
}
diff --git a/modules/repofiles/blob_test.go b/modules/repofiles/blob_test.go
index 260b775fc4..55320345f9 100644
--- a/modules/repofiles/blob_test.go
+++ b/modules/repofiles/blob_test.go
@@ -27,7 +27,7 @@ func TestGetBlobBySHA(t *testing.T) {
gbr, err := GetBlobBySHA(ctx.Repo.Repository, ctx.Params(":sha"))
expectedGBR := &api.GitBlobResponse{
- Content: "Y29tbWl0IDY1ZjFiZjI3YmMzYmY3MGY2NDY1NzY1ODYzNWU2NjA5NGVkYmNiNGQKQXV0aG9yOiB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+CkRhdGU6ICAgU3VuIE1hciAxOSAxNjo0Nzo1OSAyMDE3IC0wNDAwCgogICAgSW5pdGlhbCBjb21taXQKCmRpZmYgLS1naXQgYS9SRUFETUUubWQgYi9SRUFETUUubWQKbmV3IGZpbGUgbW9kZSAxMDA2NDQKaW5kZXggMDAwMDAwMC4uNGI0ODUxYQotLS0gL2Rldi9udWxsCisrKyBiL1JFQURNRS5tZApAQCAtMCwwICsxLDMgQEAKKyMgcmVwbzEKKworRGVzY3JpcHRpb24gZm9yIHJlcG8xClwgTm8gbmV3bGluZSBhdCBlbmQgb2YgZmlsZQo=",
+ Content: "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK",
Encoding: "base64",
URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/65f1bf27bc3bf70f64657658635e66094edbcb4d",
SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
diff --git a/modules/repofiles/content.go b/modules/repofiles/content.go
index d55ca497cd..d35c5a6a67 100644
--- a/modules/repofiles/content.go
+++ b/modules/repofiles/content.go
@@ -61,7 +61,7 @@ func GetFileContents(repo *models.Repository, treePath, ref string) (*api.FileCo
HTMLURL: htmlURL.String(),
GitURL: gitURL.String(),
DownloadURL: downloadURL.String(),
- Type: string(entry.Type),
+ Type: entry.Type(),
Links: &api.FileLinksResponse{
Self: selfURL.String(),
GitURL: gitURL.String(),
diff --git a/modules/repofiles/temp_repo.go b/modules/repofiles/temp_repo.go
index 5bf64d52a8..46e03f5650 100644
--- a/modules/repofiles/temp_repo.go
+++ b/modules/repofiles/temp_repo.go
@@ -58,7 +58,7 @@ func (t *TemporaryUploadRepository) Clone(branch string) error {
fmt.Sprintf("Clone (git clone -s --bare): %s", t.basePath),
"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 {
- return models.ErrBranchNotExist{
+ return git.ErrBranchNotExist{
Name: branch,
}
} else if matched, _ := regexp.MatchString(".* repository .* does not exist.*", stderr); matched {
diff --git a/modules/repofiles/tree.go b/modules/repofiles/tree.go
index 8766ed36d0..5b4e7aeb28 100644
--- a/modules/repofiles/tree.go
+++ b/modules/repofiles/tree.go
@@ -23,7 +23,7 @@ func GetTreeBySHA(repo *models.Repository, sha string, page, perPage int, recurs
}
}
tree := new(api.GitTreeResponse)
- tree.SHA = gitTree.ID.String()
+ tree.SHA = gitTree.CommitID.String()
tree.URL = repo.APIURL() + "/git/trees/" + tree.SHA
var entries git.Entries
if recursive {
@@ -74,11 +74,12 @@ func GetTreeBySHA(repo *models.Repository, sha string, page, perPage int, recurs
tree.Entries = make([]api.GitEntry, rangeEnd-rangeStart)
for e := rangeStart; e < rangeEnd; e++ {
i := e - rangeStart
- tree.Entries[i].Path = entries[e].Name()
- tree.Entries[i].Mode = fmt.Sprintf("%06x", entries[e].Mode())
- tree.Entries[i].Type = string(entries[e].Type)
- tree.Entries[i].Size = entries[e].Size()
- tree.Entries[i].SHA = entries[e].ID.String()
+
+ tree.Entries[e].Path = entries[e].Name()
+ tree.Entries[e].Mode = fmt.Sprintf("%06o", entries[e].Mode())
+ tree.Entries[e].Type = entries[e].Type()
+ tree.Entries[e].Size = entries[e].Size()
+ tree.Entries[e].SHA = entries[e].ID.String()
if entries[e].IsDir() {
copy(treeURL[copyPos:], entries[e].ID.String())
diff --git a/modules/repofiles/tree_test.go b/modules/repofiles/tree_test.go
index 266dc91670..c211cfcfd2 100644
--- a/modules/repofiles/tree_test.go
+++ b/modules/repofiles/tree_test.go
@@ -46,5 +46,6 @@ func TestGetTreeBySHA(t *testing.T) {
Page: 1,
TotalCount: 1,
}
- assert.EqualValues(t, tree, expectedTree)
+
+ assert.EqualValues(t, expectedTree, tree)
}
diff --git a/modules/repofiles/update.go b/modules/repofiles/update.go
index 216df18cd3..e9b3077535 100644
--- a/modules/repofiles/update.go
+++ b/modules/repofiles/update.go
@@ -62,7 +62,7 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up
BranchName: opts.NewBranch,
}
}
- if err != nil && !models.IsErrBranchNotExist(err) {
+ if err != nil && !git.IsErrBranchNotExist(err) {
return nil, err
}
} else {
diff --git a/routers/api/v1/convert/convert.go b/routers/api/v1/convert/convert.go
index aca21db4ed..f132dedb55 100644
--- a/routers/api/v1/convert/convert.go
+++ b/routers/api/v1/convert/convert.go
@@ -27,7 +27,7 @@ func ToEmail(email *models.EmailAddress) *api.Email {
}
// 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{
Name: b.Name,
Commit: ToCommit(repo, c),
diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go
index e20eef6139..b9a23d3b51 100644
--- a/routers/api/v1/repo/branch.go
+++ b/routers/api/v1/repo/branch.go
@@ -1,12 +1,13 @@
// 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
// license that can be found in the LICENSE file.
package repo
import (
- "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/routers/api/v1/convert"
api "code.gitea.io/sdk/gitea"
@@ -47,7 +48,7 @@ func GetBranch(ctx *context.APIContext) {
}
branch, err := ctx.Repo.Repository.GetBranch(ctx.Repo.BranchName)
if err != nil {
- if models.IsErrBranchNotExist(err) {
+ if git.IsErrBranchNotExist(err) {
ctx.NotFound(err)
} else {
ctx.Error(500, "GetBranch", err)
diff --git a/routers/repo/commit.go b/routers/repo/commit.go
index a1e9b8bc5d..9263bcac7f 100644
--- a/routers/repo/commit.go
+++ b/routers/repo/commit.go
@@ -243,6 +243,7 @@ func Diff(ctx *context.Context) {
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["BranchName"], err = commit.GetBranchName()
ctx.HTML(200, tplDiff)
}
diff --git a/routers/repo/editor.go b/routers/repo/editor.go
index b5bb2f0ce3..4314218bf3 100644
--- a/routers/repo/editor.go
+++ b/routers/repo/editor.go
@@ -95,11 +95,12 @@ func editFile(ctx *context.Context, isNewFile bool) {
return
}
- dataRc, err := blob.Data()
+ dataRc, err := blob.DataAsync()
if err != nil {
ctx.NotFound("blob.Data", err)
return
}
+ defer dataRc.Close()
ctx.Data["FileSize"] = blob.Size()
ctx.Data["FileName"] = blob.Name()
@@ -251,9 +252,9 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo
} else if models.IsErrRepoFileAlreadyExists(err) {
ctx.Data["Err_TreePath"] = true
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
- 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)
} else {
ctx.Error(500, err.Error())
@@ -417,9 +418,9 @@ func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
} else {
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
- 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)
} else {
ctx.Error(500, err.Error())
diff --git a/routers/repo/issue.go b/routers/repo/issue.go
index 42f3ddf4e8..7d235d84ef 100644
--- a/routers/repo/issue.go
+++ b/routers/repo/issue.go
@@ -9,7 +9,6 @@ import (
"bytes"
"errors"
"fmt"
- "io"
"io/ioutil"
"net/http"
"strconv"
@@ -363,7 +362,6 @@ func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.
}
func getFileContentFromDefaultBranch(ctx *context.Context, filename string) (string, bool) {
- var r io.Reader
var bytes []byte
if ctx.Repo.Commit == nil {
@@ -381,10 +379,11 @@ func getFileContentFromDefaultBranch(ctx *context.Context, filename string) (str
if entry.Blob().Size() >= setting.UI.MaxDisplayFileSize {
return "", false
}
- r, err = entry.Blob().Data()
+ r, err := entry.Blob().DataAsync()
if err != nil {
return "", false
}
+ defer r.Close()
bytes, err = ioutil.ReadAll(r)
if err != nil {
return "", false
diff --git a/routers/repo/setting_protected_branch.go b/routers/repo/setting_protected_branch.go
index 05ad0ec0ec..b5a115b6a4 100644
--- a/routers/repo/setting_protected_branch.go
+++ b/routers/repo/setting_protected_branch.go
@@ -103,7 +103,7 @@ func SettingsProtectedBranch(c *context.Context) {
protectBranch, err := models.GetProtectedBranchBy(c.Repo.Repository.ID, branch)
if err != nil {
- if !models.IsErrBranchNotExist(err) {
+ if !git.IsErrBranchNotExist(err) {
c.ServerError("GetProtectBranchOfRepoByName", err)
return
}
@@ -152,7 +152,7 @@ func SettingsProtectedBranchPost(ctx *context.Context, f auth.ProtectBranchForm)
protectBranch, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, branch)
if err != nil {
- if !models.IsErrBranchNotExist(err) {
+ if !git.IsErrBranchNotExist(err) {
ctx.ServerError("GetProtectBranchOfRepoByName", err)
return
}
diff --git a/routers/repo/view.go b/routers/repo/view.go
index e883ebbf89..d20f94dfba 100644
--- a/routers/repo/view.go
+++ b/routers/repo/view.go
@@ -49,7 +49,8 @@ func renderDirectory(ctx *context.Context, treeLink string) {
}
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 {
ctx.ServerError("GetCommitsInfo", err)
return
@@ -178,14 +179,6 @@ func renderDirectory(ctx *context.Context, treeLink string) {
// Show latest commit info of repository in table header,
// 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["LatestCommitVerification"] = models.ParseCommitWithSignature(latestCommit)
ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit)
diff --git a/routers/repo/wiki.go b/routers/repo/wiki.go
index 790214e8e5..c0fb370dd9 100644
--- a/routers/repo/wiki.go
+++ b/routers/repo/wiki.go
@@ -57,7 +57,7 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error)
return nil, err
}
for _, entry := range entries {
- if entry.Type == git.ObjectBlob && entry.Name() == target {
+ if entry.IsRegular() && entry.Name() == target {
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
// given tree entry. Writes to ctx if an error occurs.
func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
- reader, err := entry.Blob().Data()
+ reader, err := entry.Blob().DataAsync()
if err != nil {
ctx.ServerError("Blob.Data", err)
return nil
}
+ defer reader.Close()
content, err := ioutil.ReadAll(reader)
if err != nil {
ctx.ServerError("ReadAll", err)
@@ -125,7 +126,7 @@ func renderWikiPage(ctx *context.Context, isViewPage bool) (*git.Repository, *gi
}
pages := make([]PageMeta, 0, len(entries))
for _, entry := range entries {
- if entry.Type != git.ObjectBlob {
+ if !entry.IsRegular() {
continue
}
wikiName, err := models.WikiFilenameToName(entry.Name())
@@ -259,7 +260,7 @@ func WikiPages(ctx *context.Context) {
}
pages := make([]PageMeta, 0, len(entries))
for _, entry := range entries {
- if entry.Type != git.ObjectBlob {
+ if !entry.IsRegular() {
continue
}
c, err := wikiRepo.GetCommitByPath(entry.Name())
diff --git a/routers/repo/wiki_test.go b/routers/repo/wiki_test.go
index 7302f9e48b..4687d24f6b 100644
--- a/routers/repo/wiki_test.go
+++ b/routers/repo/wiki_test.go
@@ -40,8 +40,9 @@ func wikiContent(t *testing.T, repo *models.Repository, wikiName string) string
if !assert.NotNil(t, entry) {
return ""
}
- reader, err := entry.Blob().Data()
+ reader, err := entry.Blob().DataAsync()
assert.NoError(t, err)
+ defer reader.Close()
bytes, err := ioutil.ReadAll(reader)
assert.NoError(t, err)
return string(bytes)
diff --git a/templates/repo/diff/page.tmpl b/templates/repo/diff/page.tmpl
index 3f383add94..c8f5a3d9f0 100644
--- a/templates/repo/diff/page.tmpl
+++ b/templates/repo/diff/page.tmpl
@@ -13,7 +13,7 @@
{{if IsMultilineCommitMessage .Commit.Message}}
<pre class="commit-body">{{RenderCommitBody .Commit.Message $.RepoLink $.Repository.ComposeMetas}}</pre>
{{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 class="ui attached info segment {{if .Commit.Signature}} isSigned {{if .Verification.Verified }} isVerified {{end}}{{end}}">
<div class="ui stackable grid">
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 940b7fc875..08db5c7113 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -410,8 +410,8 @@ gopkg.in/macaron.v1
# gopkg.in/redis.v2 v2.3.2
gopkg.in/redis.v2
# 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
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/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/config
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/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/index
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/capability
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/client
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/utils/diff
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/internal/url
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/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/transport/file
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/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/plumbing/transport/internal/common
gopkg.in/src-d/go-git.v4/plumbing/transport/server