// 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 }