Update git repositories with recurring fetch.

Tested:
  ran on an existing installation; verified that fetch commands ran
  without errors.

Change-Id: Ia4966977b7ffab5c676d8e4fbfe6c34c644ba688
diff --git a/cache/cache.go b/cache/cache.go
index adb3cb0..3985bec 100644
--- a/cache/cache.go
+++ b/cache/cache.go
@@ -19,6 +19,7 @@
 import (
 	"os"
 	"path/filepath"
+	"time"
 )
 
 // Cache combines a blob, tree and git repo cache.
@@ -28,7 +29,18 @@
 	Blob *CAS
 }
 
-func NewCache(d string) (*Cache, error) {
+// Options defines configurable options for the different caches.
+type Options struct {
+	// FetchFrequency controls how often we run git fetch on the
+	// locally cached git repositories.
+	FetchFrequency time.Duration
+}
+
+func NewCache(d string, opts Options) (*Cache, error) {
+	if opts.FetchFrequency == 0 {
+		opts.FetchFrequency = 12 * time.Hour
+	}
+
 	d, err := filepath.Abs(d)
 	if err != nil {
 		return nil, err
@@ -37,7 +49,7 @@
 		return nil, err
 	}
 
-	g, err := newGitCache(filepath.Join(d, "git"))
+	g, err := newGitCache(filepath.Join(d, "git"), opts)
 	if err != nil {
 		return nil, err
 	}
diff --git a/cache/gitcache.go b/cache/gitcache.go
index e1e2604..4ef2dbd 100644
--- a/cache/gitcache.go
+++ b/cache/gitcache.go
@@ -39,7 +39,7 @@
 }
 
 // newGitCache constructs a gitCache object.
-func newGitCache(baseDir string) (*gitCache, error) {
+func newGitCache(baseDir string, opts Options) (*gitCache, error) {
 	c := gitCache{
 		dir:    filepath.Join(baseDir),
 		logDir: filepath.Join(baseDir, "slothfs-logs"),
@@ -50,9 +50,23 @@
 	if err := os.MkdirAll(c.dir, 0700); err != nil {
 		return nil, err
 	}
+	if opts.FetchFrequency > 0 {
+		go c.recurringFetch(opts.FetchFrequency)
+	}
+
 	return &c, nil
 }
 
+func (c *gitCache) recurringFetch(freq time.Duration) {
+	ticker := time.NewTicker(freq)
+	for {
+		if err := c.FetchAll(); err != nil {
+			log.Printf("FetchAll: %v", err)
+		}
+		<-ticker.C
+	}
+}
+
 // logfile returns a logfile open for writing with a unique name.
 func (c *gitCache) logfile() (*os.File, error) {
 	nm := fmt.Sprintf("%s/git.%s.log", c.logDir, time.Now().Format(time.RFC3339Nano))
@@ -61,15 +75,33 @@
 }
 
 // Fetch updates the local clone of the given repository.
-func (c *gitCache) Fetch(url string) error {
-	path, err := c.gitPath(url)
-	if err != nil {
+func (c *gitCache) Fetch(dir string) error {
+	if err := c.runGit(c.dir, "--git-dir="+dir, "fetch", "origin"); err != nil {
 		return err
 	}
-	if err := c.runGit(c.dir, "--git-dir="+path, "fetch", "origin"); err != nil {
+
+	return nil
+}
+
+// FetchAll finds all known repos and runs git-fetch on them.
+func (c *gitCache) FetchAll() error {
+	var dirs []string
+	if err := filepath.Walk(c.dir, func(n string, fi os.FileInfo, err error) error {
+		if fi.IsDir() && strings.HasSuffix(n, ".git") {
+			dirs = append(dirs, n)
+			return filepath.SkipDir
+		}
+		return nil
+	}); err != nil {
 		return err
 	}
 
+	for _, d := range dirs {
+		if err := c.Fetch(d); err != nil {
+			return fmt.Errorf("fetch %s: %v", d, err)
+		}
+	}
+
 	return nil
 }
 
diff --git a/cache/gitcache_test.go b/cache/gitcache_test.go
index 8d4a99f..a2acb0a 100644
--- a/cache/gitcache_test.go
+++ b/cache/gitcache_test.go
@@ -34,7 +34,7 @@
 	}
 	defer os.RemoveAll(dir)
 
-	cache, err := newGitCache(dir)
+	cache, err := newGitCache(dir, Options{})
 	if err != nil {
 		t.Fatalf("newGitCache(%s): %v", dir, err)
 	}
diff --git a/cmd/slothfs-gitilesfs/main.go b/cmd/slothfs-gitilesfs/main.go
index 85ad920..ebe6ca7 100644
--- a/cmd/slothfs-gitilesfs/main.go
+++ b/cmd/slothfs-gitilesfs/main.go
@@ -43,7 +43,7 @@
 	}
 
 	mntDir := flag.Arg(0)
-	cache, err := cache.NewCache(*cacheDir)
+	cache, err := cache.NewCache(*cacheDir, cache.Options{})
 	if err != nil {
 		log.Printf("NewCache: %v", err)
 	}
diff --git a/cmd/slothfs-manifestfs/main.go b/cmd/slothfs-manifestfs/main.go
index c3f57d5..dfd1a0e 100644
--- a/cmd/slothfs-manifestfs/main.go
+++ b/cmd/slothfs-manifestfs/main.go
@@ -52,7 +52,7 @@
 	}
 	mntDir := flag.Arg(0)
 
-	cache, err := cache.NewCache(*cacheDir)
+	cache, err := cache.NewCache(*cacheDir, cache.Options{})
 	if err != nil {
 		log.Printf("NewCache: %v", err)
 	}
diff --git a/cmd/slothfs-multifs/main.go b/cmd/slothfs-multifs/main.go
index 70912b8..c8b8e24 100644
--- a/cmd/slothfs-multifs/main.go
+++ b/cmd/slothfs-multifs/main.go
@@ -47,7 +47,7 @@
 	}
 	mntDir := flag.Arg(0)
 
-	cache, err := cache.NewCache(*cacheDir)
+	cache, err := cache.NewCache(*cacheDir, cache.Options{})
 	if err != nil {
 		log.Printf("NewCache: %v", err)
 	}
diff --git a/fs/gitilesfs_test.go b/fs/gitilesfs_test.go
index 0bbae9d..b004a37 100644
--- a/fs/gitilesfs_test.go
+++ b/fs/gitilesfs_test.go
@@ -565,7 +565,7 @@
 
 	fixture := &testFixture{dir: d}
 
-	fixture.cache, err = cache.NewCache(filepath.Join(d, "/cache"))
+	fixture.cache, err = cache.NewCache(filepath.Join(d, "/cache"), cache.Options{})
 	if err != nil {
 		return nil, err
 	}