|
@@ -2,6 +2,7 @@ package audit
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
"bytes"
|
|
"bytes"
|
|
|
|
|
+ "context"
|
|
|
"crypto/md5"
|
|
"crypto/md5"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"io"
|
|
"io"
|
|
@@ -39,6 +40,10 @@ type Repo struct {
|
|
|
// for those repo audits.
|
|
// for those repo audits.
|
|
|
config config.Config
|
|
config config.Config
|
|
|
|
|
|
|
|
|
|
+ // ctx is used to signal timeouts to running goroutines
|
|
|
|
|
+ ctx context.Context
|
|
|
|
|
+ cancel context.CancelFunc
|
|
|
|
|
+
|
|
|
Name string
|
|
Name string
|
|
|
Manager *manager.Manager
|
|
Manager *manager.Manager
|
|
|
}
|
|
}
|
|
@@ -48,6 +53,7 @@ func NewRepo(m *manager.Manager) *Repo {
|
|
|
return &Repo{
|
|
return &Repo{
|
|
|
Manager: m,
|
|
Manager: m,
|
|
|
config: m.Config,
|
|
config: m.Config,
|
|
|
|
|
+ ctx: context.Background(),
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -93,6 +99,10 @@ func (repo *Repo) AuditUncommitted() error {
|
|
|
repo.config = cfg
|
|
repo.config = cfg
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if err := repo.setupTimeout(); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
auditTimeStart := time.Now()
|
|
auditTimeStart := time.Now()
|
|
|
|
|
|
|
|
r, err := repo.Head()
|
|
r, err := repo.Head()
|
|
@@ -204,6 +214,10 @@ func (repo *Repo) AuditUncommitted() error {
|
|
|
// git repo. Options that can change the behavior of this function include: --commit, --depth, --branch.
|
|
// git repo. Options that can change the behavior of this function include: --commit, --depth, --branch.
|
|
|
// See options/options.go for an explanation on these options.
|
|
// See options/options.go for an explanation on these options.
|
|
|
func (repo *Repo) Audit() error {
|
|
func (repo *Repo) Audit() error {
|
|
|
|
|
+ if err := repo.setupTimeout(); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if repo.Repository == nil {
|
|
if repo.Repository == nil {
|
|
|
return fmt.Errorf("%s repo is empty", repo.Name)
|
|
return fmt.Errorf("%s repo is empty", repo.Name)
|
|
|
}
|
|
}
|
|
@@ -247,7 +261,7 @@ func (repo *Repo) Audit() error {
|
|
|
semaphore := make(chan bool, howManyThreads(repo.Manager.Opts.Threads))
|
|
semaphore := make(chan bool, howManyThreads(repo.Manager.Opts.Threads))
|
|
|
wg := sync.WaitGroup{}
|
|
wg := sync.WaitGroup{}
|
|
|
err = cIter.ForEach(func(c *object.Commit) error {
|
|
err = cIter.ForEach(func(c *object.Commit) error {
|
|
|
- if c == nil || c.Hash.String() == repo.Manager.Opts.CommitTo {
|
|
|
|
|
|
|
+ if c == nil || c.Hash.String() == repo.Manager.Opts.CommitTo || repo.timeoutReached() {
|
|
|
return storer.ErrStop
|
|
return storer.ErrStop
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -274,6 +288,9 @@ func (repo *Repo) Audit() error {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
}()
|
|
}()
|
|
|
|
|
+ if repo.timeoutReached() {
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }
|
|
|
start := time.Now()
|
|
start := time.Now()
|
|
|
patch, err := c.Patch(parent)
|
|
patch, err := c.Patch(parent)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -344,3 +361,34 @@ func (repo *Repo) loadRepoConfig() (config.Config, error) {
|
|
|
_, err = toml.DecodeReader(f, &tomlLoader)
|
|
_, err = toml.DecodeReader(f, &tomlLoader)
|
|
|
return tomlLoader.Parse()
|
|
return tomlLoader.Parse()
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// timeoutReached returns true if the timeout deadline has been met. This function should be used
|
|
|
|
|
+// at the top of loops and before potentially long running goroutines (like checking inefficient regexes)
|
|
|
|
|
+func (repo *Repo) timeoutReached() bool {
|
|
|
|
|
+ if repo.ctx.Err() == context.DeadlineExceeded {
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ return false
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// setupTimeout parses the --timeout option and assigns a context with timeout to the manager
|
|
|
|
|
+// which will exit early if the timeout has been met.
|
|
|
|
|
+func (repo *Repo) setupTimeout() error {
|
|
|
|
|
+ if repo.Manager.Opts.Timeout == "" {
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }
|
|
|
|
|
+ timeout, err := time.ParseDuration(repo.Manager.Opts.Timeout)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ repo.ctx, repo.cancel = context.WithTimeout(context.Background(), timeout)
|
|
|
|
|
+
|
|
|
|
|
+ go func() {
|
|
|
|
|
+ select {
|
|
|
|
|
+ case <-repo.ctx.Done():
|
|
|
|
|
+ log.Warnf("Timeout deadline exceeded: %s", timeout.String())
|
|
|
|
|
+ }
|
|
|
|
|
+ }()
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|