Add clone configuration, with some documentation.

Change-Id: Ib25064e2f54b86a8fe147cbfb61db423fcef3e70
diff --git a/README.md b/README.md
index 7b4d2ef..daeccd4 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,32 @@
 To create a workspace "ws" corresponding to the manifest in m.xml.
 
 
+Configuring
+===========
+
+The FUSE file system clones repositories on-demand. You can avoid cloning
+altogether for repositories you know you don't need.  This is configured through
+a JSON file.
+
+For example, if you work on Android, and build on a Linux machine, you will
+never need the Darwin related prebuilts. You can avoid a costly clone for those
+by doing:
+
+    {"Repo": ".*darwin.*", "Clone": false}
+
+Similarly, the build system system will read files (typically called '*.mk')
+across the entire tree. When any .mk file is opened, this should not trigger a
+clone. This is achieved with the following entry
+
+    {"File": ".*mk$", "Clone": false}
+
+Together, the following `config.json` file is a good start for working on
+android:
+
+    [{"Repo": ".*darwin.*", "Clone": false},
+     {"File": ".*mk$", "Clone": true}]
+
+
 DISCLAIMER
 ==========
 
diff --git a/cmd/gitfs-manifestfs/main.go b/cmd/gitfs-manifestfs/main.go
index 24d9e1d..e7ce6ba 100644
--- a/cmd/gitfs-manifestfs/main.go
+++ b/cmd/gitfs-manifestfs/main.go
@@ -31,6 +31,7 @@
 	gitilesURL := flag.String("gitiles", "", "gitiles URL. If unset, derive from manifest location.")
 	cacheDir := flag.String("cache", "", "cache dir")
 	debug := flag.Bool("debug", false, "print debug info")
+	config := flag.String("config", "", "JSON file configuring what repositories should be cloned.")
 	flag.Parse()
 
 	if *manifestPath == "" {
@@ -67,6 +68,17 @@
 		Manifest: mf,
 	}
 
+	if *config != "" {
+		configContents, err := ioutil.ReadFile(*config)
+		if err != nil {
+			log.Fatal(err)
+		}
+		opts.RepoCloneOption, opts.FileCloneOption, err = fs.ReadConfig(configContents)
+		if err != nil {
+			log.Fatal(err)
+		}
+	}
+
 	root, err := fs.NewManifestFS(service, cache, opts)
 	if err != nil {
 		log.Fatalf("NewManifestFS: %v", err)
diff --git a/cmd/gitfs-multifs/main.go b/cmd/gitfs-multifs/main.go
index f5987ef..7d4b1ef 100644
--- a/cmd/gitfs-multifs/main.go
+++ b/cmd/gitfs-multifs/main.go
@@ -16,6 +16,7 @@
 
 import (
 	"flag"
+	"io/ioutil"
 	"log"
 	"time"
 
@@ -29,6 +30,7 @@
 	gitilesURL := flag.String("gitiles", "", "gitiles URL. If unset, derive from manifest location.")
 	cacheDir := flag.String("cache", "", "cache dir")
 	debug := flag.Bool("debug", false, "print debug info")
+	config := flag.String("config", "", "JSON file configuring what repositories should be cloned.")
 	flag.Parse()
 
 	if *cacheDir == "" {
@@ -54,6 +56,16 @@
 	}
 
 	opts := fs.MultiFSOptions{}
+	if *config != "" {
+		configContents, err := ioutil.ReadFile(*config)
+		if err != nil {
+			log.Fatal(err)
+		}
+		opts.RepoCloneOption, opts.FileCloneOption, err = fs.ReadConfig(configContents)
+		if err != nil {
+			log.Fatal(err)
+		}
+	}
 
 	root := fs.NewMultiFS(service, cache, opts)
 	if err != nil {
diff --git a/fs/config.go b/fs/config.go
new file mode 100644
index 0000000..ab49e66
--- /dev/null
+++ b/fs/config.go
@@ -0,0 +1,58 @@
+// Copyright 2016 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fs
+
+import (
+	"encoding/json"
+	"fmt"
+	"regexp"
+)
+
+type configEntry struct {
+	File  string
+	Repo  string
+	Clone bool
+}
+
+// Reads a JSON file containing clone options
+func ReadConfig(contents []byte) (repo []CloneOption, file []CloneOption, err error) {
+	var cfg []configEntry
+	if err := json.Unmarshal(contents, &cfg); err != nil {
+		return nil, nil, err
+	}
+
+	for _, e := range cfg {
+		if e.File != "" {
+			re, err := regexp.Compile(e.File)
+			if err != nil {
+				return nil, nil, err
+			}
+
+			file = append(file, CloneOption{re, e.Clone})
+		} else if e.Repo != "" {
+			re, err := regexp.Compile(e.Repo)
+			if err != nil {
+				return nil, nil, err
+			}
+
+			repo = append(repo, CloneOption{re, e.Clone})
+
+		} else {
+			return nil, nil, fmt.Errorf("must set either File or Repo")
+		}
+	}
+
+	return repo, file, nil
+}
diff --git a/fs/config_test.go b/fs/config_test.go
new file mode 100644
index 0000000..9b98ba6
--- /dev/null
+++ b/fs/config_test.go
@@ -0,0 +1,26 @@
+// Copyright 2016 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fs
+
+import "testing"
+
+func TestReadConfig(t *testing.T) {
+	in := `[{ "File": ".*\\.mk$", "Clone": false},
+ { "Repo": "darwin", "Clone": false}]`
+	_, _, err := ReadConfig([]byte(in))
+	if err != nil {
+		t.Fatalf("ReadConfig: %v", err)
+	}
+}
diff --git a/fs/gitilesfs.go b/fs/gitilesfs.go
index ffe2c2a..df1452b 100644
--- a/fs/gitilesfs.go
+++ b/fs/gitilesfs.go
@@ -240,10 +240,12 @@
 
 		// Determine if file should trigger a clone.
 		clone := r.opts.CloneURL != ""
-		for _, e := range r.opts.CloneOption {
-			if e.RE.FindString(p) != "" {
-				clone = e.Clone
-				break
+		if clone {
+			for _, e := range r.opts.CloneOption {
+				if e.RE.FindString(p) != "" {
+					clone = e.Clone
+					break
+				}
 			}
 		}