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/command.py b/command.py
index 8e93787..724e4c5 100644
--- a/command.py
+++ b/command.py
@@ -15,9 +15,11 @@
import os
import optparse
+import re
import sys
from error import NoSuchProjectError
+from error import InvalidProjectGroupsError
class Command(object):
"""Base class for any command line action in repo.
@@ -63,9 +65,16 @@
all = self.manifest.projects
result = []
+ mp = self.manifest.manifestProject
+
+ groups = mp.config.GetString('manifest.groups')
+ if groups:
+ groups = re.split('[,\s]+', groups)
+
if not args:
for project in all.values():
- if missing_ok or project.Exists:
+ if ((missing_ok or project.Exists) and
+ project.MatchesGroups(groups)):
result.append(project)
else:
by_path = None
@@ -102,6 +111,8 @@
raise NoSuchProjectError(arg)
if not missing_ok and not project.Exists:
raise NoSuchProjectError(arg)
+ if not project.MatchesGroups(groups):
+ raise InvalidProjectGroupsError(arg)
result.append(project)
diff --git a/docs/manifest-format.txt b/docs/manifest-format.txt
index 21f19db..a7bb156 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest-format.txt
@@ -48,6 +48,7 @@
<!ATTLIST project path CDATA #IMPLIED>
<!ATTLIST project remote IDREF #IMPLIED>
<!ATTLIST project revision CDATA #IMPLIED>
+ <!ATTLIST project groups CDATA #IMPLIED>
<!ELEMENT remove-project (EMPTY)>
<!ATTLIST remove-project name CDATA #REQUIRED>
@@ -158,6 +159,10 @@
been extensively tested. If not supplied the revision given by
the default element is used.
+Attribute `groups`: List of groups to which this project belongs,
+whitespace or comma separated. All projects are part of the group
+"default" unless "-default" is specified in the list of groups.
+
Element remove-project
----------------------
diff --git a/error.py b/error.py
index 812585c..78c5c0e 100644
--- a/error.py
+++ b/error.py
@@ -77,6 +77,18 @@
return 'in current directory'
return self.name
+
+class InvalidProjectGroupsError(Exception):
+ """A specified project is not suitable for the specified groups
+ """
+ def __init__(self, name=None):
+ self.name = name
+
+ def __str__(self):
+ if self.Name is None:
+ return 'in current directory'
+ return self.name
+
class RepoChangedException(Exception):
"""Thrown if 'repo sync' results in repo updating its internal
repo or manifest repositories. In this special case we must
diff --git a/manifest_xml.py b/manifest_xml.py
index 4453869..a250382 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -119,6 +119,12 @@
def Save(self, fd, peg_rev=False):
"""Write the current manifest out to the given file descriptor.
"""
+ mp = self.manifestProject
+
+ groups = mp.config.GetString('manifest.groups')
+ if groups:
+ groups = re.split('[,\s]+', groups)
+
doc = xml.dom.minidom.Document()
root = doc.createElement('manifest')
doc.appendChild(root)
@@ -167,6 +173,10 @@
for p in sort_projects:
p = self.projects[p]
+
+ if not p.MatchesGroups(groups):
+ continue
+
e = doc.createElement('project')
root.appendChild(e)
e.setAttribute('name', p.name)
@@ -190,6 +200,9 @@
ce.setAttribute('dest', c.dest)
e.appendChild(ce)
+ if p.groups:
+ e.setAttribute('groups', ','.join(p.groups))
+
if self._repo_hooks_project:
root.appendChild(doc.createTextNode(''))
e = doc.createElement('repo-hooks')
@@ -504,6 +517,12 @@
else:
rebase = rebase.lower() in ("yes", "true", "1")
+ groups = node.getAttribute('groups')
+ if groups:
+ groups = re.split('[,\s]+', groups)
+ else:
+ groups = None
+
if self.IsMirror:
relpath = None
worktree = None
@@ -520,7 +539,8 @@
relpath = path,
revisionExpr = revisionExpr,
revisionId = None,
- rebase = rebase)
+ rebase = rebase,
+ groups = groups)
for n in node.childNodes:
if n.nodeName == 'copyfile':
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:
diff --git a/repo b/repo
index 1977d63..75fe9ec 100755
--- a/repo
+++ b/repo
@@ -28,7 +28,7 @@
del magic
# increment this whenever we make important changes to this script
-VERSION = (1, 14)
+VERSION = (1, 15)
# increment this if the MAINTAINER_KEYS block is modified
KEYRING_VERSION = (1,0)
@@ -125,6 +125,10 @@
group.add_option('--depth', type='int', default=None,
dest='depth',
help='create a shallow clone with given depth; see git clone')
+group.add_option('-g', '--groups',
+ dest='groups', default="",
+ help='restrict manifest projects to ones with a specified group',
+ metavar='GROUP')
# Tool
diff --git a/subcmds/init.py b/subcmds/init.py
index 1cba366..6cf39d1 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -86,6 +86,10 @@
g.add_option('--depth', type='int', default=None,
dest='depth',
help='create a shallow clone with given depth; see git clone')
+ g.add_option('-g', '--groups',
+ dest='groups', default="",
+ help='restrict manifest projects to ones with a specified group',
+ metavar='GROUP')
# Tool
g = p.add_option_group('repo Version options')
@@ -135,6 +139,8 @@
r.ResetFetch()
r.Save()
+ m.config.SetString('manifest.groups', opt.groups)
+
if opt.reference:
m.config.SetString('repo.reference', opt.reference)
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 74b3f18..63227af 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -277,7 +277,7 @@
def UpdateProjectList(self):
new_project_paths = []
- for project in self.manifest.projects.values():
+ for project in self.GetProjects(None, missing_ok=True):
if project.relpath:
new_project_paths.append(project.relpath)
file_name = 'project.list'
@@ -306,7 +306,8 @@
worktree = os.path.join(self.manifest.topdir, path),
relpath = path,
revisionExpr = 'HEAD',
- revisionId = None)
+ revisionId = None,
+ groups = None)
if project.IsDirty():
print >>sys.stderr, 'error: Cannot remove project "%s": \