mirror of
https://github.com/kubescape/kubescape.git
synced 2026-04-15 06:58:11 +00:00
247 lines
5.9 KiB
Go
247 lines
5.9 KiB
Go
package cautils
|
|
|
|
import (
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/armosec/go-git-url/apis"
|
|
gitv5 "github.com/go-git/go-git/v5"
|
|
configv5 "github.com/go-git/go-git/v5/config"
|
|
plumbingv5 "github.com/go-git/go-git/v5/plumbing"
|
|
git2go "github.com/libgit2/git2go/v33"
|
|
)
|
|
|
|
type LocalGitRepository struct {
|
|
goGitRepo *gitv5.Repository
|
|
git2GoRepo *git2go.Repository
|
|
head *plumbingv5.Reference
|
|
config *configv5.Config
|
|
fileToLastCommit map[string]*git2go.Commit
|
|
}
|
|
|
|
func NewLocalGitRepository(path string) (*LocalGitRepository, error) {
|
|
goGitRepo, err := gitv5.PlainOpenWithOptions(path, &gitv5.PlainOpenOptions{DetectDotGit: true})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
head, err := goGitRepo.Head()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !head.Name().IsBranch() {
|
|
return nil, fmt.Errorf("current HEAD reference is not a branch")
|
|
}
|
|
|
|
config, err := goGitRepo.Config()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(config.Remotes) == 0 {
|
|
return nil, fmt.Errorf("no remotes found")
|
|
}
|
|
|
|
l := &LocalGitRepository{
|
|
goGitRepo: goGitRepo,
|
|
head: head,
|
|
config: config,
|
|
}
|
|
|
|
if repoRoot, err := l.GetRootDir(); err == nil {
|
|
git2GoRepo, err := git2go.OpenRepository(repoRoot)
|
|
if err != nil {
|
|
return l, err
|
|
}
|
|
l.git2GoRepo = git2GoRepo
|
|
}
|
|
|
|
return l, nil
|
|
}
|
|
|
|
// GetBranchName get current branch name
|
|
func (g *LocalGitRepository) GetBranchName() string {
|
|
return g.head.Name().Short()
|
|
}
|
|
|
|
// GetRemoteUrl get default remote URL
|
|
func (g *LocalGitRepository) GetRemoteUrl() (string, error) {
|
|
branchName := g.GetBranchName()
|
|
if branchRef, branchFound := g.config.Branches[branchName]; branchFound {
|
|
remoteName := branchRef.Remote
|
|
if len(g.config.Remotes[remoteName].URLs) == 0 {
|
|
return "", fmt.Errorf("expected to find URLs for remote '%s', branch '%s'", remoteName, branchName)
|
|
}
|
|
return g.config.Remotes[remoteName].URLs[0], nil
|
|
}
|
|
|
|
const defaultRemoteName string = "origin"
|
|
if len(g.config.Remotes[defaultRemoteName].URLs) == 0 {
|
|
return "", fmt.Errorf("expected to find URLs for remote '%s'", defaultRemoteName)
|
|
}
|
|
return g.config.Remotes[defaultRemoteName].URLs[0], nil
|
|
}
|
|
|
|
// GetName get origin name without the .git suffix
|
|
func (g *LocalGitRepository) GetName() (string, error) {
|
|
originUrl, err := g.GetRemoteUrl()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
baseName := path.Base(originUrl)
|
|
// remove .git
|
|
return strings.TrimSuffix(baseName, ".git"), nil
|
|
}
|
|
|
|
// GetLastCommit get latest commit object
|
|
func (g *LocalGitRepository) GetLastCommit() (*apis.Commit, error) {
|
|
cIter, err := g.goGitRepo.Log(&gitv5.LogOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
commit, err := cIter.Next()
|
|
defer cIter.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &apis.Commit{
|
|
SHA: commit.Hash.String(),
|
|
Author: apis.Committer{
|
|
Name: commit.Author.Name,
|
|
Email: commit.Author.Email,
|
|
Date: commit.Author.When,
|
|
},
|
|
Message: commit.Message,
|
|
Committer: apis.Committer{},
|
|
Files: []apis.Files{},
|
|
}, nil
|
|
}
|
|
|
|
func (g *LocalGitRepository) getAllCommits() ([]*git2go.Commit, error) {
|
|
logItr, itrErr := g.git2GoRepo.Walk()
|
|
if itrErr != nil {
|
|
|
|
return nil, itrErr
|
|
}
|
|
|
|
pushErr := logItr.PushHead()
|
|
if pushErr != nil {
|
|
return nil, pushErr
|
|
}
|
|
|
|
var allCommits []*git2go.Commit
|
|
err := logItr.Iterate(func(commit *git2go.Commit) bool {
|
|
if commit != nil {
|
|
allCommits = append(allCommits, commit)
|
|
return true
|
|
}
|
|
return false
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return allCommits, nil
|
|
}
|
|
|
|
func (g *LocalGitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error) {
|
|
if len(g.fileToLastCommit) == 0 {
|
|
filePathToCommitTime := map[string]time.Time{}
|
|
filePathToCommit := map[string]*git2go.Commit{}
|
|
allCommits, _ := g.getAllCommits()
|
|
|
|
// builds a map of all files to their last commit
|
|
for _, commit := range allCommits {
|
|
// Ignore merge commits (2+ parents)
|
|
if commit.ParentCount() <= 1 {
|
|
tree, err := commit.Tree()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// ParentCount can be either 1 or 0 (initial commit)
|
|
// In case it's the initial commit, prevTree is nil
|
|
var prevTree *git2go.Tree
|
|
if commit.ParentCount() == 1 {
|
|
prevCommit := commit.Parent(0)
|
|
prevTree, err = prevCommit.Tree()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
}
|
|
|
|
diff, err := g.git2GoRepo.DiffTreeToTree(prevTree, tree, nil)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
numDeltas, err := diff.NumDeltas()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
for i := 0; i < numDeltas; i++ {
|
|
delta, err := diff.Delta(i)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
deltaFilePath := delta.NewFile.Path
|
|
commitTime := commit.Author().When
|
|
|
|
// In case we have the commit information for the file which is not the latest - we override it
|
|
if currentCommitTime, exists := filePathToCommitTime[deltaFilePath]; exists {
|
|
if currentCommitTime.Before(commitTime) {
|
|
filePathToCommitTime[deltaFilePath] = commitTime
|
|
filePathToCommit[deltaFilePath] = commit
|
|
}
|
|
} else {
|
|
filePathToCommitTime[deltaFilePath] = commitTime
|
|
filePathToCommit[deltaFilePath] = commit
|
|
}
|
|
}
|
|
}
|
|
}
|
|
g.fileToLastCommit = filePathToCommit
|
|
}
|
|
|
|
if relevantCommit, exists := g.fileToLastCommit[filePath]; exists {
|
|
return g.getCommit(relevantCommit), nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("failed to get commit information for file: %s", filePath)
|
|
}
|
|
|
|
func (g *LocalGitRepository) getCommit(commit *git2go.Commit) *apis.Commit {
|
|
return &apis.Commit{
|
|
SHA: commit.Id().String(),
|
|
Author: apis.Committer{
|
|
Name: commit.Author().Name,
|
|
Email: commit.Author().Email,
|
|
Date: commit.Author().When,
|
|
},
|
|
Message: commit.Message(),
|
|
Committer: apis.Committer{},
|
|
Files: []apis.Files{},
|
|
}
|
|
}
|
|
|
|
func (g *LocalGitRepository) GetRootDir() (string, error) {
|
|
wt, err := g.goGitRepo.Worktree()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get repo root")
|
|
}
|
|
|
|
return wt.Filesystem.Root(), nil
|
|
}
|