Add support for <overrides>

The first manifest file (e.g., .jiri_manifest) can contain an
<overrides> tag that replaces project definitions from the <projects>
tag (including from transitively imported manifests.

IN-682 #done
Change-Id: I8e97e329333355eff2a34851a64917e91f2cc487
diff --git a/manifest.md b/manifest.md
index 2d9a7d6..f3581d0 100644
--- a/manifest.md
+++ b/manifest.md
@@ -2,7 +2,8 @@
 
 Jiri manifest files describe the set of projects that get synced when running "jiri update".
 
-The first manifest file that jiri reads is in [root]/.jiri\_manifest.  This manifest **must** exist for the jiri tool to work.
+The first manifest file that jiri reads is in [root]/.jiri\_manifest.  This root manifest
+**must** exist for the jiri tool to work.
 
 Usually the manifest in [root]/.jiri\_manifest will import other manifests from remote repositories via &lt;import> tags, but it can contain its own list of projects as well.
 
@@ -29,6 +30,9 @@
     />
     ...
   </projects>
+  <overrides>
+    <project ... />
+  </overrides>
   <hooks>
     <hook name="update"
           project="mojo/public"
@@ -70,6 +74,10 @@
 
 * githooks (optional) - The path (relative to [root]) of a directory containing git hooks that will be installed in the projects .git/hooks directory during each update.
 
+The projects in the &lt;overrides> tag replace existing projects defined by in the &lt;projects> tag (and from transitively imported lt;projects> tags).
+Only the root manifest can contain overrides and repositories referenced using the
+&lt;import> tag (including from transitive imports) cannot be overridden.
+
 The &lt;hook> tag describes the hooks that must be executed after every 'jiri update' They are configured via the following attributes:
 
 * name (required) - The name of the of the hook to identify it
diff --git a/project/loader.go b/project/loader.go
index e2f916c..dd24649 100644
--- a/project/loader.go
+++ b/project/loader.go
@@ -308,6 +308,28 @@
 		ld.Projects[key] = project
 	}
 
+	// Apply overrides.
+	if parentImport == "" {
+		for _, override := range m.Overrides {
+			// Make paths absolute by prepending <root>.
+			override.absolutizePaths(filepath.Join(jirix.Root, root))
+			override.Name = filepath.Join(root, override.Name)
+			key := override.Key()
+
+			if _, ok := ld.importProjects[key]; ok {
+				return fmt.Errorf("cannot override project %q because the project contains an imported manifest", key)
+			}
+
+			if _, ok := ld.Projects[key]; !ok {
+				return fmt.Errorf("failed to override %q found in %q. Original project not found in manifest", key, shortFileName(jirix.Root, repoPath, file, ref))
+			}
+
+			ld.Projects[key] = override
+		}
+	} else if len(m.Overrides) != 0 {
+		return fmt.Errorf("manifest %q contains overrides but was imported by %q. Overrides are allowed only in the root manifest.", shortFileName(jirix.Root, repoPath, file, ref), parentImport)
+	}
+
 	for _, hook := range m.Hooks {
 		if hook.ActionPath == "" {
 			return fmt.Errorf("invalid hook %q for project %q. Please make sure you are importing project %q and this hook is in the manifest which directly/indirectly imports that project.", hook.Name, hook.ProjectName, hook.ProjectName)
diff --git a/project/manifest.go b/project/manifest.go
index ade2c96..d86a482 100644
--- a/project/manifest.go
+++ b/project/manifest.go
@@ -30,6 +30,7 @@
 	Imports      []Import      `xml:"imports>import"`
 	LocalImports []LocalImport `xml:"imports>localimport"`
 	Projects     []Project     `xml:"projects>project"`
+	Overrides    []Project     `xml:"overrides>project"`
 	Hooks        []Hook        `xml:"hooks>hook"`
 	XMLName      struct{}      `xml:"manifest"`
 }
@@ -73,6 +74,7 @@
 	newlineBytes       = []byte("\n")
 	emptyImportsBytes  = []byte("\n  <imports></imports>\n")
 	emptyProjectsBytes = []byte("\n  <projects></projects>\n")
+	emptyOverridesBytes = []byte("\n  <overrides></overrides>\n")
 	emptyHooksBytes    = []byte("\n  <hooks></hooks>\n")
 
 	endElemBytes        = []byte("/>\n")
@@ -92,6 +94,7 @@
 	x.Imports = append([]Import(nil), m.Imports...)
 	x.LocalImports = append([]LocalImport(nil), m.LocalImports...)
 	x.Projects = append([]Project(nil), m.Projects...)
+	x.Overrides = append([]Project(nil), m.Overrides...)
 	x.Hooks = append([]Hook(nil), m.Hooks...)
 	return x
 }
@@ -110,6 +113,7 @@
 	// elements, or produce short empty elements, so we post-process the data.
 	data = bytes.Replace(data, emptyImportsBytes, newlineBytes, -1)
 	data = bytes.Replace(data, emptyProjectsBytes, newlineBytes, -1)
+	data = bytes.Replace(data, emptyOverridesBytes, newlineBytes, -1)
 	data = bytes.Replace(data, emptyHooksBytes, newlineBytes, -1)
 	data = bytes.Replace(data, endImportBytes, endElemBytes, -1)
 	data = bytes.Replace(data, endLocalImportBytes, endElemBytes, -1)
@@ -163,6 +167,11 @@
 			return err
 		}
 	}
+	for index := range m.Overrides {
+		if err := m.Overrides[index].fillDefaults(); err != nil {
+			return err
+		}
+	}
 	return nil
 }
 
@@ -182,6 +191,11 @@
 			return err
 		}
 	}
+	for index := range m.Overrides {
+		if err := m.Overrides[index].unfillDefaults(); err != nil {
+			return err
+		}
+	}
 	return nil
 }