mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	* Use go-git for tree reading and commit info lookup. Signed-off-by: Filip Navara <navara@emclient.com> * Use TreeEntry.IsRegular() instead of ObjectType that was removed. Signed-off-by: Filip Navara <navara@emclient.com> * Use the treePath to optimize commit info search. Signed-off-by: Filip Navara <navara@emclient.com> * Extract the latest commit at treePath along with the other commits. Signed-off-by: Filip Navara <navara@emclient.com> * Fix listing commit info for a directory that was created in one commit and never modified after. Signed-off-by: Filip Navara <navara@emclient.com> * Avoid nearly all external 'git' invocations when doing directory listing (.editorconfig code path is still hit). Signed-off-by: Filip Navara <navara@emclient.com> * Use go-git for reading blobs. Signed-off-by: Filip Navara <navara@emclient.com> * Make SHA1 type alias for plumbing.Hash in go-git. Signed-off-by: Filip Navara <navara@emclient.com> * Make Signature type alias for object.Signature in go-git. Signed-off-by: Filip Navara <navara@emclient.com> * Fix GetCommitsInfo for repository with only one commit. Signed-off-by: Filip Navara <navara@emclient.com> * Fix PGP signature verification. Signed-off-by: Filip Navara <navara@emclient.com> * Fix issues with walking commit graph across merges. Signed-off-by: Filip Navara <navara@emclient.com> * Fix typo in condition. Signed-off-by: Filip Navara <navara@emclient.com> * Speed up loading branch list by keeping the repository reference (and thus all the loaded packfile indexes). Signed-off-by: Filip Navara <navara@emclient.com> * Fix lising submodules. Signed-off-by: Filip Navara <navara@emclient.com> * Fix build Signed-off-by: Filip Navara <navara@emclient.com> * Add back commit cache because of name-rev Signed-off-by: Filip Navara <navara@emclient.com> * Fix tests Signed-off-by: Filip Navara <navara@emclient.com> * Fix code style * Fix spelling * Address PR feedback Signed-off-by: Filip Navara <navara@emclient.com> * Update vendor module list Signed-off-by: Filip Navara <navara@emclient.com> * Fix getting trees by commit id Signed-off-by: Filip Navara <navara@emclient.com> * Fix remaining unit test failures * Fix GetTreeBySHA * Avoid running `git name-rev` if not necessary Signed-off-by: Filip Navara <navara@emclient.com> * Move Branch code to git module * Clean up GPG signature verification and fix it for tagged commits * Address PR feedback (import formatting, copyright headers) * Make blob lookup by SHA working * Update tests to use public API * Allow getting content from any type of object through the blob interface * Change test to actually expect the object content that is in the GIT repository * Change one more test to actually expect the object content that is in the GIT repository * Add comments
		
			
				
	
	
		
			242 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			242 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// 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 (
 | 
						|
	"github.com/emirpasic/gods/trees/binaryheap"
 | 
						|
	"gopkg.in/src-d/go-git.v4/plumbing"
 | 
						|
	"gopkg.in/src-d/go-git.v4/plumbing/object"
 | 
						|
)
 | 
						|
 | 
						|
// 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()
 | 
						|
	}
 | 
						|
 | 
						|
	c, err := commit.repo.gogitRepo.CommitObject(plumbing.Hash(commit.ID))
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	revs, err := getLastCommitForPaths(c, treePath, entryPaths)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	commit.repo.gogitStorage.Close()
 | 
						|
 | 
						|
	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}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 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)
 | 
						|
	}
 | 
						|
	return commitsInfo, treeCommit, nil
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
}
 | 
						|
 | 
						|
func getCommitTree(c *object.Commit, treePath string) (*object.Tree, error) {
 | 
						|
	tree, err := c.Tree()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Optimize deep traversals by focusing only on the specific tree
 | 
						|
	if treePath != "" {
 | 
						|
		tree, err = tree.Tree(treePath)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return tree, nil
 | 
						|
}
 | 
						|
 | 
						|
func getFullPath(treePath, path string) string {
 | 
						|
	if treePath != "" {
 | 
						|
		if path != "" {
 | 
						|
			return treePath + "/" + path
 | 
						|
		}
 | 
						|
		return treePath
 | 
						|
	}
 | 
						|
	return path
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	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
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return hashes, nil
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
	})
 | 
						|
 | 
						|
	result := make(map[string]*object.Commit)
 | 
						|
	initialHashes, err := getFileHashes(c, treePath, paths)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// 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
 | 
						|
		}
 | 
						|
		current := cIn.(*commitAndPaths)
 | 
						|
		currentID := current.commit.ID()
 | 
						|
 | 
						|
		if seen[currentID] {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		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
 | 
						|
			}
 | 
						|
			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)
 | 
						|
			if err != nil {
 | 
						|
				break
 | 
						|
			}
 | 
						|
 | 
						|
			for i, path := range current.paths {
 | 
						|
				if parentHashes[j][path] != plumbing.ZeroHash {
 | 
						|
					numOfParentsWithPath[i]++
 | 
						|
					if parentHashes[j][path] != current.hashes[path] {
 | 
						|
						pathChanged[i] = true
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		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)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		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]})
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return result, nil
 | 
						|
}
 |