summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--color.py5
-rw-r--r--command.py74
-rw-r--r--docs/manifest-format.txt15
-rw-r--r--git_command.py6
-rw-r--r--git_config.py5
-rw-r--r--git_refs.py4
-rwxr-xr-xmain.py19
-rw-r--r--manifest_xml.py110
-rw-r--r--project.py199
-rwxr-xr-xrepo50
-rw-r--r--subcmds/grep.py2
-rw-r--r--subcmds/init.py25
-rw-r--r--subcmds/sync.py36
-rw-r--r--subcmds/upload.py2
14 files changed, 158 insertions, 394 deletions
diff --git a/color.py b/color.py
index 9200a298..33bc8779 100644
--- a/color.py
+++ b/color.py
@@ -36,7 +36,8 @@ ATTRS = {None :-1,
36 'blink' : 5, 36 'blink' : 5,
37 'reverse': 7} 37 'reverse': 7}
38 38
39RESET = "\033[m" 39RESET = "\033[m" # pylint: disable=W1401
40 # backslash is not anomalous
40 41
41def is_color(s): 42def is_color(s):
42 return s in COLORS 43 return s in COLORS
@@ -51,7 +52,7 @@ def _Color(fg = None, bg = None, attr = None):
51 52
52 if attr >= 0 or fg >= 0 or bg >= 0: 53 if attr >= 0 or fg >= 0 or bg >= 0:
53 need_sep = False 54 need_sep = False
54 code = "\033[" 55 code = "\033[" #pylint: disable=W1401
55 56
56 if attr >= 0: 57 if attr >= 0:
57 code += chr(ord('0') + attr) 58 code += chr(ord('0') + attr)
diff --git a/command.py b/command.py
index d543e3a8..babb8338 100644
--- a/command.py
+++ b/command.py
@@ -60,32 +60,6 @@ class Command(object):
60 """ 60 """
61 raise NotImplementedError 61 raise NotImplementedError
62 62
63 def _ResetPathToProjectMap(self, projects):
64 self._by_path = dict((p.worktree, p) for p in projects)
65
66 def _UpdatePathToProjectMap(self, project):
67 self._by_path[project.worktree] = project
68
69 def _GetProjectByPath(self, path):
70 project = None
71 if os.path.exists(path):
72 oldpath = None
73 while path \
74 and path != oldpath \
75 and path != self.manifest.topdir:
76 try:
77 project = self._by_path[path]
78 break
79 except KeyError:
80 oldpath = path
81 path = os.path.dirname(path)
82 else:
83 try:
84 project = self._by_path[path]
85 except KeyError:
86 pass
87 return project
88
89 def GetProjects(self, args, missing_ok=False): 63 def GetProjects(self, args, missing_ok=False):
90 """A list of projects that match the arguments. 64 """A list of projects that match the arguments.
91 """ 65 """
@@ -97,41 +71,43 @@ class Command(object):
97 groups = mp.config.GetString('manifest.groups') 71 groups = mp.config.GetString('manifest.groups')
98 if not groups: 72 if not groups:
99 groups = 'all,-notdefault,platform-' + platform.system().lower() 73 groups = 'all,-notdefault,platform-' + platform.system().lower()
100 groups = [x for x in re.split('[,\s]+', groups) if x] 74 groups = [x for x in re.split(r'[,\s]+', groups) if x]
101 75
102 if not args: 76 if not args:
103 all_projects_list = all_projects.values() 77 for project in all_projects.values():
104 derived_projects = []
105 for project in all_projects_list:
106 if project.Registered:
107 # Do not search registered subproject for derived projects
108 # since its parent has been searched already
109 continue
110 derived_projects.extend(project.GetDerivedSubprojects())
111 all_projects_list.extend(derived_projects)
112 for project in all_projects_list:
113 if ((missing_ok or project.Exists) and 78 if ((missing_ok or project.Exists) and
114 project.MatchesGroups(groups)): 79 project.MatchesGroups(groups)):
115 result.append(project) 80 result.append(project)
116 else: 81 else:
117 self._ResetPathToProjectMap(all_projects.values()) 82 by_path = None
118 83
119 for arg in args: 84 for arg in args:
120 project = all_projects.get(arg) 85 project = all_projects.get(arg)
121 86
122 if not project: 87 if not project:
123 path = os.path.abspath(arg).replace('\\', '/') 88 path = os.path.abspath(arg).replace('\\', '/')
124 project = self._GetProjectByPath(path) 89
125 90 if not by_path:
126 # If it's not a derived project, update path->project mapping and 91 by_path = dict()
127 # search again, as arg might actually point to a derived subproject. 92 for p in all_projects.values():
128 if project and not project.Derived: 93 by_path[p.worktree] = p
129 search_again = False 94
130 for subproject in project.GetDerivedSubprojects(): 95 if os.path.exists(path):
131 self._UpdatePathToProjectMap(subproject) 96 oldpath = None
132 search_again = True 97 while path \
133 if search_again: 98 and path != oldpath \
134 project = self._GetProjectByPath(path) or project 99 and path != self.manifest.topdir:
100 try:
101 project = by_path[path]
102 break
103 except KeyError:
104 oldpath = path
105 path = os.path.dirname(path)
106 else:
107 try:
108 project = by_path[path]
109 except KeyError:
110 pass
135 111
136 if not project: 112 if not project:
137 raise NoSuchProjectError(arg) 113 raise NoSuchProjectError(arg)
diff --git a/docs/manifest-format.txt b/docs/manifest-format.txt
index a36af67c..f499868c 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest-format.txt
@@ -45,8 +45,7 @@ following DTD:
45 <!ELEMENT manifest-server (EMPTY)> 45 <!ELEMENT manifest-server (EMPTY)>
46 <!ATTLIST url CDATA #REQUIRED> 46 <!ATTLIST url CDATA #REQUIRED>
47 47
48 <!ELEMENT project (annotation?, 48 <!ELEMENT project (annotation?)>
49 project*)>
50 <!ATTLIST project name CDATA #REQUIRED> 49 <!ATTLIST project name CDATA #REQUIRED>
51 <!ATTLIST project path CDATA #IMPLIED> 50 <!ATTLIST project path CDATA #IMPLIED>
52 <!ATTLIST project remote IDREF #IMPLIED> 51 <!ATTLIST project remote IDREF #IMPLIED>
@@ -153,10 +152,7 @@ Element project
153 152
154One or more project elements may be specified. Each element 153One or more project elements may be specified. Each element
155describes a single Git repository to be cloned into the repo 154describes a single Git repository to be cloned into the repo
156client workspace. You may specify Git-submodules by creating a 155client workspace.
157nested project. Git-submodules will be automatically
158recognized and inherit their parent's attributes, but those
159may be overridden by an explicitly specified project element.
160 156
161Attribute `name`: A unique name for this project. The project's 157Attribute `name`: A unique name for this project. The project's
162name is appended onto its remote's fetch URL to generate the actual 158name is appended onto its remote's fetch URL to generate the actual
@@ -167,8 +163,7 @@ URL to configure the Git remote with. The URL gets formed as:
167where ${remote_fetch} is the remote's fetch attribute and 163where ${remote_fetch} is the remote's fetch attribute and
168${project_name} is the project's name attribute. The suffix ".git" 164${project_name} is the project's name attribute. The suffix ".git"
169is always appended as repo assumes the upstream is a forest of 165is always appended as repo assumes the upstream is a forest of
170bare Git repositories. If the project has a parent element, its 166bare Git repositories.
171name will be prefixed by the parent's.
172 167
173The project name must match the name Gerrit knows, if Gerrit is 168The project name must match the name Gerrit knows, if Gerrit is
174being used for code reviews. 169being used for code reviews.
@@ -176,8 +171,6 @@ being used for code reviews.
176Attribute `path`: An optional path relative to the top directory 171Attribute `path`: An optional path relative to the top directory
177of the repo client where the Git working directory for this project 172of the repo client where the Git working directory for this project
178should be placed. If not supplied the project name is used. 173should be placed. If not supplied the project name is used.
179If the project has a parent element, its path will be prefixed
180by the parent's.
181 174
182Attribute `remote`: Name of a previously defined remote element. 175Attribute `remote`: Name of a previously defined remote element.
183If not supplied the remote given by the default element is used. 176If not supplied the remote given by the default element is used.
@@ -197,8 +190,6 @@ its name:`name` and path:`path`. E.g. for
197definition is implicitly in the following manifest groups: 190definition is implicitly in the following manifest groups:
198default, name:monkeys, and path:barrel-of. If you place a project in the 191default, name:monkeys, and path:barrel-of. If you place a project in the
199group "notdefault", it will not be automatically downloaded by repo. 192group "notdefault", it will not be automatically downloaded by repo.
200If the project has a parent element, the `name` and `path` here
201are the prefixed ones.
202 193
203Element annotation 194Element annotation
204------------------ 195------------------
diff --git a/git_command.py b/git_command.py
index a40e6c05..39f795ff 100644
--- a/git_command.py
+++ b/git_command.py
@@ -132,15 +132,15 @@ class GitCommand(object):
132 gitdir = None): 132 gitdir = None):
133 env = os.environ.copy() 133 env = os.environ.copy()
134 134
135 for e in [REPO_TRACE, 135 for key in [REPO_TRACE,
136 GIT_DIR, 136 GIT_DIR,
137 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 137 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
138 'GIT_OBJECT_DIRECTORY', 138 'GIT_OBJECT_DIRECTORY',
139 'GIT_WORK_TREE', 139 'GIT_WORK_TREE',
140 'GIT_GRAFT_FILE', 140 'GIT_GRAFT_FILE',
141 'GIT_INDEX_FILE']: 141 'GIT_INDEX_FILE']:
142 if e in env: 142 if key in env:
143 del env[e] 143 del env[key]
144 144
145 if disable_editor: 145 if disable_editor:
146 _setenv(env, 'GIT_EDITOR', ':') 146 _setenv(env, 'GIT_EDITOR', ':')
diff --git a/git_config.py b/git_config.py
index ae288558..d6510aae 100644
--- a/git_config.py
+++ b/git_config.py
@@ -35,7 +35,7 @@ from git_command import terminate_ssh_clients
35 35
36R_HEADS = 'refs/heads/' 36R_HEADS = 'refs/heads/'
37R_TAGS = 'refs/tags/' 37R_TAGS = 'refs/tags/'
38ID_RE = re.compile('^[0-9a-f]{40}$') 38ID_RE = re.compile(r'^[0-9a-f]{40}$')
39 39
40REVIEW_CACHE = dict() 40REVIEW_CACHE = dict()
41 41
@@ -288,7 +288,8 @@ class GitConfig(object):
288 d = self._do('--null', '--list') 288 d = self._do('--null', '--list')
289 if d is None: 289 if d is None:
290 return c 290 return c
291 for line in d.rstrip('\0').split('\0'): 291 for line in d.rstrip('\0').split('\0'): # pylint: disable=W1401
292 # Backslash is not anomalous
292 if '\n' in line: 293 if '\n' in line:
293 key, val = line.split('\n', 1) 294 key, val = line.split('\n', 1)
294 else: 295 else:
diff --git a/git_refs.py b/git_refs.py
index 18c9230c..cfeffba9 100644
--- a/git_refs.py
+++ b/git_refs.py
@@ -138,14 +138,14 @@ class GitRefs(object):
138 def _ReadLoose1(self, path, name): 138 def _ReadLoose1(self, path, name):
139 try: 139 try:
140 fd = open(path, 'rb') 140 fd = open(path, 'rb')
141 except: 141 except IOError:
142 return 142 return
143 143
144 try: 144 try:
145 try: 145 try:
146 mtime = os.path.getmtime(path) 146 mtime = os.path.getmtime(path)
147 ref_id = fd.readline() 147 ref_id = fd.readline()
148 except: 148 except (IOError, OSError):
149 return 149 return
150 finally: 150 finally:
151 fd.close() 151 fd.close()
diff --git a/main.py b/main.py
index d993ee4e..7a09c6ba 100755
--- a/main.py
+++ b/main.py
@@ -23,10 +23,10 @@ if __name__ == '__main__':
23del magic 23del magic
24 24
25import getpass 25import getpass
26import imp
26import netrc 27import netrc
27import optparse 28import optparse
28import os 29import os
29import re
30import sys 30import sys
31import time 31import time
32import urllib2 32import urllib2
@@ -167,16 +167,15 @@ def _MyRepoPath():
167def _MyWrapperPath(): 167def _MyWrapperPath():
168 return os.path.join(os.path.dirname(__file__), 'repo') 168 return os.path.join(os.path.dirname(__file__), 'repo')
169 169
170_wrapper_module = None
171def WrapperModule():
172 global _wrapper_module
173 if not _wrapper_module:
174 _wrapper_module = imp.load_source('wrapper', _MyWrapperPath())
175 return _wrapper_module
176
170def _CurrentWrapperVersion(): 177def _CurrentWrapperVersion():
171 VERSION = None 178 return WrapperModule().VERSION
172 pat = re.compile(r'^VERSION *=')
173 fd = open(_MyWrapperPath())
174 for line in fd:
175 if pat.match(line):
176 fd.close()
177 exec line
178 return VERSION
179 raise NameError, 'No VERSION in repo script'
180 179
181def _CheckWrapperVersion(ver, repo_path): 180def _CheckWrapperVersion(ver, repo_path):
182 if not repo_path: 181 if not repo_path:
diff --git a/manifest_xml.py b/manifest_xml.py
index 11e4ee54..cdee87a6 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -180,25 +180,20 @@ class XmlManifest(object):
180 root.appendChild(e) 180 root.appendChild(e)
181 root.appendChild(doc.createTextNode('')) 181 root.appendChild(doc.createTextNode(''))
182 182
183 def output_projects(parent, parent_node, projects): 183 sort_projects = list(self.projects.keys())
184 for p in projects: 184 sort_projects.sort()
185 output_project(parent, parent_node, self.projects[p])
186 185
187 def output_project(parent, parent_node, p): 186 for p in sort_projects:
188 if not p.MatchesGroups(groups): 187 p = self.projects[p]
189 return
190 188
191 name = p.name 189 if not p.MatchesGroups(groups):
192 relpath = p.relpath 190 continue
193 if parent:
194 name = self._UnjoinName(parent.name, name)
195 relpath = self._UnjoinRelpath(parent.relpath, relpath)
196 191
197 e = doc.createElement('project') 192 e = doc.createElement('project')
198 parent_node.appendChild(e) 193 root.appendChild(e)
199 e.setAttribute('name', name) 194 e.setAttribute('name', p.name)
200 if relpath != name: 195 if p.relpath != p.name:
201 e.setAttribute('path', relpath) 196 e.setAttribute('path', p.relpath)
202 if not d.remote or p.remote.name != d.remote.name: 197 if not d.remote or p.remote.name != d.remote.name:
203 e.setAttribute('remote', p.remote.name) 198 e.setAttribute('remote', p.remote.name)
204 if peg_rev: 199 if peg_rev:
@@ -236,16 +231,6 @@ class XmlManifest(object):
236 if p.sync_c: 231 if p.sync_c:
237 e.setAttribute('sync-c', 'true') 232 e.setAttribute('sync-c', 'true')
238 233
239 if p.subprojects:
240 sort_projects = [subp.name for subp in p.subprojects]
241 sort_projects.sort()
242 output_projects(p, e, sort_projects)
243
244 sort_projects = [key for key in self.projects.keys()
245 if not self.projects[key].parent]
246 sort_projects.sort()
247 output_projects(None, root, sort_projects)
248
249 if self._repo_hooks_project: 234 if self._repo_hooks_project:
250 root.appendChild(doc.createTextNode('')) 235 root.appendChild(doc.createTextNode(''))
251 e = doc.createElement('repo-hooks') 236 e = doc.createElement('repo-hooks')
@@ -398,15 +383,11 @@ class XmlManifest(object):
398 for node in itertools.chain(*node_list): 383 for node in itertools.chain(*node_list):
399 if node.nodeName == 'project': 384 if node.nodeName == 'project':
400 project = self._ParseProject(node) 385 project = self._ParseProject(node)
401 def recursively_add_projects(project): 386 if self._projects.get(project.name):
402 if self._projects.get(project.name): 387 raise ManifestParseError(
403 raise ManifestParseError( 388 'duplicate project %s in %s' %
404 'duplicate project %s in %s' % 389 (project.name, self.manifestFile))
405 (project.name, self.manifestFile)) 390 self._projects[project.name] = project
406 self._projects[project.name] = project
407 for subproject in project.subprojects:
408 recursively_add_projects(subproject)
409 recursively_add_projects(project)
410 if node.nodeName == 'repo-hooks': 391 if node.nodeName == 'repo-hooks':
411 # Get the name of the project and the (space-separated) list of enabled. 392 # Get the name of the project and the (space-separated) list of enabled.
412 repo_hooks_project = self._reqatt(node, 'in-project') 393 repo_hooks_project = self._reqatt(node, 'in-project')
@@ -556,19 +537,11 @@ class XmlManifest(object):
556 537
557 return '\n'.join(cleanLines) 538 return '\n'.join(cleanLines)
558 539
559 def _JoinName(self, parent_name, name): 540 def _ParseProject(self, node):
560 return os.path.join(parent_name, name)
561
562 def _UnjoinName(self, parent_name, name):
563 return os.path.relpath(name, parent_name)
564
565 def _ParseProject(self, node, parent = None):
566 """ 541 """
567 reads a <project> element from the manifest file 542 reads a <project> element from the manifest file
568 """ 543 """
569 name = self._reqatt(node, 'name') 544 name = self._reqatt(node, 'name')
570 if parent:
571 name = self._JoinName(parent.name, name)
572 545
573 remote = self._get_remote(node) 546 remote = self._get_remote(node)
574 if remote is None: 547 if remote is None:
@@ -611,68 +584,39 @@ class XmlManifest(object):
611 groups = '' 584 groups = ''
612 if node.hasAttribute('groups'): 585 if node.hasAttribute('groups'):
613 groups = node.getAttribute('groups') 586 groups = node.getAttribute('groups')
614 groups = [x for x in re.split('[,\s]+', groups) if x] 587 groups = [x for x in re.split(r'[,\s]+', groups) if x]
615
616 if parent is None:
617 relpath, worktree, gitdir = self.GetProjectPaths(name, path)
618 else:
619 relpath, worktree, gitdir = self.GetSubprojectPaths(parent, path)
620 588
621 default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath] 589 default_groups = ['all', 'name:%s' % name, 'path:%s' % path]
622 groups.extend(set(default_groups).difference(groups)) 590 groups.extend(set(default_groups).difference(groups))
623 591
592 if self.IsMirror:
593 worktree = None
594 gitdir = os.path.join(self.topdir, '%s.git' % name)
595 else:
596 worktree = os.path.join(self.topdir, path).replace('\\', '/')
597 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
598
624 project = Project(manifest = self, 599 project = Project(manifest = self,
625 name = name, 600 name = name,
626 remote = remote.ToRemoteSpec(name), 601 remote = remote.ToRemoteSpec(name),
627 gitdir = gitdir, 602 gitdir = gitdir,
628 worktree = worktree, 603 worktree = worktree,
629 relpath = relpath, 604 relpath = path,
630 revisionExpr = revisionExpr, 605 revisionExpr = revisionExpr,
631 revisionId = None, 606 revisionId = None,
632 rebase = rebase, 607 rebase = rebase,
633 groups = groups, 608 groups = groups,
634 sync_c = sync_c, 609 sync_c = sync_c,
635 upstream = upstream, 610 upstream = upstream)
636 parent = parent)
637 611
638 for n in node.childNodes: 612 for n in node.childNodes:
639 if n.nodeName == 'copyfile': 613 if n.nodeName == 'copyfile':
640 self._ParseCopyFile(project, n) 614 self._ParseCopyFile(project, n)
641 if n.nodeName == 'annotation': 615 if n.nodeName == 'annotation':
642 self._ParseAnnotation(project, n) 616 self._ParseAnnotation(project, n)
643 if n.nodeName == 'project':
644 project.subprojects.append(self._ParseProject(n, parent = project))
645 617
646 return project 618 return project
647 619
648 def GetProjectPaths(self, name, path):
649 relpath = path
650 if self.IsMirror:
651 worktree = None
652 gitdir = os.path.join(self.topdir, '%s.git' % name)
653 else:
654 worktree = os.path.join(self.topdir, path).replace('\\', '/')
655 gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path)
656 return relpath, worktree, gitdir
657
658 def GetSubprojectName(self, parent, submodule_path):
659 return os.path.join(parent.name, submodule_path)
660
661 def _JoinRelpath(self, parent_relpath, relpath):
662 return os.path.join(parent_relpath, relpath)
663
664 def _UnjoinRelpath(self, parent_relpath, relpath):
665 return os.path.relpath(relpath, parent_relpath)
666
667 def GetSubprojectPaths(self, parent, path):
668 relpath = self._JoinRelpath(parent.relpath, path)
669 gitdir = os.path.join(parent.gitdir, 'subprojects', '%s.git' % path)
670 if self.IsMirror:
671 worktree = None
672 else:
673 worktree = os.path.join(parent.worktree, path).replace('\\', '/')
674 return relpath, worktree, gitdir
675
676 def _ParseCopyFile(self, project, node): 620 def _ParseCopyFile(self, project, node):
677 src = self._reqatt(node, 'src') 621 src = self._reqatt(node, 'src')
678 dest = self._reqatt(node, 'dest') 622 dest = self._reqatt(node, 'dest')
diff --git a/project.py b/project.py
index c5ee50fc..cdb4ecfd 100644
--- a/project.py
+++ b/project.py
@@ -22,11 +22,10 @@ import shutil
22import stat 22import stat
23import subprocess 23import subprocess
24import sys 24import sys
25import tempfile
26import time 25import time
27 26
28from color import Coloring 27from color import Coloring
29from git_command import GitCommand 28from git_command import GitCommand, git_require
30from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE 29from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
31from error import GitError, HookError, UploadError 30from error import GitError, HookError, UploadError
32from error import ManifestInvalidRevisionError 31from error import ManifestInvalidRevisionError
@@ -485,28 +484,7 @@ class Project(object):
485 rebase = True, 484 rebase = True,
486 groups = None, 485 groups = None,
487 sync_c = False, 486 sync_c = False,
488 upstream = None, 487 upstream = None):
489 parent = None,
490 is_derived = False):
491 """Init a Project object.
492
493 Args:
494 manifest: The XmlManifest object.
495 name: The `name` attribute of manifest.xml's project element.
496 remote: RemoteSpec object specifying its remote's properties.
497 gitdir: Absolute path of git directory.
498 worktree: Absolute path of git working tree.
499 relpath: Relative path of git working tree to repo's top directory.
500 revisionExpr: The `revision` attribute of manifest.xml's project element.
501 revisionId: git commit id for checking out.
502 rebase: The `rebase` attribute of manifest.xml's project element.
503 groups: The `groups` attribute of manifest.xml's project element.
504 sync_c: The `sync-c` attribute of manifest.xml's project element.
505 upstream: The `upstream` attribute of manifest.xml's project element.
506 parent: The parent Project object.
507 is_derived: False if the project was explicitly defined in the manifest;
508 True if the project is a discovered submodule.
509 """
510 self.manifest = manifest 488 self.manifest = manifest
511 self.name = name 489 self.name = name
512 self.remote = remote 490 self.remote = remote
@@ -529,9 +507,6 @@ class Project(object):
529 self.groups = groups 507 self.groups = groups
530 self.sync_c = sync_c 508 self.sync_c = sync_c
531 self.upstream = upstream 509 self.upstream = upstream
532 self.parent = parent
533 self.is_derived = is_derived
534 self.subprojects = []
535 510
536 self.snapshots = {} 511 self.snapshots = {}
537 self.copyfiles = [] 512 self.copyfiles = []
@@ -552,14 +527,6 @@ class Project(object):
552 self.enabled_repo_hooks = [] 527 self.enabled_repo_hooks = []
553 528
554 @property 529 @property
555 def Registered(self):
556 return self.parent and not self.is_derived
557
558 @property
559 def Derived(self):
560 return self.is_derived
561
562 @property
563 def Exists(self): 530 def Exists(self):
564 return os.path.isdir(self.gitdir) 531 return os.path.isdir(self.gitdir)
565 532
@@ -1045,6 +1012,10 @@ class Project(object):
1045 self.CleanPublishedCache(all_refs) 1012 self.CleanPublishedCache(all_refs)
1046 revid = self.GetRevisionId(all_refs) 1013 revid = self.GetRevisionId(all_refs)
1047 1014
1015 def _doff():
1016 self._FastForward(revid)
1017 self._CopyFiles()
1018
1048 self._InitWorkTree() 1019 self._InitWorkTree()
1049 head = self.work_git.GetHead() 1020 head = self.work_git.GetHead()
1050 if head.startswith(R_HEADS): 1021 if head.startswith(R_HEADS):
@@ -1123,9 +1094,6 @@ class Project(object):
1123 # All published commits are merged, and thus we are a 1094 # All published commits are merged, and thus we are a
1124 # strict subset. We can fast-forward safely. 1095 # strict subset. We can fast-forward safely.
1125 # 1096 #
1126 def _doff():
1127 self._FastForward(revid)
1128 self._CopyFiles()
1129 syncbuf.later1(self, _doff) 1097 syncbuf.later1(self, _doff)
1130 return 1098 return
1131 1099
@@ -1188,9 +1156,6 @@ class Project(object):
1188 syncbuf.fail(self, e) 1156 syncbuf.fail(self, e)
1189 return 1157 return
1190 else: 1158 else:
1191 def _doff():
1192 self._FastForward(revid)
1193 self._CopyFiles()
1194 syncbuf.later1(self, _doff) 1159 syncbuf.later1(self, _doff)
1195 1160
1196 def AddCopyFile(self, src, dest, absdest): 1161 def AddCopyFile(self, src, dest, absdest):
@@ -1403,150 +1368,6 @@ class Project(object):
1403 return kept 1368 return kept
1404 1369
1405 1370
1406## Submodule Management ##
1407
1408 def GetRegisteredSubprojects(self):
1409 result = []
1410 def rec(subprojects):
1411 if not subprojects:
1412 return
1413 result.extend(subprojects)
1414 for p in subprojects:
1415 rec(p.subprojects)
1416 rec(self.subprojects)
1417 return result
1418
1419 def _GetSubmodules(self):
1420 # Unfortunately we cannot call `git submodule status --recursive` here
1421 # because the working tree might not exist yet, and it cannot be used
1422 # without a working tree in its current implementation.
1423
1424 def get_submodules(gitdir, rev):
1425 # Parse .gitmodules for submodule sub_paths and sub_urls
1426 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1427 if not sub_paths:
1428 return []
1429 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1430 # revision of submodule repository
1431 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1432 submodules = []
1433 for sub_path, sub_url in zip(sub_paths, sub_urls):
1434 try:
1435 sub_rev = sub_revs[sub_path]
1436 except KeyError:
1437 # Ignore non-exist submodules
1438 continue
1439 submodules.append((sub_rev, sub_path, sub_url))
1440 return submodules
1441
1442 re_path = re.compile(r'submodule.(\w+).path')
1443 re_url = re.compile(r'submodule.(\w+).url')
1444 def parse_gitmodules(gitdir, rev):
1445 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1446 try:
1447 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1448 bare = True, gitdir = gitdir)
1449 except GitError:
1450 return [], []
1451 if p.Wait() != 0:
1452 return [], []
1453
1454 gitmodules_lines = []
1455 fd, temp_gitmodules_path = tempfile.mkstemp()
1456 try:
1457 os.write(fd, p.stdout)
1458 os.close(fd)
1459 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1460 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1461 bare = True, gitdir = gitdir)
1462 if p.Wait() != 0:
1463 return [], []
1464 gitmodules_lines = p.stdout.split('\n')
1465 except GitError:
1466 return [], []
1467 finally:
1468 os.remove(temp_gitmodules_path)
1469
1470 names = set()
1471 paths = {}
1472 urls = {}
1473 for line in gitmodules_lines:
1474 if not line:
1475 continue
1476 key, value = line.split('=')
1477 m = re_path.match(key)
1478 if m:
1479 names.add(m.group(1))
1480 paths[m.group(1)] = value
1481 continue
1482 m = re_url.match(key)
1483 if m:
1484 names.add(m.group(1))
1485 urls[m.group(1)] = value
1486 continue
1487 names = sorted(names)
1488 return [paths[name] for name in names], [urls[name] for name in names]
1489
1490 def git_ls_tree(gitdir, rev, paths):
1491 cmd = ['ls-tree', rev, '--']
1492 cmd.extend(paths)
1493 try:
1494 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1495 bare = True, gitdir = gitdir)
1496 except GitError:
1497 return []
1498 if p.Wait() != 0:
1499 return []
1500 objects = {}
1501 for line in p.stdout.split('\n'):
1502 if not line.strip():
1503 continue
1504 object_rev, object_path = line.split()[2:4]
1505 objects[object_path] = object_rev
1506 return objects
1507
1508 try:
1509 rev = self.GetRevisionId()
1510 except GitError:
1511 return []
1512 return get_submodules(self.gitdir, rev)
1513
1514 def GetDerivedSubprojects(self):
1515 result = []
1516 if not self.Exists:
1517 # If git repo does not exist yet, querying its submodules will
1518 # mess up its states; so return here.
1519 return result
1520 for rev, path, url in self._GetSubmodules():
1521 name = self.manifest.GetSubprojectName(self, path)
1522 project = self.manifest.projects.get(name)
1523 if project and project.Registered:
1524 # If it has been registered, skip it because we are searching
1525 # derived subprojects, but search for its derived subprojects.
1526 result.extend(project.GetDerivedSubprojects())
1527 continue
1528 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1529 remote = RemoteSpec(self.remote.name,
1530 url = url,
1531 review = self.remote.review)
1532 subproject = Project(manifest = self.manifest,
1533 name = name,
1534 remote = remote,
1535 gitdir = gitdir,
1536 worktree = worktree,
1537 relpath = relpath,
1538 revisionExpr = self.revisionExpr,
1539 revisionId = rev,
1540 rebase = self.rebase,
1541 groups = self.groups,
1542 sync_c = self.sync_c,
1543 parent = self,
1544 is_derived = True)
1545 result.append(subproject)
1546 result.extend(subproject.GetDerivedSubprojects())
1547 return result
1548
1549
1550## Direct Git Commands ## 1371## Direct Git Commands ##
1551 1372
1552 def _RemoteFetch(self, name=None, 1373 def _RemoteFetch(self, name=None,
@@ -2013,7 +1834,8 @@ class Project(object):
2013 if p.Wait() == 0: 1834 if p.Wait() == 0:
2014 out = p.stdout 1835 out = p.stdout
2015 if out: 1836 if out:
2016 return out[:-1].split("\0") 1837 return out[:-1].split('\0') # pylint: disable=W1401
1838 # Backslash is not anomalous
2017 return [] 1839 return []
2018 1840
2019 def DiffZ(self, name, *args): 1841 def DiffZ(self, name, *args):
@@ -2029,7 +1851,7 @@ class Project(object):
2029 out = p.process.stdout.read() 1851 out = p.process.stdout.read()
2030 r = {} 1852 r = {}
2031 if out: 1853 if out:
2032 out = iter(out[:-1].split('\0')) 1854 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
2033 while out: 1855 while out:
2034 try: 1856 try:
2035 info = out.next() 1857 info = out.next()
@@ -2165,6 +1987,9 @@ class Project(object):
2165 raise TypeError('%s() got an unexpected keyword argument %r' 1987 raise TypeError('%s() got an unexpected keyword argument %r'
2166 % (name, k)) 1988 % (name, k))
2167 if config is not None: 1989 if config is not None:
1990 if not git_require((1, 7, 2)):
1991 raise ValueError('cannot set config on command line for %s()'
1992 % name)
2168 for k, v in config.iteritems(): 1993 for k, v in config.iteritems():
2169 cmdv.append('-c') 1994 cmdv.append('-c')
2170 cmdv.append('%s=%s' % (k, v)) 1995 cmdv.append('%s=%s' % (k, v))
diff --git a/repo b/repo
index fdb852ef..9643a225 100755
--- a/repo
+++ b/repo
@@ -28,10 +28,10 @@ if __name__ == '__main__':
28del magic 28del magic
29 29
30# increment this whenever we make important changes to this script 30# increment this whenever we make important changes to this script
31VERSION = (1, 17) 31VERSION = (1, 19)
32 32
33# increment this if the MAINTAINER_KEYS block is modified 33# increment this if the MAINTAINER_KEYS block is modified
34KEYRING_VERSION = (1,0) 34KEYRING_VERSION = (1,1)
35MAINTAINER_KEYS = """ 35MAINTAINER_KEYS = """
36 36
37 Repo Maintainer <repo@android.kernel.org> 37 Repo Maintainer <repo@android.kernel.org>
@@ -75,12 +75,44 @@ zE3GNvmJosh6leayjtI9P2A6iEkEGBECAAkFAkj3uiACGwwACgkQFlMNXpIPXGWp
75TACbBS+Up3RpfYVfd63c1cDdlru13pQAn3NQy/SN858MkxN+zym86UBgOad2 75TACbBS+Up3RpfYVfd63c1cDdlru13pQAn3NQy/SN858MkxN+zym86UBgOad2
76=CMiZ 76=CMiZ
77-----END PGP PUBLIC KEY BLOCK----- 77-----END PGP PUBLIC KEY BLOCK-----
78
79 Conley Owens <cco3@android.com>
80-----BEGIN PGP PUBLIC KEY BLOCK-----
81Version: GnuPG v1.4.11 (GNU/Linux)
82
83mQENBFBiLPwBCACvISTASOgFXwADw2GYRH2I2z9RvYkYoZ6ThTTNlMXbbYYKO2Wo
84a9LQDNW0TbCEekg5UKk0FD13XOdWaqUt4Gtuvq9c43GRSjMO6NXH+0BjcQ8vUtY2
85/W4CYUevwdo4nQ1+1zsOCu1XYe/CReXq0fdugv3hgmRmh3sz1soo37Q44W2frxxg
86U7Rz3Da4FjgAL0RQ8qndD+LwRHXTY7H7wYM8V/3cYFZV7pSodd75q3MAXYQLf0ZV
87QR1XATu5l1QnXrxgHvz7MmDwb1D+jX3YPKnZveaukigQ6hDHdiVcePBiGXmk8LZC
882jQkdXeF7Su1ZYpr2nnEHLJ6vOLcCpPGb8gDABEBAAG0H0NvbmxleSBPd2VucyA8
89Y2NvM0BhbmRyb2lkLmNvbT6JATgEEwECACIFAlBiLPwCGwMGCwkIBwMCBhUIAgkK
90CwQWAgMBAh4BAheAAAoJEBkmlFUziHGkHVkH/2Hks2Cif5i2xPtv2IFZcjL42joU
91T7lO5XFqUYS9ZNHpGa/V0eiPt7rHoO16glR83NZtwlrq2cSN89i9HfOhMYV/qLu8
92fLCHcV2muw+yCB5s5bxnI5UkToiNZyBNqFkcOt/Kbj9Hpy68A1kmc6myVEaUYebq
932Chx/f3xuEthan099t746v1K+/6SvQGDNctHuaMr9cWdxZtHjdRf31SQRc99Phe5
94w+ZGR/ebxNDKRK9mKgZT8wVFHlXerJsRqWIqtx1fsW1UgLgbpcpe2MChm6B5wTu0
95s1ltzox3l4q71FyRRPUJxXyvGkDLZWpK7EpiHSCOYq/KP3HkKeXU3xqHpcG5AQ0E
96UGIs/AEIAKzO/7lO9cB6dshmZYo8Vy/b7aGicThE+ChcDSfhvyOXVdEM2GKAjsR+
97rlBWbTFX3It301p2HwZPFEi9nEvJxVlqqBiW0bPmNMkDRR55l2vbWg35wwkg6RyE
98Bc5/TQjhXI2w8IvlimoGoUff4t3JmMOnWrnKSvL+5iuRj12p9WmanCHzw3Ee7ztf
99/aU/q+FTpr3DLerb6S8xbv86ySgnJT6o5CyL2DCWRtnYQyGVi0ZmLzEouAYiO0hs
100z0AAu28Mj+12g2WwePRz6gfM9rHtI37ylYW3oT/9M9mO9ei/Bc/1D7Dz6qNV+0vg
101uSVJxM2Bl6GalHPZLhHntFEdIA6EdoUAEQEAAYkBHwQYAQIACQUCUGIs/AIbDAAK
102CRAZJpRVM4hxpNfkB/0W/hP5WK/NETXBlWXXW7JPaWO2c5kGwD0lnj5RRmridyo1
103vbm5PdM91jOsDQYqRu6YOoYBnDnEhB2wL2bPh34HWwwrA+LwB8hlcAV2z1bdwyfl
1043R823fReKN3QcvLHzmvZPrF4Rk97M9UIyKS0RtnfTWykRgDWHIsrtQPoNwsXrWoT
1059LrM2v+1+9mp3vuXnE473/NHxmiWEQH9Ez+O/mOxQ7rSOlqGRiKq/IBZCfioJOtV
106fTQeIu/yASZnsLBqr6SJEGwYBoWcyjG++k4fyw8ocOAo4uGDYbxgN7yYfNQ0OH7o
107V6pfUgqKLWa/aK7/N1ZHnPdFLD8Xt0Dmy4BPwrKC
108=O7am
109-----END PGP PUBLIC KEY BLOCK-----
78""" 110"""
79 111
80GIT = 'git' # our git command 112GIT = 'git' # our git command
81MIN_GIT_VERSION = (1, 5, 4) # minimum supported git version 113MIN_GIT_VERSION = (1, 7, 2) # minimum supported git version
82repodir = '.repo' # name of repo's private directory 114repodir = '.repo' # name of repo's private directory
83S_repo = 'repo' # special repo reposiory 115S_repo = 'repo' # special repo repository
84S_manifests = 'manifests' # special manifest repository 116S_manifests = 'manifests' # special manifest repository
85REPO_MAIN = S_repo + '/main.py' # main script 117REPO_MAIN = S_repo + '/main.py' # main script
86 118
@@ -131,7 +163,7 @@ group.add_option('-g', '--groups',
131 metavar='GROUP') 163 metavar='GROUP')
132group.add_option('-p', '--platform', 164group.add_option('-p', '--platform',
133 dest='platform', default="auto", 165 dest='platform', default="auto",
134 help='restrict manifest projects to ones with a specified' 166 help='restrict manifest projects to ones with a specified '
135 'platform group [auto|all|none|linux|darwin|...]', 167 'platform group [auto|all|none|linux|darwin|...]',
136 metavar='PLATFORM') 168 metavar='PLATFORM')
137 169
@@ -197,8 +229,8 @@ def _Init(args):
197 229
198 _CheckGitVersion() 230 _CheckGitVersion()
199 try: 231 try:
200 if _NeedSetupGnuPG(): 232 if NeedSetupGnuPG():
201 can_verify = _SetupGnuPG(opt.quiet) 233 can_verify = SetupGnuPG(opt.quiet)
202 else: 234 else:
203 can_verify = True 235 can_verify = True
204 236
@@ -247,7 +279,7 @@ def _CheckGitVersion():
247 raise CloneFailure() 279 raise CloneFailure()
248 280
249 281
250def _NeedSetupGnuPG(): 282def NeedSetupGnuPG():
251 if not os.path.isdir(home_dot_repo): 283 if not os.path.isdir(home_dot_repo):
252 return True 284 return True
253 285
@@ -265,7 +297,7 @@ def _NeedSetupGnuPG():
265 return False 297 return False
266 298
267 299
268def _SetupGnuPG(quiet): 300def SetupGnuPG(quiet):
269 if not os.path.isdir(home_dot_repo): 301 if not os.path.isdir(home_dot_repo):
270 try: 302 try:
271 os.mkdir(home_dot_repo) 303 os.mkdir(home_dot_repo)
diff --git a/subcmds/grep.py b/subcmds/grep.py
index 0dc8f9f6..b067629a 100644
--- a/subcmds/grep.py
+++ b/subcmds/grep.py
@@ -51,7 +51,7 @@ Examples
51 51
52Look for a line that has '#define' and either 'MAX_PATH or 'PATH_MAX': 52Look for a line that has '#define' and either 'MAX_PATH or 'PATH_MAX':
53 53
54 repo grep -e '#define' --and -\( -e MAX_PATH -e PATH_MAX \) 54 repo grep -e '#define' --and -\\( -e MAX_PATH -e PATH_MAX \\)
55 55
56Look for a line that has 'NODE' or 'Unexpected' in files that 56Look for a line that has 'NODE' or 'Unexpected' in files that
57contain a line that matches both expressions: 57contain a line that matches both expressions:
diff --git a/subcmds/init.py b/subcmds/init.py
index b6b98076..48df9e89 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -147,7 +147,7 @@ to update the working directory files.
147 r.ResetFetch() 147 r.ResetFetch()
148 r.Save() 148 r.Save()
149 149
150 groups = re.split('[,\s]+', opt.groups) 150 groups = re.split(r'[,\s]+', opt.groups)
151 all_platforms = ['linux', 'darwin'] 151 all_platforms = ['linux', 'darwin']
152 platformize = lambda x: 'platform-' + x 152 platformize = lambda x: 'platform-' + x
153 if opt.platform == 'auto': 153 if opt.platform == 'auto':
@@ -313,6 +313,21 @@ to update the working directory files.
313 # We store the depth in the main manifest project. 313 # We store the depth in the main manifest project.
314 self.manifest.manifestProject.config.SetString('repo.depth', depth) 314 self.manifest.manifestProject.config.SetString('repo.depth', depth)
315 315
316 def _DisplayResult(self):
317 if self.manifest.IsMirror:
318 init_type = 'mirror '
319 else:
320 init_type = ''
321
322 print ''
323 print 'repo %shas been initialized in %s' % (init_type, self.manifest.topdir)
324
325 current_dir = os.getcwd()
326 if current_dir != self.manifest.topdir:
327 print 'If this is not the directory in which you want to initialize repo, please run:'
328 print ' rm -r %s/.repo' % self.manifest.topdir
329 print 'and try again.'
330
316 def Execute(self, opt, args): 331 def Execute(self, opt, args):
317 git_require(MIN_GIT_VERSION, fail=True) 332 git_require(MIN_GIT_VERSION, fail=True)
318 333
@@ -329,10 +344,4 @@ to update the working directory files.
329 344
330 self._ConfigureDepth(opt) 345 self._ConfigureDepth(opt)
331 346
332 if self.manifest.IsMirror: 347 self._DisplayResult()
333 init_type = 'mirror '
334 else:
335 init_type = ''
336
337 print ''
338 print 'repo %sinitialized in %s' % (init_type, self.manifest.topdir)
diff --git a/subcmds/sync.py b/subcmds/sync.py
index a4ca344a..15f69f7b 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -44,8 +44,9 @@ try:
44except ImportError: 44except ImportError:
45 multiprocessing = None 45 multiprocessing = None
46 46
47from git_command import GIT 47from git_command import GIT, git_require
48from git_refs import R_HEADS, HEAD 48from git_refs import R_HEADS, HEAD
49from main import WrapperModule
49from project import Project 50from project import Project
50from project import RemoteSpec 51from project import RemoteSpec
51from command import Command, MirrorSafeCommand 52from command import Command, MirrorSafeCommand
@@ -309,7 +310,8 @@ later is required to fix a server side protocol bug.
309 return fetched 310 return fetched
310 311
311 def _GCProjects(self, projects): 312 def _GCProjects(self, projects):
312 if multiprocessing: 313 has_dash_c = git_require((1, 7, 2))
314 if multiprocessing and has_dash_c:
313 cpu_count = multiprocessing.cpu_count() 315 cpu_count = multiprocessing.cpu_count()
314 else: 316 else:
315 cpu_count = 1 317 cpu_count = 1
@@ -537,7 +539,7 @@ uncommitted changes are present' % project.relpath
537 mp.PreSync() 539 mp.PreSync()
538 540
539 if opt.repo_upgraded: 541 if opt.repo_upgraded:
540 _PostRepoUpgrade(self.manifest) 542 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
541 543
542 if not opt.local_only: 544 if not opt.local_only:
543 mp.Sync_NetworkHalf(quiet=opt.quiet, 545 mp.Sync_NetworkHalf(quiet=opt.quiet,
@@ -562,31 +564,12 @@ uncommitted changes are present' % project.relpath
562 to_fetch.extend(all_projects) 564 to_fetch.extend(all_projects)
563 to_fetch.sort(key=self._fetch_times.Get, reverse=True) 565 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
564 566
565 fetched = self._Fetch(to_fetch, opt) 567 self._Fetch(to_fetch, opt)
566 _PostRepoFetch(rp, opt.no_repo_verify) 568 _PostRepoFetch(rp, opt.no_repo_verify)
567 if opt.network_only: 569 if opt.network_only:
568 # bail out now; the rest touches the working tree 570 # bail out now; the rest touches the working tree
569 return 571 return
570 572
571 # Iteratively fetch missing and/or nested unregistered submodules
572 previously_missing_set = set()
573 while True:
574 self.manifest._Unload()
575 all_projects = self.GetProjects(args, missing_ok=True)
576 missing = []
577 for project in all_projects:
578 if project.gitdir not in fetched:
579 missing.append(project)
580 if not missing:
581 break
582 # Stop us from non-stopped fetching actually-missing repos: If set of
583 # missing repos has not been changed from last fetch, we break.
584 missing_set = set(p.name for p in missing)
585 if previously_missing_set == missing_set:
586 break
587 previously_missing_set = missing_set
588 fetched.update(self._Fetch(missing, opt))
589
590 if self.manifest.IsMirror: 573 if self.manifest.IsMirror:
591 # bail out now, we have no working tree 574 # bail out now, we have no working tree
592 return 575 return
@@ -611,7 +594,10 @@ uncommitted changes are present' % project.relpath
611 if self.manifest.notice: 594 if self.manifest.notice:
612 print self.manifest.notice 595 print self.manifest.notice
613 596
614def _PostRepoUpgrade(manifest): 597def _PostRepoUpgrade(manifest, quiet=False):
598 wrapper = WrapperModule()
599 if wrapper.NeedSetupGnuPG():
600 wrapper.SetupGnuPG(quiet)
615 for project in manifest.projects.values(): 601 for project in manifest.projects.values():
616 if project.Exists: 602 if project.Exists:
617 project.PostRepoUpgrade() 603 project.PostRepoUpgrade()
@@ -711,7 +697,7 @@ class _FetchTimes(object):
711 try: 697 try:
712 try: 698 try:
713 self._times = pickle.load(f) 699 self._times = pickle.load(f)
714 except: 700 except IOError:
715 try: 701 try:
716 os.remove(self._path) 702 os.remove(self._path)
717 except OSError: 703 except OSError:
diff --git a/subcmds/upload.py b/subcmds/upload.py
index 84a5e440..39721ac2 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -297,7 +297,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
297 try: 297 try:
298 # refs/changes/XYZ/N --> XYZ 298 # refs/changes/XYZ/N --> XYZ
299 return refs.get(last_pub).split('/')[-2] 299 return refs.get(last_pub).split('/')[-2]
300 except: 300 except (AttributeError, IndexError):
301 return "" 301 return ""
302 302
303 def _UploadAndReport(self, opt, todo, original_people): 303 def _UploadAndReport(self, opt, todo, original_people):