Run VCS commands with a timeout
Fixes issue #268.
diff --git a/gosrc/vcs.go b/gosrc/vcs.go
index 9bc5fd0..95e7c4a 100644
--- a/gosrc/vcs.go
+++ b/gosrc/vcs.go
@@ -10,6 +10,7 @@
import (
"bytes"
+ "errors"
"io/ioutil"
"log"
"net/http"
@@ -19,6 +20,7 @@
"path/filepath"
"regexp"
"strings"
+ "time"
)
func init() {
@@ -30,6 +32,13 @@
getVCSDirFn = getVCSDir
}
+const (
+ lsRemoteTimeout = 5 * time.Minute
+ cloneTimeout = 10 * time.Minute
+ fetchTimeout = 5 * time.Minute
+ checkoutTimeout = 1 * time.Minute
+)
+
// Store temporary data in this directory.
var TempDir = filepath.Join(os.TempDir(), "gddo")
@@ -115,7 +124,7 @@
cmd := exec.Command("git", "ls-remote", "--heads", "--tags", schemes[i]+"://"+repo+".git")
log.Println(strings.Join(cmd.Args, " "))
var err error
- p, err = cmd.Output()
+ p, err = outputWithTimeout(cmd, lsRemoteTimeout)
if err == nil {
scheme = schemes[i]
break
@@ -151,7 +160,7 @@
}
cmd := exec.Command("git", "clone", scheme+"://"+repo+".git", dir)
log.Println(strings.Join(cmd.Args, " "))
- if err := cmd.Run(); err != nil {
+ if err := runWithTimeout(cmd, cloneTimeout); err != nil {
return "", "", err
}
case string(bytes.TrimRight(p, "\n")) == commit:
@@ -160,14 +169,14 @@
cmd := exec.Command("git", "fetch")
log.Println(strings.Join(cmd.Args, " "))
cmd.Dir = dir
- if err := cmd.Run(); err != nil {
+ if err := runWithTimeout(cmd, fetchTimeout); err != nil {
return "", "", err
}
}
cmd := exec.Command("git", "checkout", "--detach", "--force", commit)
cmd.Dir = dir
- if err := cmd.Run(); err != nil {
+ if err := runWithTimeout(cmd, checkoutTimeout); err != nil {
return "", "", err
}
@@ -205,14 +214,14 @@
}
cmd := exec.Command("svn", "checkout", scheme+"://"+repo, "-r", revno, dir)
log.Println(strings.Join(cmd.Args, " "))
- if err := cmd.Run(); err != nil {
+ if err := runWithTimeout(cmd, cloneTimeout); err != nil {
return "", "", err
}
case localRevno != revno:
cmd := exec.Command("svn", "update", "-r", revno)
log.Println(strings.Join(cmd.Args, " "))
cmd.Dir = dir
- if err := cmd.Run(); err != nil {
+ if err := runWithTimeout(cmd, fetchTimeout); err != nil {
return "", "", err
}
}
@@ -225,7 +234,7 @@
func getSVNRevision(target string) (string, error) {
cmd := exec.Command("svn", "info", target)
log.Println(strings.Join(cmd.Args, " "))
- out, err := cmd.Output()
+ out, err := outputWithTimeout(cmd, lsRemoteTimeout)
if err != nil {
return "", err
}
@@ -319,3 +328,22 @@
Files: files,
}, nil
}
+
+func runWithTimeout(cmd *exec.Cmd, timeout time.Duration) error {
+ if err := cmd.Start(); err != nil {
+ return err
+ }
+ t := time.AfterFunc(timeout, func() { cmd.Process.Kill() })
+ defer t.Stop()
+ return cmd.Wait()
+}
+
+func outputWithTimeout(cmd *exec.Cmd, timeout time.Duration) ([]byte, error) {
+ if cmd.Stdout != nil {
+ return nil, errors.New("exec: Stdout already set")
+ }
+ var b bytes.Buffer
+ cmd.Stdout = &b
+ err := runWithTimeout(cmd, timeout)
+ return b.Bytes(), err
+}