673 lines
19 KiB
Go
673 lines
19 KiB
Go
package git
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
diskbufferreader "github.com/bill-rich/disk-buffer-reader"
|
|
gitparse "github.com/bill-rich/gitparser"
|
|
"github.com/go-errors/errors"
|
|
"github.com/go-git/go-git/v5"
|
|
"github.com/go-git/go-git/v5/plumbing"
|
|
"github.com/google/go-github/v42/github"
|
|
"github.com/rs/zerolog"
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/oauth2"
|
|
"golang.org/x/sync/semaphore"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/types/known/anypb"
|
|
|
|
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
|
"github.com/trufflesecurity/trufflehog/v3/pkg/handlers"
|
|
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
|
|
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
|
|
"github.com/trufflesecurity/trufflehog/v3/pkg/sanitizer"
|
|
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
|
)
|
|
|
|
type Source struct {
|
|
name string
|
|
sourceId int64
|
|
jobId int64
|
|
verify bool
|
|
git *Git
|
|
aCtx context.Context
|
|
sources.Progress
|
|
conn *sourcespb.Git
|
|
}
|
|
|
|
type Git struct {
|
|
sourceType sourcespb.SourceType
|
|
sourceName string
|
|
sourceID int64
|
|
jobID int64
|
|
sourceMetadataFunc func(file, email, commit, timestamp, repository string, line int64) *source_metadatapb.MetaData
|
|
verify bool
|
|
concurrency *semaphore.Weighted
|
|
}
|
|
|
|
func NewGit(sourceType sourcespb.SourceType, jobID, sourceID int64, sourceName string, verify bool, concurrency int,
|
|
sourceMetadataFunc func(file, email, commit, timestamp, repository string, line int64) *source_metadatapb.MetaData,
|
|
) *Git {
|
|
return &Git{
|
|
sourceType: sourceType,
|
|
sourceName: sourceName,
|
|
sourceID: sourceID,
|
|
jobID: jobID,
|
|
sourceMetadataFunc: sourceMetadataFunc,
|
|
verify: verify,
|
|
concurrency: semaphore.NewWeighted(int64(concurrency)),
|
|
}
|
|
}
|
|
|
|
// Ensure the Source satisfies the interface at compile time.
|
|
var _ sources.Source = (*Source)(nil)
|
|
|
|
// Type returns the type of source.
|
|
// It is used for matching source types in configuration and job input.
|
|
func (s *Source) Type() sourcespb.SourceType {
|
|
return sourcespb.SourceType_SOURCE_TYPE_GIT
|
|
}
|
|
|
|
func (s *Source) SourceID() int64 {
|
|
return s.sourceId
|
|
}
|
|
|
|
func (s *Source) JobID() int64 {
|
|
return s.jobId
|
|
}
|
|
|
|
// Init returns an initialized GitHub source.
|
|
func (s *Source) Init(aCtx context.Context, name string, jobId, sourceId int64, verify bool, connection *anypb.Any, concurrency int) error {
|
|
|
|
s.aCtx = aCtx
|
|
s.name = name
|
|
s.sourceId = sourceId
|
|
s.jobId = jobId
|
|
s.verify = verify
|
|
|
|
var conn sourcespb.Git
|
|
if err := anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{}); err != nil {
|
|
return errors.WrapPrefix(err, "error unmarshalling connection", 0)
|
|
}
|
|
|
|
s.conn = &conn
|
|
|
|
if concurrency == 0 {
|
|
concurrency = runtime.NumCPU()
|
|
}
|
|
|
|
s.git = NewGit(s.Type(), s.jobId, s.sourceId, s.name, s.verify, concurrency,
|
|
func(file, email, commit, timestamp, repository string, line int64) *source_metadatapb.MetaData {
|
|
return &source_metadatapb.MetaData{
|
|
Data: &source_metadatapb.MetaData_Git{
|
|
Git: &source_metadatapb.Git{
|
|
Commit: sanitizer.UTF8(commit),
|
|
File: sanitizer.UTF8(file),
|
|
Email: sanitizer.UTF8(email),
|
|
Repository: sanitizer.UTF8(repository),
|
|
Timestamp: sanitizer.UTF8(timestamp),
|
|
Line: line,
|
|
},
|
|
},
|
|
}
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// Chunks emits chunks of bytes over a channel.
|
|
func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk) error {
|
|
switch cred := s.conn.GetCredential().(type) {
|
|
case *sourcespb.Git_BasicAuth:
|
|
user := cred.BasicAuth.Username
|
|
token := cred.BasicAuth.Password
|
|
|
|
for i, repoURI := range s.conn.Repositories {
|
|
s.SetProgressComplete(i, len(s.conn.Repositories), fmt.Sprintf("Repo: %s", repoURI), "")
|
|
if len(repoURI) == 0 {
|
|
continue
|
|
}
|
|
err := func(repoURI string) error {
|
|
path, repo, err := CloneRepoUsingToken(token, repoURI, user)
|
|
defer os.RemoveAll(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.git.ScanRepo(ctx, repo, path, NewScanOptions(), chunksChan)
|
|
}(repoURI)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case *sourcespb.Git_Unauthenticated:
|
|
for i, repoURI := range s.conn.Repositories {
|
|
s.SetProgressComplete(i, len(s.conn.Repositories), fmt.Sprintf("Repo: %s", repoURI), "")
|
|
if len(repoURI) == 0 {
|
|
continue
|
|
}
|
|
err := func(repoURI string) error {
|
|
path, repo, err := CloneRepoUsingUnauthenticated(repoURI)
|
|
defer os.RemoveAll(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.git.ScanRepo(ctx, repo, path, NewScanOptions(), chunksChan)
|
|
}(repoURI)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
default:
|
|
return errors.New("invalid connection type for git source")
|
|
}
|
|
|
|
for i, u := range s.conn.Directories {
|
|
s.SetProgressComplete(i, len(s.conn.Repositories), fmt.Sprintf("Repo: %s", u), "")
|
|
|
|
if len(u) == 0 {
|
|
continue
|
|
}
|
|
if !strings.HasSuffix(u, "git") {
|
|
// try paths instead of url
|
|
repo, err := RepoFromPath(u)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = func(repoPath string) error {
|
|
if strings.HasPrefix(repoPath, filepath.Join(os.TempDir(), "trufflehog")) {
|
|
defer os.RemoveAll(repoPath)
|
|
}
|
|
|
|
return s.git.ScanRepo(ctx, repo, repoPath, NewScanOptions(), chunksChan)
|
|
}(u)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func RepoFromPath(path string) (*git.Repository, error) {
|
|
return git.PlainOpen(path)
|
|
}
|
|
|
|
func CleanOnError(err *error, path string) {
|
|
if *err != nil {
|
|
os.RemoveAll(path)
|
|
}
|
|
}
|
|
|
|
func CloneRepo(userInfo *url.Userinfo, gitUrl string, args ...string) (clonePath string, repo *git.Repository, err error) {
|
|
if err = GitCmdCheck(); err != nil {
|
|
return
|
|
}
|
|
clonePath, err = ioutil.TempDir(os.TempDir(), "trufflehog")
|
|
if err != nil {
|
|
err = errors.New(err)
|
|
return
|
|
}
|
|
defer CleanOnError(&err, clonePath)
|
|
cloneURL, err := url.Parse(gitUrl)
|
|
if err != nil {
|
|
err = errors.WrapPrefix(err, "could not parse url", 0)
|
|
return
|
|
}
|
|
cloneURL.User = userInfo
|
|
|
|
gitArgs := []string{"clone", cloneURL.String(), clonePath}
|
|
gitArgs = append(gitArgs, args...)
|
|
cloneCmd := exec.Command("git", gitArgs...)
|
|
|
|
output, err := cloneCmd.CombinedOutput()
|
|
if err != nil {
|
|
err = errors.WrapPrefix(err, "error running 'git clone'", 0)
|
|
}
|
|
|
|
if cloneCmd.ProcessState == nil {
|
|
return "", nil, errors.New("clone command exited with no output")
|
|
}
|
|
if cloneCmd.ProcessState != nil && cloneCmd.ProcessState.ExitCode() != 0 {
|
|
safeUrl, err := stripPassword(gitUrl)
|
|
if err != nil {
|
|
log.WithError(err).Errorf("failed to strip credentials from git url")
|
|
}
|
|
log.WithField("exit_code", cloneCmd.ProcessState.ExitCode()).WithField("repo", safeUrl).WithField("output", string(output)).Errorf("failed to clone repo")
|
|
return "", nil, fmt.Errorf("could not clone repo: %s", safeUrl)
|
|
}
|
|
repo, err = git.PlainOpen(clonePath)
|
|
if err != nil {
|
|
err = errors.WrapPrefix(err, "could not open cloned repo", 0)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// CloneRepoUsingToken clones a repo using a provided token.
|
|
func CloneRepoUsingToken(token, gitUrl, user string, args ...string) (string, *git.Repository, error) {
|
|
userInfo := url.UserPassword(user, token)
|
|
return CloneRepo(userInfo, gitUrl, args...)
|
|
}
|
|
|
|
// CloneRepoUsingUnauthenticated clones a repo with no authentication required.
|
|
func CloneRepoUsingUnauthenticated(url string, args ...string) (string, *git.Repository, error) {
|
|
return CloneRepo(nil, url, args...)
|
|
}
|
|
|
|
func GitCmdCheck() error {
|
|
if errors.Is(exec.Command("git").Run(), exec.ErrNotFound) {
|
|
return fmt.Errorf("'git' command not found in $PATH. Make sure git is installed and included in $PATH")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Git) ScanCommits(repo *git.Repository, path string, scanOptions *ScanOptions, chunksChan chan *sources.Chunk) error {
|
|
if err := GitCmdCheck(); err != nil {
|
|
return err
|
|
}
|
|
if log.GetLevel() < log.DebugLevel {
|
|
zerolog.SetGlobalLevel(zerolog.Disabled)
|
|
}
|
|
|
|
// Errors returned on errChan aren't blocking, so just ignore them.
|
|
var gitLogArgs []string
|
|
if scanOptions.HeadHash != "" {
|
|
gitLogArgs = append(gitLogArgs, scanOptions.HeadHash)
|
|
}
|
|
commitChan := gitparse.BillParser(path)
|
|
// parser can return nil chan and nil error
|
|
// get the URL metadata for reporting (may be empty)
|
|
urlMetadata := getSafeRemoteURL(repo, "origin")
|
|
|
|
var depth int64
|
|
var reachedBase = false
|
|
// for file := range fileChan {
|
|
log.Debugf("Scanning repo")
|
|
for commit := range commitChan {
|
|
log.Debugf("Commit: %s", commit.Hash)
|
|
for _, diff := range commit.Diffs {
|
|
log.Debugf("Chunk: %s", diff.Content.Bytes())
|
|
log.WithField("commit", commit.Hash).WithField("file", diff.PathB).Trace("Scanning file from git")
|
|
if scanOptions.MaxDepth > 0 && depth >= scanOptions.MaxDepth {
|
|
log.Debugf("reached max depth")
|
|
break
|
|
}
|
|
depth++
|
|
if reachedBase && commit.Hash != scanOptions.BaseHash {
|
|
break
|
|
}
|
|
if len(scanOptions.BaseHash) > 0 {
|
|
if commit.Hash == scanOptions.BaseHash {
|
|
log.Debugf("Reached base commit. Finishing scanning files.")
|
|
reachedBase = true
|
|
}
|
|
}
|
|
/*
|
|
if !scanOptions.Filter.Pass(diff.PathB) {
|
|
continue
|
|
}
|
|
*/
|
|
|
|
fileName := diff.PathB
|
|
if fileName == "" {
|
|
continue
|
|
}
|
|
var email, hash, when string
|
|
email = commit.Author
|
|
hash = commit.Hash
|
|
when = commit.Date.GoString()
|
|
|
|
// Handle binary files by reading the entire file rather than using the diff.
|
|
if diff.IsBinary {
|
|
commitHash := plumbing.NewHash(hash)
|
|
metadata := s.sourceMetadataFunc(fileName, email, hash, when, urlMetadata, 0)
|
|
chunkSkel := &sources.Chunk{
|
|
SourceName: s.sourceName,
|
|
SourceID: s.sourceID,
|
|
SourceType: s.sourceType,
|
|
SourceMetadata: metadata,
|
|
Verify: s.verify,
|
|
}
|
|
if err := handleBinary(repo, chunksChan, chunkSkel, commitHash, fileName); err != nil {
|
|
log.WithError(err).Error("Error handling binary file")
|
|
}
|
|
continue
|
|
}
|
|
|
|
/*
|
|
for _, frag := range file.TextFragments {
|
|
var sb strings.Builder
|
|
newLineNumber := frag.NewPosition
|
|
for _, line := range frag.Lines {
|
|
if line.Op == gitdiff.OpAdd {
|
|
sb.WriteString(line.Line)
|
|
}
|
|
}
|
|
*/
|
|
|
|
metadata := s.sourceMetadataFunc(fileName, email, hash, when, urlMetadata, int64(diff.LineStart))
|
|
chunksChan <- &sources.Chunk{
|
|
SourceName: s.sourceName,
|
|
SourceID: s.sourceID,
|
|
SourceType: s.sourceType,
|
|
SourceMetadata: metadata,
|
|
Data: diff.Content.Bytes(),
|
|
Verify: s.verify,
|
|
}
|
|
//}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Git) ScanUnstaged(repo *git.Repository, scanOptions *ScanOptions, chunksChan chan *sources.Chunk) error {
|
|
// get the URL metadata for reporting (may be empty)
|
|
urlMetadata := getSafeRemoteURL(repo, "origin")
|
|
|
|
// Also scan any unstaged changes in the working tree of the repo
|
|
_, err := repo.Head()
|
|
if err == nil || err == plumbing.ErrReferenceNotFound {
|
|
wt, err := repo.Worktree()
|
|
if err != nil {
|
|
log.WithError(err).Error("error obtaining repo worktree")
|
|
return err
|
|
}
|
|
|
|
status, err := wt.Status()
|
|
if err != nil {
|
|
log.WithError(err).Error("error obtaining worktree status")
|
|
return err
|
|
}
|
|
for fh := range status {
|
|
/*
|
|
if !scanOptions.Filter.Pass(fh) {
|
|
continue
|
|
}
|
|
*/
|
|
metadata := s.sourceMetadataFunc(
|
|
fh, "unstaged", "unstaged", time.Now().String(), urlMetadata, 0,
|
|
)
|
|
|
|
fileBuf := bytes.NewBuffer(nil)
|
|
fileHandle, err := wt.Filesystem.Open(fh)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
defer fileHandle.Close()
|
|
_, err = io.Copy(fileBuf, fileHandle)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
chunksChan <- &sources.Chunk{
|
|
SourceType: s.sourceType,
|
|
SourceName: s.sourceName,
|
|
SourceID: s.sourceID,
|
|
Data: fileBuf.Bytes(),
|
|
SourceMetadata: metadata,
|
|
Verify: s.verify,
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Git) ScanRepo(_ context.Context, repo *git.Repository, repoPath string, scanOptions *ScanOptions, chunksChan chan *sources.Chunk) error {
|
|
start := time.Now().UnixNano()
|
|
if err := s.ScanCommits(repo, repoPath, scanOptions, chunksChan); err != nil {
|
|
return err
|
|
}
|
|
if err := s.ScanUnstaged(repo, scanOptions, chunksChan); err != nil {
|
|
// https://github.com/src-d/go-git/issues/879
|
|
if strings.Contains(err.Error(), "object not found") {
|
|
log.WithError(err).Error("known issue: probably caused by a dangling reference in the repo")
|
|
} else {
|
|
return errors.New(err)
|
|
}
|
|
return err
|
|
}
|
|
scanTime := time.Now().UnixNano() - start
|
|
log.Debugf("Scanning complete. Scan time: %f", time.Duration(scanTime).Seconds())
|
|
return nil
|
|
}
|
|
|
|
// GenerateLink crafts a link to the specific file from a commit. This works in most major git providers (Github/Gitlab)
|
|
func GenerateLink(repo, commit, file string) string {
|
|
// bitbucket links are commits not commit...
|
|
if strings.Contains(repo, "bitbucket.org/") {
|
|
return repo[:len(repo)-4] + "/commits/" + commit
|
|
}
|
|
link := repo[:len(repo)-4] + "/blob/" + commit + "/" + file
|
|
|
|
if file == "" {
|
|
link = repo[:len(repo)-4] + "/commit/" + commit
|
|
}
|
|
return link
|
|
}
|
|
|
|
func stripPassword(u string) (string, error) {
|
|
if strings.HasPrefix(u, "git@") {
|
|
return u, nil
|
|
}
|
|
|
|
repoURL, err := url.Parse(u)
|
|
if err != nil {
|
|
return "", errors.WrapPrefix(err, "repo remote cannot be sanitized as URI", 0)
|
|
}
|
|
|
|
repoURL.User = nil
|
|
|
|
return repoURL.String(), nil
|
|
}
|
|
|
|
// TryAdditionalBaseRefs looks for additional possible base refs for a repo and returns a hash if found.
|
|
func TryAdditionalBaseRefs(repo *git.Repository, base string) (*plumbing.Hash, error) {
|
|
revisionPrefixes := []string{
|
|
"",
|
|
"refs/heads/",
|
|
"refs/remotes/origin/",
|
|
}
|
|
for _, prefix := range revisionPrefixes {
|
|
outHash, err := repo.ResolveRevision(plumbing.Revision(prefix + base))
|
|
if err == plumbing.ErrReferenceNotFound {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return outHash, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("no base refs succeeded for base: %q", base)
|
|
}
|
|
|
|
// PrepareRepoSinceCommit clones a repo starting at the given commitHash and returns the cloned repo path.
|
|
func PrepareRepoSinceCommit(uriString, commitHash string) (string, bool, error) {
|
|
if commitHash == "" {
|
|
return PrepareRepo(uriString)
|
|
}
|
|
// TODO: refactor with PrepareRepo to remove duplicated logic
|
|
|
|
// The git CLI doesn't have an option to shallow clone starting at a commit
|
|
// hash, but it does have an option to shallow clone since a timestamp. If
|
|
// the uriString is github.com, then we query the API for the timestamp of the
|
|
// hash and use that to clone.
|
|
|
|
uri, err := url.Parse(uriString)
|
|
if err != nil {
|
|
return "", false, fmt.Errorf("unable to parse Git URI: %s", err)
|
|
}
|
|
|
|
if uri.Scheme == "file" || uri.Host != "github.com" {
|
|
return PrepareRepo(uriString)
|
|
}
|
|
|
|
uriPath := strings.TrimPrefix(uri.Path, "/")
|
|
owner, repoName, found := strings.Cut(uriPath, "/")
|
|
if !found {
|
|
return PrepareRepo(uriString)
|
|
}
|
|
|
|
client := github.NewClient(nil)
|
|
if token := os.Getenv("GITHUB_TOKEN"); token != "" {
|
|
ts := oauth2.StaticTokenSource(
|
|
&oauth2.Token{AccessToken: token},
|
|
)
|
|
tc := oauth2.NewClient(context.TODO(), ts)
|
|
client = github.NewClient(tc)
|
|
}
|
|
|
|
commit, _, err := client.Git.GetCommit(context.Background(), owner, repoName, commitHash)
|
|
if err != nil {
|
|
return PrepareRepo(uriString)
|
|
}
|
|
var timestamp string
|
|
{
|
|
author := commit.GetAuthor()
|
|
if author == nil {
|
|
return PrepareRepo(uriString)
|
|
}
|
|
timestamp = author.GetDate().Format(time.RFC3339)
|
|
}
|
|
|
|
remotePath := uri.String()
|
|
var path string
|
|
switch {
|
|
case uri.User != nil:
|
|
log.Debugf("Cloning remote Git repo with authentication")
|
|
password, ok := uri.User.Password()
|
|
if !ok {
|
|
return "", true, fmt.Errorf("password must be included in Git repo URL when username is provided")
|
|
}
|
|
path, _, err = CloneRepoUsingToken(password, remotePath, uri.User.Username(), "--shallow-since", timestamp)
|
|
if err != nil {
|
|
return path, true, fmt.Errorf("failed to clone authenticated Git repo (%s): %s", remotePath, err)
|
|
}
|
|
default:
|
|
log.Debugf("Cloning remote Git repo without authentication")
|
|
path, _, err = CloneRepoUsingUnauthenticated(remotePath, "--shallow-since", timestamp)
|
|
if err != nil {
|
|
return path, true, fmt.Errorf("failed to clone unauthenticated Git repo (%s): %s", remotePath, err)
|
|
}
|
|
}
|
|
log.Debugf("Git repo local path: %s", path)
|
|
return path, true, nil
|
|
}
|
|
|
|
// PrepareRepo clones a repo if possible and returns the cloned repo path.
|
|
func PrepareRepo(uriString string) (string, bool, error) {
|
|
var path string
|
|
uri, err := url.Parse(uriString)
|
|
if err != nil {
|
|
return "", false, fmt.Errorf("unable to parse Git URI: %s", err)
|
|
}
|
|
|
|
remote := false
|
|
switch uri.Scheme {
|
|
case "file":
|
|
path = fmt.Sprintf("%s%s", uri.Host, uri.Path)
|
|
case "http", "https":
|
|
remotePath := uri.String()
|
|
remote = true
|
|
switch {
|
|
case uri.User != nil:
|
|
log.Debugf("Cloning remote Git repo with authentication")
|
|
password, ok := uri.User.Password()
|
|
if !ok {
|
|
return "", remote, fmt.Errorf("password must be included in Git repo URL when username is provided")
|
|
}
|
|
path, _, err = CloneRepoUsingToken(password, remotePath, uri.User.Username())
|
|
if err != nil {
|
|
return path, remote, fmt.Errorf("failed to clone authenticated Git repo (%s): %s", remotePath, err)
|
|
}
|
|
default:
|
|
log.Debugf("Cloning remote Git repo without authentication")
|
|
path, _, err = CloneRepoUsingUnauthenticated(remotePath)
|
|
if err != nil {
|
|
return path, remote, fmt.Errorf("failed to clone unauthenticated Git repo (%s): %s", remotePath, err)
|
|
}
|
|
}
|
|
default:
|
|
return "", remote, fmt.Errorf("unsupported Git URI: %s", uriString)
|
|
}
|
|
log.Debugf("Git repo local path: %s", path)
|
|
return path, remote, nil
|
|
}
|
|
|
|
// getSafeRemoteURL is a helper function that will attempt to get a safe URL first
|
|
// from the preferred remote name, falling back to the first remote name
|
|
// available, or an empty string if there are no remotes.
|
|
func getSafeRemoteURL(repo *git.Repository, preferred string) string {
|
|
remote, err := repo.Remote(preferred)
|
|
if err != nil {
|
|
var remotes []*git.Remote
|
|
if remotes, err = repo.Remotes(); err != nil {
|
|
return ""
|
|
}
|
|
if len(remotes) == 0 {
|
|
return ""
|
|
}
|
|
remote = remotes[0]
|
|
}
|
|
// URLs is guaranteed to be non-empty
|
|
safeURL, err := stripPassword(remote.Config().URLs[0])
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return safeURL
|
|
}
|
|
|
|
func handleBinary(repo *git.Repository, chunksChan chan *sources.Chunk, chunkSkel *sources.Chunk, commitHash plumbing.Hash, path string) error {
|
|
log.WithField("path", path).Trace("Binary file found in repository.")
|
|
commit, err := repo.CommitObject(commitHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Error(path)
|
|
file, err := commit.File(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fileReader, err := file.Reader()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer fileReader.Close()
|
|
|
|
reader, err := diskbufferreader.New(fileReader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if handlers.HandleFile(reader, chunkSkel, chunksChan) {
|
|
return nil
|
|
}
|
|
|
|
log.WithField("path", path).Trace("Binary file is not recognized by file handlers. Chunking raw.")
|
|
if err := reader.Reset(); err != nil {
|
|
return err
|
|
}
|
|
reader.Stop()
|
|
|
|
for chunkData := range common.ChunkReader(reader) {
|
|
chunk := *chunkSkel
|
|
chunk.Data = chunkData
|
|
chunksChan <- &chunk
|
|
}
|
|
|
|
return nil
|
|
}
|