summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.pydevproject2
-rw-r--r--.pylintrc301
-rw-r--r--SUBMITTING_PATCHES12
-rw-r--r--command.py76
-rw-r--r--docs/manifest-format.txt15
-rw-r--r--editor.py2
-rw-r--r--git_command.py2
-rw-r--r--git_config.py6
-rwxr-xr-xmain.py10
-rw-r--r--manifest_xml.py112
-rw-r--r--project.py189
-rwxr-xr-xrepo20
-rw-r--r--subcmds/forall.py4
-rw-r--r--subcmds/init.py2
-rw-r--r--subcmds/list.py38
-rw-r--r--subcmds/sync.py27
-rw-r--r--subcmds/upload.py4
17 files changed, 723 insertions, 99 deletions
diff --git a/.pydevproject b/.pydevproject
index b74568ab..880abd62 100644
--- a/.pydevproject
+++ b/.pydevproject
@@ -5,6 +5,6 @@
5<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH"> 5<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
6<path>/repo</path> 6<path>/repo</path>
7</pydev_pathproperty> 7</pydev_pathproperty>
8<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.4</pydev_property> 8<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</pydev_property>
9<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property> 9<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
10</pydev_project> 10</pydev_project>
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 00000000..9f81ee15
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,301 @@
1# lint Python modules using external checkers.
2#
3# This is the main checker controling the other ones and the reports
4# generation. It is itself both a raw checker and an astng checker in order
5# to:
6# * handle message activation / deactivation at the module level
7# * handle some basic but necessary stats'data (number of classes, methods...)
8#
9[MASTER]
10
11# Specify a configuration file.
12#rcfile=
13
14# Python code to execute, usually for sys.path manipulation such as
15# pygtk.require().
16#init-hook=
17
18# Profiled execution.
19profile=no
20
21# Add <file or directory> to the black list. It should be a base name, not a
22# path. You may set this option multiple times.
23ignore=SVN
24
25# Pickle collected data for later comparisons.
26persistent=yes
27
28# Set the cache size for astng objects.
29cache-size=500
30
31# List of plugins (as comma separated values of python modules names) to load,
32# usually to register additional checkers.
33load-plugins=
34
35
36[MESSAGES CONTROL]
37
38# Enable only checker(s) with the given id(s). This option conflicts with the
39# disable-checker option
40#enable-checker=
41
42# Enable all checker(s) except those with the given id(s). This option
43# conflicts with the enable-checker option
44#disable-checker=
45
46# Enable all messages in the listed categories.
47#enable-msg-cat=
48
49# Disable all messages in the listed categories.
50#disable-msg-cat=
51
52# Enable the message(s) with the given id(s).
53enable=RP0004
54
55# Disable the message(s) with the given id(s).
56disable=R0903,R0912,R0913,R0914,R0915,W0141,C0111,C0103,C0323,C0322,C0324,W0603,W0703,R0911,C0301,C0302,R0902,R0904,W0142,W0212,E1101,E1103,R0201,W0201,W0122,W0232,W0311,RP0001,RP0003,RP0101,RP0002,RP0401,RP0701,RP0801
57
58[REPORTS]
59
60# set the output format. Available formats are text, parseable, colorized, msvs
61# (visual studio) and html
62output-format=text
63
64# Include message's id in output
65include-ids=yes
66
67# Put messages in a separate file for each module / package specified on the
68# command line instead of printing them on stdout. Reports (if any) will be
69# written in a file name "pylint_global.[txt|html]".
70files-output=no
71
72# Tells whether to display a full report or only the messages
73reports=yes
74
75# Python expression which should return a note less than 10 (10 is the highest
76# note).You have access to the variables errors warning, statement which
77# respectivly contain the number of errors / warnings messages and the total
78# number of statements analyzed. This is used by the global evaluation report
79# (R0004).
80evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
81
82# Add a comment according to your evaluation note. This is used by the global
83# evaluation report (R0004).
84comment=no
85
86# checks for
87# * unused variables / imports
88# * undefined variables
89# * redefinition of variable from builtins or from an outer scope
90# * use of variable before assigment
91#
92[VARIABLES]
93
94# Tells whether we should check for unused import in __init__ files.
95init-import=no
96
97# A regular expression matching names used for dummy variables (i.e. not used).
98dummy-variables-rgx=_|dummy
99
100# List of additional names supposed to be defined in builtins. Remember that
101# you should avoid to define new builtins when possible.
102additional-builtins=
103
104
105# try to find bugs in the code using type inference
106#
107[TYPECHECK]
108
109# Tells whether missing members accessed in mixin class should be ignored. A
110# mixin class is detected if its name ends with "mixin" (case insensitive).
111ignore-mixin-members=yes
112
113# List of classes names for which member attributes should not be checked
114# (useful for classes with attributes dynamicaly set).
115ignored-classes=SQLObject
116
117# When zope mode is activated, consider the acquired-members option to ignore
118# access to some undefined attributes.
119zope=no
120
121# List of members which are usually get through zope's acquisition mecanism and
122# so shouldn't trigger E0201 when accessed (need zope=yes to be considered).
123acquired-members=REQUEST,acl_users,aq_parent
124
125
126# checks for :
127# * doc strings
128# * modules / classes / functions / methods / arguments / variables name
129# * number of arguments, local variables, branchs, returns and statements in
130# functions, methods
131# * required module attributes
132# * dangerous default values as arguments
133# * redefinition of function / method / class
134# * uses of the global statement
135#
136[BASIC]
137
138# Required attributes for module, separated by a comma
139required-attributes=
140
141# Regular expression which should only match functions or classes name which do
142# not require a docstring
143no-docstring-rgx=_main|__.*__
144
145# Regular expression which should only match correct module names
146module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
147
148# Regular expression which should only match correct module level names
149const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))|(log)$
150
151# Regular expression which should only match correct class names
152class-rgx=[A-Z_][a-zA-Z0-9]+$
153
154# Regular expression which should only match correct function names
155function-rgx=[a-z_][a-z0-9_]{2,30}$
156
157# Regular expression which should only match correct method names
158method-rgx=[a-z_][a-z0-9_]{2,30}$
159
160# Regular expression which should only match correct instance attribute names
161attr-rgx=[a-z_][a-z0-9_]{2,30}$
162
163# Regular expression which should only match correct argument names
164argument-rgx=[a-z_][a-z0-9_]{2,30}$
165
166# Regular expression which should only match correct variable names
167variable-rgx=[a-z_][a-z0-9_]{2,30}$
168
169# Regular expression which should only match correct list comprehension /
170# generator expression variable names
171inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
172
173# Good variable names which should always be accepted, separated by a comma
174good-names=i,j,k,ex,Run,_,e,d1,d2,v,f,l,d
175
176# Bad variable names which should always be refused, separated by a comma
177bad-names=foo,bar,baz,toto,tutu,tata
178
179# List of builtins function names that should not be used, separated by a comma
180bad-functions=map,filter,apply,input
181
182
183# checks for sign of poor/misdesign:
184# * number of methods, attributes, local variables...
185# * size, complexity of functions, methods
186#
187[DESIGN]
188
189# Maximum number of arguments for function / method
190max-args=5
191
192# Maximum number of locals for function / method body
193max-locals=15
194
195# Maximum number of return / yield for function / method body
196max-returns=6
197
198# Maximum number of branch for function / method body
199max-branchs=12
200
201# Maximum number of statements in function / method body
202max-statements=50
203
204# Maximum number of parents for a class (see R0901).
205max-parents=7
206
207# Maximum number of attributes for a class (see R0902).
208max-attributes=20
209
210# Minimum number of public methods for a class (see R0903).
211min-public-methods=2
212
213# Maximum number of public methods for a class (see R0904).
214max-public-methods=30
215
216
217# checks for
218# * external modules dependencies
219# * relative / wildcard imports
220# * cyclic imports
221# * uses of deprecated modules
222#
223[IMPORTS]
224
225# Deprecated modules which should not be used, separated by a comma
226deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
227
228# Create a graph of every (i.e. internal and external) dependencies in the
229# given file (report R0402 must not be disabled)
230import-graph=
231
232# Create a graph of external dependencies in the given file (report R0402 must
233# not be disabled)
234ext-import-graph=
235
236# Create a graph of internal dependencies in the given file (report R0402 must
237# not be disabled)
238int-import-graph=
239
240
241# checks for :
242# * methods without self as first argument
243# * overridden methods signature
244# * access only to existant members via self
245# * attributes not defined in the __init__ method
246# * supported interfaces implementation
247# * unreachable code
248#
249[CLASSES]
250
251# List of interface methods to ignore, separated by a comma. This is used for
252# instance to not check methods defines in Zope's Interface base class.
253ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
254
255# List of method names used to declare (i.e. assign) instance attributes.
256defining-attr-methods=__init__,__new__,setUp
257
258
259# checks for similarities and duplicated code. This computation may be
260# memory / CPU intensive, so you should disable it if you experiments some
261# problems.
262#
263[SIMILARITIES]
264
265# Minimum lines number of a similarity.
266min-similarity-lines=4
267
268# Ignore comments when computing similarities.
269ignore-comments=yes
270
271# Ignore docstrings when computing similarities.
272ignore-docstrings=yes
273
274
275# checks for:
276# * warning notes in the code like FIXME, XXX
277# * PEP 263: source code with non ascii character but no encoding declaration
278#
279[MISCELLANEOUS]
280
281# List of note tags to take in consideration, separated by a comma.
282notes=FIXME,XXX,TODO
283
284
285# checks for :
286# * unauthorized constructions
287# * strict indentation
288# * line length
289# * use of <> instead of !=
290#
291[FORMAT]
292
293# Maximum number of characters on a single line.
294max-line-length=80
295
296# Maximum number of lines in a module
297max-module-lines=1000
298
299# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
300# tab). In repo it is 2 spaces.
301indent-string=' '
diff --git a/SUBMITTING_PATCHES b/SUBMITTING_PATCHES
index cba67416..50e2cf77 100644
--- a/SUBMITTING_PATCHES
+++ b/SUBMITTING_PATCHES
@@ -2,6 +2,7 @@ Short Version:
2 2
3 - Make small logical changes. 3 - Make small logical changes.
4 - Provide a meaningful commit message. 4 - Provide a meaningful commit message.
5 - Check for coding errors with pylint
5 - Make sure all code is under the Apache License, 2.0. 6 - Make sure all code is under the Apache License, 2.0.
6 - Publish your changes for review: 7 - Publish your changes for review:
7 8
@@ -33,7 +34,14 @@ If your description starts to get too long, that's a sign that you
33probably need to split up your commit to finer grained pieces. 34probably need to split up your commit to finer grained pieces.
34 35
35 36
36(2) Check the license 37(2) Check for coding errors with pylint
38
39Run pylint on changed modules using the provided configuration:
40
41 pylint --rcfile=.pylintrc file.py
42
43
44(3) Check the license
37 45
38repo is licensed under the Apache License, 2.0. 46repo is licensed under the Apache License, 2.0.
39 47
@@ -49,7 +57,7 @@ your patch. It is virtually impossible to remove a patch once it
49has been applied and pushed out. 57has been applied and pushed out.
50 58
51 59
52(3) Sending your patches. 60(4) Sending your patches.
53 61
54Do not email your patches to anyone. 62Do not email your patches to anyone.
55 63
diff --git a/command.py b/command.py
index 5a5f468f..d543e3a8 100644
--- a/command.py
+++ b/command.py
@@ -60,6 +60,32 @@ 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
63 def GetProjects(self, args, missing_ok=False): 89 def GetProjects(self, args, missing_ok=False):
64 """A list of projects that match the arguments. 90 """A list of projects that match the arguments.
65 """ 91 """
@@ -74,40 +100,38 @@ class Command(object):
74 groups = [x for x in re.split('[,\s]+', groups) if x] 100 groups = [x for x in re.split('[,\s]+', groups) if x]
75 101
76 if not args: 102 if not args:
77 for project in all_projects.values(): 103 all_projects_list = 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:
78 if ((missing_ok or project.Exists) and 113 if ((missing_ok or project.Exists) and
79 project.MatchesGroups(groups)): 114 project.MatchesGroups(groups)):
80 result.append(project) 115 result.append(project)
81 else: 116 else:
82 by_path = None 117 self._ResetPathToProjectMap(all_projects.values())
83 118
84 for arg in args: 119 for arg in args:
85 project = all_projects.get(arg) 120 project = all_projects.get(arg)
86 121
87 if not project: 122 if not project:
88 path = os.path.abspath(arg).replace('\\', '/') 123 path = os.path.abspath(arg).replace('\\', '/')
89 124 project = self._GetProjectByPath(path)
90 if not by_path: 125
91 by_path = dict() 126 # If it's not a derived project, update path->project mapping and
92 for p in all_projects.values(): 127 # search again, as arg might actually point to a derived subproject.
93 by_path[p.worktree] = p 128 if project and not project.Derived:
94 129 search_again = False
95 if os.path.exists(path): 130 for subproject in project.GetDerivedSubprojects():
96 oldpath = None 131 self._UpdatePathToProjectMap(subproject)
97 while path \ 132 search_again = True
98 and path != oldpath \ 133 if search_again:
99 and path != self.manifest.topdir: 134 project = self._GetProjectByPath(path) or project
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
111 135
112 if not project: 136 if not project:
113 raise NoSuchProjectError(arg) 137 raise NoSuchProjectError(arg)
@@ -123,7 +147,7 @@ class Command(object):
123 result.sort(key=_getpath) 147 result.sort(key=_getpath)
124 return result 148 return result
125 149
126# pylint: disable-msg=W0223 150# pylint: disable=W0223
127# Pylint warns that the `InteractiveCommand` and `PagedCommand` classes do not 151# Pylint warns that the `InteractiveCommand` and `PagedCommand` classes do not
128# override method `Execute` which is abstract in `Command`. Since that method 152# override method `Execute` which is abstract in `Command`. Since that method
129# is always implemented in classes derived from `InteractiveCommand` and 153# is always implemented in classes derived from `InteractiveCommand` and
@@ -142,7 +166,7 @@ class PagedCommand(Command):
142 def WantPager(self, opt): 166 def WantPager(self, opt):
143 return True 167 return True
144 168
145# pylint: enable-msg=W0223 169# pylint: enable=W0223
146 170
147class MirrorSafeCommand(object): 171class MirrorSafeCommand(object):
148 """Command permits itself to run within a mirror, 172 """Command permits itself to run within a mirror,
diff --git a/docs/manifest-format.txt b/docs/manifest-format.txt
index f499868c..a36af67c 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest-format.txt
@@ -45,7 +45,8 @@ 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*)>
49 <!ATTLIST project name CDATA #REQUIRED> 50 <!ATTLIST project name CDATA #REQUIRED>
50 <!ATTLIST project path CDATA #IMPLIED> 51 <!ATTLIST project path CDATA #IMPLIED>
51 <!ATTLIST project remote IDREF #IMPLIED> 52 <!ATTLIST project remote IDREF #IMPLIED>
@@ -152,7 +153,10 @@ Element project
152 153
153One or more project elements may be specified. Each element 154One or more project elements may be specified. Each element
154describes a single Git repository to be cloned into the repo 155describes a single Git repository to be cloned into the repo
155client workspace. 156client workspace. You may specify Git-submodules by creating a
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.
156 160
157Attribute `name`: A unique name for this project. The project's 161Attribute `name`: A unique name for this project. The project's
158name is appended onto its remote's fetch URL to generate the actual 162name is appended onto its remote's fetch URL to generate the actual
@@ -163,7 +167,8 @@ URL to configure the Git remote with. The URL gets formed as:
163where ${remote_fetch} is the remote's fetch attribute and 167where ${remote_fetch} is the remote's fetch attribute and
164${project_name} is the project's name attribute. The suffix ".git" 168${project_name} is the project's name attribute. The suffix ".git"
165is always appended as repo assumes the upstream is a forest of 169is always appended as repo assumes the upstream is a forest of
166bare Git repositories. 170bare Git repositories. If the project has a parent element, its
171name will be prefixed by the parent's.
167 172
168The project name must match the name Gerrit knows, if Gerrit is 173The project name must match the name Gerrit knows, if Gerrit is
169being used for code reviews. 174being used for code reviews.
@@ -171,6 +176,8 @@ being used for code reviews.
171Attribute `path`: An optional path relative to the top directory 176Attribute `path`: An optional path relative to the top directory
172of the repo client where the Git working directory for this project 177of the repo client where the Git working directory for this project
173should be placed. If not supplied the project name is used. 178should 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.
174 181
175Attribute `remote`: Name of a previously defined remote element. 182Attribute `remote`: Name of a previously defined remote element.
176If not supplied the remote given by the default element is used. 183If not supplied the remote given by the default element is used.
@@ -190,6 +197,8 @@ its name:`name` and path:`path`. E.g. for
190definition is implicitly in the following manifest groups: 197definition is implicitly in the following manifest groups:
191default, name:monkeys, and path:barrel-of. If you place a project in the 198default, name:monkeys, and path:barrel-of. If you place a project in the
192group "notdefault", it will not be automatically downloaded by repo. 199group "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.
193 202
194Element annotation 203Element annotation
195------------------ 204------------------
diff --git a/editor.py b/editor.py
index 62afbb91..489c6cd3 100644
--- a/editor.py
+++ b/editor.py
@@ -91,7 +91,7 @@ least one of these before using this command."""
91 91
92 try: 92 try:
93 rc = subprocess.Popen(args, shell=shell).wait() 93 rc = subprocess.Popen(args, shell=shell).wait()
94 except OSError, e: 94 except OSError as e:
95 raise EditorError('editor failed, %s: %s %s' 95 raise EditorError('editor failed, %s: %s %s'
96 % (str(e), editor, path)) 96 % (str(e), editor, path))
97 if rc != 0: 97 if rc != 0:
diff --git a/git_command.py b/git_command.py
index 82709b91..a40e6c05 100644
--- a/git_command.py
+++ b/git_command.py
@@ -217,7 +217,7 @@ class GitCommand(object):
217 stdin = stdin, 217 stdin = stdin,
218 stdout = stdout, 218 stdout = stdout,
219 stderr = stderr) 219 stderr = stderr)
220 except Exception, e: 220 except Exception as e:
221 raise GitError('%s: %s' % (command[1], e)) 221 raise GitError('%s: %s' % (command[1], e))
222 222
223 if ssh_proxy: 223 if ssh_proxy:
diff --git a/git_config.py b/git_config.py
index afaa6f15..ae288558 100644
--- a/git_config.py
+++ b/git_config.py
@@ -449,7 +449,7 @@ def _open_ssh(host, port=None):
449 try: 449 try:
450 Trace(': %s', ' '.join(command)) 450 Trace(': %s', ' '.join(command))
451 p = subprocess.Popen(command) 451 p = subprocess.Popen(command)
452 except Exception, e: 452 except Exception as e:
453 _ssh_master = False 453 _ssh_master = False
454 print >>sys.stderr, \ 454 print >>sys.stderr, \
455 '\nwarn: cannot enable ssh control master for %s:%s\n%s' \ 455 '\nwarn: cannot enable ssh control master for %s:%s\n%s' \
@@ -592,9 +592,9 @@ class Remote(object):
592 else: 592 else:
593 host, port = info.split() 593 host, port = info.split()
594 self._review_url = self._SshReviewUrl(userEmail, host, port) 594 self._review_url = self._SshReviewUrl(userEmail, host, port)
595 except urllib2.HTTPError, e: 595 except urllib2.HTTPError as e:
596 raise UploadError('%s: %s' % (self.review, str(e))) 596 raise UploadError('%s: %s' % (self.review, str(e)))
597 except urllib2.URLError, e: 597 except urllib2.URLError as e:
598 raise UploadError('%s: %s' % (self.review, str(e))) 598 raise UploadError('%s: %s' % (self.review, str(e)))
599 599
600 REVIEW_CACHE[u] = self._review_url 600 REVIEW_CACHE[u] = self._review_url
diff --git a/main.py b/main.py
index 278fd36f..d993ee4e 100755
--- a/main.py
+++ b/main.py
@@ -146,13 +146,13 @@ class _Repo(object):
146 else: 146 else:
147 print >>sys.stderr, 'real\t%dh%dm%.3fs' \ 147 print >>sys.stderr, 'real\t%dh%dm%.3fs' \
148 % (hours, minutes, seconds) 148 % (hours, minutes, seconds)
149 except DownloadError, e: 149 except DownloadError as e:
150 print >>sys.stderr, 'error: %s' % str(e) 150 print >>sys.stderr, 'error: %s' % str(e)
151 return 1 151 return 1
152 except ManifestInvalidRevisionError, e: 152 except ManifestInvalidRevisionError as e:
153 print >>sys.stderr, 'error: %s' % str(e) 153 print >>sys.stderr, 'error: %s' % str(e)
154 return 1 154 return 1
155 except NoSuchProjectError, e: 155 except NoSuchProjectError as e:
156 if e.name: 156 if e.name:
157 print >>sys.stderr, 'error: project %s not found' % e.name 157 print >>sys.stderr, 'error: project %s not found' % e.name
158 else: 158 else:
@@ -390,14 +390,14 @@ def _Main(argv):
390 close_ssh() 390 close_ssh()
391 except KeyboardInterrupt: 391 except KeyboardInterrupt:
392 result = 1 392 result = 1
393 except RepoChangedException, rce: 393 except RepoChangedException as rce:
394 # If repo changed, re-exec ourselves. 394 # If repo changed, re-exec ourselves.
395 # 395 #
396 argv = list(sys.argv) 396 argv = list(sys.argv)
397 argv.extend(rce.extra_args) 397 argv.extend(rce.extra_args)
398 try: 398 try:
399 os.execv(__file__, argv) 399 os.execv(__file__, argv)
400 except OSError, e: 400 except OSError as e:
401 print >>sys.stderr, 'fatal: cannot restart repo after upgrade' 401 print >>sys.stderr, 'fatal: cannot restart repo after upgrade'
402 print >>sys.stderr, 'fatal: %s' % e 402 print >>sys.stderr, 'fatal: %s' % e
403 result = 128 403 result = 128
diff --git a/manifest_xml.py b/manifest_xml.py
index 04cabaad..11e4ee54 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -180,20 +180,25 @@ class XmlManifest(object):
180 root.appendChild(e) 180 root.appendChild(e)
181 root.appendChild(doc.createTextNode('')) 181 root.appendChild(doc.createTextNode(''))
182 182
183 sort_projects = list(self.projects.keys()) 183 def output_projects(parent, parent_node, projects):
184 sort_projects.sort() 184 for p in projects:
185 185 output_project(parent, parent_node, self.projects[p])
186 for p in sort_projects:
187 p = self.projects[p]
188 186
187 def output_project(parent, parent_node, p):
189 if not p.MatchesGroups(groups): 188 if not p.MatchesGroups(groups):
190 continue 189 return
190
191 name = p.name
192 relpath = p.relpath
193 if parent:
194 name = self._UnjoinName(parent.name, name)
195 relpath = self._UnjoinRelpath(parent.relpath, relpath)
191 196
192 e = doc.createElement('project') 197 e = doc.createElement('project')
193 root.appendChild(e) 198 parent_node.appendChild(e)
194 e.setAttribute('name', p.name) 199 e.setAttribute('name', name)
195 if p.relpath != p.name: 200 if relpath != name:
196 e.setAttribute('path', p.relpath) 201 e.setAttribute('path', relpath)
197 if not d.remote or p.remote.name != d.remote.name: 202 if not d.remote or p.remote.name != d.remote.name:
198 e.setAttribute('remote', p.remote.name) 203 e.setAttribute('remote', p.remote.name)
199 if peg_rev: 204 if peg_rev:
@@ -231,6 +236,16 @@ class XmlManifest(object):
231 if p.sync_c: 236 if p.sync_c:
232 e.setAttribute('sync-c', 'true') 237 e.setAttribute('sync-c', 'true')
233 238
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
234 if self._repo_hooks_project: 249 if self._repo_hooks_project:
235 root.appendChild(doc.createTextNode('')) 250 root.appendChild(doc.createTextNode(''))
236 e = doc.createElement('repo-hooks') 251 e = doc.createElement('repo-hooks')
@@ -321,7 +336,7 @@ class XmlManifest(object):
321 raise ManifestParseError("no <manifest> in %s" % (path,)) 336 raise ManifestParseError("no <manifest> in %s" % (path,))
322 337
323 nodes = [] 338 nodes = []
324 for node in manifest.childNodes: # pylint:disable-msg=W0631 339 for node in manifest.childNodes: # pylint:disable=W0631
325 # We only get here if manifest is initialised 340 # We only get here if manifest is initialised
326 if node.nodeName == 'include': 341 if node.nodeName == 'include':
327 name = self._reqatt(node, 'name') 342 name = self._reqatt(node, 'name')
@@ -336,7 +351,7 @@ class XmlManifest(object):
336 # tricky. actual parsing implementation may vary. 351 # tricky. actual parsing implementation may vary.
337 except (KeyboardInterrupt, RuntimeError, SystemExit): 352 except (KeyboardInterrupt, RuntimeError, SystemExit):
338 raise 353 raise
339 except Exception, e: 354 except Exception as e:
340 raise ManifestParseError( 355 raise ManifestParseError(
341 "failed parsing included manifest %s: %s", (name, e)) 356 "failed parsing included manifest %s: %s", (name, e))
342 else: 357 else:
@@ -383,11 +398,15 @@ class XmlManifest(object):
383 for node in itertools.chain(*node_list): 398 for node in itertools.chain(*node_list):
384 if node.nodeName == 'project': 399 if node.nodeName == 'project':
385 project = self._ParseProject(node) 400 project = self._ParseProject(node)
386 if self._projects.get(project.name): 401 def recursively_add_projects(project):
387 raise ManifestParseError( 402 if self._projects.get(project.name):
388 'duplicate project %s in %s' % 403 raise ManifestParseError(
389 (project.name, self.manifestFile)) 404 'duplicate project %s in %s' %
390 self._projects[project.name] = project 405 (project.name, self.manifestFile))
406 self._projects[project.name] = project
407 for subproject in project.subprojects:
408 recursively_add_projects(subproject)
409 recursively_add_projects(project)
391 if node.nodeName == 'repo-hooks': 410 if node.nodeName == 'repo-hooks':
392 # Get the name of the project and the (space-separated) list of enabled. 411 # Get the name of the project and the (space-separated) list of enabled.
393 repo_hooks_project = self._reqatt(node, 'in-project') 412 repo_hooks_project = self._reqatt(node, 'in-project')
@@ -537,11 +556,19 @@ class XmlManifest(object):
537 556
538 return '\n'.join(cleanLines) 557 return '\n'.join(cleanLines)
539 558
540 def _ParseProject(self, node): 559 def _JoinName(self, parent_name, name):
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):
541 """ 566 """
542 reads a <project> element from the manifest file 567 reads a <project> element from the manifest file
543 """ 568 """
544 name = self._reqatt(node, 'name') 569 name = self._reqatt(node, 'name')
570 if parent:
571 name = self._JoinName(parent.name, name)
545 572
546 remote = self._get_remote(node) 573 remote = self._get_remote(node)
547 if remote is None: 574 if remote is None:
@@ -586,37 +613,66 @@ class XmlManifest(object):
586 groups = node.getAttribute('groups') 613 groups = node.getAttribute('groups')
587 groups = [x for x in re.split('[,\s]+', groups) if x] 614 groups = [x for x in re.split('[,\s]+', groups) if x]
588 615
589 default_groups = ['all', 'name:%s' % name, 'path:%s' % path] 616 if parent is None:
590 groups.extend(set(default_groups).difference(groups)) 617 relpath, worktree, gitdir = self.GetProjectPaths(name, path)
591
592 if self.IsMirror:
593 worktree = None
594 gitdir = os.path.join(self.topdir, '%s.git' % name)
595 else: 618 else:
596 worktree = os.path.join(self.topdir, path).replace('\\', '/') 619 relpath, worktree, gitdir = self.GetSubprojectPaths(parent, path)
597 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path) 620
621 default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath]
622 groups.extend(set(default_groups).difference(groups))
598 623
599 project = Project(manifest = self, 624 project = Project(manifest = self,
600 name = name, 625 name = name,
601 remote = remote.ToRemoteSpec(name), 626 remote = remote.ToRemoteSpec(name),
602 gitdir = gitdir, 627 gitdir = gitdir,
603 worktree = worktree, 628 worktree = worktree,
604 relpath = path, 629 relpath = relpath,
605 revisionExpr = revisionExpr, 630 revisionExpr = revisionExpr,
606 revisionId = None, 631 revisionId = None,
607 rebase = rebase, 632 rebase = rebase,
608 groups = groups, 633 groups = groups,
609 sync_c = sync_c, 634 sync_c = sync_c,
610 upstream = upstream) 635 upstream = upstream,
636 parent = parent)
611 637
612 for n in node.childNodes: 638 for n in node.childNodes:
613 if n.nodeName == 'copyfile': 639 if n.nodeName == 'copyfile':
614 self._ParseCopyFile(project, n) 640 self._ParseCopyFile(project, n)
615 if n.nodeName == 'annotation': 641 if n.nodeName == 'annotation':
616 self._ParseAnnotation(project, n) 642 self._ParseAnnotation(project, n)
643 if n.nodeName == 'project':
644 project.subprojects.append(self._ParseProject(n, parent = project))
617 645
618 return project 646 return project
619 647
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
620 def _ParseCopyFile(self, project, node): 676 def _ParseCopyFile(self, project, node):
621 src = self._reqatt(node, 'src') 677 src = self._reqatt(node, 'src')
622 dest = self._reqatt(node, 'dest') 678 dest = self._reqatt(node, 'dest')
diff --git a/project.py b/project.py
index 472b1d32..c3452aef 100644
--- a/project.py
+++ b/project.py
@@ -22,6 +22,7 @@ import shutil
22import stat 22import stat
23import subprocess 23import subprocess
24import sys 24import sys
25import tempfile
25import time 26import time
26 27
27from color import Coloring 28from color import Coloring
@@ -484,7 +485,28 @@ class Project(object):
484 rebase = True, 485 rebase = True,
485 groups = None, 486 groups = None,
486 sync_c = False, 487 sync_c = False,
487 upstream = None): 488 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 """
488 self.manifest = manifest 510 self.manifest = manifest
489 self.name = name 511 self.name = name
490 self.remote = remote 512 self.remote = remote
@@ -507,6 +529,9 @@ class Project(object):
507 self.groups = groups 529 self.groups = groups
508 self.sync_c = sync_c 530 self.sync_c = sync_c
509 self.upstream = upstream 531 self.upstream = upstream
532 self.parent = parent
533 self.is_derived = is_derived
534 self.subprojects = []
510 535
511 self.snapshots = {} 536 self.snapshots = {}
512 self.copyfiles = [] 537 self.copyfiles = []
@@ -527,6 +552,14 @@ class Project(object):
527 self.enabled_repo_hooks = [] 552 self.enabled_repo_hooks = []
528 553
529 @property 554 @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
530 def Exists(self): 563 def Exists(self):
531 return os.path.isdir(self.gitdir) 564 return os.path.isdir(self.gitdir)
532 565
@@ -1044,7 +1077,7 @@ class Project(object):
1044 1077
1045 try: 1078 try:
1046 self._Checkout(revid, quiet=True) 1079 self._Checkout(revid, quiet=True)
1047 except GitError, e: 1080 except GitError as e:
1048 syncbuf.fail(self, e) 1081 syncbuf.fail(self, e)
1049 return 1082 return
1050 self._CopyFiles() 1083 self._CopyFiles()
@@ -1066,7 +1099,7 @@ class Project(object):
1066 branch.name) 1099 branch.name)
1067 try: 1100 try:
1068 self._Checkout(revid, quiet=True) 1101 self._Checkout(revid, quiet=True)
1069 except GitError, e: 1102 except GitError as e:
1070 syncbuf.fail(self, e) 1103 syncbuf.fail(self, e)
1071 return 1104 return
1072 self._CopyFiles() 1105 self._CopyFiles()
@@ -1151,7 +1184,7 @@ class Project(object):
1151 try: 1184 try:
1152 self._ResetHard(revid) 1185 self._ResetHard(revid)
1153 self._CopyFiles() 1186 self._CopyFiles()
1154 except GitError, e: 1187 except GitError as e:
1155 syncbuf.fail(self, e) 1188 syncbuf.fail(self, e)
1156 return 1189 return
1157 else: 1190 else:
@@ -1370,6 +1403,150 @@ class Project(object):
1370 return kept 1403 return kept
1371 1404
1372 1405
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
1373## Direct Git Commands ## 1550## Direct Git Commands ##
1374 1551
1375 def _RemoteFetch(self, name=None, 1552 def _RemoteFetch(self, name=None,
@@ -1723,7 +1900,7 @@ class Project(object):
1723 continue 1900 continue
1724 try: 1901 try:
1725 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst) 1902 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
1726 except OSError, e: 1903 except OSError as e:
1727 if e.errno == errno.EPERM: 1904 if e.errno == errno.EPERM:
1728 raise GitError('filesystem must support symlinks') 1905 raise GitError('filesystem must support symlinks')
1729 else: 1906 else:
@@ -1786,7 +1963,7 @@ class Project(object):
1786 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst) 1963 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
1787 else: 1964 else:
1788 raise GitError('cannot overwrite a local work tree') 1965 raise GitError('cannot overwrite a local work tree')
1789 except OSError, e: 1966 except OSError as e:
1790 if e.errno == errno.EPERM: 1967 if e.errno == errno.EPERM:
1791 raise GitError('filesystem must support symlinks') 1968 raise GitError('filesystem must support symlinks')
1792 else: 1969 else:
diff --git a/repo b/repo
index 32cd1782..7942851b 100755
--- a/repo
+++ b/repo
@@ -185,7 +185,7 @@ def _Init(args):
185 if not os.path.isdir(repodir): 185 if not os.path.isdir(repodir):
186 try: 186 try:
187 os.mkdir(repodir) 187 os.mkdir(repodir)
188 except OSError, e: 188 except OSError as e:
189 print >>sys.stderr, \ 189 print >>sys.stderr, \
190 'fatal: cannot make %s directory: %s' % ( 190 'fatal: cannot make %s directory: %s' % (
191 repodir, e.strerror) 191 repodir, e.strerror)
@@ -221,7 +221,7 @@ def _CheckGitVersion():
221 cmd = [GIT, '--version'] 221 cmd = [GIT, '--version']
222 try: 222 try:
223 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) 223 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
224 except OSError, e: 224 except OSError as e:
225 print >>sys.stderr 225 print >>sys.stderr
226 print >>sys.stderr, "fatal: '%s' is not available" % GIT 226 print >>sys.stderr, "fatal: '%s' is not available" % GIT
227 print >>sys.stderr, 'fatal: %s' % e 227 print >>sys.stderr, 'fatal: %s' % e
@@ -268,7 +268,7 @@ def _SetupGnuPG(quiet):
268 if not os.path.isdir(home_dot_repo): 268 if not os.path.isdir(home_dot_repo):
269 try: 269 try:
270 os.mkdir(home_dot_repo) 270 os.mkdir(home_dot_repo)
271 except OSError, e: 271 except OSError as e:
272 print >>sys.stderr, \ 272 print >>sys.stderr, \
273 'fatal: cannot make %s directory: %s' % ( 273 'fatal: cannot make %s directory: %s' % (
274 home_dot_repo, e.strerror) 274 home_dot_repo, e.strerror)
@@ -277,7 +277,7 @@ def _SetupGnuPG(quiet):
277 if not os.path.isdir(gpg_dir): 277 if not os.path.isdir(gpg_dir):
278 try: 278 try:
279 os.mkdir(gpg_dir, 0700) 279 os.mkdir(gpg_dir, 0700)
280 except OSError, e: 280 except OSError as e:
281 print >>sys.stderr, \ 281 print >>sys.stderr, \
282 'fatal: cannot make %s directory: %s' % ( 282 'fatal: cannot make %s directory: %s' % (
283 gpg_dir, e.strerror) 283 gpg_dir, e.strerror)
@@ -291,7 +291,7 @@ def _SetupGnuPG(quiet):
291 proc = subprocess.Popen(cmd, 291 proc = subprocess.Popen(cmd,
292 env = env, 292 env = env,
293 stdin = subprocess.PIPE) 293 stdin = subprocess.PIPE)
294 except OSError, e: 294 except OSError as e:
295 if not quiet: 295 if not quiet:
296 print >>sys.stderr, 'warning: gpg (GnuPG) is not available.' 296 print >>sys.stderr, 'warning: gpg (GnuPG) is not available.'
297 print >>sys.stderr, 'warning: Installing it is strongly encouraged.' 297 print >>sys.stderr, 'warning: Installing it is strongly encouraged.'
@@ -392,13 +392,13 @@ def _DownloadBundle(url, local, quiet):
392 try: 392 try:
393 try: 393 try:
394 r = urllib2.urlopen(url) 394 r = urllib2.urlopen(url)
395 except urllib2.HTTPError, e: 395 except urllib2.HTTPError as e:
396 if e.code == 404: 396 if e.code == 404:
397 return False 397 return False
398 print >>sys.stderr, 'fatal: Cannot get %s' % url 398 print >>sys.stderr, 'fatal: Cannot get %s' % url
399 print >>sys.stderr, 'fatal: HTTP error %s' % e.code 399 print >>sys.stderr, 'fatal: HTTP error %s' % e.code
400 raise CloneFailure() 400 raise CloneFailure()
401 except urllib2.URLError, e: 401 except urllib2.URLError as e:
402 print >>sys.stderr, 'fatal: Cannot get %s' % url 402 print >>sys.stderr, 'fatal: Cannot get %s' % url
403 print >>sys.stderr, 'fatal: error %s' % e.reason 403 print >>sys.stderr, 'fatal: error %s' % e.reason
404 raise CloneFailure() 404 raise CloneFailure()
@@ -427,7 +427,7 @@ def _Clone(url, local, quiet):
427 """ 427 """
428 try: 428 try:
429 os.mkdir(local) 429 os.mkdir(local)
430 except OSError, e: 430 except OSError as e:
431 print >>sys.stderr, \ 431 print >>sys.stderr, \
432 'fatal: cannot make %s directory: %s' \ 432 'fatal: cannot make %s directory: %s' \
433 % (local, e.strerror) 433 % (local, e.strerror)
@@ -436,7 +436,7 @@ def _Clone(url, local, quiet):
436 cmd = [GIT, 'init', '--quiet'] 436 cmd = [GIT, 'init', '--quiet']
437 try: 437 try:
438 proc = subprocess.Popen(cmd, cwd = local) 438 proc = subprocess.Popen(cmd, cwd = local)
439 except OSError, e: 439 except OSError as e:
440 print >>sys.stderr 440 print >>sys.stderr
441 print >>sys.stderr, "fatal: '%s' is not available" % GIT 441 print >>sys.stderr, "fatal: '%s' is not available" % GIT
442 print >>sys.stderr, 'fatal: %s' % e 442 print >>sys.stderr, 'fatal: %s' % e
@@ -699,7 +699,7 @@ def main(orig_args):
699 me.extend(extra_args) 699 me.extend(extra_args)
700 try: 700 try:
701 os.execv(repo_main, me) 701 os.execv(repo_main, me)
702 except OSError, e: 702 except OSError as e:
703 print >>sys.stderr, "fatal: unable to start %s" % repo_main 703 print >>sys.stderr, "fatal: unable to start %s" % repo_main
704 print >>sys.stderr, "fatal: %s" % e 704 print >>sys.stderr, "fatal: %s" % e
705 sys.exit(148) 705 sys.exit(148)
diff --git a/subcmds/forall.py b/subcmds/forall.py
index 2ece95ed..b633b7d4 100644
--- a/subcmds/forall.py
+++ b/subcmds/forall.py
@@ -143,14 +143,14 @@ terminal and are not redirected.
143 break 143 break
144 else: 144 else:
145 cn = None 145 cn = None
146 # pylint: disable-msg=W0631 146 # pylint: disable=W0631
147 if cn and cn in _CAN_COLOR: 147 if cn and cn in _CAN_COLOR:
148 class ColorCmd(Coloring): 148 class ColorCmd(Coloring):
149 def __init__(self, config, cmd): 149 def __init__(self, config, cmd):
150 Coloring.__init__(self, config, cmd) 150 Coloring.__init__(self, config, cmd)
151 if ColorCmd(self.manifest.manifestProject.config, cn).is_on: 151 if ColorCmd(self.manifest.manifestProject.config, cn).is_on:
152 cmd.insert(cmd.index(cn) + 1, '--color') 152 cmd.insert(cmd.index(cn) + 1, '--color')
153 # pylint: enable-msg=W0631 153 # pylint: enable=W0631
154 154
155 mirror = self.manifest.IsMirror 155 mirror = self.manifest.IsMirror
156 out = ForallColoring(self.manifest.manifestProject.config) 156 out = ForallColoring(self.manifest.manifestProject.config)
diff --git a/subcmds/init.py b/subcmds/init.py
index 007667e2..b6b98076 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -207,7 +207,7 @@ to update the working directory files.
207 207
208 try: 208 try:
209 self.manifest.Link(name) 209 self.manifest.Link(name)
210 except ManifestParseError, e: 210 except ManifestParseError as e:
211 print >>sys.stderr, "fatal: manifest '%s' not available" % name 211 print >>sys.stderr, "fatal: manifest '%s' not available" % name
212 print >>sys.stderr, 'fatal: %s' % str(e) 212 print >>sys.stderr, 'fatal: %s' % str(e)
213 sys.exit(1) 213 sys.exit(1)
diff --git a/subcmds/list.py b/subcmds/list.py
index 2be82570..6058a755 100644
--- a/subcmds/list.py
+++ b/subcmds/list.py
@@ -13,13 +13,16 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16import re
17
16from command import Command, MirrorSafeCommand 18from command import Command, MirrorSafeCommand
17 19
18class List(Command, MirrorSafeCommand): 20class List(Command, MirrorSafeCommand):
19 common = True 21 common = True
20 helpSummary = "List projects and their associated directories" 22 helpSummary = "List projects and their associated directories"
21 helpUsage = """ 23 helpUsage = """
22%prog [<project>...] 24%prog [-f] [<project>...]
25%prog [-f] -r str1 [str2]..."
23""" 26"""
24 helpDescription = """ 27 helpDescription = """
25List all projects; pass '.' to list the project for the cwd. 28List all projects; pass '.' to list the project for the cwd.
@@ -27,6 +30,14 @@ List all projects; pass '.' to list the project for the cwd.
27This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'. 30This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
28""" 31"""
29 32
33 def _Options(self, p, show_smart=True):
34 p.add_option('-r', '--regex',
35 dest='regex', action='store_true',
36 help="Filter the project list based on regex or wildcard matching of strings")
37 p.add_option('-f', '--fullpath',
38 dest='fullpath', action='store_true',
39 help="Display the full work tree path instead of the relative path")
40
30 def Execute(self, opt, args): 41 def Execute(self, opt, args):
31 """List all projects and the associated directories. 42 """List all projects and the associated directories.
32 43
@@ -35,14 +46,33 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
35 discoverable. 46 discoverable.
36 47
37 Args: 48 Args:
38 opt: The options. We don't take any. 49 opt: The options.
39 args: Positional args. Can be a list of projects to list, or empty. 50 args: Positional args. Can be a list of projects to list, or empty.
40 """ 51 """
41 projects = self.GetProjects(args) 52 if not opt.regex:
53 projects = self.GetProjects(args)
54 else:
55 projects = self.FindProjects(args)
56
57 def _getpath(x):
58 if opt.fullpath:
59 return x.worktree
60 return x.relpath
42 61
43 lines = [] 62 lines = []
44 for project in projects: 63 for project in projects:
45 lines.append("%s : %s" % (project.relpath, project.name)) 64 lines.append("%s : %s" % (_getpath(project), project.name))
46 65
47 lines.sort() 66 lines.sort()
48 print '\n'.join(lines) 67 print '\n'.join(lines)
68
69 def FindProjects(self, args):
70 result = []
71 for project in self.GetProjects(''):
72 for arg in args:
73 pattern = re.compile(r'%s' % arg, re.IGNORECASE)
74 if pattern.search(project.name) or pattern.search(project.relpath):
75 result.append(project)
76 break
77 result.sort(key=lambda project: project.relpath)
78 return result
diff --git a/subcmds/sync.py b/subcmds/sync.py
index a8022d9d..27dd877d 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -423,7 +423,7 @@ uncommitted changes are present' % project.relpath
423 # in the .netrc file. 423 # in the .netrc file.
424 print >>sys.stderr, 'No credentials found for %s in .netrc' % \ 424 print >>sys.stderr, 'No credentials found for %s in .netrc' % \
425 parse_result.hostname 425 parse_result.hostname
426 except netrc.NetrcParseError, e: 426 except netrc.NetrcParseError as e:
427 print >>sys.stderr, 'Error parsing .netrc file: %s' % e 427 print >>sys.stderr, 'Error parsing .netrc file: %s' % e
428 428
429 if (username and password): 429 if (username and password):
@@ -470,11 +470,11 @@ uncommitted changes are present' % project.relpath
470 else: 470 else:
471 print >>sys.stderr, 'error: %s' % manifest_str 471 print >>sys.stderr, 'error: %s' % manifest_str
472 sys.exit(1) 472 sys.exit(1)
473 except (socket.error, IOError, xmlrpclib.Fault), e: 473 except (socket.error, IOError, xmlrpclib.Fault) as e:
474 print >>sys.stderr, 'error: cannot connect to manifest server %s:\n%s' % ( 474 print >>sys.stderr, 'error: cannot connect to manifest server %s:\n%s' % (
475 self.manifest.manifest_server, e) 475 self.manifest.manifest_server, e)
476 sys.exit(1) 476 sys.exit(1)
477 except xmlrpclib.ProtocolError, e: 477 except xmlrpclib.ProtocolError as e:
478 print >>sys.stderr, 'error: cannot connect to manifest server %s:\n%d %s' % ( 478 print >>sys.stderr, 'error: cannot connect to manifest server %s:\n%d %s' % (
479 self.manifest.manifest_server, e.errcode, e.errmsg) 479 self.manifest.manifest_server, e.errcode, e.errmsg)
480 sys.exit(1) 480 sys.exit(1)
@@ -512,12 +512,31 @@ uncommitted changes are present' % project.relpath
512 to_fetch.sort(key=self._fetch_times.Get, reverse=True) 512 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
513 self._fetch_times.Clear() 513 self._fetch_times.Clear()
514 514
515 self._Fetch(to_fetch, opt) 515 fetched = self._Fetch(to_fetch, opt)
516 _PostRepoFetch(rp, opt.no_repo_verify) 516 _PostRepoFetch(rp, opt.no_repo_verify)
517 if opt.network_only: 517 if opt.network_only:
518 # bail out now; the rest touches the working tree 518 # bail out now; the rest touches the working tree
519 return 519 return
520 520
521 # Iteratively fetch missing and/or nested unregistered submodules
522 previously_missing_set = set()
523 while True:
524 self.manifest._Unload()
525 all_projects = self.GetProjects(args, missing_ok=True)
526 missing = []
527 for project in all_projects:
528 if project.gitdir not in fetched:
529 missing.append(project)
530 if not missing:
531 break
532 # Stop us from non-stopped fetching actually-missing repos: If set of
533 # missing repos has not been changed from last fetch, we break.
534 missing_set = set(p.name for p in missing)
535 if previously_missing_set == missing_set:
536 break
537 previously_missing_set = missing_set
538 fetched.update(self._Fetch(missing, opt))
539
521 if self.manifest.IsMirror: 540 if self.manifest.IsMirror:
522 # bail out now, we have no working tree 541 # bail out now, we have no working tree
523 return 542 return
diff --git a/subcmds/upload.py b/subcmds/upload.py
index 685e3420..84a5e440 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -329,7 +329,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
329 329
330 branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft) 330 branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft)
331 branch.uploaded = True 331 branch.uploaded = True
332 except UploadError, e: 332 except UploadError as e:
333 branch.error = e 333 branch.error = e
334 branch.uploaded = False 334 branch.uploaded = False
335 have_errors = True 335 have_errors = True
@@ -384,7 +384,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
384 pending_proj_names = [project.name for (project, avail) in pending] 384 pending_proj_names = [project.name for (project, avail) in pending]
385 try: 385 try:
386 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names) 386 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
387 except HookError, e: 387 except HookError as e:
388 print >>sys.stderr, "ERROR: %s" % str(e) 388 print >>sys.stderr, "ERROR: %s" % str(e)
389 return 389 return
390 390