summaryrefslogtreecommitdiffstats
path: root/modules/git/commit_info_nogogit.go
diff options
context:
space:
mode:
Diffstat (limited to 'modules/git/commit_info_nogogit.go')
-rw-r--r--modules/git/commit_info_nogogit.go370
1 files changed, 370 insertions, 0 deletions
diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go
new file mode 100644
index 0000000000..ac0c7cff5d
--- /dev/null
+++ b/modules/git/commit_info_nogogit.go
@@ -0,0 +1,370 @@
+// Copyright 2017 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.
+
+// +build !gogit
+
+package git
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "math"
+ "path"
+ "sort"
+ "strings"
+)
+
+// GetCommitsInfo gets information of all commits that are corresponding to these entries
+func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache *LastCommitCache) ([]CommitInfo, *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()
+ }
+
+ var err error
+
+ var revs map[string]*Commit
+ if cache != nil {
+ var unHitPaths []string
+ revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, cache)
+ if err != nil {
+ return nil, nil, err
+ }
+ if len(unHitPaths) > 0 {
+ sort.Strings(unHitPaths)
+ commits, err := GetLastCommitForPaths(commit, treePath, unHitPaths)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ for i, found := range commits {
+ if err := cache.Put(commit.ID.String(), path.Join(treePath, unHitPaths[i]), found.ID.String()); err != nil {
+ return nil, nil, err
+ }
+ revs[unHitPaths[i]] = found
+ }
+ }
+ } else {
+ sort.Strings(entryPaths)
+ revs = map[string]*Commit{}
+ var foundCommits []*Commit
+ foundCommits, err = GetLastCommitForPaths(commit, treePath, entryPaths)
+ for i, found := range foundCommits {
+ revs[entryPaths[i]] = found
+ }
+ }
+ if err != nil {
+ return nil, nil, err
+ }
+
+ commitsInfo := make([]CommitInfo, len(tes))
+ for i, entry := range tes {
+ commitsInfo[i] = CommitInfo{
+ Entry: entry,
+ }
+ if entryCommit, ok := revs[entry.Name()]; ok {
+ commitsInfo[i].Commit = entryCommit
+ if entry.IsSubModule() {
+ subModuleURL := ""
+ var fullPath string
+ if len(treePath) > 0 {
+ fullPath = treePath + "/" + entry.Name()
+ } else {
+ fullPath = entry.Name()
+ }
+ if subModule, err := commit.GetSubModule(fullPath); err != nil {
+ return nil, nil, err
+ } else if subModule != nil {
+ subModuleURL = subModule.URL
+ }
+ subModuleFile := NewSubModuleFile(entryCommit, subModuleURL, entry.ID.String())
+ commitsInfo[i].SubModuleFile = subModuleFile
+ }
+ }
+ }
+
+ // 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
+ var ok bool
+ if treePath == "" {
+ treeCommit = commit
+ } else if treeCommit, ok = revs[""]; ok {
+ treeCommit.repo = commit.repo
+ }
+ return commitsInfo, treeCommit, nil
+}
+
+func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*Commit, []string, error) {
+ var unHitEntryPaths []string
+ var results = make(map[string]*Commit)
+ for _, p := range paths {
+ lastCommit, err := cache.Get(commitID, path.Join(treePath, p))
+ if err != nil {
+ return nil, nil, err
+ }
+ if lastCommit != nil {
+ results[p] = lastCommit.(*Commit)
+ continue
+ }
+
+ unHitEntryPaths = append(unHitEntryPaths, p)
+ }
+
+ return results, unHitEntryPaths, nil
+}
+
+// GetLastCommitForPaths returns last commit information
+func GetLastCommitForPaths(commit *Commit, treePath string, paths []string) ([]*Commit, error) {
+ // We read backwards from the commit to obtain all of the commits
+
+ // We'll do this by using rev-list to provide us with parent commits in order
+ revListReader, revListWriter := io.Pipe()
+ defer func() {
+ _ = revListWriter.Close()
+ _ = revListReader.Close()
+ }()
+
+ go func() {
+ stderr := strings.Builder{}
+ err := NewCommand("rev-list", "--format=%T", commit.ID.String()).RunInDirPipeline(commit.repo.Path, revListWriter, &stderr)
+ if err != nil {
+ _ = revListWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
+ } else {
+ _ = revListWriter.Close()
+ }
+ }()
+
+ // We feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
+ // so let's create a batch stdin and stdout
+ batchStdinReader, batchStdinWriter := io.Pipe()
+ batchStdoutReader, batchStdoutWriter := io.Pipe()
+ defer func() {
+ _ = batchStdinReader.Close()
+ _ = batchStdinWriter.Close()
+ _ = batchStdoutReader.Close()
+ _ = batchStdoutWriter.Close()
+ }()
+
+ go func() {
+ stderr := strings.Builder{}
+ err := NewCommand("cat-file", "--batch").RunInDirFullPipeline(commit.repo.Path, batchStdoutWriter, &stderr, batchStdinReader)
+ if err != nil {
+ _ = revListWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
+ } else {
+ _ = revListWriter.Close()
+ }
+ }()
+
+ // For simplicities sake we'll us a buffered reader
+ batchReader := bufio.NewReader(batchStdoutReader)
+
+ mapsize := 4096
+ if len(paths) > mapsize {
+ mapsize = len(paths)
+ }
+
+ path2idx := make(map[string]int, mapsize)
+ for i, path := range paths {
+ path2idx[path] = i
+ }
+
+ fnameBuf := make([]byte, 4096)
+ modeBuf := make([]byte, 40)
+
+ allShaBuf := make([]byte, (len(paths)+1)*20)
+ shaBuf := make([]byte, 20)
+ tmpTreeID := make([]byte, 40)
+
+ // commits is the returnable commits matching the paths provided
+ commits := make([]string, len(paths))
+ // ids are the blob/tree ids for the paths
+ ids := make([][]byte, len(paths))
+
+ // We'll use a scanner for the revList because it's simpler than a bufio.Reader
+ scan := bufio.NewScanner(revListReader)
+revListLoop:
+ for scan.Scan() {
+ // Get the next parent commit ID
+ commitID := scan.Text()
+ if !scan.Scan() {
+ break revListLoop
+ }
+ commitID = commitID[7:]
+ rootTreeID := scan.Text()
+
+ // push the tree to the cat-file --batch process
+ _, err := batchStdinWriter.Write([]byte(rootTreeID + "\n"))
+ if err != nil {
+ return nil, err
+ }
+
+ currentPath := ""
+
+ // OK if the target tree path is "" and the "" is in the paths just set this now
+ if treePath == "" && paths[0] == "" {
+ // If this is the first time we see this set the id appropriate for this paths to this tree and set the last commit to curCommit
+ if len(ids[0]) == 0 {
+ ids[0] = []byte(rootTreeID)
+ commits[0] = string(commitID)
+ } else if bytes.Equal(ids[0], []byte(rootTreeID)) {
+ commits[0] = string(commitID)
+ }
+ }
+
+ treeReadingLoop:
+ for {
+ _, _, size, err := ReadBatchLine(batchReader)
+ if err != nil {
+ return nil, err
+ }
+
+ // Handle trees
+
+ // n is counter for file position in the tree file
+ var n int64
+
+ // Two options: currentPath is the targetTreepath
+ if treePath == currentPath {
+ // We are in the right directory
+ // Parse each tree line in turn. (don't care about mode here.)
+ for n < size {
+ fname, sha, count, err := ParseTreeLineSkipMode(batchReader, fnameBuf, shaBuf)
+ shaBuf = sha
+ if err != nil {
+ return nil, err
+ }
+ n += int64(count)
+ idx, ok := path2idx[string(fname)]
+ if ok {
+ // Now if this is the first time round set the initial Blob(ish) SHA ID and the commit
+ if len(ids[idx]) == 0 {
+ copy(allShaBuf[20*(idx+1):20*(idx+2)], shaBuf)
+ ids[idx] = allShaBuf[20*(idx+1) : 20*(idx+2)]
+ commits[idx] = string(commitID)
+ } else if bytes.Equal(ids[idx], shaBuf) {
+ commits[idx] = string(commitID)
+ }
+ }
+ // FIXME: is there any order to the way strings are emitted from cat-file?
+ // if there is - then we could skip once we've passed all of our data
+ }
+ break treeReadingLoop
+ }
+
+ var treeID []byte
+
+ // We're in the wrong directory
+ // Find target directory in this directory
+ idx := len(currentPath)
+ if idx > 0 {
+ idx++
+ }
+ target := strings.SplitN(treePath[idx:], "/", 2)[0]
+
+ for n < size {
+ // Read each tree entry in turn
+ mode, fname, sha, count, err := ParseTreeLine(batchReader, modeBuf, fnameBuf, shaBuf)
+ if err != nil {
+ return nil, err
+ }
+ n += int64(count)
+
+ // if we have found the target directory
+ if bytes.Equal(fname, []byte(target)) && bytes.Equal(mode, []byte("40000")) {
+ copy(tmpTreeID, sha)
+ treeID = tmpTreeID
+ break
+ }
+ }
+
+ if n < size {
+ // Discard any remaining entries in the current tree
+ discard := size - n
+ for discard > math.MaxInt32 {
+ _, err := batchReader.Discard(math.MaxInt32)
+ if err != nil {
+ return nil, err
+ }
+ discard -= math.MaxInt32
+ }
+ _, err := batchReader.Discard(int(discard))
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // if we haven't found a treeID for the target directory our search is over
+ if len(treeID) == 0 {
+ break treeReadingLoop
+ }
+
+ // add the target to the current path
+ if idx > 0 {
+ currentPath += "/"
+ }
+ currentPath += target
+
+ // if we've now found the current path check its sha id and commit status
+ if treePath == currentPath && paths[0] == "" {
+ if len(ids[0]) == 0 {
+ copy(allShaBuf[0:20], treeID)
+ ids[0] = allShaBuf[0:20]
+ commits[0] = string(commitID)
+ } else if bytes.Equal(ids[0], treeID) {
+ commits[0] = string(commitID)
+ }
+ }
+ treeID = to40ByteSHA(treeID)
+ _, err = batchStdinWriter.Write(treeID)
+ if err != nil {
+ return nil, err
+ }
+ _, err = batchStdinWriter.Write([]byte("\n"))
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ commitsMap := make(map[string]*Commit, len(commits))
+ commitsMap[commit.ID.String()] = commit
+
+ commitCommits := make([]*Commit, len(commits))
+ for i, commitID := range commits {
+ c, ok := commitsMap[commitID]
+ if ok {
+ commitCommits[i] = c
+ continue
+ }
+
+ if len(commitID) == 0 {
+ continue
+ }
+
+ _, err := batchStdinWriter.Write([]byte(commitID + "\n"))
+ if err != nil {
+ return nil, err
+ }
+ _, typ, size, err := ReadBatchLine(batchReader)
+ if err != nil {
+ return nil, err
+ }
+ if typ != "commit" {
+ return nil, fmt.Errorf("unexpected type: %s for commit id: %s", typ, commitID)
+ }
+ c, err = CommitFromReader(commit.repo, MustIDFromString(string(commitID)), io.LimitReader(batchReader, int64(size)))
+ if err != nil {
+ return nil, err
+ }
+ commitCommits[i] = c
+ }
+
+ return commitCommits, scan.Err()
+}