summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.flake83
-rw-r--r--.pylintrc298
-rw-r--r--SUBMITTING_PATCHES.md28
-rw-r--r--docs/manifest-format.txt7
-rw-r--r--git_config.py11
-rw-r--r--manifest_xml.py24
-rw-r--r--project.py57
-rwxr-xr-xrepo3
-rw-r--r--subcmds/abandon.py71
-rw-r--r--subcmds/start.py3
-rw-r--r--subcmds/status.py12
-rw-r--r--subcmds/sync.py110
-rw-r--r--subcmds/upload.py10
13 files changed, 247 insertions, 390 deletions
diff --git a/.flake8 b/.flake8
new file mode 100644
index 00000000..45ab6562
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,3 @@
1[flake8]
2max-line-length=80
3ignore=E111,E114,E402
diff --git a/.pylintrc b/.pylintrc
deleted file mode 100644
index 413d66a1..00000000
--- a/.pylintrc
+++ /dev/null
@@ -1,298 +0,0 @@
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=C0326,R0903,R0912,R0913,R0914,R0915,W0141,C0111,C0103,W0603,W0703,R0911,C0301,C0302,R0902,R0904,W0142,W0212,E1101,E1103,R0201,W0201,W0122,W0232,RP0001,RP0003,RP0101,RP0002,RP0401,RP0701,RP0801,F0401,E0611,R0801,I0011
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# Put messages in a separate file for each module / package specified on the
65# command line instead of printing them on stdout. Reports (if any) will be
66# written in a file name "pylint_global.[txt|html]".
67files-output=no
68
69# Tells whether to display a full report or only the messages
70reports=yes
71
72# Python expression which should return a note less than 10 (10 is the highest
73# note).You have access to the variables errors warning, statement which
74# respectivly contain the number of errors / warnings messages and the total
75# number of statements analyzed. This is used by the global evaluation report
76# (R0004).
77evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
78
79# Add a comment according to your evaluation note. This is used by the global
80# evaluation report (R0004).
81comment=no
82
83# checks for
84# * unused variables / imports
85# * undefined variables
86# * redefinition of variable from builtins or from an outer scope
87# * use of variable before assigment
88#
89[VARIABLES]
90
91# Tells whether we should check for unused import in __init__ files.
92init-import=no
93
94# A regular expression matching names used for dummy variables (i.e. not used).
95dummy-variables-rgx=_|dummy
96
97# List of additional names supposed to be defined in builtins. Remember that
98# you should avoid to define new builtins when possible.
99additional-builtins=
100
101
102# try to find bugs in the code using type inference
103#
104[TYPECHECK]
105
106# Tells whether missing members accessed in mixin class should be ignored. A
107# mixin class is detected if its name ends with "mixin" (case insensitive).
108ignore-mixin-members=yes
109
110# List of classes names for which member attributes should not be checked
111# (useful for classes with attributes dynamicaly set).
112ignored-classes=SQLObject
113
114# When zope mode is activated, consider the acquired-members option to ignore
115# access to some undefined attributes.
116zope=no
117
118# List of members which are usually get through zope's acquisition mecanism and
119# so shouldn't trigger E0201 when accessed (need zope=yes to be considered).
120acquired-members=REQUEST,acl_users,aq_parent
121
122
123# checks for :
124# * doc strings
125# * modules / classes / functions / methods / arguments / variables name
126# * number of arguments, local variables, branchs, returns and statements in
127# functions, methods
128# * required module attributes
129# * dangerous default values as arguments
130# * redefinition of function / method / class
131# * uses of the global statement
132#
133[BASIC]
134
135# Required attributes for module, separated by a comma
136required-attributes=
137
138# Regular expression which should only match functions or classes name which do
139# not require a docstring
140no-docstring-rgx=_main|__.*__
141
142# Regular expression which should only match correct module names
143module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
144
145# Regular expression which should only match correct module level names
146const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))|(log)$
147
148# Regular expression which should only match correct class names
149class-rgx=[A-Z_][a-zA-Z0-9]+$
150
151# Regular expression which should only match correct function names
152function-rgx=[a-z_][a-z0-9_]{2,30}$
153
154# Regular expression which should only match correct method names
155method-rgx=[a-z_][a-z0-9_]{2,30}$
156
157# Regular expression which should only match correct instance attribute names
158attr-rgx=[a-z_][a-z0-9_]{2,30}$
159
160# Regular expression which should only match correct argument names
161argument-rgx=[a-z_][a-z0-9_]{2,30}$
162
163# Regular expression which should only match correct variable names
164variable-rgx=[a-z_][a-z0-9_]{2,30}$
165
166# Regular expression which should only match correct list comprehension /
167# generator expression variable names
168inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
169
170# Good variable names which should always be accepted, separated by a comma
171good-names=i,j,k,ex,Run,_,e,d1,d2,v,f,l,d
172
173# Bad variable names which should always be refused, separated by a comma
174bad-names=foo,bar,baz,toto,tutu,tata
175
176# List of builtins function names that should not be used, separated by a comma
177bad-functions=map,filter,apply,input
178
179
180# checks for sign of poor/misdesign:
181# * number of methods, attributes, local variables...
182# * size, complexity of functions, methods
183#
184[DESIGN]
185
186# Maximum number of arguments for function / method
187max-args=5
188
189# Maximum number of locals for function / method body
190max-locals=15
191
192# Maximum number of return / yield for function / method body
193max-returns=6
194
195# Maximum number of branch for function / method body
196max-branchs=12
197
198# Maximum number of statements in function / method body
199max-statements=50
200
201# Maximum number of parents for a class (see R0901).
202max-parents=7
203
204# Maximum number of attributes for a class (see R0902).
205max-attributes=20
206
207# Minimum number of public methods for a class (see R0903).
208min-public-methods=2
209
210# Maximum number of public methods for a class (see R0904).
211max-public-methods=30
212
213
214# checks for
215# * external modules dependencies
216# * relative / wildcard imports
217# * cyclic imports
218# * uses of deprecated modules
219#
220[IMPORTS]
221
222# Deprecated modules which should not be used, separated by a comma
223deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
224
225# Create a graph of every (i.e. internal and external) dependencies in the
226# given file (report R0402 must not be disabled)
227import-graph=
228
229# Create a graph of external dependencies in the given file (report R0402 must
230# not be disabled)
231ext-import-graph=
232
233# Create a graph of internal dependencies in the given file (report R0402 must
234# not be disabled)
235int-import-graph=
236
237
238# checks for :
239# * methods without self as first argument
240# * overridden methods signature
241# * access only to existant members via self
242# * attributes not defined in the __init__ method
243# * supported interfaces implementation
244# * unreachable code
245#
246[CLASSES]
247
248# List of interface methods to ignore, separated by a comma. This is used for
249# instance to not check methods defines in Zope's Interface base class.
250ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
251
252# List of method names used to declare (i.e. assign) instance attributes.
253defining-attr-methods=__init__,__new__,setUp
254
255
256# checks for similarities and duplicated code. This computation may be
257# memory / CPU intensive, so you should disable it if you experiments some
258# problems.
259#
260[SIMILARITIES]
261
262# Minimum lines number of a similarity.
263min-similarity-lines=4
264
265# Ignore comments when computing similarities.
266ignore-comments=yes
267
268# Ignore docstrings when computing similarities.
269ignore-docstrings=yes
270
271
272# checks for:
273# * warning notes in the code like FIXME, XXX
274# * PEP 263: source code with non ascii character but no encoding declaration
275#
276[MISCELLANEOUS]
277
278# List of note tags to take in consideration, separated by a comma.
279notes=FIXME,XXX,TODO
280
281
282# checks for :
283# * unauthorized constructions
284# * strict indentation
285# * line length
286# * use of <> instead of !=
287#
288[FORMAT]
289
290# Maximum number of characters on a single line.
291max-line-length=80
292
293# Maximum number of lines in a module
294max-module-lines=1000
295
296# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
297# tab). In repo it is 2 spaces.
298indent-string=' '
diff --git a/SUBMITTING_PATCHES.md b/SUBMITTING_PATCHES.md
index 085ae06a..07f76616 100644
--- a/SUBMITTING_PATCHES.md
+++ b/SUBMITTING_PATCHES.md
@@ -2,7 +2,7 @@
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 - Check for coding errors and style nits with pyflakes and flake8
6 - Make sure all code is under the Apache License, 2.0. 6 - Make sure all code is under the Apache License, 2.0.
7 - Publish your changes for review. 7 - Publish your changes for review.
8 - Make corrections if requested. 8 - Make corrections if requested.
@@ -36,12 +36,32 @@ If your description starts to get too long, that's a sign that you
36probably need to split up your commit to finer grained pieces. 36probably need to split up your commit to finer grained pieces.
37 37
38 38
39## Check for coding errors with pylint 39## Check for coding errors and style nits with pyflakes and flake8
40 40
41Run pylint on changed modules using the provided configuration: 41### Coding errors
42 42
43 pylint --rcfile=.pylintrc file.py 43Run `pyflakes` on changed modules:
44 44
45 pyflakes file.py
46
47Ideally there should be no new errors or warnings introduced.
48
49### Style violations
50
51Run `flake8` on changes modules:
52
53 flake8 file.py
54
55Note that repo generally follows [Google's python style guide]
56(https://google.github.io/styleguide/pyguide.html) rather than [PEP 8]
57(https://www.python.org/dev/peps/pep-0008/), so it's possible that
58the output of `flake8` will be quite noisy. It's not mandatory to
59avoid all warnings, but at least the maximum line length should be
60followed.
61
62If there are many occurrences of the same warning that cannot be
63avoided without going against the Google style guide, these may be
64suppressed in the included `.flake8` file.
45 65
46## Check the license 66## Check the license
47 67
diff --git a/docs/manifest-format.txt b/docs/manifest-format.txt
index 8fd9137c..2a07f199 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest-format.txt
@@ -35,6 +35,7 @@ following DTD:
35 <!ATTLIST remote name ID #REQUIRED> 35 <!ATTLIST remote name ID #REQUIRED>
36 <!ATTLIST remote alias CDATA #IMPLIED> 36 <!ATTLIST remote alias CDATA #IMPLIED>
37 <!ATTLIST remote fetch CDATA #REQUIRED> 37 <!ATTLIST remote fetch CDATA #REQUIRED>
38 <!ATTLIST remote pushurl CDATA #IMPLIED>
38 <!ATTLIST remote review CDATA #IMPLIED> 39 <!ATTLIST remote review CDATA #IMPLIED>
39 <!ATTLIST remote revision CDATA #IMPLIED> 40 <!ATTLIST remote revision CDATA #IMPLIED>
40 41
@@ -125,6 +126,12 @@ Attribute `fetch`: The Git URL prefix for all projects which use
125this remote. Each project's name is appended to this prefix to 126this remote. Each project's name is appended to this prefix to
126form the actual URL used to clone the project. 127form the actual URL used to clone the project.
127 128
129Attribute `pushurl`: The Git "push" URL prefix for all projects
130which use this remote. Each project's name is appended to this
131prefix to form the actual URL used to "git push" the project.
132This attribute is optional; if not specified then "git push"
133will use the same URL as the `fetch` attribute.
134
128Attribute `review`: Hostname of the Gerrit server where reviews 135Attribute `review`: Hostname of the Gerrit server where reviews
129are uploaded to by `repo upload`. This attribute is optional; 136are uploaded to by `repo upload`. This attribute is optional;
130if not specified then `repo upload` will not function. 137if not specified then `repo upload` will not function.
diff --git a/git_config.py b/git_config.py
index 0379181a..e2236785 100644
--- a/git_config.py
+++ b/git_config.py
@@ -464,9 +464,13 @@ def _open_ssh(host, port=None):
464 % (host,port, str(e)), file=sys.stderr) 464 % (host,port, str(e)), file=sys.stderr)
465 return False 465 return False
466 466
467 time.sleep(1)
468 ssh_died = (p.poll() is not None)
469 if ssh_died:
470 return False
471
467 _master_processes.append(p) 472 _master_processes.append(p)
468 _master_keys.add(key) 473 _master_keys.add(key)
469 time.sleep(1)
470 return True 474 return True
471 finally: 475 finally:
472 _master_keys_lock.release() 476 _master_keys_lock.release()
@@ -568,6 +572,7 @@ class Remote(object):
568 self._config = config 572 self._config = config
569 self.name = name 573 self.name = name
570 self.url = self._Get('url') 574 self.url = self._Get('url')
575 self.pushUrl = self._Get('pushurl')
571 self.review = self._Get('review') 576 self.review = self._Get('review')
572 self.projectname = self._Get('projectname') 577 self.projectname = self._Get('projectname')
573 self.fetch = list(map(RefSpec.FromString, 578 self.fetch = list(map(RefSpec.FromString,
@@ -694,6 +699,10 @@ class Remote(object):
694 """Save this remote to the configuration. 699 """Save this remote to the configuration.
695 """ 700 """
696 self._Set('url', self.url) 701 self._Set('url', self.url)
702 if self.pushUrl is not None:
703 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
704 else:
705 self._Set('pushurl', self.pushUrl)
697 self._Set('review', self.review) 706 self._Set('review', self.review)
698 self._Set('projectname', self.projectname) 707 self._Set('projectname', self.projectname)
699 self._Set('fetch', list(map(str, self.fetch))) 708 self._Set('fetch', list(map(str, self.fetch)))
diff --git a/manifest_xml.py b/manifest_xml.py
index 295493de..0859e1fb 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -40,8 +40,18 @@ LOCAL_MANIFEST_NAME = 'local_manifest.xml'
40LOCAL_MANIFESTS_DIR_NAME = 'local_manifests' 40LOCAL_MANIFESTS_DIR_NAME = 'local_manifests'
41 41
42# urljoin gets confused if the scheme is not known. 42# urljoin gets confused if the scheme is not known.
43urllib.parse.uses_relative.extend(['ssh', 'git', 'persistent-https', 'rpc']) 43urllib.parse.uses_relative.extend([
44urllib.parse.uses_netloc.extend(['ssh', 'git', 'persistent-https', 'rpc']) 44 'ssh',
45 'git',
46 'persistent-https',
47 'sso',
48 'rpc'])
49urllib.parse.uses_netloc.extend([
50 'ssh',
51 'git',
52 'persistent-https',
53 'sso',
54 'rpc'])
45 55
46class _Default(object): 56class _Default(object):
47 """Project defaults within the manifest.""" 57 """Project defaults within the manifest."""
@@ -64,11 +74,13 @@ class _XmlRemote(object):
64 name, 74 name,
65 alias=None, 75 alias=None,
66 fetch=None, 76 fetch=None,
77 pushUrl=None,
67 manifestUrl=None, 78 manifestUrl=None,
68 review=None, 79 review=None,
69 revision=None): 80 revision=None):
70 self.name = name 81 self.name = name
71 self.fetchUrl = fetch 82 self.fetchUrl = fetch
83 self.pushUrl = pushUrl
72 self.manifestUrl = manifestUrl 84 self.manifestUrl = manifestUrl
73 self.remoteAlias = alias 85 self.remoteAlias = alias
74 self.reviewUrl = review 86 self.reviewUrl = review
@@ -104,6 +116,7 @@ class _XmlRemote(object):
104 remoteName = self.remoteAlias 116 remoteName = self.remoteAlias
105 return RemoteSpec(remoteName, 117 return RemoteSpec(remoteName,
106 url=url, 118 url=url,
119 pushUrl=self.pushUrl,
107 review=self.reviewUrl, 120 review=self.reviewUrl,
108 orig_name=self.name) 121 orig_name=self.name)
109 122
@@ -160,6 +173,8 @@ class XmlManifest(object):
160 root.appendChild(e) 173 root.appendChild(e)
161 e.setAttribute('name', r.name) 174 e.setAttribute('name', r.name)
162 e.setAttribute('fetch', r.fetchUrl) 175 e.setAttribute('fetch', r.fetchUrl)
176 if r.pushUrl is not None:
177 e.setAttribute('pushurl', r.pushUrl)
163 if r.remoteAlias is not None: 178 if r.remoteAlias is not None:
164 e.setAttribute('alias', r.remoteAlias) 179 e.setAttribute('alias', r.remoteAlias)
165 if r.reviewUrl is not None: 180 if r.reviewUrl is not None:
@@ -639,6 +654,9 @@ class XmlManifest(object):
639 if alias == '': 654 if alias == '':
640 alias = None 655 alias = None
641 fetch = self._reqatt(node, 'fetch') 656 fetch = self._reqatt(node, 'fetch')
657 pushUrl = node.getAttribute('pushurl')
658 if pushUrl == '':
659 pushUrl = None
642 review = node.getAttribute('review') 660 review = node.getAttribute('review')
643 if review == '': 661 if review == '':
644 review = None 662 review = None
@@ -646,7 +664,7 @@ class XmlManifest(object):
646 if revision == '': 664 if revision == '':
647 revision = None 665 revision = None
648 manifestUrl = self.manifestProject.config.GetString('remote.origin.url') 666 manifestUrl = self.manifestProject.config.GetString('remote.origin.url')
649 return _XmlRemote(name, alias, fetch, manifestUrl, review, revision) 667 return _XmlRemote(name, alias, fetch, pushUrl, manifestUrl, review, revision)
650 668
651 def _ParseDefault(self, node): 669 def _ParseDefault(self, node):
652 """ 670 """
diff --git a/project.py b/project.py
index 46e06bf8..633ae073 100644
--- a/project.py
+++ b/project.py
@@ -320,11 +320,13 @@ class RemoteSpec(object):
320 def __init__(self, 320 def __init__(self,
321 name, 321 name,
322 url=None, 322 url=None,
323 pushUrl=None,
323 review=None, 324 review=None,
324 revision=None, 325 revision=None,
325 orig_name=None): 326 orig_name=None):
326 self.name = name 327 self.name = name
327 self.url = url 328 self.url = url
329 self.pushUrl = pushUrl
328 self.review = review 330 self.review = review
329 self.revision = revision 331 self.revision = revision
330 self.orig_name = orig_name 332 self.orig_name = orig_name
@@ -909,11 +911,13 @@ class Project(object):
909 else: 911 else:
910 return False 912 return False
911 913
912 def PrintWorkTreeStatus(self, output_redir=None): 914 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
913 """Prints the status of the repository to stdout. 915 """Prints the status of the repository to stdout.
914 916
915 Args: 917 Args:
916 output: If specified, redirect the output to this object. 918 output: If specified, redirect the output to this object.
919 quiet: If True then only print the project name. Do not print
920 the modified files, branch name, etc.
917 """ 921 """
918 if not os.path.isdir(self.worktree): 922 if not os.path.isdir(self.worktree):
919 if output_redir is None: 923 if output_redir is None:
@@ -939,6 +943,10 @@ class Project(object):
939 out.redirect(output_redir) 943 out.redirect(output_redir)
940 out.project('project %-40s', self.relpath + '/ ') 944 out.project('project %-40s', self.relpath + '/ ')
941 945
946 if quiet:
947 out.nl()
948 return 'DIRTY'
949
942 branch = self.CurrentBranch 950 branch = self.CurrentBranch
943 if branch is None: 951 if branch is None:
944 out.nobranch('(*** NO BRANCH ***)') 952 out.nobranch('(*** NO BRANCH ***)')
@@ -1256,13 +1264,18 @@ class Project(object):
1256 elif self.manifest.default.sync_c: 1264 elif self.manifest.default.sync_c:
1257 current_branch_only = True 1265 current_branch_only = True
1258 1266
1267 if self.clone_depth:
1268 depth = self.clone_depth
1269 else:
1270 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1271
1259 need_to_fetch = not (optimized_fetch and 1272 need_to_fetch = not (optimized_fetch and
1260 (ID_RE.match(self.revisionExpr) and 1273 (ID_RE.match(self.revisionExpr) and
1261 self._CheckForSha1())) 1274 self._CheckForSha1()))
1262 if (need_to_fetch and 1275 if (need_to_fetch and
1263 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir, 1276 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1264 current_branch_only=current_branch_only, 1277 current_branch_only=current_branch_only,
1265 no_tags=no_tags, prune=prune)): 1278 no_tags=no_tags, prune=prune, depth=depth)):
1266 return False 1279 return False
1267 1280
1268 if self.worktree: 1281 if self.worktree:
@@ -1825,6 +1838,7 @@ class Project(object):
1825 1838
1826 remote = RemoteSpec(self.remote.name, 1839 remote = RemoteSpec(self.remote.name,
1827 url=url, 1840 url=url,
1841 pushUrl=self.remote.pushUrl,
1828 review=self.remote.review, 1842 review=self.remote.review,
1829 revision=self.remote.revision) 1843 revision=self.remote.revision)
1830 subproject = Project(manifest=self.manifest, 1844 subproject = Project(manifest=self.manifest,
@@ -1834,7 +1848,7 @@ class Project(object):
1834 objdir=objdir, 1848 objdir=objdir,
1835 worktree=worktree, 1849 worktree=worktree,
1836 relpath=relpath, 1850 relpath=relpath,
1837 revisionExpr=self.revisionExpr, 1851 revisionExpr=rev,
1838 revisionId=rev, 1852 revisionId=rev,
1839 rebase=self.rebase, 1853 rebase=self.rebase,
1840 groups=self.groups, 1854 groups=self.groups,
@@ -1877,23 +1891,17 @@ class Project(object):
1877 quiet=False, 1891 quiet=False,
1878 alt_dir=None, 1892 alt_dir=None,
1879 no_tags=False, 1893 no_tags=False,
1880 prune=False): 1894 prune=False,
1895 depth=None):
1881 1896
1882 is_sha1 = False 1897 is_sha1 = False
1883 tag_name = None 1898 tag_name = None
1884 depth = None
1885
1886 # The depth should not be used when fetching to a mirror because 1899 # The depth should not be used when fetching to a mirror because
1887 # it will result in a shallow repository that cannot be cloned or 1900 # it will result in a shallow repository that cannot be cloned or
1888 # fetched from. 1901 # fetched from.
1889 if not self.manifest.IsMirror: 1902 # The repo project should also never be synced with partial depth.
1890 if self.clone_depth: 1903 if self.manifest.IsMirror or self.relpath == '.repo/repo':
1891 depth = self.clone_depth 1904 depth = None
1892 else:
1893 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1894 # The repo project should never be synced with partial depth
1895 if self.relpath == '.repo/repo':
1896 depth = None
1897 1905
1898 if depth: 1906 if depth:
1899 current_branch_only = True 1907 current_branch_only = True
@@ -2054,21 +2062,22 @@ class Project(object):
2054 os.remove(packed_refs) 2062 os.remove(packed_refs)
2055 self.bare_git.pack_refs('--all', '--prune') 2063 self.bare_git.pack_refs('--all', '--prune')
2056 2064
2057 if is_sha1 and current_branch_only and self.upstream: 2065 if is_sha1 and current_branch_only:
2058 # We just synced the upstream given branch; verify we 2066 # We just synced the upstream given branch; verify we
2059 # got what we wanted, else trigger a second run of all 2067 # got what we wanted, else trigger a second run of all
2060 # refs. 2068 # refs.
2061 if not self._CheckForSha1(): 2069 if not self._CheckForSha1():
2062 if not depth: 2070 if current_branch_only and depth:
2063 # Avoid infinite recursion when depth is True (since depth implies 2071 # Sync the current branch only with depth set to None
2064 # current_branch_only)
2065 return self._RemoteFetch(name=name, current_branch_only=False,
2066 initial=False, quiet=quiet, alt_dir=alt_dir)
2067 if self.clone_depth:
2068 self.clone_depth = None
2069 return self._RemoteFetch(name=name, 2072 return self._RemoteFetch(name=name,
2070 current_branch_only=current_branch_only, 2073 current_branch_only=current_branch_only,
2071 initial=False, quiet=quiet, alt_dir=alt_dir) 2074 initial=False, quiet=quiet, alt_dir=alt_dir,
2075 depth=None)
2076 else:
2077 # Avoid infinite recursion: sync all branches with depth set to None
2078 return self._RemoteFetch(name=name, current_branch_only=False,
2079 initial=False, quiet=quiet, alt_dir=alt_dir,
2080 depth=None)
2072 2081
2073 return ok 2082 return ok
2074 2083
@@ -2346,6 +2355,7 @@ class Project(object):
2346 if self.remote.url: 2355 if self.remote.url:
2347 remote = self.GetRemote(self.remote.name) 2356 remote = self.GetRemote(self.remote.name)
2348 remote.url = self.remote.url 2357 remote.url = self.remote.url
2358 remote.pushUrl = self.remote.pushUrl
2349 remote.review = self.remote.review 2359 remote.review = self.remote.review
2350 remote.projectname = self.name 2360 remote.projectname = self.name
2351 2361
@@ -2390,6 +2400,7 @@ class Project(object):
2390 src = os.path.realpath(os.path.join(srcdir, name)) 2400 src = os.path.realpath(os.path.join(srcdir, name))
2391 # Fail if the links are pointing to the wrong place 2401 # Fail if the links are pointing to the wrong place
2392 if src != dst: 2402 if src != dst:
2403 _error('%s is different in %s vs %s', name, destdir, srcdir)
2393 raise GitError('--force-sync not enabled; cannot overwrite a local ' 2404 raise GitError('--force-sync not enabled; cannot overwrite a local '
2394 'work tree. If you\'re comfortable with the ' 2405 'work tree. If you\'re comfortable with the '
2395 'possibility of losing the work tree\'s git metadata,' 2406 'possibility of losing the work tree\'s git metadata,'
diff --git a/repo b/repo
index 36af5114..4293c792 100755
--- a/repo
+++ b/repo
@@ -27,6 +27,9 @@ VERSION = (1, 23)
27 27
28# increment this if the MAINTAINER_KEYS block is modified 28# increment this if the MAINTAINER_KEYS block is modified
29KEYRING_VERSION = (1, 2) 29KEYRING_VERSION = (1, 2)
30
31# Each individual key entry is created by using:
32# gpg --armor --export keyid
30MAINTAINER_KEYS = """ 33MAINTAINER_KEYS = """
31 34
32 Repo Maintainer <repo@android.kernel.org> 35 Repo Maintainer <repo@android.kernel.org>
diff --git a/subcmds/abandon.py b/subcmds/abandon.py
index b94ccdd3..6f78da74 100644
--- a/subcmds/abandon.py
+++ b/subcmds/abandon.py
@@ -16,6 +16,7 @@
16from __future__ import print_function 16from __future__ import print_function
17import sys 17import sys
18from command import Command 18from command import Command
19from collections import defaultdict
19from git_command import git 20from git_command import git
20from progress import Progress 21from progress import Progress
21 22
@@ -23,49 +24,75 @@ class Abandon(Command):
23 common = True 24 common = True
24 helpSummary = "Permanently abandon a development branch" 25 helpSummary = "Permanently abandon a development branch"
25 helpUsage = """ 26 helpUsage = """
26%prog <branchname> [<project>...] 27%prog [--all | <branchname>] [<project>...]
27 28
28This subcommand permanently abandons a development branch by 29This subcommand permanently abandons a development branch by
29deleting it (and all its history) from your local repository. 30deleting it (and all its history) from your local repository.
30 31
31It is equivalent to "git branch -D <branchname>". 32It is equivalent to "git branch -D <branchname>".
32""" 33"""
34 def _Options(self, p):
35 p.add_option('--all',
36 dest='all', action='store_true',
37 help='delete all branches in all projects')
33 38
34 def Execute(self, opt, args): 39 def Execute(self, opt, args):
35 if not args: 40 if not opt.all and not args:
36 self.Usage() 41 self.Usage()
37 42
38 nb = args[0] 43 if not opt.all:
39 if not git.check_ref_format('heads/%s' % nb): 44 nb = args[0]
40 print("error: '%s' is not a valid name" % nb, file=sys.stderr) 45 if not git.check_ref_format('heads/%s' % nb):
41 sys.exit(1) 46 print("error: '%s' is not a valid name" % nb, file=sys.stderr)
47 sys.exit(1)
48 else:
49 args.insert(0,None)
50 nb = "'All local branches'"
42 51
43 nb = args[0] 52 err = defaultdict(list)
44 err = [] 53 success = defaultdict(list)
45 success = []
46 all_projects = self.GetProjects(args[1:]) 54 all_projects = self.GetProjects(args[1:])
47 55
48 pm = Progress('Abandon %s' % nb, len(all_projects)) 56 pm = Progress('Abandon %s' % nb, len(all_projects))
49 for project in all_projects: 57 for project in all_projects:
50 pm.update() 58 pm.update()
51 59
52 status = project.AbandonBranch(nb) 60 if opt.all:
53 if status is not None: 61 branches = project.GetBranches().keys()
54 if status: 62 else:
55 success.append(project) 63 branches = [nb]
56 else: 64
57 err.append(project) 65 for name in branches:
66 status = project.AbandonBranch(name)
67 if status is not None:
68 if status:
69 success[name].append(project)
70 else:
71 err[name].append(project)
58 pm.end() 72 pm.end()
59 73
74 width = 25
75 for name in branches:
76 if width < len(name):
77 width = len(name)
78
60 if err: 79 if err:
61 for p in err: 80 for br in err.keys():
62 print("error: %s/: cannot abandon %s" % (p.relpath, nb), 81 err_msg = "error: cannot abandon %s" %br
63 file=sys.stderr) 82 print(err_msg, file=sys.stderr)
83 for proj in err[br]:
84 print(' '*len(err_msg) + " | %s" % p.relpath, file=sys.stderr)
64 sys.exit(1) 85 sys.exit(1)
65 elif not success: 86 elif not success:
66 print('error: no project has branch %s' % nb, file=sys.stderr) 87 print('error: no project has local branch(es) : %s' % nb,
88 file=sys.stderr)
67 sys.exit(1) 89 sys.exit(1)
68 else: 90 else:
69 print('Abandoned in %d project(s):\n %s' 91 print('Abandoned branches:', file=sys.stderr)
70 % (len(success), '\n '.join(p.relpath for p in success)), 92 for br in success.keys():
71 file=sys.stderr) 93 if len(all_projects) > 1 and len(all_projects) == len(success[br]):
94 result = "all project"
95 else:
96 result = "%s" % (
97 ('\n'+' '*width + '| ').join(p.relpath for p in success[br]))
98 print("%s%s| %s\n" % (br,' '*(width-len(br)), result),file=sys.stderr)
diff --git a/subcmds/start.py b/subcmds/start.py
index d1430a9d..290b6897 100644
--- a/subcmds/start.py
+++ b/subcmds/start.py
@@ -54,8 +54,7 @@ revision specified in the manifest.
54 if not opt.all: 54 if not opt.all:
55 projects = args[1:] 55 projects = args[1:]
56 if len(projects) < 1: 56 if len(projects) < 1:
57 print("error: at least one project must be specified", file=sys.stderr) 57 projects = ['.',] # start it in the local project by default
58 sys.exit(1)
59 58
60 all_projects = self.GetProjects(projects, 59 all_projects = self.GetProjects(projects,
61 missing_ok=bool(self.gitc_manifest)) 60 missing_ok=bool(self.gitc_manifest))
diff --git a/subcmds/status.py b/subcmds/status.py
index 38c229b1..60e26ff4 100644
--- a/subcmds/status.py
+++ b/subcmds/status.py
@@ -89,8 +89,10 @@ the following meanings:
89 p.add_option('-o', '--orphans', 89 p.add_option('-o', '--orphans',
90 dest='orphans', action='store_true', 90 dest='orphans', action='store_true',
91 help="include objects in working directory outside of repo projects") 91 help="include objects in working directory outside of repo projects")
92 p.add_option('-q', '--quiet', action='store_true',
93 help="only print the name of modified projects")
92 94
93 def _StatusHelper(self, project, clean_counter, sem): 95 def _StatusHelper(self, project, clean_counter, sem, quiet):
94 """Obtains the status for a specific project. 96 """Obtains the status for a specific project.
95 97
96 Obtains the status for a project, redirecting the output to 98 Obtains the status for a project, redirecting the output to
@@ -104,7 +106,7 @@ the following meanings:
104 output: Where to output the status. 106 output: Where to output the status.
105 """ 107 """
106 try: 108 try:
107 state = project.PrintWorkTreeStatus() 109 state = project.PrintWorkTreeStatus(quiet=quiet)
108 if state == 'CLEAN': 110 if state == 'CLEAN':
109 next(clean_counter) 111 next(clean_counter)
110 finally: 112 finally:
@@ -132,7 +134,7 @@ the following meanings:
132 134
133 if opt.jobs == 1: 135 if opt.jobs == 1:
134 for project in all_projects: 136 for project in all_projects:
135 state = project.PrintWorkTreeStatus() 137 state = project.PrintWorkTreeStatus(quiet=opt.quiet)
136 if state == 'CLEAN': 138 if state == 'CLEAN':
137 next(counter) 139 next(counter)
138 else: 140 else:
@@ -142,13 +144,13 @@ the following meanings:
142 sem.acquire() 144 sem.acquire()
143 145
144 t = _threading.Thread(target=self._StatusHelper, 146 t = _threading.Thread(target=self._StatusHelper,
145 args=(project, counter, sem)) 147 args=(project, counter, sem, opt.quiet))
146 threads.append(t) 148 threads.append(t)
147 t.daemon = True 149 t.daemon = True
148 t.start() 150 t.start()
149 for t in threads: 151 for t in threads:
150 t.join() 152 t.join()
151 if len(all_projects) == next(counter): 153 if not opt.quiet and len(all_projects) == next(counter):
152 print('nothing to commit (working directory clean)') 154 print('nothing to commit (working directory clean)')
153 155
154 if opt.orphans: 156 if opt.orphans:
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 9124a653..bbb166c0 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -255,7 +255,7 @@ later is required to fix a server side protocol bug.
255 dest='repo_upgraded', action='store_true', 255 dest='repo_upgraded', action='store_true',
256 help=SUPPRESS_HELP) 256 help=SUPPRESS_HELP)
257 257
258 def _FetchProjectList(self, opt, projects, *args, **kwargs): 258 def _FetchProjectList(self, opt, projects, sem, *args, **kwargs):
259 """Main function of the fetch threads when jobs are > 1. 259 """Main function of the fetch threads when jobs are > 1.
260 260
261 Delegates most of the work to _FetchHelper. 261 Delegates most of the work to _FetchHelper.
@@ -263,15 +263,20 @@ later is required to fix a server side protocol bug.
263 Args: 263 Args:
264 opt: Program options returned from optparse. See _Options(). 264 opt: Program options returned from optparse. See _Options().
265 projects: Projects to fetch. 265 projects: Projects to fetch.
266 sem: We'll release() this semaphore when we exit so that another thread
267 can be started up.
266 *args, **kwargs: Remaining arguments to pass to _FetchHelper. See the 268 *args, **kwargs: Remaining arguments to pass to _FetchHelper. See the
267 _FetchHelper docstring for details. 269 _FetchHelper docstring for details.
268 """ 270 """
269 for project in projects: 271 try:
270 success = self._FetchHelper(opt, project, *args, **kwargs) 272 for project in projects:
271 if not success and not opt.force_broken: 273 success = self._FetchHelper(opt, project, *args, **kwargs)
272 break 274 if not success and not opt.force_broken:
275 break
276 finally:
277 sem.release()
273 278
274 def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event): 279 def _FetchHelper(self, opt, project, lock, fetched, pm, err_event):
275 """Fetch git objects for a single project. 280 """Fetch git objects for a single project.
276 281
277 Args: 282 Args:
@@ -283,8 +288,6 @@ later is required to fix a server side protocol bug.
283 (with our lock held). 288 (with our lock held).
284 pm: Instance of a Project object. We will call pm.update() (with our 289 pm: Instance of a Project object. We will call pm.update() (with our
285 lock held). 290 lock held).
286 sem: We'll release() this semaphore when we exit so that another thread
287 can be started up.
288 err_event: We'll set this event in the case of an error (after printing 291 err_event: We'll set this event in the case of an error (after printing
289 out info about the error). 292 out info about the error).
290 293
@@ -340,7 +343,6 @@ later is required to fix a server side protocol bug.
340 finally: 343 finally:
341 if did_lock: 344 if did_lock:
342 lock.release() 345 lock.release()
343 sem.release()
344 346
345 return success 347 return success
346 348
@@ -365,10 +367,10 @@ later is required to fix a server side protocol bug.
365 sem.acquire() 367 sem.acquire()
366 kwargs = dict(opt=opt, 368 kwargs = dict(opt=opt,
367 projects=project_list, 369 projects=project_list,
370 sem=sem,
368 lock=lock, 371 lock=lock,
369 fetched=fetched, 372 fetched=fetched,
370 pm=pm, 373 pm=pm,
371 sem=sem,
372 err_event=err_event) 374 err_event=err_event)
373 if self.jobs > 1: 375 if self.jobs > 1:
374 t = _threading.Thread(target = self._FetchProjectList, 376 t = _threading.Thread(target = self._FetchProjectList,
@@ -397,9 +399,12 @@ later is required to fix a server side protocol bug.
397 return fetched 399 return fetched
398 400
399 def _GCProjects(self, projects): 401 def _GCProjects(self, projects):
400 gitdirs = {} 402 gc_gitdirs = {}
401 for project in projects: 403 for project in projects:
402 gitdirs[project.gitdir] = project.bare_git 404 if len(project.manifest.GetProjectsWithName(project.name)) > 1:
405 print('Shared project %s found, disabling pruning.' % project.name)
406 project.bare_git.config('--replace-all', 'gc.pruneExpire', 'never')
407 gc_gitdirs[project.gitdir] = project.bare_git
403 408
404 has_dash_c = git_require((1, 7, 2)) 409 has_dash_c = git_require((1, 7, 2))
405 if multiprocessing and has_dash_c: 410 if multiprocessing and has_dash_c:
@@ -409,7 +414,7 @@ later is required to fix a server side protocol bug.
409 jobs = min(self.jobs, cpu_count) 414 jobs = min(self.jobs, cpu_count)
410 415
411 if jobs < 2: 416 if jobs < 2:
412 for bare_git in gitdirs.values(): 417 for bare_git in gc_gitdirs.values():
413 bare_git.gc('--auto') 418 bare_git.gc('--auto')
414 return 419 return
415 420
@@ -431,7 +436,7 @@ later is required to fix a server side protocol bug.
431 finally: 436 finally:
432 sem.release() 437 sem.release()
433 438
434 for bare_git in gitdirs.values(): 439 for bare_git in gc_gitdirs.values():
435 if err_event.isSet(): 440 if err_event.isSet():
436 break 441 break
437 sem.acquire() 442 sem.acquire()
@@ -454,6 +459,65 @@ later is required to fix a server side protocol bug.
454 else: 459 else:
455 self.manifest._Unload() 460 self.manifest._Unload()
456 461
462 def _DeleteProject(self, path):
463 print('Deleting obsolete path %s' % path, file=sys.stderr)
464
465 # Delete the .git directory first, so we're less likely to have a partially
466 # working git repository around. There shouldn't be any git projects here,
467 # so rmtree works.
468 try:
469 shutil.rmtree(os.path.join(path, '.git'))
470 except OSError:
471 print('Failed to remove %s' % os.path.join(path, '.git'), file=sys.stderr)
472 print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
473 print(' remove manually, then run sync again', file=sys.stderr)
474 return -1
475
476 # Delete everything under the worktree, except for directories that contain
477 # another git project
478 dirs_to_remove = []
479 failed = False
480 for root, dirs, files in os.walk(path):
481 for f in files:
482 try:
483 os.remove(os.path.join(root, f))
484 except OSError:
485 print('Failed to remove %s' % os.path.join(root, f), file=sys.stderr)
486 failed = True
487 dirs[:] = [d for d in dirs
488 if not os.path.lexists(os.path.join(root, d, '.git'))]
489 dirs_to_remove += [os.path.join(root, d) for d in dirs
490 if os.path.join(root, d) not in dirs_to_remove]
491 for d in reversed(dirs_to_remove):
492 if os.path.islink(d):
493 try:
494 os.remove(d)
495 except OSError:
496 print('Failed to remove %s' % os.path.join(root, d), file=sys.stderr)
497 failed = True
498 elif len(os.listdir(d)) == 0:
499 try:
500 os.rmdir(d)
501 except OSError:
502 print('Failed to remove %s' % os.path.join(root, d), file=sys.stderr)
503 failed = True
504 continue
505 if failed:
506 print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
507 print(' remove manually, then run sync again', file=sys.stderr)
508 return -1
509
510 # Try deleting parent dirs if they are empty
511 project_dir = path
512 while project_dir != self.manifest.topdir:
513 if len(os.listdir(project_dir)) == 0:
514 os.rmdir(project_dir)
515 else:
516 break
517 project_dir = os.path.dirname(project_dir)
518
519 return 0
520
457 def UpdateProjectList(self): 521 def UpdateProjectList(self):
458 new_project_paths = [] 522 new_project_paths = []
459 for project in self.GetProjects(None, missing_ok=True): 523 for project in self.GetProjects(None, missing_ok=True):
@@ -474,8 +538,8 @@ later is required to fix a server side protocol bug.
474 continue 538 continue
475 if path not in new_project_paths: 539 if path not in new_project_paths:
476 # If the path has already been deleted, we don't need to do it 540 # If the path has already been deleted, we don't need to do it
477 if os.path.exists(self.manifest.topdir + '/' + path): 541 gitdir = os.path.join(self.manifest.topdir, path, '.git')
478 gitdir = os.path.join(self.manifest.topdir, path, '.git') 542 if os.path.exists(gitdir):
479 project = Project( 543 project = Project(
480 manifest = self.manifest, 544 manifest = self.manifest,
481 name = path, 545 name = path,
@@ -494,18 +558,8 @@ later is required to fix a server side protocol bug.
494 print(' commit changes, then run sync again', 558 print(' commit changes, then run sync again',
495 file=sys.stderr) 559 file=sys.stderr)
496 return -1 560 return -1
497 else: 561 elif self._DeleteProject(project.worktree):
498 print('Deleting obsolete path %s' % project.worktree, 562 return -1
499 file=sys.stderr)
500 shutil.rmtree(project.worktree)
501 # Try deleting parent subdirs if they are empty
502 project_dir = os.path.dirname(project.worktree)
503 while project_dir != self.manifest.topdir:
504 try:
505 os.rmdir(project_dir)
506 except OSError:
507 break
508 project_dir = os.path.dirname(project_dir)
509 563
510 new_project_paths.sort() 564 new_project_paths.sort()
511 fd = open(file_path, 'w') 565 fd = open(file_path, 'w')
diff --git a/subcmds/upload.py b/subcmds/upload.py
index 4b05f1e8..1172dadc 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -454,7 +454,11 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
454 if avail: 454 if avail:
455 pending.append((project, avail)) 455 pending.append((project, avail))
456 456
457 if pending and (not opt.bypass_hooks): 457 if not pending:
458 print("no branches ready for upload", file=sys.stderr)
459 return
460
461 if not opt.bypass_hooks:
458 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project, 462 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
459 self.manifest.topdir, 463 self.manifest.topdir,
460 self.manifest.manifestProject.GetRemote('origin').url, 464 self.manifest.manifestProject.GetRemote('origin').url,
@@ -474,9 +478,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
474 cc = _SplitEmails(opt.cc) 478 cc = _SplitEmails(opt.cc)
475 people = (reviewers, cc) 479 people = (reviewers, cc)
476 480
477 if not pending: 481 if len(pending) == 1 and len(pending[0][1]) == 1:
478 print("no branches ready for upload", file=sys.stderr)
479 elif len(pending) == 1 and len(pending[0][1]) == 1:
480 self._SingleBranch(opt, pending[0][1][0], people) 482 self._SingleBranch(opt, pending[0][1][0], people)
481 else: 483 else:
482 self._MultipleBranches(opt, pending, people) 484 self._MultipleBranches(opt, pending, people)