summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.project2
-rw-r--r--.pydevproject2
-rw-r--r--.pylintrc2
-rw-r--r--command.py15
-rw-r--r--docs/manifest-format.txt48
-rw-r--r--git_config.py43
-rw-r--r--git_refs.py9
-rwxr-xr-xmain.py16
-rw-r--r--manifest_xml.py103
-rw-r--r--project.py164
-rw-r--r--pyversion.py19
-rwxr-xr-xrepo202
-rw-r--r--subcmds/__init__.py4
-rw-r--r--subcmds/branches.py5
-rw-r--r--subcmds/cherry_pick.py2
-rw-r--r--subcmds/forall.py21
-rw-r--r--subcmds/help.py12
-rw-r--r--subcmds/info.py12
-rw-r--r--subcmds/init.py30
-rw-r--r--subcmds/list.py33
-rw-r--r--subcmds/overview.py2
-rw-r--r--subcmds/rebase.py2
-rw-r--r--subcmds/stage.py8
-rw-r--r--subcmds/status.py12
-rw-r--r--subcmds/sync.py55
-rw-r--r--subcmds/upload.py56
27 files changed, 597 insertions, 283 deletions
diff --git a/.gitignore b/.gitignore
index cef6b78f..59d7b62f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
1*.pyc 1*.pyc
2.repopickle_* 2.repopickle_*
3/repoc
diff --git a/.project b/.project
index 67e4a0f1..3aefb86b 100644
--- a/.project
+++ b/.project
@@ -1,6 +1,6 @@
1<?xml version="1.0" encoding="UTF-8"?> 1<?xml version="1.0" encoding="UTF-8"?>
2<projectDescription> 2<projectDescription>
3 <name>repo</name> 3 <name>git-repo</name>
4 <comment></comment> 4 <comment></comment>
5 <projects> 5 <projects>
6 </projects> 6 </projects>
diff --git a/.pydevproject b/.pydevproject
index 880abd62..27c2485a 100644
--- a/.pydevproject
+++ b/.pydevproject
@@ -3,7 +3,7 @@
3 3
4<pydev_project> 4<pydev_project>
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>/git-repo</path>
7</pydev_pathproperty> 7</pydev_pathproperty>
8<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</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>
diff --git a/.pylintrc b/.pylintrc
index 9e8882ee..2ed0940d 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -53,7 +53,7 @@ load-plugins=
53enable=RP0004 53enable=RP0004
54 54
55# Disable the message(s) with the given id(s). 55# Disable the message(s) with the given id(s).
56disable=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 56disable=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 57
58[REPORTS] 58[REPORTS]
59 59
diff --git a/command.py b/command.py
index 96d7848f..287f4d30 100644
--- a/command.py
+++ b/command.py
@@ -136,11 +136,11 @@ class Command(object):
136 136
137 groups = mp.config.GetString('manifest.groups') 137 groups = mp.config.GetString('manifest.groups')
138 if not groups: 138 if not groups:
139 groups = 'all,-notdefault,platform-' + platform.system().lower() 139 groups = 'default,platform-' + platform.system().lower()
140 groups = [x for x in re.split(r'[,\s]+', groups) if x] 140 groups = [x for x in re.split(r'[,\s]+', groups) if x]
141 141
142 if not args: 142 if not args:
143 all_projects_list = all_projects.values() 143 all_projects_list = list(all_projects.values())
144 derived_projects = {} 144 derived_projects = {}
145 for project in all_projects_list: 145 for project in all_projects_list:
146 if submodules_ok or project.sync_s: 146 if submodules_ok or project.sync_s:
@@ -186,6 +186,17 @@ class Command(object):
186 result.sort(key=_getpath) 186 result.sort(key=_getpath)
187 return result 187 return result
188 188
189 def FindProjects(self, args):
190 result = []
191 patterns = [re.compile(r'%s' % a, re.IGNORECASE) for a in args]
192 for project in self.GetProjects(''):
193 for pattern in patterns:
194 if pattern.search(project.name) or pattern.search(project.relpath):
195 result.append(project)
196 break
197 result.sort(key=lambda project: project.relpath)
198 return result
199
189# pylint: disable=W0223 200# pylint: disable=W0223
190# Pylint warns that the `InteractiveCommand` and `PagedCommand` classes do not 201# Pylint warns that the `InteractiveCommand` and `PagedCommand` classes do not
191# override method `Execute` which is abstract in `Command`. Since that method 202# override method `Execute` which is abstract in `Command`. Since that method
diff --git a/docs/manifest-format.txt b/docs/manifest-format.txt
index 0bf09f6f..dcc90d07 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest-format.txt
@@ -37,25 +37,29 @@ following DTD:
37 <!ATTLIST remote review CDATA #IMPLIED> 37 <!ATTLIST remote review CDATA #IMPLIED>
38 38
39 <!ELEMENT default (EMPTY)> 39 <!ELEMENT default (EMPTY)>
40 <!ATTLIST default remote IDREF #IMPLIED> 40 <!ATTLIST default remote IDREF #IMPLIED>
41 <!ATTLIST default revision CDATA #IMPLIED> 41 <!ATTLIST default revision CDATA #IMPLIED>
42 <!ATTLIST default sync-j CDATA #IMPLIED> 42 <!ATTLIST default dest-branch CDATA #IMPLIED>
43 <!ATTLIST default sync-c CDATA #IMPLIED> 43 <!ATTLIST default sync-j CDATA #IMPLIED>
44 <!ATTLIST default sync-s CDATA #IMPLIED> 44 <!ATTLIST default sync-c CDATA #IMPLIED>
45 <!ATTLIST default sync-s CDATA #IMPLIED>
45 46
46 <!ELEMENT manifest-server (EMPTY)> 47 <!ELEMENT manifest-server (EMPTY)>
47 <!ATTLIST url CDATA #REQUIRED> 48 <!ATTLIST url CDATA #REQUIRED>
48 49
49 <!ELEMENT project (annotation?, 50 <!ELEMENT project (annotation?,
50 project*)> 51 project*)>
51 <!ATTLIST project name CDATA #REQUIRED> 52 <!ATTLIST project name CDATA #REQUIRED>
52 <!ATTLIST project path CDATA #IMPLIED> 53 <!ATTLIST project path CDATA #IMPLIED>
53 <!ATTLIST project remote IDREF #IMPLIED> 54 <!ATTLIST project remote IDREF #IMPLIED>
54 <!ATTLIST project revision CDATA #IMPLIED> 55 <!ATTLIST project revision CDATA #IMPLIED>
55 <!ATTLIST project groups CDATA #IMPLIED> 56 <!ATTLIST project dest-branch CDATA #IMPLIED>
56 <!ATTLIST project sync-c CDATA #IMPLIED> 57 <!ATTLIST project groups CDATA #IMPLIED>
57 <!ATTLIST project sync-s CDATA #IMPLIED> 58 <!ATTLIST project sync-c CDATA #IMPLIED>
59 <!ATTLIST project sync-s CDATA #IMPLIED>
58 <!ATTLIST project upstream CDATA #IMPLIED> 60 <!ATTLIST project upstream CDATA #IMPLIED>
61 <!ATTLIST project clone-depth CDATA #IMPLIED>
62 <!ATTLIST project force-path CDATA #IMPLIED>
59 63
60 <!ELEMENT annotation (EMPTY)> 64 <!ELEMENT annotation (EMPTY)>
61 <!ATTLIST annotation name CDATA #REQUIRED> 65 <!ATTLIST annotation name CDATA #REQUIRED>
@@ -123,6 +127,11 @@ Attribute `revision`: Name of a Git branch (e.g. `master` or
123`refs/heads/master`). Project elements lacking their own 127`refs/heads/master`). Project elements lacking their own
124revision attribute will use this revision. 128revision attribute will use this revision.
125 129
130Attribute `dest-branch`: Name of a Git branch (e.g. `master`).
131Project elements not setting their own `dest-branch` will inherit
132this value. If this value is not set, projects will use `revision`
133by default instead.
134
126Attribute `sync_j`: Number of parallel jobs to use when synching. 135Attribute `sync_j`: Number of parallel jobs to use when synching.
127 136
128Attribute `sync_c`: Set to true to only sync the given Git 137Attribute `sync_c`: Set to true to only sync the given Git
@@ -201,6 +210,11 @@ Tags and/or explicit SHA-1s should work in theory, but have not
201been extensively tested. If not supplied the revision given by 210been extensively tested. If not supplied the revision given by
202the default element is used. 211the default element is used.
203 212
213Attribute `dest-branch`: Name of a Git branch (e.g. `master`).
214When using `repo upload`, changes will be submitted for code
215review on this branch. If unspecified both here and in the
216default element, `revision` is used instead.
217
204Attribute `groups`: List of groups to which this project belongs, 218Attribute `groups`: List of groups to which this project belongs,
205whitespace or comma separated. All projects belong to the group 219whitespace or comma separated. All projects belong to the group
206"all", and each project automatically belongs to a group of 220"all", and each project automatically belongs to a group of
@@ -222,6 +236,16 @@ Attribute `upstream`: Name of the Git branch in which a sha1
222can be found. Used when syncing a revision locked manifest in 236can be found. Used when syncing a revision locked manifest in
223-c mode to avoid having to sync the entire ref space. 237-c mode to avoid having to sync the entire ref space.
224 238
239Attribute `clone-depth`: Set the depth to use when fetching this
240project. If specified, this value will override any value given
241to repo init with the --depth option on the command line.
242
243Attribute `force-path`: Set to true to force this project to create the
244local mirror repository according to its `path` attribute (if supplied)
245rather than the `name` attribute. This attribute only applies to the
246local mirrors syncing, it will be ignored when syncing the projects in a
247client working directory.
248
225Element annotation 249Element annotation
226------------------ 250------------------
227 251
diff --git a/git_config.py b/git_config.py
index 56cc6a24..a294a0b6 100644
--- a/git_config.py
+++ b/git_config.py
@@ -14,8 +14,9 @@
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function 16from __future__ import print_function
17import cPickle 17
18import os 18import os
19import pickle
19import re 20import re
20import subprocess 21import subprocess
21import sys 22import sys
@@ -24,14 +25,13 @@ try:
24except ImportError: 25except ImportError:
25 import dummy_threading as _threading 26 import dummy_threading as _threading
26import time 27import time
27try: 28
28 import urllib2 29from pyversion import is_python3
29except ImportError: 30if is_python3():
30 # For python3
31 import urllib.request 31 import urllib.request
32 import urllib.error 32 import urllib.error
33else: 33else:
34 # For python2 34 import urllib2
35 import imp 35 import imp
36 urllib = imp.new_module('urllib') 36 urllib = imp.new_module('urllib')
37 urllib.request = urllib2 37 urllib.request = urllib2
@@ -40,6 +40,10 @@ else:
40from signal import SIGTERM 40from signal import SIGTERM
41from error import GitError, UploadError 41from error import GitError, UploadError
42from trace import Trace 42from trace import Trace
43if is_python3():
44 from http.client import HTTPException
45else:
46 from httplib import HTTPException
43 47
44from git_command import GitCommand 48from git_command import GitCommand
45from git_command import ssh_sock 49from git_command import ssh_sock
@@ -262,7 +266,7 @@ class GitConfig(object):
262 Trace(': unpickle %s', self.file) 266 Trace(': unpickle %s', self.file)
263 fd = open(self._pickle, 'rb') 267 fd = open(self._pickle, 'rb')
264 try: 268 try:
265 return cPickle.load(fd) 269 return pickle.load(fd)
266 finally: 270 finally:
267 fd.close() 271 fd.close()
268 except EOFError: 272 except EOFError:
@@ -271,7 +275,7 @@ class GitConfig(object):
271 except IOError: 275 except IOError:
272 os.remove(self._pickle) 276 os.remove(self._pickle)
273 return None 277 return None
274 except cPickle.PickleError: 278 except pickle.PickleError:
275 os.remove(self._pickle) 279 os.remove(self._pickle)
276 return None 280 return None
277 281
@@ -279,13 +283,13 @@ class GitConfig(object):
279 try: 283 try:
280 fd = open(self._pickle, 'wb') 284 fd = open(self._pickle, 'wb')
281 try: 285 try:
282 cPickle.dump(cache, fd, cPickle.HIGHEST_PROTOCOL) 286 pickle.dump(cache, fd, pickle.HIGHEST_PROTOCOL)
283 finally: 287 finally:
284 fd.close() 288 fd.close()
285 except IOError: 289 except IOError:
286 if os.path.exists(self._pickle): 290 if os.path.exists(self._pickle):
287 os.remove(self._pickle) 291 os.remove(self._pickle)
288 except cPickle.PickleError: 292 except pickle.PickleError:
289 if os.path.exists(self._pickle): 293 if os.path.exists(self._pickle):
290 os.remove(self._pickle) 294 os.remove(self._pickle)
291 295
@@ -537,8 +541,8 @@ class Remote(object):
537 self.url = self._Get('url') 541 self.url = self._Get('url')
538 self.review = self._Get('review') 542 self.review = self._Get('review')
539 self.projectname = self._Get('projectname') 543 self.projectname = self._Get('projectname')
540 self.fetch = map(RefSpec.FromString, 544 self.fetch = list(map(RefSpec.FromString,
541 self._Get('fetch', all_keys=True)) 545 self._Get('fetch', all_keys=True)))
542 self._review_url = None 546 self._review_url = None
543 547
544 def _InsteadOf(self): 548 def _InsteadOf(self):
@@ -592,14 +596,11 @@ class Remote(object):
592 try: 596 try:
593 info_url = u + 'ssh_info' 597 info_url = u + 'ssh_info'
594 info = urllib.request.urlopen(info_url).read() 598 info = urllib.request.urlopen(info_url).read()
595 if '<' in info: 599 if info == 'NOT_AVAILABLE' or '<' in info:
596 # Assume the server gave us some sort of HTML 600 # If `info` contains '<', we assume the server gave us some sort
597 # response back, like maybe a login page. 601 # of HTML response back, like maybe a login page.
598 # 602 #
599 raise UploadError('%s: Cannot parse response' % info_url) 603 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
600
601 if info == 'NOT_AVAILABLE':
602 # Assume HTTP if SSH is not enabled.
603 self._review_url = http_url + 'p/' 604 self._review_url = http_url + 'p/'
604 else: 605 else:
605 host, port = info.split() 606 host, port = info.split()
@@ -608,6 +609,8 @@ class Remote(object):
608 raise UploadError('%s: %s' % (self.review, str(e))) 609 raise UploadError('%s: %s' % (self.review, str(e)))
609 except urllib.error.URLError as e: 610 except urllib.error.URLError as e:
610 raise UploadError('%s: %s' % (self.review, str(e))) 611 raise UploadError('%s: %s' % (self.review, str(e)))
612 except HTTPException as e:
613 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
611 614
612 REVIEW_CACHE[u] = self._review_url 615 REVIEW_CACHE[u] = self._review_url
613 return self._review_url + self.projectname 616 return self._review_url + self.projectname
@@ -657,7 +660,7 @@ class Remote(object):
657 self._Set('url', self.url) 660 self._Set('url', self.url)
658 self._Set('review', self.review) 661 self._Set('review', self.review)
659 self._Set('projectname', self.projectname) 662 self._Set('projectname', self.projectname)
660 self._Set('fetch', map(str, self.fetch)) 663 self._Set('fetch', list(map(str, self.fetch)))
661 664
662 def _Set(self, key, value): 665 def _Set(self, key, value):
663 key = 'remote.%s.%s' % (self.name, key) 666 key = 'remote.%s.%s' % (self.name, key)
diff --git a/git_refs.py b/git_refs.py
index cfeffba9..4dd68769 100644
--- a/git_refs.py
+++ b/git_refs.py
@@ -66,7 +66,7 @@ class GitRefs(object):
66 def _NeedUpdate(self): 66 def _NeedUpdate(self):
67 Trace(': scan refs %s', self._gitdir) 67 Trace(': scan refs %s', self._gitdir)
68 68
69 for name, mtime in self._mtime.iteritems(): 69 for name, mtime in self._mtime.items():
70 try: 70 try:
71 if mtime != os.path.getmtime(os.path.join(self._gitdir, name)): 71 if mtime != os.path.getmtime(os.path.join(self._gitdir, name)):
72 return True 72 return True
@@ -89,7 +89,7 @@ class GitRefs(object):
89 attempts = 0 89 attempts = 0
90 while scan and attempts < 5: 90 while scan and attempts < 5:
91 scan_next = {} 91 scan_next = {}
92 for name, dest in scan.iteritems(): 92 for name, dest in scan.items():
93 if dest in self._phyref: 93 if dest in self._phyref:
94 self._phyref[name] = self._phyref[dest] 94 self._phyref[name] = self._phyref[dest]
95 else: 95 else:
@@ -108,6 +108,7 @@ class GitRefs(object):
108 return 108 return
109 try: 109 try:
110 for line in fd: 110 for line in fd:
111 line = str(line)
111 if line[0] == '#': 112 if line[0] == '#':
112 continue 113 continue
113 if line[0] == '^': 114 if line[0] == '^':
@@ -150,6 +151,10 @@ class GitRefs(object):
150 finally: 151 finally:
151 fd.close() 152 fd.close()
152 153
154 try:
155 ref_id = ref_id.decode()
156 except AttributeError:
157 pass
153 if not ref_id: 158 if not ref_id:
154 return 159 return
155 ref_id = ref_id[:-1] 160 ref_id = ref_id[:-1]
diff --git a/main.py b/main.py
index 9cc2639a..e4cdeb0f 100755
--- a/main.py
+++ b/main.py
@@ -22,13 +22,12 @@ import optparse
22import os 22import os
23import sys 23import sys
24import time 24import time
25try: 25
26 import urllib2 26from pyversion import is_python3
27except ImportError: 27if is_python3():
28 # For python3
29 import urllib.request 28 import urllib.request
30else: 29else:
31 # For python2 30 import urllib2
32 urllib = imp.new_module('urllib') 31 urllib = imp.new_module('urllib')
33 urllib.request = urllib2 32 urllib.request = urllib2
34 33
@@ -50,6 +49,11 @@ from pager import RunPager
50 49
51from subcmds import all_commands 50from subcmds import all_commands
52 51
52if not is_python3():
53 # pylint:disable=W0622
54 input = raw_input
55 # pylint:enable=W0622
56
53global_options = optparse.OptionParser( 57global_options = optparse.OptionParser(
54 usage="repo [-p|--paginate|--no-pager] COMMAND [ARGS]" 58 usage="repo [-p|--paginate|--no-pager] COMMAND [ARGS]"
55 ) 59 )
@@ -286,7 +290,7 @@ def _AddPasswordFromUserInput(handler, msg, req):
286 if user is None: 290 if user is None:
287 print(msg) 291 print(msg)
288 try: 292 try:
289 user = raw_input('User: ') 293 user = input('User: ')
290 password = getpass.getpass() 294 password = getpass.getpass()
291 except KeyboardInterrupt: 295 except KeyboardInterrupt:
292 return 296 return
diff --git a/manifest_xml.py b/manifest_xml.py
index 07f0c66a..bdbb1d40 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -18,9 +18,17 @@ import itertools
18import os 18import os
19import re 19import re
20import sys 20import sys
21import urlparse
22import xml.dom.minidom 21import xml.dom.minidom
23 22
23from pyversion import is_python3
24if is_python3():
25 import urllib.parse
26else:
27 import imp
28 import urlparse
29 urllib = imp.new_module('urllib')
30 urllib.parse = urlparse
31
24from git_config import GitConfig 32from git_config import GitConfig
25from git_refs import R_HEADS, HEAD 33from git_refs import R_HEADS, HEAD
26from project import RemoteSpec, Project, MetaProject 34from project import RemoteSpec, Project, MetaProject
@@ -30,13 +38,14 @@ MANIFEST_FILE_NAME = 'manifest.xml'
30LOCAL_MANIFEST_NAME = 'local_manifest.xml' 38LOCAL_MANIFEST_NAME = 'local_manifest.xml'
31LOCAL_MANIFESTS_DIR_NAME = 'local_manifests' 39LOCAL_MANIFESTS_DIR_NAME = 'local_manifests'
32 40
33urlparse.uses_relative.extend(['ssh', 'git']) 41urllib.parse.uses_relative.extend(['ssh', 'git'])
34urlparse.uses_netloc.extend(['ssh', 'git']) 42urllib.parse.uses_netloc.extend(['ssh', 'git'])
35 43
36class _Default(object): 44class _Default(object):
37 """Project defaults within the manifest.""" 45 """Project defaults within the manifest."""
38 46
39 revisionExpr = None 47 revisionExpr = None
48 destBranchExpr = None
40 remote = None 49 remote = None
41 sync_j = 1 50 sync_j = 1
42 sync_c = False 51 sync_c = False
@@ -73,7 +82,7 @@ class _XmlRemote(object):
73 # ie, if manifestUrl is of the form <hostname:port> 82 # ie, if manifestUrl is of the form <hostname:port>
74 if manifestUrl.find(':') != manifestUrl.find('/') - 1: 83 if manifestUrl.find(':') != manifestUrl.find('/') - 1:
75 manifestUrl = 'gopher://' + manifestUrl 84 manifestUrl = 'gopher://' + manifestUrl
76 url = urlparse.urljoin(manifestUrl, url) 85 url = urllib.parse.urljoin(manifestUrl, url)
77 url = re.sub(r'^gopher://', '', url) 86 url = re.sub(r'^gopher://', '', url)
78 if p: 87 if p:
79 url = 'persistent-' + url 88 url = 'persistent-' + url
@@ -83,7 +92,7 @@ class _XmlRemote(object):
83 url = self.resolvedFetchUrl.rstrip('/') + '/' + projectName 92 url = self.resolvedFetchUrl.rstrip('/') + '/' + projectName
84 remoteName = self.name 93 remoteName = self.name
85 if self.remoteAlias: 94 if self.remoteAlias:
86 remoteName = self.remoteAlias 95 remoteName = self.remoteAlias
87 return RemoteSpec(remoteName, url, self.reviewUrl) 96 return RemoteSpec(remoteName, url, self.reviewUrl)
88 97
89class XmlManifest(object): 98class XmlManifest(object):
@@ -94,6 +103,7 @@ class XmlManifest(object):
94 self.topdir = os.path.dirname(self.repodir) 103 self.topdir = os.path.dirname(self.repodir)
95 self.manifestFile = os.path.join(self.repodir, MANIFEST_FILE_NAME) 104 self.manifestFile = os.path.join(self.repodir, MANIFEST_FILE_NAME)
96 self.globalConfig = GitConfig.ForUser() 105 self.globalConfig = GitConfig.ForUser()
106 self.localManifestWarning = False
97 107
98 self.repoProject = MetaProject(self, 'repo', 108 self.repoProject = MetaProject(self, 'repo',
99 gitdir = os.path.join(repodir, 'repo/.git'), 109 gitdir = os.path.join(repodir, 'repo/.git'),
@@ -137,6 +147,8 @@ class XmlManifest(object):
137 root.appendChild(e) 147 root.appendChild(e)
138 e.setAttribute('name', r.name) 148 e.setAttribute('name', r.name)
139 e.setAttribute('fetch', r.fetchUrl) 149 e.setAttribute('fetch', r.fetchUrl)
150 if r.remoteAlias is not None:
151 e.setAttribute('alias', r.remoteAlias)
140 if r.reviewUrl is not None: 152 if r.reviewUrl is not None:
141 e.setAttribute('review', r.reviewUrl) 153 e.setAttribute('review', r.reviewUrl)
142 154
@@ -163,10 +175,8 @@ class XmlManifest(object):
163 notice_element.appendChild(doc.createTextNode(indented_notice)) 175 notice_element.appendChild(doc.createTextNode(indented_notice))
164 176
165 d = self.default 177 d = self.default
166 sort_remotes = list(self.remotes.keys())
167 sort_remotes.sort()
168 178
169 for r in sort_remotes: 179 for r in sorted(self.remotes):
170 self._RemoteToXml(self.remotes[r], doc, root) 180 self._RemoteToXml(self.remotes[r], doc, root)
171 if self.remotes: 181 if self.remotes:
172 root.appendChild(doc.createTextNode('')) 182 root.appendChild(doc.createTextNode(''))
@@ -217,7 +227,8 @@ class XmlManifest(object):
217 e.setAttribute('name', name) 227 e.setAttribute('name', name)
218 if relpath != name: 228 if relpath != name:
219 e.setAttribute('path', relpath) 229 e.setAttribute('path', relpath)
220 if not d.remote or p.remote.name != d.remote.name: 230 remoteName = d.remote.remoteAlias or d.remote.name
231 if not d.remote or p.remote.name != remoteName:
221 e.setAttribute('remote', p.remote.name) 232 e.setAttribute('remote', p.remote.name)
222 if peg_rev: 233 if peg_rev:
223 if self.IsMirror: 234 if self.IsMirror:
@@ -258,12 +269,11 @@ class XmlManifest(object):
258 e.setAttribute('sync-s', 'true') 269 e.setAttribute('sync-s', 'true')
259 270
260 if p.subprojects: 271 if p.subprojects:
261 sort_projects = [subp.name for subp in p.subprojects] 272 sort_projects = list(sorted([subp.name for subp in p.subprojects]))
262 sort_projects.sort()
263 output_projects(p, e, sort_projects) 273 output_projects(p, e, sort_projects)
264 274
265 sort_projects = [key for key in self.projects.keys() 275 sort_projects = list(sorted([key for key, value in self.projects.items()
266 if not self.projects[key].parent] 276 if not value.parent]))
267 sort_projects.sort() 277 sort_projects.sort()
268 output_projects(None, root, sort_projects) 278 output_projects(None, root, sort_projects)
269 279
@@ -335,9 +345,11 @@ class XmlManifest(object):
335 345
336 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME) 346 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
337 if os.path.exists(local): 347 if os.path.exists(local):
338 print('warning: %s is deprecated; put local manifests in `%s` instead' 348 if not self.localManifestWarning:
339 % (LOCAL_MANIFEST_NAME, os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME)), 349 self.localManifestWarning = True
340 file=sys.stderr) 350 print('warning: %s is deprecated; put local manifests in `%s` instead'
351 % (LOCAL_MANIFEST_NAME, os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME)),
352 file=sys.stderr)
341 nodes.append(self._ParseManifestXml(local, self.repodir)) 353 nodes.append(self._ParseManifestXml(local, self.repodir))
342 354
343 local_dir = os.path.abspath(os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME)) 355 local_dir = os.path.abspath(os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME))
@@ -385,9 +397,8 @@ class XmlManifest(object):
385 name = self._reqatt(node, 'name') 397 name = self._reqatt(node, 'name')
386 fp = os.path.join(include_root, name) 398 fp = os.path.join(include_root, name)
387 if not os.path.isfile(fp): 399 if not os.path.isfile(fp):
388 raise ManifestParseError, \ 400 raise ManifestParseError("include %s doesn't exist or isn't a file"
389 "include %s doesn't exist or isn't a file" % \ 401 % (name,))
390 (name,)
391 try: 402 try:
392 nodes.extend(self._ParseManifestXml(fp, include_root)) 403 nodes.extend(self._ParseManifestXml(fp, include_root))
393 # should isolate this to the exact exception, but that's 404 # should isolate this to the exact exception, but that's
@@ -493,7 +504,7 @@ class XmlManifest(object):
493 name = None 504 name = None
494 m_url = m.GetRemote(m.remote.name).url 505 m_url = m.GetRemote(m.remote.name).url
495 if m_url.endswith('/.git'): 506 if m_url.endswith('/.git'):
496 raise ManifestParseError, 'refusing to mirror %s' % m_url 507 raise ManifestParseError('refusing to mirror %s' % m_url)
497 508
498 if self._default and self._default.remote: 509 if self._default and self._default.remote:
499 url = self._default.remote.resolvedFetchUrl 510 url = self._default.remote.resolvedFetchUrl
@@ -550,6 +561,8 @@ class XmlManifest(object):
550 if d.revisionExpr == '': 561 if d.revisionExpr == '':
551 d.revisionExpr = None 562 d.revisionExpr = None
552 563
564 d.destBranchExpr = node.getAttribute('dest-branch') or None
565
553 sync_j = node.getAttribute('sync-j') 566 sync_j = node.getAttribute('sync-j')
554 if sync_j == '' or sync_j is None: 567 if sync_j == '' or sync_j is None:
555 d.sync_j = 1 568 d.sync_j = 1
@@ -587,7 +600,7 @@ class XmlManifest(object):
587 600
588 # Figure out minimum indentation, skipping the first line (the same line 601 # Figure out minimum indentation, skipping the first line (the same line
589 # as the <notice> tag)... 602 # as the <notice> tag)...
590 minIndent = sys.maxint 603 minIndent = sys.maxsize
591 lines = notice.splitlines() 604 lines = notice.splitlines()
592 for line in lines[1:]: 605 for line in lines[1:]:
593 lstrippedLine = line.lstrip() 606 lstrippedLine = line.lstrip()
@@ -626,25 +639,22 @@ class XmlManifest(object):
626 if remote is None: 639 if remote is None:
627 remote = self._default.remote 640 remote = self._default.remote
628 if remote is None: 641 if remote is None:
629 raise ManifestParseError, \ 642 raise ManifestParseError("no remote for project %s within %s" %
630 "no remote for project %s within %s" % \ 643 (name, self.manifestFile))
631 (name, self.manifestFile)
632 644
633 revisionExpr = node.getAttribute('revision') 645 revisionExpr = node.getAttribute('revision')
634 if not revisionExpr: 646 if not revisionExpr:
635 revisionExpr = self._default.revisionExpr 647 revisionExpr = self._default.revisionExpr
636 if not revisionExpr: 648 if not revisionExpr:
637 raise ManifestParseError, \ 649 raise ManifestParseError("no revision for project %s within %s" %
638 "no revision for project %s within %s" % \ 650 (name, self.manifestFile))
639 (name, self.manifestFile)
640 651
641 path = node.getAttribute('path') 652 path = node.getAttribute('path')
642 if not path: 653 if not path:
643 path = name 654 path = name
644 if path.startswith('/'): 655 if path.startswith('/'):
645 raise ManifestParseError, \ 656 raise ManifestParseError("project %s path cannot be absolute in %s" %
646 "project %s path cannot be absolute in %s" % \ 657 (name, self.manifestFile))
647 (name, self.manifestFile)
648 658
649 rebase = node.getAttribute('rebase') 659 rebase = node.getAttribute('rebase')
650 if not rebase: 660 if not rebase:
@@ -664,6 +674,18 @@ class XmlManifest(object):
664 else: 674 else:
665 sync_s = sync_s.lower() in ("yes", "true", "1") 675 sync_s = sync_s.lower() in ("yes", "true", "1")
666 676
677 clone_depth = node.getAttribute('clone-depth')
678 if clone_depth:
679 try:
680 clone_depth = int(clone_depth)
681 if clone_depth <= 0:
682 raise ValueError()
683 except ValueError:
684 raise ManifestParseError('invalid clone-depth %s in %s' %
685 (clone_depth, self.manifestFile))
686
687 dest_branch = node.getAttribute('dest-branch') or self._default.destBranchExpr
688
667 upstream = node.getAttribute('upstream') 689 upstream = node.getAttribute('upstream')
668 690
669 groups = '' 691 groups = ''
@@ -679,6 +701,10 @@ class XmlManifest(object):
679 default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath] 701 default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath]
680 groups.extend(set(default_groups).difference(groups)) 702 groups.extend(set(default_groups).difference(groups))
681 703
704 if self.IsMirror and node.hasAttribute('force-path'):
705 if node.getAttribute('force-path').lower() in ("yes", "true", "1"):
706 gitdir = os.path.join(self.topdir, '%s.git' % path)
707
682 project = Project(manifest = self, 708 project = Project(manifest = self,
683 name = name, 709 name = name,
684 remote = remote.ToRemoteSpec(name), 710 remote = remote.ToRemoteSpec(name),
@@ -691,8 +717,10 @@ class XmlManifest(object):
691 groups = groups, 717 groups = groups,
692 sync_c = sync_c, 718 sync_c = sync_c,
693 sync_s = sync_s, 719 sync_s = sync_s,
720 clone_depth = clone_depth,
694 upstream = upstream, 721 upstream = upstream,
695 parent = parent) 722 parent = parent,
723 dest_branch = dest_branch)
696 724
697 for n in node.childNodes: 725 for n in node.childNodes:
698 if n.nodeName == 'copyfile': 726 if n.nodeName == 'copyfile':
@@ -748,7 +776,8 @@ class XmlManifest(object):
748 except ManifestParseError: 776 except ManifestParseError:
749 keep = "true" 777 keep = "true"
750 if keep != "true" and keep != "false": 778 if keep != "true" and keep != "false":
751 raise ManifestParseError, "optional \"keep\" attribute must be \"true\" or \"false\"" 779 raise ManifestParseError('optional "keep" attribute must be '
780 '"true" or "false"')
752 project.AddAnnotation(name, value, keep) 781 project.AddAnnotation(name, value, keep)
753 782
754 def _get_remote(self, node): 783 def _get_remote(self, node):
@@ -758,9 +787,8 @@ class XmlManifest(object):
758 787
759 v = self._remotes.get(name) 788 v = self._remotes.get(name)
760 if not v: 789 if not v:
761 raise ManifestParseError, \ 790 raise ManifestParseError("remote %s not defined in %s" %
762 "remote %s not defined in %s" % \ 791 (name, self.manifestFile))
763 (name, self.manifestFile)
764 return v 792 return v
765 793
766 def _reqatt(self, node, attname): 794 def _reqatt(self, node, attname):
@@ -769,7 +797,6 @@ class XmlManifest(object):
769 """ 797 """
770 v = node.getAttribute(attname) 798 v = node.getAttribute(attname)
771 if not v: 799 if not v:
772 raise ManifestParseError, \ 800 raise ManifestParseError("no %s in <%s> within %s" %
773 "no %s in <%s> within %s" % \ 801 (attname, node.nodeName, self.manifestFile))
774 (attname, node.nodeName, self.manifestFile)
775 return v 802 return v
diff --git a/project.py b/project.py
index 22e4a5d6..dec21ab1 100644
--- a/project.py
+++ b/project.py
@@ -36,6 +36,12 @@ from trace import IsTrace, Trace
36 36
37from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M 37from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
38 38
39from pyversion import is_python3
40if not is_python3():
41 # pylint:disable=W0622
42 input = raw_input
43 # pylint:enable=W0622
44
39def _lwrite(path, content): 45def _lwrite(path, content):
40 lock = '%s.lock' % path 46 lock = '%s.lock' % path
41 47
@@ -78,7 +84,7 @@ def _ProjectHooks():
78 if _project_hook_list is None: 84 if _project_hook_list is None:
79 d = os.path.abspath(os.path.dirname(__file__)) 85 d = os.path.abspath(os.path.dirname(__file__))
80 d = os.path.join(d , 'hooks') 86 d = os.path.join(d , 'hooks')
81 _project_hook_list = map(lambda x: os.path.join(d, x), os.listdir(d)) 87 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
82 return _project_hook_list 88 return _project_hook_list
83 89
84 90
@@ -151,11 +157,12 @@ class ReviewableBranch(object):
151 R_HEADS + self.name, 157 R_HEADS + self.name,
152 '--') 158 '--')
153 159
154 def UploadForReview(self, people, auto_topic=False, draft=False): 160 def UploadForReview(self, people, auto_topic=False, draft=False, dest_branch=None):
155 self.project.UploadForReview(self.name, 161 self.project.UploadForReview(self.name,
156 people, 162 people,
157 auto_topic=auto_topic, 163 auto_topic=auto_topic,
158 draft=draft) 164 draft=draft,
165 dest_branch=dest_branch)
159 166
160 def GetPublishedRefs(self): 167 def GetPublishedRefs(self):
161 refs = {} 168 refs = {}
@@ -361,7 +368,7 @@ class RepoHook(object):
361 'Do you want to allow this script to run ' 368 'Do you want to allow this script to run '
362 '(yes/yes-never-ask-again/NO)? ') % ( 369 '(yes/yes-never-ask-again/NO)? ') % (
363 self._GetMustVerb(), self._script_fullpath) 370 self._GetMustVerb(), self._script_fullpath)
364 response = raw_input(prompt).lower() 371 response = input(prompt).lower()
365 print() 372 print()
366 373
367 # User is doing a one-time approval. 374 # User is doing a one-time approval.
@@ -488,9 +495,11 @@ class Project(object):
488 groups = None, 495 groups = None,
489 sync_c = False, 496 sync_c = False,
490 sync_s = False, 497 sync_s = False,
498 clone_depth = None,
491 upstream = None, 499 upstream = None,
492 parent = None, 500 parent = None,
493 is_derived = False): 501 is_derived = False,
502 dest_branch = None):
494 """Init a Project object. 503 """Init a Project object.
495 504
496 Args: 505 Args:
@@ -510,6 +519,7 @@ class Project(object):
510 parent: The parent Project object. 519 parent: The parent Project object.
511 is_derived: False if the project was explicitly defined in the manifest; 520 is_derived: False if the project was explicitly defined in the manifest;
512 True if the project is a discovered submodule. 521 True if the project is a discovered submodule.
522 dest_branch: The branch to which to push changes for review by default.
513 """ 523 """
514 self.manifest = manifest 524 self.manifest = manifest
515 self.name = name 525 self.name = name
@@ -533,6 +543,7 @@ class Project(object):
533 self.groups = groups 543 self.groups = groups
534 self.sync_c = sync_c 544 self.sync_c = sync_c
535 self.sync_s = sync_s 545 self.sync_s = sync_s
546 self.clone_depth = clone_depth
536 self.upstream = upstream 547 self.upstream = upstream
537 self.parent = parent 548 self.parent = parent
538 self.is_derived = is_derived 549 self.is_derived = is_derived
@@ -551,6 +562,7 @@ class Project(object):
551 self.work_git = None 562 self.work_git = None
552 self.bare_git = self._GitGetByExec(self, bare=True) 563 self.bare_git = self._GitGetByExec(self, bare=True)
553 self.bare_ref = GitRefs(gitdir) 564 self.bare_ref = GitRefs(gitdir)
565 self.dest_branch = dest_branch
554 566
555 # This will be filled in if a project is later identified to be the 567 # This will be filled in if a project is later identified to be the
556 # project containing repo hooks. 568 # project containing repo hooks.
@@ -644,7 +656,7 @@ class Project(object):
644 all_refs = self._allrefs 656 all_refs = self._allrefs
645 heads = {} 657 heads = {}
646 658
647 for name, ref_id in all_refs.iteritems(): 659 for name, ref_id in all_refs.items():
648 if name.startswith(R_HEADS): 660 if name.startswith(R_HEADS):
649 name = name[len(R_HEADS):] 661 name = name[len(R_HEADS):]
650 b = self.GetBranch(name) 662 b = self.GetBranch(name)
@@ -653,7 +665,7 @@ class Project(object):
653 b.revision = ref_id 665 b.revision = ref_id
654 heads[name] = b 666 heads[name] = b
655 667
656 for name, ref_id in all_refs.iteritems(): 668 for name, ref_id in all_refs.items():
657 if name.startswith(R_PUB): 669 if name.startswith(R_PUB):
658 name = name[len(R_PUB):] 670 name = name[len(R_PUB):]
659 b = heads.get(name) 671 b = heads.get(name)
@@ -672,9 +684,14 @@ class Project(object):
672 project_groups: "all,group1,group2" 684 project_groups: "all,group1,group2"
673 manifest_groups: "-group1,group2" 685 manifest_groups: "-group1,group2"
674 the project will be matched. 686 the project will be matched.
687
688 The special manifest group "default" will match any project that
689 does not have the special project group "notdefault"
675 """ 690 """
676 expanded_manifest_groups = manifest_groups or ['all', '-notdefault'] 691 expanded_manifest_groups = manifest_groups or ['default']
677 expanded_project_groups = ['all'] + (self.groups or []) 692 expanded_project_groups = ['all'] + (self.groups or [])
693 if not 'notdefault' in expanded_project_groups:
694 expanded_project_groups += ['default']
678 695
679 matched = False 696 matched = False
680 for group in expanded_manifest_groups: 697 for group in expanded_manifest_groups:
@@ -754,10 +771,7 @@ class Project(object):
754 paths.extend(df.keys()) 771 paths.extend(df.keys())
755 paths.extend(do) 772 paths.extend(do)
756 773
757 paths = list(set(paths)) 774 for p in sorted(set(paths)):
758 paths.sort()
759
760 for p in paths:
761 try: 775 try:
762 i = di[p] 776 i = di[p]
763 except KeyError: 777 except KeyError:
@@ -849,13 +863,13 @@ class Project(object):
849 all_refs = self._allrefs 863 all_refs = self._allrefs
850 heads = set() 864 heads = set()
851 canrm = {} 865 canrm = {}
852 for name, ref_id in all_refs.iteritems(): 866 for name, ref_id in all_refs.items():
853 if name.startswith(R_HEADS): 867 if name.startswith(R_HEADS):
854 heads.add(name) 868 heads.add(name)
855 elif name.startswith(R_PUB): 869 elif name.startswith(R_PUB):
856 canrm[name] = ref_id 870 canrm[name] = ref_id
857 871
858 for name, ref_id in canrm.iteritems(): 872 for name, ref_id in canrm.items():
859 n = name[len(R_PUB):] 873 n = name[len(R_PUB):]
860 if R_HEADS + n not in heads: 874 if R_HEADS + n not in heads:
861 self.bare_git.DeleteRef(name, ref_id) 875 self.bare_git.DeleteRef(name, ref_id)
@@ -866,14 +880,14 @@ class Project(object):
866 heads = {} 880 heads = {}
867 pubed = {} 881 pubed = {}
868 882
869 for name, ref_id in self._allrefs.iteritems(): 883 for name, ref_id in self._allrefs.items():
870 if name.startswith(R_HEADS): 884 if name.startswith(R_HEADS):
871 heads[name[len(R_HEADS):]] = ref_id 885 heads[name[len(R_HEADS):]] = ref_id
872 elif name.startswith(R_PUB): 886 elif name.startswith(R_PUB):
873 pubed[name[len(R_PUB):]] = ref_id 887 pubed[name[len(R_PUB):]] = ref_id
874 888
875 ready = [] 889 ready = []
876 for branch, ref_id in heads.iteritems(): 890 for branch, ref_id in heads.items():
877 if branch in pubed and pubed[branch] == ref_id: 891 if branch in pubed and pubed[branch] == ref_id:
878 continue 892 continue
879 if selected_branch and branch != selected_branch: 893 if selected_branch and branch != selected_branch:
@@ -898,7 +912,8 @@ class Project(object):
898 def UploadForReview(self, branch=None, 912 def UploadForReview(self, branch=None,
899 people=([],[]), 913 people=([],[]),
900 auto_topic=False, 914 auto_topic=False,
901 draft=False): 915 draft=False,
916 dest_branch=None):
902 """Uploads the named branch for code review. 917 """Uploads the named branch for code review.
903 """ 918 """
904 if branch is None: 919 if branch is None:
@@ -912,7 +927,10 @@ class Project(object):
912 if not branch.remote.review: 927 if not branch.remote.review:
913 raise GitError('remote %s has no review url' % branch.remote.name) 928 raise GitError('remote %s has no review url' % branch.remote.name)
914 929
915 dest_branch = branch.merge 930 if dest_branch is None:
931 dest_branch = self.dest_branch
932 if dest_branch is None:
933 dest_branch = branch.merge
916 if not dest_branch.startswith(R_HEADS): 934 if not dest_branch.startswith(R_HEADS):
917 dest_branch = R_HEADS + dest_branch 935 dest_branch = R_HEADS + dest_branch
918 936
@@ -977,6 +995,8 @@ class Project(object):
977 is_new = not self.Exists 995 is_new = not self.Exists
978 if is_new: 996 if is_new:
979 self._InitGitDir() 997 self._InitGitDir()
998 else:
999 self._UpdateHooks()
980 self._InitRemote() 1000 self._InitRemote()
981 1001
982 if is_new: 1002 if is_new:
@@ -1216,7 +1236,6 @@ class Project(object):
1216 cmd = ['fetch', remote.name] 1236 cmd = ['fetch', remote.name]
1217 cmd.append('refs/changes/%2.2d/%d/%d' \ 1237 cmd.append('refs/changes/%2.2d/%d/%d' \
1218 % (change_id % 100, change_id, patch_id)) 1238 % (change_id % 100, change_id, patch_id))
1219 cmd.extend(map(str, remote.fetch))
1220 if GitCommand(self, cmd, bare=True).Wait() != 0: 1239 if GitCommand(self, cmd, bare=True).Wait() != 0:
1221 return None 1240 return None
1222 return DownloadedChange(self, 1241 return DownloadedChange(self,
@@ -1605,7 +1624,7 @@ class Project(object):
1605 ids = set(all_refs.values()) 1624 ids = set(all_refs.values())
1606 tmp = set() 1625 tmp = set()
1607 1626
1608 for r, ref_id in GitRefs(ref_dir).all.iteritems(): 1627 for r, ref_id in GitRefs(ref_dir).all.items():
1609 if r not in all_refs: 1628 if r not in all_refs:
1610 if r.startswith(R_TAGS) or remote.WritesTo(r): 1629 if r.startswith(R_TAGS) or remote.WritesTo(r):
1611 all_refs[r] = ref_id 1630 all_refs[r] = ref_id
@@ -1620,13 +1639,10 @@ class Project(object):
1620 ids.add(ref_id) 1639 ids.add(ref_id)
1621 tmp.add(r) 1640 tmp.add(r)
1622 1641
1623 ref_names = list(all_refs.keys())
1624 ref_names.sort()
1625
1626 tmp_packed = '' 1642 tmp_packed = ''
1627 old_packed = '' 1643 old_packed = ''
1628 1644
1629 for r in ref_names: 1645 for r in sorted(all_refs):
1630 line = '%s %s\n' % (all_refs[r], r) 1646 line = '%s %s\n' % (all_refs[r], r)
1631 tmp_packed += line 1647 tmp_packed += line
1632 if r not in tmp: 1648 if r not in tmp:
@@ -1640,7 +1656,10 @@ class Project(object):
1640 1656
1641 # The --depth option only affects the initial fetch; after that we'll do 1657 # The --depth option only affects the initial fetch; after that we'll do
1642 # full fetches of changes. 1658 # full fetches of changes.
1643 depth = self.manifest.manifestProject.config.GetString('repo.depth') 1659 if self.clone_depth:
1660 depth = self.clone_depth
1661 else:
1662 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1644 if depth and initial: 1663 if depth and initial:
1645 cmd.append('--depth=%s' % depth) 1664 cmd.append('--depth=%s' % depth)
1646 1665
@@ -1652,11 +1671,13 @@ class Project(object):
1652 1671
1653 if not current_branch_only: 1672 if not current_branch_only:
1654 # Fetch whole repo 1673 # Fetch whole repo
1655 if no_tags: 1674 # If using depth then we should not get all the tags since they may
1675 # be outside of the depth.
1676 if no_tags or depth:
1656 cmd.append('--no-tags') 1677 cmd.append('--no-tags')
1657 else: 1678 else:
1658 cmd.append('--tags') 1679 cmd.append('--tags')
1659 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')) 1680 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
1660 elif tag_name is not None: 1681 elif tag_name is not None:
1661 cmd.append('tag') 1682 cmd.append('tag')
1662 cmd.append(tag_name) 1683 cmd.append(tag_name)
@@ -1666,7 +1687,7 @@ class Project(object):
1666 branch = self.upstream 1687 branch = self.upstream
1667 if branch.startswith(R_HEADS): 1688 if branch.startswith(R_HEADS):
1668 branch = branch[len(R_HEADS):] 1689 branch = branch[len(R_HEADS):]
1669 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)) 1690 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
1670 1691
1671 ok = False 1692 ok = False
1672 for _i in range(2): 1693 for _i in range(2):
@@ -1700,15 +1721,14 @@ class Project(object):
1700 return ok 1721 return ok
1701 1722
1702 def _ApplyCloneBundle(self, initial=False, quiet=False): 1723 def _ApplyCloneBundle(self, initial=False, quiet=False):
1703 if initial and self.manifest.manifestProject.config.GetString('repo.depth'): 1724 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
1704 return False 1725 return False
1705 1726
1706 remote = self.GetRemote(self.remote.name) 1727 remote = self.GetRemote(self.remote.name)
1707 bundle_url = remote.url + '/clone.bundle' 1728 bundle_url = remote.url + '/clone.bundle'
1708 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url) 1729 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
1709 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'): 1730 if GetSchemeFromUrl(bundle_url) not in (
1710 bundle_url = bundle_url[len('persistent-'):] 1731 'http', 'https', 'persistent-http', 'persistent-https'):
1711 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1712 return False 1732 return False
1713 1733
1714 bundle_dst = os.path.join(self.gitdir, 'clone.bundle') 1734 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
@@ -1757,9 +1777,11 @@ class Project(object):
1757 os.remove(tmpPath) 1777 os.remove(tmpPath)
1758 if 'http_proxy' in os.environ and 'darwin' == sys.platform: 1778 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1759 cmd += ['--proxy', os.environ['http_proxy']] 1779 cmd += ['--proxy', os.environ['http_proxy']]
1760 cookiefile = GitConfig.ForUser().GetString('http.cookiefile') 1780 cookiefile = self._GetBundleCookieFile(srcUrl)
1761 if cookiefile: 1781 if cookiefile:
1762 cmd += ['--cookie', cookiefile] 1782 cmd += ['--cookie', cookiefile]
1783 if srcUrl.startswith('persistent-'):
1784 srcUrl = srcUrl[len('persistent-'):]
1763 cmd += [srcUrl] 1785 cmd += [srcUrl]
1764 1786
1765 if IsTrace(): 1787 if IsTrace():
@@ -1782,7 +1804,7 @@ class Project(object):
1782 return False 1804 return False
1783 1805
1784 if os.path.exists(tmpPath): 1806 if os.path.exists(tmpPath):
1785 if curlret == 0 and os.stat(tmpPath).st_size > 16: 1807 if curlret == 0 and self._IsValidBundle(tmpPath):
1786 os.rename(tmpPath, dstPath) 1808 os.rename(tmpPath, dstPath)
1787 return True 1809 return True
1788 else: 1810 else:
@@ -1791,6 +1813,46 @@ class Project(object):
1791 else: 1813 else:
1792 return False 1814 return False
1793 1815
1816 def _IsValidBundle(self, path):
1817 try:
1818 with open(path) as f:
1819 if f.read(16) == '# v2 git bundle\n':
1820 return True
1821 else:
1822 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1823 return False
1824 except OSError:
1825 return False
1826
1827 def _GetBundleCookieFile(self, url):
1828 if url.startswith('persistent-'):
1829 try:
1830 p = subprocess.Popen(
1831 ['git-remote-persistent-https', '-print_config', url],
1832 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1833 stderr=subprocess.PIPE)
1834 p.stdin.close() # Tell subprocess it's ok to close.
1835 prefix = 'http.cookiefile='
1836 cookiefile = None
1837 for line in p.stdout:
1838 line = line.strip()
1839 if line.startswith(prefix):
1840 cookiefile = line[len(prefix):]
1841 break
1842 if p.wait():
1843 line = iter(p.stderr).next()
1844 if ' -print_config' in line:
1845 pass # Persistent proxy doesn't support -print_config.
1846 else:
1847 print(line + p.stderr.read(), file=sys.stderr)
1848 if cookiefile:
1849 return cookiefile
1850 except OSError as e:
1851 if e.errno == errno.ENOENT:
1852 pass # No persistent proxy.
1853 raise
1854 return GitConfig.ForUser().GetString('http.cookiefile')
1855
1794 def _Checkout(self, rev, quiet=False): 1856 def _Checkout(self, rev, quiet=False):
1795 cmd = ['checkout'] 1857 cmd = ['checkout']
1796 if quiet: 1858 if quiet:
@@ -1841,16 +1903,17 @@ class Project(object):
1841 if GitCommand(self, cmd).Wait() != 0: 1903 if GitCommand(self, cmd).Wait() != 0:
1842 raise GitError('%s merge %s ' % (self.name, head)) 1904 raise GitError('%s merge %s ' % (self.name, head))
1843 1905
1844 def _InitGitDir(self): 1906 def _InitGitDir(self, mirror_git=None):
1845 if not os.path.exists(self.gitdir): 1907 if not os.path.exists(self.gitdir):
1846 os.makedirs(self.gitdir) 1908 os.makedirs(self.gitdir)
1847 self.bare_git.init() 1909 self.bare_git.init()
1848 1910
1849 mp = self.manifest.manifestProject 1911 mp = self.manifest.manifestProject
1850 ref_dir = mp.config.GetString('repo.reference') 1912 ref_dir = mp.config.GetString('repo.reference') or ''
1851 1913
1852 if ref_dir: 1914 if ref_dir or mirror_git:
1853 mirror_git = os.path.join(ref_dir, self.name + '.git') 1915 if not mirror_git:
1916 mirror_git = os.path.join(ref_dir, self.name + '.git')
1854 repo_git = os.path.join(ref_dir, '.repo', 'projects', 1917 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1855 self.relpath + '.git') 1918 self.relpath + '.git')
1856 1919
@@ -1867,11 +1930,21 @@ class Project(object):
1867 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'), 1930 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1868 os.path.join(ref_dir, 'objects') + '\n') 1931 os.path.join(ref_dir, 'objects') + '\n')
1869 1932
1933 self._UpdateHooks()
1934
1935 m = self.manifest.manifestProject.config
1936 for key in ['user.name', 'user.email']:
1937 if m.Has(key, include_defaults = False):
1938 self.config.SetString(key, m.GetString(key))
1870 if self.manifest.IsMirror: 1939 if self.manifest.IsMirror:
1871 self.config.SetString('core.bare', 'true') 1940 self.config.SetString('core.bare', 'true')
1872 else: 1941 else:
1873 self.config.SetString('core.bare', None) 1942 self.config.SetString('core.bare', None)
1874 1943
1944 def _UpdateHooks(self):
1945 if os.path.exists(self.gitdir):
1946 # Always recreate hooks since they can have been changed
1947 # since the latest update.
1875 hooks = self._gitdir_path('hooks') 1948 hooks = self._gitdir_path('hooks')
1876 try: 1949 try:
1877 to_rm = os.listdir(hooks) 1950 to_rm = os.listdir(hooks)
@@ -1881,11 +1954,6 @@ class Project(object):
1881 os.remove(os.path.join(hooks, old_hook)) 1954 os.remove(os.path.join(hooks, old_hook))
1882 self._InitHooks() 1955 self._InitHooks()
1883 1956
1884 m = self.manifest.manifestProject.config
1885 for key in ['user.name', 'user.email']:
1886 if m.Has(key, include_defaults = False):
1887 self.config.SetString(key, m.GetString(key))
1888
1889 def _InitHooks(self): 1957 def _InitHooks(self):
1890 hooks = self._gitdir_path('hooks') 1958 hooks = self._gitdir_path('hooks')
1891 if not os.path.exists(hooks): 1959 if not os.path.exists(hooks):
@@ -2092,6 +2160,10 @@ class Project(object):
2092 line = fd.read() 2160 line = fd.read()
2093 finally: 2161 finally:
2094 fd.close() 2162 fd.close()
2163 try:
2164 line = line.decode()
2165 except AttributeError:
2166 pass
2095 if line.startswith('ref: '): 2167 if line.startswith('ref: '):
2096 return line[5:-1] 2168 return line[5:-1]
2097 return line[:-1] 2169 return line[:-1]
@@ -2185,7 +2257,7 @@ class Project(object):
2185 if not git_require((1, 7, 2)): 2257 if not git_require((1, 7, 2)):
2186 raise ValueError('cannot set config on command line for %s()' 2258 raise ValueError('cannot set config on command line for %s()'
2187 % name) 2259 % name)
2188 for k, v in config.iteritems(): 2260 for k, v in config.items():
2189 cmdv.append('-c') 2261 cmdv.append('-c')
2190 cmdv.append('%s=%s' % (k, v)) 2262 cmdv.append('%s=%s' % (k, v))
2191 cmdv.append(name) 2263 cmdv.append(name)
@@ -2201,6 +2273,10 @@ class Project(object):
2201 name, 2273 name,
2202 p.stderr)) 2274 p.stderr))
2203 r = p.stdout 2275 r = p.stdout
2276 try:
2277 r = r.decode('utf-8')
2278 except AttributeError:
2279 pass
2204 if r.endswith('\n') and r.index('\n') == len(r) - 1: 2280 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2205 return r[:-1] 2281 return r[:-1]
2206 return r 2282 return r
diff --git a/pyversion.py b/pyversion.py
new file mode 100644
index 00000000..5b348d91
--- /dev/null
+++ b/pyversion.py
@@ -0,0 +1,19 @@
1#
2# Copyright (C) 2013 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import sys
17
18def is_python3():
19 return sys.version_info[0] == 3
diff --git a/repo b/repo
index 602cf0cf..62e6ea51 100755
--- a/repo
+++ b/repo
@@ -2,7 +2,6 @@
2 2
3## repo default configuration 3## repo default configuration
4## 4##
5from __future__ import print_function
6REPO_URL = 'https://gerrit.googlesource.com/git-repo' 5REPO_URL = 'https://gerrit.googlesource.com/git-repo'
7REPO_REV = 'stable' 6REPO_REV = 'stable'
8 7
@@ -21,10 +20,10 @@ REPO_REV = 'stable'
21# limitations under the License. 20# limitations under the License.
22 21
23# increment this whenever we make important changes to this script 22# increment this whenever we make important changes to this script
24VERSION = (1, 19) 23VERSION = (1, 20)
25 24
26# increment this if the MAINTAINER_KEYS block is modified 25# increment this if the MAINTAINER_KEYS block is modified
27KEYRING_VERSION = (1, 1) 26KEYRING_VERSION = (1, 2)
28MAINTAINER_KEYS = """ 27MAINTAINER_KEYS = """
29 28
30 Repo Maintainer <repo@android.kernel.org> 29 Repo Maintainer <repo@android.kernel.org>
@@ -73,32 +72,32 @@ TACbBS+Up3RpfYVfd63c1cDdlru13pQAn3NQy/SN858MkxN+zym86UBgOad2
73-----BEGIN PGP PUBLIC KEY BLOCK----- 72-----BEGIN PGP PUBLIC KEY BLOCK-----
74Version: GnuPG v1.4.11 (GNU/Linux) 73Version: GnuPG v1.4.11 (GNU/Linux)
75 74
76mQENBFBiLPwBCACvISTASOgFXwADw2GYRH2I2z9RvYkYoZ6ThTTNlMXbbYYKO2Wo 75mQENBFHRvc8BCADFg45Xx/y6QDC+T7Y/gGc7vx0ww7qfOwIKlAZ9xG3qKunMxo+S
77a9LQDNW0TbCEekg5UKk0FD13XOdWaqUt4Gtuvq9c43GRSjMO6NXH+0BjcQ8vUtY2 76hPCnzEl3cq+6I1Ww/ndop/HB3N3toPXRCoN8Vs4/Hc7by+SnaLFnacrm+tV5/OgT
78/W4CYUevwdo4nQ1+1zsOCu1XYe/CReXq0fdugv3hgmRmh3sz1soo37Q44W2frxxg 77V37Lzt8lhay1Kl+YfpFwHYYpIEBLFV9knyfRXS/428W2qhdzYfvB15/AasRmwmor
79U7Rz3Da4FjgAL0RQ8qndD+LwRHXTY7H7wYM8V/3cYFZV7pSodd75q3MAXYQLf0ZV 78py4NIzSs8UD/SPr1ihqNCdZM76+MQyN5HMYXW/ALZXUFG0pwluHFA7hrfPG74i8C
80QR1XATu5l1QnXrxgHvz7MmDwb1D+jX3YPKnZveaukigQ6hDHdiVcePBiGXmk8LZC 79zMiP7qvMWIl/r/jtzHioH1dRKgbod+LZsrDJ8mBaqsZaDmNJMhss9g76XvfMyLra
812jQkdXeF7Su1ZYpr2nnEHLJ6vOLcCpPGb8gDABEBAAG0H0NvbmxleSBPd2VucyA8 809DI9/iFuBpGzeqBv0hwOGQspLRrEoyTeR6n1ABEBAAG0H0NvbmxleSBPd2VucyA8
82Y2NvM0BhbmRyb2lkLmNvbT6JATgEEwECACIFAlBiLPwCGwMGCwkIBwMCBhUIAgkK 81Y2NvM0BhbmRyb2lkLmNvbT6JATgEEwECACIFAlHRvc8CGwMGCwkIBwMCBhUIAgkK
83CwQWAgMBAh4BAheAAAoJEBkmlFUziHGkHVkH/2Hks2Cif5i2xPtv2IFZcjL42joU 82CwQWAgMBAh4BAheAAAoJEGe35EhpKzgsP6AIAJKJmNtn4l7hkYHKHFSo3egb6RjQ
84T7lO5XFqUYS9ZNHpGa/V0eiPt7rHoO16glR83NZtwlrq2cSN89i9HfOhMYV/qLu8 83zEIP3MFTcu8HFX1kF1ZFbrp7xqurLaE53kEkKuAAvjJDAgI8mcZHP1JyplubqjQA
85fLCHcV2muw+yCB5s5bxnI5UkToiNZyBNqFkcOt/Kbj9Hpy68A1kmc6myVEaUYebq 84xvv84gK+OGP3Xk+QK1ZjUQSbjOpjEiSZpRhWcHci3dgOUH4blJfByHw25hlgHowd
862Chx/f3xuEthan099t746v1K+/6SvQGDNctHuaMr9cWdxZtHjdRf31SQRc99Phe5 85a/2PrNKZVcJ92YienaxxGjcXEUcd0uYEG2+rwllQigFcnMFDhr9B71MfalRHjFKE
87w+ZGR/ebxNDKRK9mKgZT8wVFHlXerJsRqWIqtx1fsW1UgLgbpcpe2MChm6B5wTu0 86fmdoypqLrri61YBc59P88Rw2/WUpTQjgNubSqa3A2+CKdaRyaRw+2fdF4TdR0h8W
88s1ltzox3l4q71FyRRPUJxXyvGkDLZWpK7EpiHSCOYq/KP3HkKeXU3xqHpcG5AQ0E 87zbg+lbaPtJHsV+3mJC7fq26MiJDRJa5ZztpMn8su20gbLgi2ShBOaHAYDDi5AQ0E
89UGIs/AEIAKzO/7lO9cB6dshmZYo8Vy/b7aGicThE+ChcDSfhvyOXVdEM2GKAjsR+ 88UdG9zwEIAMoOBq+QLNozAhxOOl5GL3StTStGRgPRXINfmViTsihrqGCWBBUfXlUE
90rlBWbTFX3It301p2HwZPFEi9nEvJxVlqqBiW0bPmNMkDRR55l2vbWg35wwkg6RyE 89OytC0mYcrDUQev/8ToVoyqw+iGSwDkcSXkrEUCKFtHV/GECWtk1keyHgR10YKI1R
91Bc5/TQjhXI2w8IvlimoGoUff4t3JmMOnWrnKSvL+5iuRj12p9WmanCHzw3Ee7ztf 90mquSXoubWGqPeG1PAI74XWaRx8UrL8uCXUtmD8Q5J7mDjKR5NpxaXrwlA0bKsf2E
92/aU/q+FTpr3DLerb6S8xbv86ySgnJT6o5CyL2DCWRtnYQyGVi0ZmLzEouAYiO0hs 91Gp9tu1kKauuToZhWHMRMqYSOGikQJwWSFYKT1KdNcOXLQF6+bfoJ6sjVYdwfmNQL
93z0AAu28Mj+12g2WwePRz6gfM9rHtI37ylYW3oT/9M9mO9ei/Bc/1D7Dz6qNV+0vg 92Ixn8QVhoTDedcqClSWB17VDEFDFa7MmqXZz2qtM3X1R/MUMHqPtegQzBGNhRdnI2
94uSVJxM2Bl6GalHPZLhHntFEdIA6EdoUAEQEAAYkBHwQYAQIACQUCUGIs/AIbDAAK 93V45+1Nnx/uuCxDbeI4RbHzujnxDiq70AEQEAAYkBHwQYAQIACQUCUdG9zwIbDAAK
95CRAZJpRVM4hxpNfkB/0W/hP5WK/NETXBlWXXW7JPaWO2c5kGwD0lnj5RRmridyo1 94CRBnt+RIaSs4LNVeB/0Y2pZ8I7gAAcEM0Xw8drr4omg2fUoK1J33ozlA/RxeA/lJ
96vbm5PdM91jOsDQYqRu6YOoYBnDnEhB2wL2bPh34HWwwrA+LwB8hlcAV2z1bdwyfl 95I3KnyCDTpXuIeBKPGkdL8uMATC9Z8DnBBajRlftNDVZS3Hz4G09G9QpMojvJkFJV
973R823fReKN3QcvLHzmvZPrF4Rk97M9UIyKS0RtnfTWykRgDWHIsrtQPoNwsXrWoT 96By+01Flw/X+eeN8NpqSuLV4W+AjEO8at/VvgKr1AFvBRdZ7GkpI1o6DgPe7ZqX+1
989LrM2v+1+9mp3vuXnE473/NHxmiWEQH9Ez+O/mOxQ7rSOlqGRiKq/IBZCfioJOtV 97dzQZt3e13W0rVBb/bUgx9iSLoeWP3aq/k+/GRGOR+S6F6BBSl0SQ2EF2+dIywb1x
99fTQeIu/yASZnsLBqr6SJEGwYBoWcyjG++k4fyw8ocOAo4uGDYbxgN7yYfNQ0OH7o 98JuinEP+AwLAUZ1Bsx9ISC0Agpk2VeHXPL3FGhroEmoMvBzO0kTFGyoeT7PR/BfKv
100V6pfUgqKLWa/aK7/N1ZHnPdFLD8Xt0Dmy4BPwrKC 99+H/g3HsL2LOB9uoIm8/5p2TTU5ttYCXMHhQZ81AY
101=O7am 100=AUp4
102-----END PGP PUBLIC KEY BLOCK----- 101-----END PGP PUBLIC KEY BLOCK-----
103""" 102"""
104 103
@@ -108,6 +107,7 @@ repodir = '.repo' # name of repo's private directory
108S_repo = 'repo' # special repo repository 107S_repo = 'repo' # special repo repository
109S_manifests = 'manifests' # special manifest repository 108S_manifests = 'manifests' # special manifest repository
110REPO_MAIN = S_repo + '/main.py' # main script 109REPO_MAIN = S_repo + '/main.py' # main script
110MIN_PYTHON_VERSION = (2, 6) # minimum supported python version
111 111
112 112
113import optparse 113import optparse
@@ -116,19 +116,38 @@ import re
116import stat 116import stat
117import subprocess 117import subprocess
118import sys 118import sys
119try: 119
120 import urllib2 120if sys.version_info[0] == 3:
121except ImportError:
122 # For python3
123 import urllib.request 121 import urllib.request
124 import urllib.error 122 import urllib.error
125else: 123else:
126 # For python2
127 import imp 124 import imp
125 import urllib2
128 urllib = imp.new_module('urllib') 126 urllib = imp.new_module('urllib')
129 urllib.request = urllib2 127 urllib.request = urllib2
130 urllib.error = urllib2 128 urllib.error = urllib2
131 129
130
131def _print(*objects, **kwargs):
132 sep = kwargs.get('sep', ' ')
133 end = kwargs.get('end', '\n')
134 out = kwargs.get('file', sys.stdout)
135 out.write(sep.join(objects) + end)
136
137
138# Python version check
139ver = sys.version_info
140if ver[0] == 3:
141 _print('error: Python 3 support is not fully implemented in repo yet.\n'
142 'Please use Python 2.6 - 2.7 instead.',
143 file=sys.stderr)
144 sys.exit(1)
145if (ver[0], ver[1]) < MIN_PYTHON_VERSION:
146 _print('error: Python version %s unsupported.\n'
147 'Please use Python 2.6 - 2.7 instead.'
148 % sys.version.split(' ')[0], file=sys.stderr)
149 sys.exit(1)
150
132home_dot_repo = os.path.expanduser('~/.repoconfig') 151home_dot_repo = os.path.expanduser('~/.repoconfig')
133gpg_dir = os.path.join(home_dot_repo, 'gnupg') 152gpg_dir = os.path.join(home_dot_repo, 'gnupg')
134 153
@@ -164,7 +183,8 @@ group.add_option('--depth', type='int', default=None,
164 help='create a shallow clone with given depth; see git clone') 183 help='create a shallow clone with given depth; see git clone')
165group.add_option('-g', '--groups', 184group.add_option('-g', '--groups',
166 dest='groups', default='default', 185 dest='groups', default='default',
167 help='restrict manifest projects to ones with a specified group', 186 help='restrict manifest projects to ones with specified '
187 'group(s) [default|all|G1,G2,G3|G4,-G5,-G6]',
168 metavar='GROUP') 188 metavar='GROUP')
169group.add_option('-p', '--platform', 189group.add_option('-p', '--platform',
170 dest='platform', default="auto", 190 dest='platform', default="auto",
@@ -217,15 +237,15 @@ def _Init(args):
217 if branch.startswith('refs/heads/'): 237 if branch.startswith('refs/heads/'):
218 branch = branch[len('refs/heads/'):] 238 branch = branch[len('refs/heads/'):]
219 if branch.startswith('refs/'): 239 if branch.startswith('refs/'):
220 print("fatal: invalid branch name '%s'" % branch, file=sys.stderr) 240 _print("fatal: invalid branch name '%s'" % branch, file=sys.stderr)
221 raise CloneFailure() 241 raise CloneFailure()
222 242
223 if not os.path.isdir(repodir): 243 if not os.path.isdir(repodir):
224 try: 244 try:
225 os.mkdir(repodir) 245 os.mkdir(repodir)
226 except OSError as e: 246 except OSError as e:
227 print('fatal: cannot make %s directory: %s' 247 _print('fatal: cannot make %s directory: %s'
228 % (repodir, e.strerror), file=sys.stderr) 248 % (repodir, e.strerror), file=sys.stderr)
229 # Don't raise CloneFailure; that would delete the 249 # Don't raise CloneFailure; that would delete the
230 # name. Instead exit immediately. 250 # name. Instead exit immediately.
231 # 251 #
@@ -249,8 +269,8 @@ def _Init(args):
249 _Checkout(dst, branch, rev, opt.quiet) 269 _Checkout(dst, branch, rev, opt.quiet)
250 except CloneFailure: 270 except CloneFailure:
251 if opt.quiet: 271 if opt.quiet:
252 print('fatal: repo init failed; run without --quiet to see why', 272 _print('fatal: repo init failed; run without --quiet to see why',
253 file=sys.stderr) 273 file=sys.stderr)
254 raise 274 raise
255 275
256 276
@@ -259,12 +279,12 @@ def _CheckGitVersion():
259 try: 279 try:
260 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) 280 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
261 except OSError as e: 281 except OSError as e:
262 print(file=sys.stderr) 282 _print(file=sys.stderr)
263 print("fatal: '%s' is not available" % GIT, file=sys.stderr) 283 _print("fatal: '%s' is not available" % GIT, file=sys.stderr)
264 print('fatal: %s' % e, file=sys.stderr) 284 _print('fatal: %s' % e, file=sys.stderr)
265 print(file=sys.stderr) 285 _print(file=sys.stderr)
266 print('Please make sure %s is installed and in your path.' % GIT, 286 _print('Please make sure %s is installed and in your path.' % GIT,
267 file=sys.stderr) 287 file=sys.stderr)
268 raise CloneFailure() 288 raise CloneFailure()
269 289
270 ver_str = proc.stdout.read().strip() 290 ver_str = proc.stdout.read().strip()
@@ -272,14 +292,14 @@ def _CheckGitVersion():
272 proc.wait() 292 proc.wait()
273 293
274 if not ver_str.startswith('git version '): 294 if not ver_str.startswith('git version '):
275 print('error: "%s" unsupported' % ver_str, file=sys.stderr) 295 _print('error: "%s" unsupported' % ver_str, file=sys.stderr)
276 raise CloneFailure() 296 raise CloneFailure()
277 297
278 ver_str = ver_str[len('git version '):].strip() 298 ver_str = ver_str[len('git version '):].strip()
279 ver_act = tuple(map(int, ver_str.split('.')[0:3])) 299 ver_act = tuple(map(int, ver_str.split('.')[0:3]))
280 if ver_act < MIN_GIT_VERSION: 300 if ver_act < MIN_GIT_VERSION:
281 need = '.'.join(map(str, MIN_GIT_VERSION)) 301 need = '.'.join(map(str, MIN_GIT_VERSION))
282 print('fatal: git %s or later required' % need, file=sys.stderr) 302 _print('fatal: git %s or later required' % need, file=sys.stderr)
283 raise CloneFailure() 303 raise CloneFailure()
284 304
285 305
@@ -306,16 +326,16 @@ def SetupGnuPG(quiet):
306 try: 326 try:
307 os.mkdir(home_dot_repo) 327 os.mkdir(home_dot_repo)
308 except OSError as e: 328 except OSError as e:
309 print('fatal: cannot make %s directory: %s' 329 _print('fatal: cannot make %s directory: %s'
310 % (home_dot_repo, e.strerror), file=sys.stderr) 330 % (home_dot_repo, e.strerror), file=sys.stderr)
311 sys.exit(1) 331 sys.exit(1)
312 332
313 if not os.path.isdir(gpg_dir): 333 if not os.path.isdir(gpg_dir):
314 try: 334 try:
315 os.mkdir(gpg_dir, stat.S_IRWXU) 335 os.mkdir(gpg_dir, stat.S_IRWXU)
316 except OSError as e: 336 except OSError as e:
317 print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror), 337 _print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror),
318 file=sys.stderr) 338 file=sys.stderr)
319 sys.exit(1) 339 sys.exit(1)
320 340
321 env = os.environ.copy() 341 env = os.environ.copy()
@@ -328,18 +348,18 @@ def SetupGnuPG(quiet):
328 stdin = subprocess.PIPE) 348 stdin = subprocess.PIPE)
329 except OSError as e: 349 except OSError as e:
330 if not quiet: 350 if not quiet:
331 print('warning: gpg (GnuPG) is not available.', file=sys.stderr) 351 _print('warning: gpg (GnuPG) is not available.', file=sys.stderr)
332 print('warning: Installing it is strongly encouraged.', file=sys.stderr) 352 _print('warning: Installing it is strongly encouraged.', file=sys.stderr)
333 print(file=sys.stderr) 353 _print(file=sys.stderr)
334 return False 354 return False
335 355
336 proc.stdin.write(MAINTAINER_KEYS) 356 proc.stdin.write(MAINTAINER_KEYS)
337 proc.stdin.close() 357 proc.stdin.close()
338 358
339 if proc.wait() != 0: 359 if proc.wait() != 0:
340 print('fatal: registering repo maintainer keys failed', file=sys.stderr) 360 _print('fatal: registering repo maintainer keys failed', file=sys.stderr)
341 sys.exit(1) 361 sys.exit(1)
342 print() 362 _print()
343 363
344 fd = open(os.path.join(home_dot_repo, 'keyring-version'), 'w') 364 fd = open(os.path.join(home_dot_repo, 'keyring-version'), 'w')
345 fd.write('.'.join(map(str, KEYRING_VERSION)) + '\n') 365 fd.write('.'.join(map(str, KEYRING_VERSION)) + '\n')
@@ -381,7 +401,7 @@ def _InitHttp():
381 401
382def _Fetch(url, local, src, quiet): 402def _Fetch(url, local, src, quiet):
383 if not quiet: 403 if not quiet:
384 print('Get %s' % url, file=sys.stderr) 404 _print('Get %s' % url, file=sys.stderr)
385 405
386 cmd = [GIT, 'fetch'] 406 cmd = [GIT, 'fetch']
387 if quiet: 407 if quiet:
@@ -430,16 +450,16 @@ def _DownloadBundle(url, local, quiet):
430 except urllib.error.HTTPError as e: 450 except urllib.error.HTTPError as e:
431 if e.code in [403, 404]: 451 if e.code in [403, 404]:
432 return False 452 return False
433 print('fatal: Cannot get %s' % url, file=sys.stderr) 453 _print('fatal: Cannot get %s' % url, file=sys.stderr)
434 print('fatal: HTTP error %s' % e.code, file=sys.stderr) 454 _print('fatal: HTTP error %s' % e.code, file=sys.stderr)
435 raise CloneFailure() 455 raise CloneFailure()
436 except urllib.error.URLError as e: 456 except urllib.error.URLError as e:
437 print('fatal: Cannot get %s' % url, file=sys.stderr) 457 _print('fatal: Cannot get %s' % url, file=sys.stderr)
438 print('fatal: error %s' % e.reason, file=sys.stderr) 458 _print('fatal: error %s' % e.reason, file=sys.stderr)
439 raise CloneFailure() 459 raise CloneFailure()
440 try: 460 try:
441 if not quiet: 461 if not quiet:
442 print('Get %s' % url, file=sys.stderr) 462 _print('Get %s' % url, file=sys.stderr)
443 while True: 463 while True:
444 buf = r.read(8192) 464 buf = r.read(8192)
445 if buf == '': 465 if buf == '':
@@ -463,23 +483,23 @@ def _Clone(url, local, quiet):
463 try: 483 try:
464 os.mkdir(local) 484 os.mkdir(local)
465 except OSError as e: 485 except OSError as e:
466 print('fatal: cannot make %s directory: %s' % (local, e.strerror), 486 _print('fatal: cannot make %s directory: %s' % (local, e.strerror),
467 file=sys.stderr) 487 file=sys.stderr)
468 raise CloneFailure() 488 raise CloneFailure()
469 489
470 cmd = [GIT, 'init', '--quiet'] 490 cmd = [GIT, 'init', '--quiet']
471 try: 491 try:
472 proc = subprocess.Popen(cmd, cwd = local) 492 proc = subprocess.Popen(cmd, cwd = local)
473 except OSError as e: 493 except OSError as e:
474 print(file=sys.stderr) 494 _print(file=sys.stderr)
475 print("fatal: '%s' is not available" % GIT, file=sys.stderr) 495 _print("fatal: '%s' is not available" % GIT, file=sys.stderr)
476 print('fatal: %s' % e, file=sys.stderr) 496 _print('fatal: %s' % e, file=sys.stderr)
477 print(file=sys.stderr) 497 _print(file=sys.stderr)
478 print('Please make sure %s is installed and in your path.' % GIT, 498 _print('Please make sure %s is installed and in your path.' % GIT,
479 file=sys.stderr) 499 file=sys.stderr)
480 raise CloneFailure() 500 raise CloneFailure()
481 if proc.wait() != 0: 501 if proc.wait() != 0:
482 print('fatal: could not create %s' % local, file=sys.stderr) 502 _print('fatal: could not create %s' % local, file=sys.stderr)
483 raise CloneFailure() 503 raise CloneFailure()
484 504
485 _InitHttp() 505 _InitHttp()
@@ -507,18 +527,18 @@ def _Verify(cwd, branch, quiet):
507 proc.stderr.close() 527 proc.stderr.close()
508 528
509 if proc.wait() != 0 or not cur: 529 if proc.wait() != 0 or not cur:
510 print(file=sys.stderr) 530 _print(file=sys.stderr)
511 print("fatal: branch '%s' has not been signed" % branch, file=sys.stderr) 531 _print("fatal: branch '%s' has not been signed" % branch, file=sys.stderr)
512 raise CloneFailure() 532 raise CloneFailure()
513 533
514 m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur) 534 m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur)
515 if m: 535 if m:
516 cur = m.group(1) 536 cur = m.group(1)
517 if not quiet: 537 if not quiet:
518 print(file=sys.stderr) 538 _print(file=sys.stderr)
519 print("info: Ignoring branch '%s'; using tagged release '%s'" 539 _print("info: Ignoring branch '%s'; using tagged release '%s'"
520 % (branch, cur), file=sys.stderr) 540 % (branch, cur), file=sys.stderr)
521 print(file=sys.stderr) 541 _print(file=sys.stderr)
522 542
523 env = os.environ.copy() 543 env = os.environ.copy()
524 env['GNUPGHOME'] = gpg_dir.encode() 544 env['GNUPGHOME'] = gpg_dir.encode()
@@ -536,10 +556,10 @@ def _Verify(cwd, branch, quiet):
536 proc.stderr.close() 556 proc.stderr.close()
537 557
538 if proc.wait() != 0: 558 if proc.wait() != 0:
539 print(file=sys.stderr) 559 _print(file=sys.stderr)
540 print(out, file=sys.stderr) 560 _print(out, file=sys.stderr)
541 print(err, file=sys.stderr) 561 _print(err, file=sys.stderr)
542 print(file=sys.stderr) 562 _print(file=sys.stderr)
543 raise CloneFailure() 563 raise CloneFailure()
544 return '%s^0' % cur 564 return '%s^0' % cur
545 565
@@ -606,7 +626,7 @@ def _ParseArguments(args):
606 626
607 627
608def _Usage(): 628def _Usage():
609 print( 629 _print(
610"""usage: repo COMMAND [ARGS] 630"""usage: repo COMMAND [ARGS]
611 631
612repo is not yet installed. Use "repo init" to install it here. 632repo is not yet installed. Use "repo init" to install it here.
@@ -627,23 +647,23 @@ def _Help(args):
627 init_optparse.print_help() 647 init_optparse.print_help()
628 sys.exit(0) 648 sys.exit(0)
629 else: 649 else:
630 print("error: '%s' is not a bootstrap command.\n" 650 _print("error: '%s' is not a bootstrap command.\n"
631 ' For access to online help, install repo ("repo init").' 651 ' For access to online help, install repo ("repo init").'
632 % args[0], file=sys.stderr) 652 % args[0], file=sys.stderr)
633 else: 653 else:
634 _Usage() 654 _Usage()
635 sys.exit(1) 655 sys.exit(1)
636 656
637 657
638def _NotInstalled(): 658def _NotInstalled():
639 print('error: repo is not installed. Use "repo init" to install it here.', 659 _print('error: repo is not installed. Use "repo init" to install it here.',
640 file=sys.stderr) 660 file=sys.stderr)
641 sys.exit(1) 661 sys.exit(1)
642 662
643 663
644def _NoCommands(cmd): 664def _NoCommands(cmd):
645 print("""error: command '%s' requires repo to be installed first. 665 _print("""error: command '%s' requires repo to be installed first.
646 Use "repo init" to install it here.""" % cmd, file=sys.stderr) 666 Use "repo init" to install it here.""" % cmd, file=sys.stderr)
647 sys.exit(1) 667 sys.exit(1)
648 668
649 669
@@ -680,7 +700,7 @@ def _SetDefaultsTo(gitdir):
680 proc.stderr.close() 700 proc.stderr.close()
681 701
682 if proc.wait() != 0: 702 if proc.wait() != 0:
683 print('fatal: %s has no current branch' % gitdir, file=sys.stderr) 703 _print('fatal: %s has no current branch' % gitdir, file=sys.stderr)
684 sys.exit(1) 704 sys.exit(1)
685 705
686 706
@@ -729,8 +749,8 @@ def main(orig_args):
729 try: 749 try:
730 os.execv(sys.executable, me) 750 os.execv(sys.executable, me)
731 except OSError as e: 751 except OSError as e:
732 print("fatal: unable to start %s" % repo_main, file=sys.stderr) 752 _print("fatal: unable to start %s" % repo_main, file=sys.stderr)
733 print("fatal: %s" % e, file=sys.stderr) 753 _print("fatal: %s" % e, file=sys.stderr)
734 sys.exit(148) 754 sys.exit(148)
735 755
736 756
diff --git a/subcmds/__init__.py b/subcmds/__init__.py
index 1fac802e..84efb4de 100644
--- a/subcmds/__init__.py
+++ b/subcmds/__init__.py
@@ -38,8 +38,8 @@ for py in os.listdir(my_dir):
38 try: 38 try:
39 cmd = getattr(mod, clsn)() 39 cmd = getattr(mod, clsn)()
40 except AttributeError: 40 except AttributeError:
41 raise SyntaxError, '%s/%s does not define class %s' % ( 41 raise SyntaxError('%s/%s does not define class %s' % (
42 __name__, py, clsn) 42 __name__, py, clsn))
43 43
44 name = name.replace('_', '-') 44 name = name.replace('_', '-')
45 cmd.NAME = name 45 cmd.NAME = name
diff --git a/subcmds/branches.py b/subcmds/branches.py
index 06d45abe..c2e7c4b9 100644
--- a/subcmds/branches.py
+++ b/subcmds/branches.py
@@ -98,14 +98,13 @@ is shown, then the branch appears in all projects.
98 project_cnt = len(projects) 98 project_cnt = len(projects)
99 99
100 for project in projects: 100 for project in projects:
101 for name, b in project.GetBranches().iteritems(): 101 for name, b in project.GetBranches().items():
102 b.project = project 102 b.project = project
103 if name not in all_branches: 103 if name not in all_branches:
104 all_branches[name] = BranchInfo(name) 104 all_branches[name] = BranchInfo(name)
105 all_branches[name].add(b) 105 all_branches[name].add(b)
106 106
107 names = all_branches.keys() 107 names = list(sorted(all_branches))
108 names.sort()
109 108
110 if not names: 109 if not names:
111 print(' (no branches)', file=sys.stderr) 110 print(' (no branches)', file=sys.stderr)
diff --git a/subcmds/cherry_pick.py b/subcmds/cherry_pick.py
index 01b97e07..520e4c32 100644
--- a/subcmds/cherry_pick.py
+++ b/subcmds/cherry_pick.py
@@ -81,7 +81,7 @@ change id will be added.
81 sys.exit(1) 81 sys.exit(1)
82 82
83 else: 83 else:
84 print('NOTE: When committing (please see above) and editing the commit' 84 print('NOTE: When committing (please see above) and editing the commit '
85 'message, please remove the old Change-Id-line and add:') 85 'message, please remove the old Change-Id-line and add:')
86 print(self._GetReference(sha1), file=sys.stderr) 86 print(self._GetReference(sha1), file=sys.stderr)
87 print(file=sys.stderr) 87 print(file=sys.stderr)
diff --git a/subcmds/forall.py b/subcmds/forall.py
index 4c1c9ff8..e2a420a9 100644
--- a/subcmds/forall.py
+++ b/subcmds/forall.py
@@ -42,10 +42,14 @@ class Forall(Command, MirrorSafeCommand):
42 helpSummary = "Run a shell command in each project" 42 helpSummary = "Run a shell command in each project"
43 helpUsage = """ 43 helpUsage = """
44%prog [<project>...] -c <command> [<arg>...] 44%prog [<project>...] -c <command> [<arg>...]
45%prog -r str1 [str2] ... -c <command> [<arg>...]"
45""" 46"""
46 helpDescription = """ 47 helpDescription = """
47Executes the same shell command in each project. 48Executes the same shell command in each project.
48 49
50The -r option allows running the command only on projects matching
51regex or wildcard expression.
52
49Output Formatting 53Output Formatting
50----------------- 54-----------------
51 55
@@ -103,6 +107,9 @@ without iterating through the remaining projects.
103 setattr(parser.values, option.dest, list(parser.rargs)) 107 setattr(parser.values, option.dest, list(parser.rargs))
104 while parser.rargs: 108 while parser.rargs:
105 del parser.rargs[0] 109 del parser.rargs[0]
110 p.add_option('-r', '--regex',
111 dest='regex', action='store_true',
112 help="Execute the command only on projects matching regex or wildcard expression")
106 p.add_option('-c', '--command', 113 p.add_option('-c', '--command',
107 help='Command (and arguments) to execute', 114 help='Command (and arguments) to execute',
108 dest='command', 115 dest='command',
@@ -166,7 +173,12 @@ without iterating through the remaining projects.
166 rc = 0 173 rc = 0
167 first = True 174 first = True
168 175
169 for project in self.GetProjects(args): 176 if not opt.regex:
177 projects = self.GetProjects(args)
178 else:
179 projects = self.FindProjects(args)
180
181 for project in projects:
170 env = os.environ.copy() 182 env = os.environ.copy()
171 def setenv(name, val): 183 def setenv(name, val):
172 if val is None: 184 if val is None:
@@ -248,7 +260,12 @@ without iterating through the remaining projects.
248 first = False 260 first = False
249 else: 261 else:
250 out.nl() 262 out.nl()
251 out.project('project %s/', project.relpath) 263
264 if mirror:
265 project_header_path = project.name
266 else:
267 project_header_path = project.relpath
268 out.project('project %s/', project_header_path)
252 out.nl() 269 out.nl()
253 out.flush() 270 out.flush()
254 if errbuf: 271 if errbuf:
diff --git a/subcmds/help.py b/subcmds/help.py
index 15aab7f9..4aa3f863 100644
--- a/subcmds/help.py
+++ b/subcmds/help.py
@@ -34,8 +34,7 @@ Displays detailed usage information about a command.
34 def _PrintAllCommands(self): 34 def _PrintAllCommands(self):
35 print('usage: repo COMMAND [ARGS]') 35 print('usage: repo COMMAND [ARGS]')
36 print('The complete list of recognized repo commands are:') 36 print('The complete list of recognized repo commands are:')
37 commandNames = self.commands.keys() 37 commandNames = list(sorted(self.commands))
38 commandNames.sort()
39 38
40 maxlen = 0 39 maxlen = 0
41 for name in commandNames: 40 for name in commandNames:
@@ -49,16 +48,15 @@ Displays detailed usage information about a command.
49 except AttributeError: 48 except AttributeError:
50 summary = '' 49 summary = ''
51 print(fmt % (name, summary)) 50 print(fmt % (name, summary))
52 print("See 'repo help <command>' for more information on a" 51 print("See 'repo help <command>' for more information on a "
53 'specific command.') 52 'specific command.')
54 53
55 def _PrintCommonCommands(self): 54 def _PrintCommonCommands(self):
56 print('usage: repo COMMAND [ARGS]') 55 print('usage: repo COMMAND [ARGS]')
57 print('The most commonly used repo commands are:') 56 print('The most commonly used repo commands are:')
58 commandNames = [name 57 commandNames = list(sorted([name
59 for name in self.commands.keys() 58 for name, command in self.commands.items()
60 if self.commands[name].common] 59 if command.common]))
61 commandNames.sort()
62 60
63 maxlen = 0 61 maxlen = 0
64 for name in commandNames: 62 for name in commandNames:
diff --git a/subcmds/info.py b/subcmds/info.py
index 8fb363f3..d42860ae 100644
--- a/subcmds/info.py
+++ b/subcmds/info.py
@@ -27,7 +27,7 @@ class Info(PagedCommand):
27 helpSummary = "Get info on the manifest branch, current branch or unmerged branches" 27 helpSummary = "Get info on the manifest branch, current branch or unmerged branches"
28 helpUsage = "%prog [-dl] [-o [-b]] [<project>...]" 28 helpUsage = "%prog [-dl] [-o [-b]] [<project>...]"
29 29
30 def _Options(self, p, show_smart=True): 30 def _Options(self, p):
31 p.add_option('-d', '--diff', 31 p.add_option('-d', '--diff',
32 dest='all', action='store_true', 32 dest='all', action='store_true',
33 help="show full info and commit diff including remote branches") 33 help="show full info and commit diff including remote branches")
@@ -53,7 +53,10 @@ class Info(PagedCommand):
53 53
54 self.opt = opt 54 self.opt = opt
55 55
56 mergeBranch = self.manifest.manifestProject.config.GetBranch("default").merge 56 manifestConfig = self.manifest.manifestProject.config
57 mergeBranch = manifestConfig.GetBranch("default").merge
58 manifestGroups = (manifestConfig.GetString('manifest.groups')
59 or 'all,-notdefault')
57 60
58 self.heading("Manifest branch: ") 61 self.heading("Manifest branch: ")
59 self.headtext(self.manifest.default.revisionExpr) 62 self.headtext(self.manifest.default.revisionExpr)
@@ -61,6 +64,9 @@ class Info(PagedCommand):
61 self.heading("Manifest merge branch: ") 64 self.heading("Manifest merge branch: ")
62 self.headtext(mergeBranch) 65 self.headtext(mergeBranch)
63 self.out.nl() 66 self.out.nl()
67 self.heading("Manifest groups: ")
68 self.headtext(manifestGroups)
69 self.out.nl()
64 70
65 self.printSeparator() 71 self.printSeparator()
66 72
@@ -157,7 +163,7 @@ class Info(PagedCommand):
157 all_branches = [] 163 all_branches = []
158 for project in self.GetProjects(args): 164 for project in self.GetProjects(args):
159 br = [project.GetUploadableBranch(x) 165 br = [project.GetUploadableBranch(x)
160 for x in project.GetBranches().keys()] 166 for x in project.GetBranches()]
161 br = [x for x in br if x] 167 br = [x for x in br if x]
162 if self.opt.current_branch: 168 if self.opt.current_branch:
163 br = [x for x in br if x.name == project.CurrentBranch] 169 br = [x for x in br if x.name == project.CurrentBranch]
diff --git a/subcmds/init.py b/subcmds/init.py
index 11312601..a44fb7a9 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -20,6 +20,15 @@ import re
20import shutil 20import shutil
21import sys 21import sys
22 22
23from pyversion import is_python3
24if is_python3():
25 import urllib.parse
26else:
27 import imp
28 import urlparse
29 urllib = imp.new_module('urllib')
30 urllib.parse = urlparse.urlparse
31
23from color import Coloring 32from color import Coloring
24from command import InteractiveCommand, MirrorSafeCommand 33from command import InteractiveCommand, MirrorSafeCommand
25from error import ManifestParseError 34from error import ManifestParseError
@@ -91,8 +100,9 @@ to update the working directory files.
91 dest='depth', 100 dest='depth',
92 help='create a shallow clone with given depth; see git clone') 101 help='create a shallow clone with given depth; see git clone')
93 g.add_option('-g', '--groups', 102 g.add_option('-g', '--groups',
94 dest='groups', default='all,-notdefault', 103 dest='groups', default='default',
95 help='restrict manifest projects to ones with a specified group', 104 help='restrict manifest projects to ones with specified '
105 'group(s) [default|all|G1,G2,G3|G4,-G5,-G6]',
96 metavar='GROUP') 106 metavar='GROUP')
97 g.add_option('-p', '--platform', 107 g.add_option('-p', '--platform',
98 dest='platform', default='auto', 108 dest='platform', default='auto',
@@ -134,7 +144,19 @@ to update the working directory files.
134 if not opt.quiet: 144 if not opt.quiet:
135 print('Get %s' % GitConfig.ForUser().UrlInsteadOf(opt.manifest_url), 145 print('Get %s' % GitConfig.ForUser().UrlInsteadOf(opt.manifest_url),
136 file=sys.stderr) 146 file=sys.stderr)
137 m._InitGitDir() 147
148 # The manifest project object doesn't keep track of the path on the
149 # server where this git is located, so let's save that here.
150 mirrored_manifest_git = None
151 if opt.reference:
152 manifest_git_path = urllib.parse(opt.manifest_url).path[1:]
153 mirrored_manifest_git = os.path.join(opt.reference, manifest_git_path)
154 if not mirrored_manifest_git.endswith(".git"):
155 mirrored_manifest_git += ".git"
156 if not os.path.exists(mirrored_manifest_git):
157 mirrored_manifest_git = os.path.join(opt.reference + '/.repo/manifests.git')
158
159 m._InitGitDir(mirror_git=mirrored_manifest_git)
138 160
139 if opt.manifest_branch: 161 if opt.manifest_branch:
140 m.revisionExpr = opt.manifest_branch 162 m.revisionExpr = opt.manifest_branch
@@ -169,7 +191,7 @@ to update the working directory files.
169 191
170 groups = [x for x in groups if x] 192 groups = [x for x in groups if x]
171 groupstr = ','.join(groups) 193 groupstr = ','.join(groups)
172 if opt.platform == 'auto' and groupstr == 'all,-notdefault,platform-' + platform.system().lower(): 194 if opt.platform == 'auto' and groupstr == 'default,platform-' + platform.system().lower():
173 groupstr = None 195 groupstr = None
174 m.config.SetString('manifest.groups', groupstr) 196 m.config.SetString('manifest.groups', groupstr)
175 197
diff --git a/subcmds/list.py b/subcmds/list.py
index 0d5c27f7..945c28d8 100644
--- a/subcmds/list.py
+++ b/subcmds/list.py
@@ -14,7 +14,7 @@
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function 16from __future__ import print_function
17import re 17import sys
18 18
19from command import Command, MirrorSafeCommand 19from command import Command, MirrorSafeCommand
20 20
@@ -31,13 +31,19 @@ List all projects; pass '.' to list the project for the cwd.
31This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'. 31This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
32""" 32"""
33 33
34 def _Options(self, p, show_smart=True): 34 def _Options(self, p):
35 p.add_option('-r', '--regex', 35 p.add_option('-r', '--regex',
36 dest='regex', action='store_true', 36 dest='regex', action='store_true',
37 help="Filter the project list based on regex or wildcard matching of strings") 37 help="Filter the project list based on regex or wildcard matching of strings")
38 p.add_option('-f', '--fullpath', 38 p.add_option('-f', '--fullpath',
39 dest='fullpath', action='store_true', 39 dest='fullpath', action='store_true',
40 help="Display the full work tree path instead of the relative path") 40 help="Display the full work tree path instead of the relative path")
41 p.add_option('-n', '--name-only',
42 dest='name_only', action='store_true',
43 help="Display only the name of the repository")
44 p.add_option('-p', '--path-only',
45 dest='path_only', action='store_true',
46 help="Display only the path of the repository")
41 47
42 def Execute(self, opt, args): 48 def Execute(self, opt, args):
43 """List all projects and the associated directories. 49 """List all projects and the associated directories.
@@ -50,6 +56,11 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
50 opt: The options. 56 opt: The options.
51 args: Positional args. Can be a list of projects to list, or empty. 57 args: Positional args. Can be a list of projects to list, or empty.
52 """ 58 """
59
60 if opt.fullpath and opt.name_only:
61 print('error: cannot combine -f and -n', file=sys.stderr)
62 sys.exit(1)
63
53 if not opt.regex: 64 if not opt.regex:
54 projects = self.GetProjects(args) 65 projects = self.GetProjects(args)
55 else: 66 else:
@@ -62,18 +73,12 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
62 73
63 lines = [] 74 lines = []
64 for project in projects: 75 for project in projects:
65 lines.append("%s : %s" % (_getpath(project), project.name)) 76 if opt.name_only and not opt.path_only:
77 lines.append("%s" % ( project.name))
78 elif opt.path_only and not opt.name_only:
79 lines.append("%s" % (_getpath(project)))
80 else:
81 lines.append("%s : %s" % (_getpath(project), project.name))
66 82
67 lines.sort() 83 lines.sort()
68 print('\n'.join(lines)) 84 print('\n'.join(lines))
69
70 def FindProjects(self, args):
71 result = []
72 for project in self.GetProjects(''):
73 for arg in args:
74 pattern = re.compile(r'%s' % arg, re.IGNORECASE)
75 if pattern.search(project.name) or pattern.search(project.relpath):
76 result.append(project)
77 break
78 result.sort(key=lambda project: project.relpath)
79 return result
diff --git a/subcmds/overview.py b/subcmds/overview.py
index 418459ae..eed8cf20 100644
--- a/subcmds/overview.py
+++ b/subcmds/overview.py
@@ -42,7 +42,7 @@ are displayed.
42 all_branches = [] 42 all_branches = []
43 for project in self.GetProjects(args): 43 for project in self.GetProjects(args):
44 br = [project.GetUploadableBranch(x) 44 br = [project.GetUploadableBranch(x)
45 for x in project.GetBranches().keys()] 45 for x in project.GetBranches()]
46 br = [x for x in br if x] 46 br = [x for x in br if x]
47 if opt.current_branch: 47 if opt.current_branch:
48 br = [x for x in br if x.name == project.CurrentBranch] 48 br = [x for x in br if x.name == project.CurrentBranch]
diff --git a/subcmds/rebase.py b/subcmds/rebase.py
index 06cda22c..b9a7774d 100644
--- a/subcmds/rebase.py
+++ b/subcmds/rebase.py
@@ -68,7 +68,7 @@ branch but need to incorporate new upstream changes "underneath" them.
68 cb = project.CurrentBranch 68 cb = project.CurrentBranch
69 if not cb: 69 if not cb:
70 if one_project: 70 if one_project:
71 print("error: project %s has a detatched HEAD" % project.relpath, 71 print("error: project %s has a detached HEAD" % project.relpath,
72 file=sys.stderr) 72 file=sys.stderr)
73 return -1 73 return -1
74 # ignore branches with detatched HEADs 74 # ignore branches with detatched HEADs
diff --git a/subcmds/stage.py b/subcmds/stage.py
index ff15ee0c..28849764 100644
--- a/subcmds/stage.py
+++ b/subcmds/stage.py
@@ -49,7 +49,7 @@ The '%prog' command stages files to prepare the next commit.
49 self.Usage() 49 self.Usage()
50 50
51 def _Interactive(self, opt, args): 51 def _Interactive(self, opt, args):
52 all_projects = filter(lambda x: x.IsDirty(), self.GetProjects(args)) 52 all_projects = [p for p in self.GetProjects(args) if p.IsDirty()]
53 if not all_projects: 53 if not all_projects:
54 print('no projects have uncommitted modifications', file=sys.stderr) 54 print('no projects have uncommitted modifications', file=sys.stderr)
55 return 55 return
@@ -98,9 +98,9 @@ The '%prog' command stages files to prepare the next commit.
98 _AddI(all_projects[a_index - 1]) 98 _AddI(all_projects[a_index - 1])
99 continue 99 continue
100 100
101 p = filter(lambda x: x.name == a or x.relpath == a, all_projects) 101 projects = [p for p in all_projects if a in [p.name, p.relpath]]
102 if len(p) == 1: 102 if len(projects) == 1:
103 _AddI(p[0]) 103 _AddI(projects[0])
104 continue 104 continue
105 print('Bye.') 105 print('Bye.')
106 106
diff --git a/subcmds/status.py b/subcmds/status.py
index cce00c81..41c4429a 100644
--- a/subcmds/status.py
+++ b/subcmds/status.py
@@ -21,10 +21,16 @@ except ImportError:
21 import dummy_threading as _threading 21 import dummy_threading as _threading
22 22
23import glob 23import glob
24
25from pyversion import is_python3
26if is_python3():
27 import io
28else:
29 import StringIO as io
30
24import itertools 31import itertools
25import os 32import os
26import sys 33import sys
27import StringIO
28 34
29from color import Coloring 35from color import Coloring
30 36
@@ -142,7 +148,7 @@ the following meanings:
142 for project in all_projects: 148 for project in all_projects:
143 sem.acquire() 149 sem.acquire()
144 150
145 class BufList(StringIO.StringIO): 151 class BufList(io.StringIO):
146 def dump(self, ostream): 152 def dump(self, ostream):
147 for entry in self.buflist: 153 for entry in self.buflist:
148 ostream.write(entry) 154 ostream.write(entry)
@@ -182,7 +188,7 @@ the following meanings:
182 try: 188 try:
183 os.chdir(self.manifest.topdir) 189 os.chdir(self.manifest.topdir)
184 190
185 outstring = StringIO.StringIO() 191 outstring = io.StringIO()
186 self._FindOrphans(glob.glob('.*') + \ 192 self._FindOrphans(glob.glob('.*') + \
187 glob.glob('*'), \ 193 glob.glob('*'), \
188 proj_dirs, proj_dirs_parents, outstring) 194 proj_dirs, proj_dirs_parents, outstring)
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 5c369a74..e9d52b7b 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -24,8 +24,19 @@ import socket
24import subprocess 24import subprocess
25import sys 25import sys
26import time 26import time
27import urlparse 27
28import xmlrpclib 28from pyversion import is_python3
29if is_python3():
30 import urllib.parse
31 import xmlrpc.client
32else:
33 import imp
34 import urlparse
35 import xmlrpclib
36 urllib = imp.new_module('urllib')
37 urllib.parse = urlparse
38 xmlrpc = imp.new_module('xmlrpc')
39 xmlrpc.client = xmlrpclib
29 40
30try: 41try:
31 import threading as _threading 42 import threading as _threading
@@ -228,6 +239,9 @@ later is required to fix a server side protocol bug.
228 # We'll set to true once we've locked the lock. 239 # We'll set to true once we've locked the lock.
229 did_lock = False 240 did_lock = False
230 241
242 if not opt.quiet:
243 print('Fetching project %s' % project.name)
244
231 # Encapsulate everything in a try/except/finally so that: 245 # Encapsulate everything in a try/except/finally so that:
232 # - We always set err_event in the case of an exception. 246 # - We always set err_event in the case of an exception.
233 # - We always make sure we call sem.release(). 247 # - We always make sure we call sem.release().
@@ -274,6 +288,8 @@ later is required to fix a server side protocol bug.
274 if self.jobs == 1: 288 if self.jobs == 1:
275 for project in projects: 289 for project in projects:
276 pm.update() 290 pm.update()
291 if not opt.quiet:
292 print('Fetching project %s' % project.name)
277 if project.Sync_NetworkHalf( 293 if project.Sync_NetworkHalf(
278 quiet=opt.quiet, 294 quiet=opt.quiet,
279 current_branch_only=opt.current_branch_only, 295 current_branch_only=opt.current_branch_only,
@@ -372,6 +388,13 @@ later is required to fix a server side protocol bug.
372 print('\nerror: Exited sync due to gc errors', file=sys.stderr) 388 print('\nerror: Exited sync due to gc errors', file=sys.stderr)
373 sys.exit(1) 389 sys.exit(1)
374 390
391 def _ReloadManifest(self, manifest_name=None):
392 if manifest_name:
393 # Override calls _Unload already
394 self.manifest.Override(manifest_name)
395 else:
396 self.manifest._Unload()
397
375 def UpdateProjectList(self): 398 def UpdateProjectList(self):
376 new_project_paths = [] 399 new_project_paths = []
377 for project in self.GetProjects(None, missing_ok=True): 400 for project in self.GetProjects(None, missing_ok=True):
@@ -406,7 +429,7 @@ later is required to fix a server side protocol bug.
406 groups = None) 429 groups = None)
407 430
408 if project.IsDirty(): 431 if project.IsDirty():
409 print('error: Cannot remove project "%s": uncommitted changes' 432 print('error: Cannot remove project "%s": uncommitted changes '
410 'are present' % project.relpath, file=sys.stderr) 433 'are present' % project.relpath, file=sys.stderr)
411 print(' commit changes, then run sync again', 434 print(' commit changes, then run sync again',
412 file=sys.stderr) 435 file=sys.stderr)
@@ -464,13 +487,17 @@ later is required to fix a server side protocol bug.
464 if opt.manifest_name: 487 if opt.manifest_name:
465 self.manifest.Override(opt.manifest_name) 488 self.manifest.Override(opt.manifest_name)
466 489
490 manifest_name = opt.manifest_name
491
467 if opt.smart_sync or opt.smart_tag: 492 if opt.smart_sync or opt.smart_tag:
468 if not self.manifest.manifest_server: 493 if not self.manifest.manifest_server:
469 print('error: cannot smart sync: no manifest server defined in' 494 print('error: cannot smart sync: no manifest server defined in '
470 'manifest', file=sys.stderr) 495 'manifest', file=sys.stderr)
471 sys.exit(1) 496 sys.exit(1)
472 497
473 manifest_server = self.manifest.manifest_server 498 manifest_server = self.manifest.manifest_server
499 if not opt.quiet:
500 print('Using manifest server %s' % manifest_server)
474 501
475 if not '@' in manifest_server: 502 if not '@' in manifest_server:
476 username = None 503 username = None
@@ -486,7 +513,7 @@ later is required to fix a server side protocol bug.
486 file=sys.stderr) 513 file=sys.stderr)
487 else: 514 else:
488 try: 515 try:
489 parse_result = urlparse.urlparse(manifest_server) 516 parse_result = urllib.parse.urlparse(manifest_server)
490 if parse_result.hostname: 517 if parse_result.hostname:
491 username, _account, password = \ 518 username, _account, password = \
492 info.authenticators(parse_result.hostname) 519 info.authenticators(parse_result.hostname)
@@ -504,7 +531,7 @@ later is required to fix a server side protocol bug.
504 1) 531 1)
505 532
506 try: 533 try:
507 server = xmlrpclib.Server(manifest_server) 534 server = xmlrpc.client.Server(manifest_server)
508 if opt.smart_sync: 535 if opt.smart_sync:
509 p = self.manifest.manifestProject 536 p = self.manifest.manifestProject
510 b = p.GetBranch(p.CurrentBranch) 537 b = p.GetBranch(p.CurrentBranch)
@@ -513,8 +540,7 @@ later is required to fix a server side protocol bug.
513 branch = branch[len(R_HEADS):] 540 branch = branch[len(R_HEADS):]
514 541
515 env = os.environ.copy() 542 env = os.environ.copy()
516 if (env.has_key('TARGET_PRODUCT') and 543 if 'TARGET_PRODUCT' in env and 'TARGET_BUILD_VARIANT' in env:
517 env.has_key('TARGET_BUILD_VARIANT')):
518 target = '%s-%s' % (env['TARGET_PRODUCT'], 544 target = '%s-%s' % (env['TARGET_PRODUCT'],
519 env['TARGET_BUILD_VARIANT']) 545 env['TARGET_BUILD_VARIANT'])
520 [success, manifest_str] = server.GetApprovedManifest(branch, target) 546 [success, manifest_str] = server.GetApprovedManifest(branch, target)
@@ -538,15 +564,16 @@ later is required to fix a server side protocol bug.
538 print('error: cannot write manifest to %s' % manifest_path, 564 print('error: cannot write manifest to %s' % manifest_path,
539 file=sys.stderr) 565 file=sys.stderr)
540 sys.exit(1) 566 sys.exit(1)
541 self.manifest.Override(manifest_name) 567 self._ReloadManifest(manifest_name)
542 else: 568 else:
543 print('error: %s' % manifest_str, file=sys.stderr) 569 print('error: manifest server RPC call failed: %s' %
570 manifest_str, file=sys.stderr)
544 sys.exit(1) 571 sys.exit(1)
545 except (socket.error, IOError, xmlrpclib.Fault) as e: 572 except (socket.error, IOError, xmlrpc.client.Fault) as e:
546 print('error: cannot connect to manifest server %s:\n%s' 573 print('error: cannot connect to manifest server %s:\n%s'
547 % (self.manifest.manifest_server, e), file=sys.stderr) 574 % (self.manifest.manifest_server, e), file=sys.stderr)
548 sys.exit(1) 575 sys.exit(1)
549 except xmlrpclib.ProtocolError as e: 576 except xmlrpc.client.ProtocolError as e:
550 print('error: cannot connect to manifest server %s:\n%d %s' 577 print('error: cannot connect to manifest server %s:\n%d %s'
551 % (self.manifest.manifest_server, e.errcode, e.errmsg), 578 % (self.manifest.manifest_server, e.errcode, e.errmsg),
552 file=sys.stderr) 579 file=sys.stderr)
@@ -571,7 +598,7 @@ later is required to fix a server side protocol bug.
571 mp.Sync_LocalHalf(syncbuf) 598 mp.Sync_LocalHalf(syncbuf)
572 if not syncbuf.Finish(): 599 if not syncbuf.Finish():
573 sys.exit(1) 600 sys.exit(1)
574 self.manifest._Unload() 601 self._ReloadManifest(manifest_name)
575 if opt.jobs is None: 602 if opt.jobs is None:
576 self.jobs = self.manifest.default.sync_j 603 self.jobs = self.manifest.default.sync_j
577 all_projects = self.GetProjects(args, 604 all_projects = self.GetProjects(args,
@@ -596,7 +623,7 @@ later is required to fix a server side protocol bug.
596 # Iteratively fetch missing and/or nested unregistered submodules 623 # Iteratively fetch missing and/or nested unregistered submodules
597 previously_missing_set = set() 624 previously_missing_set = set()
598 while True: 625 while True:
599 self.manifest._Unload() 626 self._ReloadManifest(manifest_name)
600 all_projects = self.GetProjects(args, 627 all_projects = self.GetProjects(args,
601 missing_ok=True, 628 missing_ok=True,
602 submodules_ok=opt.fetch_submodules) 629 submodules_ok=opt.fetch_submodules)
diff --git a/subcmds/upload.py b/subcmds/upload.py
index e314032a..8d801e08 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -21,19 +21,26 @@ import sys
21from command import InteractiveCommand 21from command import InteractiveCommand
22from editor import Editor 22from editor import Editor
23from error import HookError, UploadError 23from error import HookError, UploadError
24from git_command import GitCommand
24from project import RepoHook 25from project import RepoHook
25 26
27from pyversion import is_python3
28if not is_python3():
29 # pylint:disable=W0622
30 input = raw_input
31 # pylint:enable=W0622
32
26UNUSUAL_COMMIT_THRESHOLD = 5 33UNUSUAL_COMMIT_THRESHOLD = 5
27 34
28def _ConfirmManyUploads(multiple_branches=False): 35def _ConfirmManyUploads(multiple_branches=False):
29 if multiple_branches: 36 if multiple_branches:
30 print('ATTENTION: One or more branches has an unusually high number' 37 print('ATTENTION: One or more branches has an unusually high number '
31 'of commits.') 38 'of commits.')
32 else: 39 else:
33 print('ATTENTION: You are uploading an unusually high number of commits.') 40 print('ATTENTION: You are uploading an unusually high number of commits.')
34 print('YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across' 41 print('YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across '
35 'branches?)') 42 'branches?)')
36 answer = raw_input("If you are sure you intend to do this, type 'yes': ").strip() 43 answer = input("If you are sure you intend to do this, type 'yes': ").strip()
37 return answer == "yes" 44 return answer == "yes"
38 45
39def _die(fmt, *args): 46def _die(fmt, *args):
@@ -140,6 +147,10 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
140 p.add_option('-d', '--draft', 147 p.add_option('-d', '--draft',
141 action='store_true', dest='draft', default=False, 148 action='store_true', dest='draft', default=False,
142 help='If specified, upload as a draft.') 149 help='If specified, upload as a draft.')
150 p.add_option('-D', '--destination', '--dest',
151 type='string', action='store', dest='dest_branch',
152 metavar='BRANCH',
153 help='Submit for review on this target branch.')
143 154
144 # Options relating to upload hook. Note that verify and no-verify are NOT 155 # Options relating to upload hook. Note that verify and no-verify are NOT
145 # opposites of each other, which is why they store to different locations. 156 # opposites of each other, which is why they store to different locations.
@@ -179,7 +190,8 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
179 date = branch.date 190 date = branch.date
180 commit_list = branch.commits 191 commit_list = branch.commits
181 192
182 print('Upload project %s/ to remote branch %s:' % (project.relpath, project.revisionExpr)) 193 destination = opt.dest_branch or project.dest_branch or project.revisionExpr
194 print('Upload project %s/ to remote branch %s:' % (project.relpath, destination))
183 print(' branch %s (%2d commit%s, %s):' % ( 195 print(' branch %s (%2d commit%s, %s):' % (
184 name, 196 name,
185 len(commit_list), 197 len(commit_list),
@@ -213,18 +225,21 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
213 225
214 b = {} 226 b = {}
215 for branch in avail: 227 for branch in avail:
228 if branch is None:
229 continue
216 name = branch.name 230 name = branch.name
217 date = branch.date 231 date = branch.date
218 commit_list = branch.commits 232 commit_list = branch.commits
219 233
220 if b: 234 if b:
221 script.append('#') 235 script.append('#')
236 destination = opt.dest_branch or project.dest_branch or project.revisionExpr
222 script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % ( 237 script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % (
223 name, 238 name,
224 len(commit_list), 239 len(commit_list),
225 len(commit_list) != 1 and 's' or '', 240 len(commit_list) != 1 and 's' or '',
226 date, 241 date,
227 project.revisionExpr)) 242 destination))
228 for commit in commit_list: 243 for commit in commit_list:
229 script.append('# %s' % commit) 244 script.append('# %s' % commit)
230 b[name] = branch 245 b[name] = branch
@@ -330,7 +345,21 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
330 key = 'review.%s.uploadtopic' % branch.project.remote.review 345 key = 'review.%s.uploadtopic' % branch.project.remote.review
331 opt.auto_topic = branch.project.config.GetBoolean(key) 346 opt.auto_topic = branch.project.config.GetBoolean(key)
332 347
333 branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft) 348 destination = opt.dest_branch or branch.project.dest_branch
349
350 # Make sure our local branch is not setup to track a different remote branch
351 merge_branch = self._GetMergeBranch(branch.project)
352 full_dest = 'refs/heads/%s' % destination
353 if not opt.dest_branch and merge_branch and merge_branch != full_dest:
354 print('merge branch %s does not match destination branch %s'
355 % (merge_branch, full_dest))
356 print('skipping upload.')
357 print('Please use `--destination %s` if this is intentional'
358 % destination)
359 branch.uploaded = False
360 continue
361
362 branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft, dest_branch=destination)
334 branch.uploaded = True 363 branch.uploaded = True
335 except UploadError as e: 364 except UploadError as e:
336 branch.error = e 365 branch.error = e
@@ -364,6 +393,21 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
364 if have_errors: 393 if have_errors:
365 sys.exit(1) 394 sys.exit(1)
366 395
396 def _GetMergeBranch(self, project):
397 p = GitCommand(project,
398 ['rev-parse', '--abbrev-ref', 'HEAD'],
399 capture_stdout = True,
400 capture_stderr = True)
401 p.Wait()
402 local_branch = p.stdout.strip()
403 p = GitCommand(project,
404 ['config', '--get', 'branch.%s.merge' % local_branch],
405 capture_stdout = True,
406 capture_stderr = True)
407 p.Wait()
408 merge_branch = p.stdout.strip()
409 return merge_branch
410
367 def Execute(self, opt, args): 411 def Execute(self, opt, args):
368 project_list = self.GetProjects(args) 412 project_list = self.GetProjects(args)
369 pending = [] 413 pending = []