mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-23 07:29:00 -08:00
188
pkg/git/git.go
Normal file
188
pkg/git/git.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/knqyf263/trivy/pkg/log"
|
||||
"github.com/knqyf263/trivy/pkg/utils"
|
||||
"golang.org/x/xerrors"
|
||||
git "gopkg.in/src-d/go-git.v4"
|
||||
"gopkg.in/src-d/go-git.v4/plumbing/object"
|
||||
"gopkg.in/src-d/go-git.v4/plumbing/storer"
|
||||
)
|
||||
|
||||
func CloneOrPull(url, repoPath string) (map[string]struct{}, error) {
|
||||
exists, err := utils.Exists(filepath.Join(repoPath, ".git"))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to check if a file exists: %w", err)
|
||||
}
|
||||
|
||||
updatedFiles := map[string]struct{}{}
|
||||
if exists {
|
||||
log.Logger.Debug("git pull")
|
||||
files, err := pull(repoPath)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to pull repository: %w", err)
|
||||
}
|
||||
|
||||
for _, filename := range files {
|
||||
updatedFiles[strings.TrimSpace(filename)] = struct{}{}
|
||||
}
|
||||
} else {
|
||||
log.Logger.Debug("remove an existed directory")
|
||||
|
||||
s := spinner.New(spinner.CharSets[36], 100*time.Millisecond)
|
||||
s.Suffix = " The first time will take a while..."
|
||||
s.Start()
|
||||
defer s.Stop()
|
||||
|
||||
if err = os.RemoveAll(repoPath); err != nil {
|
||||
return nil, xerrors.Errorf("failed to remove an existed directory: %w", err)
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(repoPath, 0700); err != nil {
|
||||
return nil, xerrors.Errorf("failed to mkdir: %w", err)
|
||||
}
|
||||
if err := clone(url, repoPath); err != nil {
|
||||
return nil, xerrors.Errorf("failed to clone repository: %w", err)
|
||||
}
|
||||
|
||||
err = filepath.Walk(repoPath, func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
rel, err := filepath.Rel(repoPath, path)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to get a relative path: %w", err)
|
||||
}
|
||||
updatedFiles[rel] = struct{}{}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("error in file walk: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return updatedFiles, nil
|
||||
}
|
||||
|
||||
func clone(url, repoPath string) error {
|
||||
if utils.IsCommandAvailable("git") {
|
||||
return cloneByOSCommand(url, repoPath)
|
||||
}
|
||||
log.Logger.Warn("Recommend installing git (if not, DB update is very slow)")
|
||||
|
||||
_, err := git.PlainClone(repoPath, false, &git.CloneOptions{
|
||||
URL: url,
|
||||
})
|
||||
if err != nil && err != git.ErrRepositoryAlreadyExists {
|
||||
return xerrors.Errorf("unexpected error in git clone: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cloneByOSCommand(url, repoPath string) error {
|
||||
commandAndArgs := []string{"clone", url, repoPath}
|
||||
_, err := utils.Exec("git", commandAndArgs)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error in git clone: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pull(repoPath string) ([]string, error) {
|
||||
if utils.IsCommandAvailable("git") {
|
||||
return pullByOSCommand(repoPath)
|
||||
}
|
||||
|
||||
r, err := git.PlainOpen(repoPath)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to open repository: %w", err)
|
||||
}
|
||||
|
||||
log.Logger.Debug("Retrieve the branch being pointed by HEAD")
|
||||
ref, err := r.Head()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to get HEAD: %w", err)
|
||||
}
|
||||
|
||||
log.Logger.Debug("Get the working directory for the repository")
|
||||
w, err := r.Worktree()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to get the working directory: %w", err)
|
||||
}
|
||||
|
||||
log.Logger.Debug("Pull the latest changes from the origin remote and merge into the current branch")
|
||||
err = w.Pull(&git.PullOptions{RemoteName: "origin"})
|
||||
if err != nil && err != git.NoErrAlreadyUpToDate {
|
||||
return nil, err
|
||||
} else if err == git.NoErrAlreadyUpToDate {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
log.Logger.Debug("Retrieve the commit history")
|
||||
commits, err := r.Log(&git.LogOptions{})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("error in git log: %w", err)
|
||||
}
|
||||
|
||||
log.Logger.Debug("Detect the updated files")
|
||||
var prevCommit *object.Commit
|
||||
var updatedFiles []string
|
||||
err = commits.ForEach(func(commit *object.Commit) error {
|
||||
if prevCommit == nil {
|
||||
prevCommit = commit
|
||||
return nil
|
||||
}
|
||||
|
||||
patch, err := commit.Patch(prevCommit)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error in patch: %w", err)
|
||||
}
|
||||
for _, stat := range patch.Stats() {
|
||||
updatedFiles = append(updatedFiles, stat.Name)
|
||||
}
|
||||
|
||||
if commit.Hash == ref.Hash() {
|
||||
return storer.ErrStop
|
||||
}
|
||||
|
||||
prevCommit = commit
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("error in commit foreach: %w", err)
|
||||
}
|
||||
|
||||
return updatedFiles, nil
|
||||
}
|
||||
|
||||
func pullByOSCommand(repoPath string) ([]string, error) {
|
||||
gitDir := filepath.Join(repoPath, ".git")
|
||||
commandArgs := []string{"--git-dir", gitDir, "--work-tree", repoPath}
|
||||
|
||||
revParseCmd := []string{"rev-parse", "HEAD"}
|
||||
output, err := utils.Exec("git", append(commandArgs, revParseCmd...))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("error in git rev-parse: %w", err)
|
||||
}
|
||||
commitHash := strings.TrimSpace(output)
|
||||
|
||||
pullCmd := []string{"pull", "origin", "master"}
|
||||
_, err = utils.Exec("git", append(commandArgs, pullCmd...))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("error in git pull: %w", err)
|
||||
}
|
||||
|
||||
diffCmd := []string{"diff", commitHash, "HEAD", "--name-only"}
|
||||
output, err = utils.Exec("git", append(commandArgs, diffCmd...))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("error in git diff: %w", err)
|
||||
}
|
||||
updatedFiles := strings.Split(strings.TrimSpace(output), "\n")
|
||||
return updatedFiles, nil
|
||||
}
|
||||
Reference in New Issue
Block a user