// Copyright 2017 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package git import ( "context" "maps" "path" "time" "gitea.dev/modules/log" "gitea.dev/modules/util" ) // CommitInfo describes the first commit with the provided entry type CommitInfo struct { Entry *TreeEntry Commit *Commit SubmoduleFile *CommitSubmoduleFile } func GetCommitInfoSubmoduleFile(ctx context.Context, repoLink, fullPath string, gitRepo *Repository, commit *Commit, refCommitID ObjectID) (*CommitSubmoduleFile, error) { submodule, err := commit.GetSubModule(ctx, gitRepo, fullPath) if err != nil { return nil, err } if submodule == nil { // unable to find submodule from ".gitmodules" file return NewCommitSubmoduleFile(repoLink, fullPath, "", refCommitID.String()), nil } return NewCommitSubmoduleFile(repoLink, fullPath, submodule.URL, refCommitID.String()), nil } func getLastCommitForPathsWithTimeout(ctx context.Context, timeout time.Duration, gitRepo *Repository, commit *Commit, treePath string, entryNames []string) (map[string]*Commit, error) { // Actually using "timeout ctx" here is not ideal: it creates a new git cat-file batch process // (the other one uses the request's ctx and is cached by the gitRepo). // Ideally, the underlying functions only need to return in predictable time, // so it should use the shared (cached) git cat-file batcher from the request context and just use "time.After" for deadline. // However, it's difficult to make it right at the moment. ctxInner := ctx if timeout > 0 { var cancel context.CancelFunc ctxInner, cancel = context.WithTimeout(ctx, timeout) defer cancel() } revs, err := GetLastCommitForPaths(ctxInner, gitRepo, commit, treePath, entryNames) if err != nil && ctxInner.Err() == nil { // only return the err if context is not canceled return nil, err } return revs, nil } // GetCommitsInfo gets information of all commits that are corresponding to these entries func (tes Entries) GetCommitsInfo(ctx context.Context, timeout time.Duration, repoLink string, gitRepo *Repository, commit *Commit, treePath string) (commitsInfo []CommitInfo, treeLastCommit *Commit, err error) { entryNames := make([]string, 0, len(tes)+1) entryNames = append(entryNames, "") // include the commit for the treePath itself for _, entry := range tes { entryNames = append(entryNames, entry.Name()) } var revs map[string]*Commit var remainingEntryNames []string if gitRepo.LastCommitCache != nil { revs, remainingEntryNames, err = getLastCommitForPathsByCache(ctx, commit.ID.String(), treePath, entryNames, gitRepo.LastCommitCache) if err != nil { return nil, nil, err } } else { revs, remainingEntryNames = map[string]*Commit{}, entryNames } if len(remainingEntryNames) > 0 { remainingRevs, err := getLastCommitForPathsWithTimeout(ctx, timeout, gitRepo, commit, treePath, remainingEntryNames) if err != nil { return nil, nil, err } maps.Copy(revs, remainingRevs) } for _, entry := range tes { ci := CommitInfo{Entry: entry, Commit: revs[entry.Name()]} if ci.Commit == nil { log.Debug("missing commit for %s", entry.Name()) } // If the entry is a submodule, add a submodule file for this if entry.IsSubModule() { ci.SubmoduleFile, err = GetCommitInfoSubmoduleFile(ctx, repoLink, path.Join(treePath, entry.Name()), gitRepo, commit, entry.ID) if err != nil { return nil, nil, err } } commitsInfo = append(commitsInfo, ci) } // 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 the newest commit for a given path. treeLastCommit = util.Iif(treePath == "", commit, revs[""]) return commitsInfo, treeLastCommit, nil } func getLastCommitForPathsByCache(ctx context.Context, 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(ctx, 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 }