Add manifest groups

Allows specifying a list of groups with a -g argument to repo init.
The groups act on a group= attribute specified on projects in the
manifest.
All projects are implicitly labelled with "default" unless they are
explicitly labelled "-default".
Prefixing a group with "-" removes matching projects from the list
of projects to sync.
If any non-inverted manifest groups are specified, the default label
is ignored.

Change-Id: I3a0dd7a93a8a1756205de1d03eee8c00906af0e5
Reviewed-on: https://gerrit-review.googlesource.com/34570
Reviewed-by: Shawn Pearce <sop@google.com>
Tested-by: Shawn Pearce <sop@google.com>
diff --git a/project.py b/project.py
index 303abe3..b2eaa87 100644
--- a/project.py
+++ b/project.py
@@ -504,7 +504,8 @@
                relpath,
                revisionExpr,
                revisionId,
-               rebase = True):
+               rebase = True,
+               groups = None):
     self.manifest = manifest
     self.name = name
     self.remote = remote
@@ -524,6 +525,7 @@
       self.revisionId = revisionId
 
     self.rebase = rebase
+    self.groups = groups
 
     self.snapshots = {}
     self.copyfiles = []
@@ -645,6 +647,45 @@
 
     return heads
 
+  def MatchesGroups(self, manifest_groups):
+    """Returns true if the manifest groups specified at init should cause
+       this project to be synced.
+       Prefixing a manifest group with "-" inverts the meaning of a group.
+       All projects are implicitly labelled with "default" unless they are
+       explicitly labelled "-default".
+       If any non-inverted manifest groups are specified, the default label
+       is ignored.
+       Specifying only inverted groups implies "default".
+    """
+    project_groups = self.groups
+    if not manifest_groups:
+      return not project_groups or not "-default" in project_groups
+
+    if not project_groups:
+      project_groups = ["default"]
+    elif not ("default" in project_groups or "-default" in project_groups):
+      project_groups.append("default")
+
+    plus_groups = [x for x in manifest_groups if not x.startswith("-")]
+    minus_groups = [x[1:] for x in manifest_groups if x.startswith("-")]
+
+    if not plus_groups:
+      plus_groups.append("default")
+
+    for group in minus_groups:
+      if group in project_groups:
+        # project was excluded by -group
+        return False
+
+    for group in plus_groups:
+      if group in project_groups:
+        # project was included by group
+        return True
+
+    # groups were specified that did not include this project
+    if plus_groups:
+      return False
+    return True
 
 ## Status Display ##
 
@@ -2091,7 +2132,8 @@
                      remote = RemoteSpec('origin'),
                      relpath = '.repo/%s' % name,
                      revisionExpr = 'refs/heads/master',
-                     revisionId = None)
+                     revisionId = None,
+                     groups = None)
 
   def PreSync(self):
     if self.Exists: