summaryrefslogtreecommitdiffstats
path: root/modules/git/commit_info.go
diff options
context:
space:
mode:
Diffstat (limited to 'modules/git/commit_info.go')
-rw-r--r--modules/git/commit_info.go176
1 files changed, 176 insertions, 0 deletions
diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go
new file mode 100644
index 0000000..39e30b1
--- /dev/null
+++ b/modules/git/commit_info.go
@@ -0,0 +1,176 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "path"
+ "sort"
+
+ "code.gitea.io/gitea/modules/log"
+)
+
+// CommitInfo describes the first commit with the provided entry
+type CommitInfo struct {
+ Entry *TreeEntry
+ Commit *Commit
+ SubModuleFile *SubModuleFile
+}
+
+// GetCommitsInfo gets information of all commits that are corresponding to these entries
+func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath string) ([]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 commit.repo.LastCommitCache != nil {
+ var unHitPaths []string
+ revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, commit.repo.LastCommitCache)
+ if err != nil {
+ return nil, nil, err
+ }
+ if len(unHitPaths) > 0 {
+ sort.Strings(unHitPaths)
+ commits, err := GetLastCommitForPaths(ctx, commit, treePath, unHitPaths)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ for pth, found := range commits {
+ revs[pth] = found
+ }
+ }
+ } else {
+ sort.Strings(entryPaths)
+ revs, err = GetLastCommitForPaths(ctx, commit, treePath, entryPaths)
+ }
+ if err != nil {
+ return nil, nil, err
+ }
+
+ commitsInfo := make([]CommitInfo, len(tes))
+ for i, entry := range tes {
+ commitsInfo[i] = CommitInfo{
+ Entry: entry,
+ }
+
+ // Check if we have found a commit for this entry in time
+ if entryCommit, ok := revs[entry.Name()]; ok {
+ commitsInfo[i].Commit = entryCommit
+ } else {
+ log.Debug("missing commit for %s", entry.Name())
+ }
+
+ // If the entry if a submodule add a submodule file for this
+ if entry.IsSubModule() {
+ var fullPath string
+ if len(treePath) > 0 {
+ fullPath = treePath + "/" + entry.Name()
+ } else {
+ fullPath = entry.Name()
+ }
+ subModuleURL, err := commit.GetSubModule(fullPath)
+ if err != nil {
+ return nil, nil, err
+ }
+ subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, 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
+ 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
+ continue
+ }
+
+ unHitEntryPaths = append(unHitEntryPaths, p)
+ }
+
+ return results, unHitEntryPaths, nil
+}
+
+// GetLastCommitForPaths returns last commit information
+func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) {
+ // We read backwards from the commit to obtain all of the commits
+ revs, err := WalkGitLog(ctx, commit.repo, commit, treePath, paths...)
+ if err != nil {
+ return nil, err
+ }
+
+ batchStdinWriter, batchReader, cancel, err := commit.repo.CatFileBatch(ctx)
+ if err != nil {
+ return nil, err
+ }
+ defer cancel()
+
+ commitsMap := map[string]*Commit{}
+ commitsMap[commit.ID.String()] = commit
+
+ commitCommits := map[string]*Commit{}
+ for path, commitID := range revs {
+ c, ok := commitsMap[commitID]
+ if ok {
+ commitCommits[path] = 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" {
+ if err := DiscardFull(batchReader, size+1); err != nil {
+ return nil, err
+ }
+ return nil, fmt.Errorf("unexpected type: %s for commit id: %s", typ, commitID)
+ }
+ c, err = CommitFromReader(commit.repo, MustIDFromString(commitID), io.LimitReader(batchReader, size))
+ if err != nil {
+ return nil, err
+ }
+ if _, err := batchReader.Discard(1); err != nil {
+ return nil, err
+ }
+ commitCommits[path] = c
+ }
+
+ return commitCommits, nil
+}