2017-12-10 21:23:34 -05:00
|
|
|
// 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.
|
|
|
|
|
|
|
|
package git
|
|
|
|
|
|
|
|
import (
|
2019-04-19 07:17:27 -05:00
|
|
|
"github.com/emirpasic/gods/trees/binaryheap"
|
|
|
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
|
|
|
"gopkg.in/src-d/go-git.v4/plumbing/object"
|
2017-12-10 21:23:34 -05:00
|
|
|
)
|
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
// GetCommitsInfo gets information of all commits that are corresponding to these entries
|
|
|
|
func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCommitCache) ([][]interface{}, *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()
|
|
|
|
}
|
2017-12-10 21:23:34 -05:00
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
c, err := commit.repo.gogitRepo.CommitObject(plumbing.Hash(commit.ID))
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2017-12-10 21:23:34 -05:00
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
revs, err := getLastCommitForPaths(c, treePath, entryPaths)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2017-12-10 21:23:34 -05:00
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
commit.repo.gogitStorage.Close()
|
2017-12-10 21:23:34 -05:00
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
commitsInfo := make([][]interface{}, len(tes))
|
|
|
|
for i, entry := range tes {
|
|
|
|
if rev, ok := revs[entry.Name()]; ok {
|
|
|
|
entryCommit := convertCommit(rev)
|
|
|
|
if entry.IsSubModule() {
|
|
|
|
subModuleURL := ""
|
|
|
|
if subModule, err := commit.GetSubModule(entry.Name()); err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
} else if subModule != nil {
|
|
|
|
subModuleURL = subModule.URL
|
|
|
|
}
|
|
|
|
subModuleFile := NewSubModuleFile(entryCommit, subModuleURL, entry.ID.String())
|
|
|
|
commitsInfo[i] = []interface{}{entry, subModuleFile}
|
|
|
|
} else {
|
|
|
|
commitsInfo[i] = []interface{}{entry, entryCommit}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
commitsInfo[i] = []interface{}{entry, nil}
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
// 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
|
|
|
|
if rev, ok := revs[""]; ok {
|
|
|
|
treeCommit = convertCommit(rev)
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
2019-04-19 07:17:27 -05:00
|
|
|
return commitsInfo, treeCommit, nil
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
type commitAndPaths struct {
|
|
|
|
commit *object.Commit
|
|
|
|
// Paths that are still on the branch represented by commit
|
|
|
|
paths []string
|
|
|
|
// Set of hashes for the paths
|
|
|
|
hashes map[string]plumbing.Hash
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
func getCommitTree(c *object.Commit, treePath string) (*object.Tree, error) {
|
|
|
|
tree, err := c.Tree()
|
|
|
|
if err != nil {
|
2017-12-10 21:23:34 -05:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
// Optimize deep traversals by focusing only on the specific tree
|
|
|
|
if treePath != "" {
|
|
|
|
tree, err = tree.Tree(treePath)
|
2017-12-10 21:23:34 -05:00
|
|
|
if err != nil {
|
2019-04-19 07:17:27 -05:00
|
|
|
return nil, err
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
return tree, nil
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
func getFullPath(treePath, path string) string {
|
|
|
|
if treePath != "" {
|
|
|
|
if path != "" {
|
|
|
|
return treePath + "/" + path
|
|
|
|
}
|
|
|
|
return treePath
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
2019-04-19 07:17:27 -05:00
|
|
|
return path
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
func getFileHashes(c *object.Commit, treePath string, paths []string) (map[string]plumbing.Hash, error) {
|
|
|
|
tree, err := getCommitTree(c, treePath)
|
|
|
|
if err == object.ErrDirectoryNotFound {
|
|
|
|
// The whole tree didn't exist, so return empty map
|
|
|
|
return make(map[string]plumbing.Hash), nil
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
|
|
|
if err != nil {
|
2019-04-19 07:17:27 -05:00
|
|
|
return nil, err
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
hashes := make(map[string]plumbing.Hash)
|
|
|
|
for _, path := range paths {
|
|
|
|
if path != "" {
|
|
|
|
entry, err := tree.FindEntry(path)
|
|
|
|
if err == nil {
|
|
|
|
hashes[path] = entry.Hash
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
hashes[path] = tree.Hash
|
|
|
|
}
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
return hashes, nil
|
|
|
|
}
|
2017-12-10 21:23:34 -05:00
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (map[string]*object.Commit, error) {
|
|
|
|
// We do a tree traversal with nodes sorted by commit time
|
|
|
|
seen := make(map[plumbing.Hash]bool)
|
|
|
|
heap := binaryheap.NewWith(func(a, b interface{}) int {
|
|
|
|
if a.(*commitAndPaths).commit.Committer.When.Before(b.(*commitAndPaths).commit.Committer.When) {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
})
|
2017-12-22 02:00:30 -05:00
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
result := make(map[string]*object.Commit)
|
|
|
|
initialHashes, err := getFileHashes(c, treePath, paths)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-12-22 02:00:30 -05:00
|
|
|
}
|
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
// Start search from the root commit and with full set of paths
|
|
|
|
heap.Push(&commitAndPaths{c, paths, initialHashes})
|
|
|
|
|
|
|
|
for {
|
|
|
|
cIn, ok := heap.Pop()
|
|
|
|
if !ok {
|
|
|
|
break
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
2019-04-19 07:17:27 -05:00
|
|
|
current := cIn.(*commitAndPaths)
|
|
|
|
currentID := current.commit.ID()
|
2017-12-10 21:23:34 -05:00
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
if seen[currentID] {
|
2017-12-10 21:23:34 -05:00
|
|
|
continue
|
|
|
|
}
|
2019-04-19 07:17:27 -05:00
|
|
|
seen[currentID] = true
|
|
|
|
|
|
|
|
// Load the parent commits for the one we are currently examining
|
|
|
|
numParents := current.commit.NumParents()
|
|
|
|
var parents []*object.Commit
|
|
|
|
for i := 0; i < numParents; i++ {
|
|
|
|
parent, err := current.commit.Parent(i)
|
|
|
|
if err != nil {
|
|
|
|
break
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
2019-04-19 07:17:27 -05:00
|
|
|
parents = append(parents, parent)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Examine the current commit and set of interesting paths
|
|
|
|
numOfParentsWithPath := make([]int, len(current.paths))
|
|
|
|
pathChanged := make([]bool, len(current.paths))
|
|
|
|
parentHashes := make([]map[string]plumbing.Hash, len(parents))
|
|
|
|
for j, parent := range parents {
|
|
|
|
parentHashes[j], err = getFileHashes(parent, treePath, current.paths)
|
2017-12-10 21:23:34 -05:00
|
|
|
if err != nil {
|
2019-04-19 07:17:27 -05:00
|
|
|
break
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
2019-04-19 07:17:27 -05:00
|
|
|
|
|
|
|
for i, path := range current.paths {
|
|
|
|
if parentHashes[j][path] != plumbing.ZeroHash {
|
|
|
|
numOfParentsWithPath[i]++
|
|
|
|
if parentHashes[j][path] != current.hashes[path] {
|
|
|
|
pathChanged[i] = true
|
|
|
|
}
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
var remainingPaths []string
|
|
|
|
for i, path := range current.paths {
|
|
|
|
switch numOfParentsWithPath[i] {
|
|
|
|
case 0:
|
|
|
|
// The path didn't exist in any parent, so it must have been created by
|
|
|
|
// this commit. The results could already contain some newer change from
|
|
|
|
// different path, so don't override that.
|
|
|
|
if result[path] == nil {
|
|
|
|
result[path] = current.commit
|
|
|
|
}
|
|
|
|
case 1:
|
|
|
|
// The file is present on exactly one parent, so check if it was changed
|
|
|
|
// and save the revision if it did.
|
|
|
|
if pathChanged[i] {
|
|
|
|
if result[path] == nil {
|
|
|
|
result[path] = current.commit
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
remainingPaths = append(remainingPaths, path)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
// The file is present on more than one of the parent paths, so this is
|
|
|
|
// a merge. We have to examine all the parent trees to find out where
|
|
|
|
// the change occurred. pathChanged[i] would tell us that the file was
|
|
|
|
// changed during the merge, but it wouldn't tell us the relevant commit
|
|
|
|
// that introduced it.
|
|
|
|
remainingPaths = append(remainingPaths, path)
|
|
|
|
}
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
|
|
|
|
2019-04-19 07:17:27 -05:00
|
|
|
if len(remainingPaths) > 0 {
|
|
|
|
// Add the parent nodes along with remaining paths to the heap for further
|
|
|
|
// processing.
|
|
|
|
for j, parent := range parents {
|
|
|
|
if seen[parent.ID()] {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Combine remainingPath with paths available on the parent branch
|
|
|
|
// and make union of them
|
|
|
|
var remainingPathsForParent []string
|
|
|
|
for _, path := range remainingPaths {
|
|
|
|
if parentHashes[j][path] != plumbing.ZeroHash {
|
|
|
|
remainingPathsForParent = append(remainingPathsForParent, path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
heap.Push(&commitAndPaths{parent, remainingPathsForParent, parentHashes[j]})
|
|
|
|
}
|
|
|
|
}
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|
2019-04-19 07:17:27 -05:00
|
|
|
|
|
|
|
return result, nil
|
2017-12-10 21:23:34 -05:00
|
|
|
}
|