1// Copyright 2017 The Gitea Authors. All rights reserved. 2// Use of this source code is governed by a MIT-style 3// license that can be found in the LICENSE file. 4 5//go:build !gogit 6// +build !gogit 7 8package git 9 10import ( 11 "context" 12 "fmt" 13 "io" 14 "path" 15 "sort" 16 17 "code.gitea.io/gitea/modules/log" 18) 19 20// GetCommitsInfo gets information of all commits that are corresponding to these entries 21func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath string, cache *LastCommitCache) ([]CommitInfo, *Commit, error) { 22 entryPaths := make([]string, len(tes)+1) 23 // Get the commit for the treePath itself 24 entryPaths[0] = "" 25 for i, entry := range tes { 26 entryPaths[i+1] = entry.Name() 27 } 28 29 var err error 30 31 var revs map[string]*Commit 32 if cache != nil { 33 var unHitPaths []string 34 revs, unHitPaths, err = getLastCommitForPathsByCache(ctx, commit.ID.String(), treePath, entryPaths, cache) 35 if err != nil { 36 return nil, nil, err 37 } 38 if len(unHitPaths) > 0 { 39 sort.Strings(unHitPaths) 40 commits, err := GetLastCommitForPaths(ctx, cache, commit, treePath, unHitPaths) 41 if err != nil { 42 return nil, nil, err 43 } 44 45 for pth, found := range commits { 46 revs[pth] = found 47 } 48 } 49 } else { 50 sort.Strings(entryPaths) 51 revs, err = GetLastCommitForPaths(ctx, nil, commit, treePath, entryPaths) 52 } 53 if err != nil { 54 return nil, nil, err 55 } 56 57 commitsInfo := make([]CommitInfo, len(tes)) 58 for i, entry := range tes { 59 commitsInfo[i] = CommitInfo{ 60 Entry: entry, 61 } 62 63 // Check if we have found a commit for this entry in time 64 if entryCommit, ok := revs[entry.Name()]; ok { 65 commitsInfo[i].Commit = entryCommit 66 } else { 67 log.Debug("missing commit for %s", entry.Name()) 68 } 69 70 // If the entry if a submodule add a submodule file for this 71 if entry.IsSubModule() { 72 subModuleURL := "" 73 var fullPath string 74 if len(treePath) > 0 { 75 fullPath = treePath + "/" + entry.Name() 76 } else { 77 fullPath = entry.Name() 78 } 79 if subModule, err := commit.GetSubModule(fullPath); err != nil { 80 return nil, nil, err 81 } else if subModule != nil { 82 subModuleURL = subModule.URL 83 } 84 subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String()) 85 commitsInfo[i].SubModuleFile = subModuleFile 86 } 87 } 88 89 // Retrieve the commit for the treePath itself (see above). We basically 90 // get it for free during the tree traversal and it's used for listing 91 // pages to display information about newest commit for a given path. 92 var treeCommit *Commit 93 var ok bool 94 if treePath == "" { 95 treeCommit = commit 96 } else if treeCommit, ok = revs[""]; ok { 97 treeCommit.repo = commit.repo 98 } 99 return commitsInfo, treeCommit, nil 100} 101 102func getLastCommitForPathsByCache(ctx context.Context, commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*Commit, []string, error) { 103 wr, rd, cancel := cache.repo.CatFileBatch(ctx) 104 defer cancel() 105 106 var unHitEntryPaths []string 107 var results = make(map[string]*Commit) 108 for _, p := range paths { 109 lastCommit, err := cache.Get(commitID, path.Join(treePath, p), wr, rd) 110 if err != nil { 111 return nil, nil, err 112 } 113 if lastCommit != nil { 114 results[p] = lastCommit.(*Commit) 115 continue 116 } 117 118 unHitEntryPaths = append(unHitEntryPaths, p) 119 } 120 121 return results, unHitEntryPaths, nil 122} 123 124// GetLastCommitForPaths returns last commit information 125func GetLastCommitForPaths(ctx context.Context, cache *LastCommitCache, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) { 126 // We read backwards from the commit to obtain all of the commits 127 revs, err := WalkGitLog(ctx, cache, commit.repo, commit, treePath, paths...) 128 if err != nil { 129 return nil, err 130 } 131 132 batchStdinWriter, batchReader, cancel := commit.repo.CatFileBatch(ctx) 133 defer cancel() 134 135 commitsMap := map[string]*Commit{} 136 commitsMap[commit.ID.String()] = commit 137 138 commitCommits := map[string]*Commit{} 139 for path, commitID := range revs { 140 c, ok := commitsMap[commitID] 141 if ok { 142 commitCommits[path] = c 143 continue 144 } 145 146 if len(commitID) == 0 { 147 continue 148 } 149 150 _, err := batchStdinWriter.Write([]byte(commitID + "\n")) 151 if err != nil { 152 return nil, err 153 } 154 _, typ, size, err := ReadBatchLine(batchReader) 155 if err != nil { 156 return nil, err 157 } 158 if typ != "commit" { 159 return nil, fmt.Errorf("unexpected type: %s for commit id: %s", typ, commitID) 160 } 161 c, err = CommitFromReader(commit.repo, MustIDFromString(string(commitID)), io.LimitReader(batchReader, int64(size))) 162 if err != nil { 163 return nil, err 164 } 165 if _, err := batchReader.Discard(1); err != nil { 166 return nil, err 167 } 168 commitCommits[path] = c 169 } 170 171 return commitCommits, nil 172} 173