summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.pylintrc2
-rw-r--r--color.py65
-rw-r--r--command.py115
-rw-r--r--docs/manifest-format.txt72
-rw-r--r--editor.py7
-rw-r--r--error.py4
-rw-r--r--git_command.py15
-rw-r--r--git_config.py44
-rw-r--r--git_refs.py4
-rwxr-xr-xhooks/commit-msg4
-rwxr-xr-xmain.py158
-rw-r--r--manifest_xml.py250
-rwxr-xr-xpager.py3
-rw-r--r--project.py260
-rwxr-xr-xrepo203
-rw-r--r--subcmds/abandon.py15
-rw-r--r--subcmds/branches.py3
-rw-r--r--subcmds/checkout.py8
-rw-r--r--subcmds/cherry_pick.py21
-rw-r--r--subcmds/download.py27
-rw-r--r--subcmds/forall.py18
-rw-r--r--subcmds/grep.py24
-rw-r--r--subcmds/help.py35
-rw-r--r--subcmds/info.py195
-rw-r--r--subcmds/init.py79
-rw-r--r--subcmds/list.py3
-rw-r--r--subcmds/manifest.py7
-rw-r--r--subcmds/overview.py10
-rw-r--r--subcmds/prune.py5
-rw-r--r--subcmds/rebase.py14
-rw-r--r--subcmds/selfupdate.py3
-rw-r--r--subcmds/stage.py7
-rw-r--r--subcmds/start.py10
-rw-r--r--subcmds/status.py74
-rw-r--r--subcmds/sync.py323
-rw-r--r--subcmds/upload.py81
-rw-r--r--subcmds/version.py13
-rw-r--r--tests/test_git_config.py82
-rw-r--r--trace.py3
39 files changed, 1529 insertions, 737 deletions
diff --git a/.pylintrc b/.pylintrc
index 9f81ee15..9e8882ee 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,C0323,C0322,C0324,W0603,W0703,R0911,C0301,C0302,R0902,R0904,W0142,W0212,E1101,E1103,R0201,W0201,W0122,W0232,W0311,RP0001,RP0003,RP0101,RP0002,RP0401,RP0701,RP0801 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
57 57
58[REPORTS] 58[REPORTS]
59 59
diff --git a/color.py b/color.py
index 9200a298..d856313f 100644
--- a/color.py
+++ b/color.py
@@ -36,50 +36,51 @@ ATTRS = {None :-1,
36 'blink' : 5, 36 'blink' : 5,
37 'reverse': 7} 37 'reverse': 7}
38 38
39RESET = "\033[m" 39RESET = "\033[m" # pylint: disable=W1401
40 # backslash is not anomalous
40 41
41def is_color(s): 42def is_color(s):
42 return s in COLORS 43 return s in COLORS
43 44
44def is_attr(s): 45def is_attr(s):
45 return s in ATTRS 46 return s in ATTRS
46 47
47def _Color(fg = None, bg = None, attr = None): 48def _Color(fg = None, bg = None, attr = None):
48 fg = COLORS[fg] 49 fg = COLORS[fg]
49 bg = COLORS[bg] 50 bg = COLORS[bg]
50 attr = ATTRS[attr] 51 attr = ATTRS[attr]
51 52
52 if attr >= 0 or fg >= 0 or bg >= 0: 53 if attr >= 0 or fg >= 0 or bg >= 0:
53 need_sep = False 54 need_sep = False
54 code = "\033[" 55 code = "\033[" #pylint: disable=W1401
55 56
56 if attr >= 0: 57 if attr >= 0:
57 code += chr(ord('0') + attr) 58 code += chr(ord('0') + attr)
58 need_sep = True 59 need_sep = True
59 60
60 if fg >= 0: 61 if fg >= 0:
61 if need_sep: 62 if need_sep:
62 code += ';' 63 code += ';'
63 need_sep = True 64 need_sep = True
64 65
65 if fg < 8: 66 if fg < 8:
66 code += '3%c' % (ord('0') + fg) 67 code += '3%c' % (ord('0') + fg)
67 else: 68 else:
68 code += '38;5;%d' % fg 69 code += '38;5;%d' % fg
69 70
70 if bg >= 0: 71 if bg >= 0:
71 if need_sep: 72 if need_sep:
72 code += ';' 73 code += ';'
73 need_sep = True 74 need_sep = True
74 75
75 if bg < 8: 76 if bg < 8:
76 code += '4%c' % (ord('0') + bg) 77 code += '4%c' % (ord('0') + bg)
77 else: 78 else:
78 code += '48;5;%d' % bg 79 code += '48;5;%d' % bg
79 code += 'm' 80 code += 'm'
80 else: 81 else:
81 code = '' 82 code = ''
82 return code 83 return code
83 84
84 85
85class Coloring(object): 86class Coloring(object):
diff --git a/command.py b/command.py
index 0c3b360c..96d7848f 100644
--- a/command.py
+++ b/command.py
@@ -22,6 +22,7 @@ import sys
22from error import NoSuchProjectError 22from error import NoSuchProjectError
23from error import InvalidProjectGroupsError 23from error import InvalidProjectGroupsError
24 24
25
25class Command(object): 26class Command(object):
26 """Base class for any command line action in repo. 27 """Base class for any command line action in repo.
27 """ 28 """
@@ -33,6 +34,27 @@ class Command(object):
33 def WantPager(self, opt): 34 def WantPager(self, opt):
34 return False 35 return False
35 36
37 def ReadEnvironmentOptions(self, opts):
38 """ Set options from environment variables. """
39
40 env_options = self._RegisteredEnvironmentOptions()
41
42 for env_key, opt_key in env_options.items():
43 # Get the user-set option value if any
44 opt_value = getattr(opts, opt_key)
45
46 # If the value is set, it means the user has passed it as a command
47 # line option, and we should use that. Otherwise we can try to set it
48 # with the value from the corresponding environment variable.
49 if opt_value is not None:
50 continue
51
52 env_value = os.environ.get(env_key)
53 if env_value is not None:
54 setattr(opts, opt_key, env_value)
55
56 return opts
57
36 @property 58 @property
37 def OptionParser(self): 59 def OptionParser(self):
38 if self._optparse is None: 60 if self._optparse is None:
@@ -49,6 +71,24 @@ class Command(object):
49 """Initialize the option parser. 71 """Initialize the option parser.
50 """ 72 """
51 73
74 def _RegisteredEnvironmentOptions(self):
75 """Get options that can be set from environment variables.
76
77 Return a dictionary mapping environment variable name
78 to option key name that it can override.
79
80 Example: {'REPO_MY_OPTION': 'my_option'}
81
82 Will allow the option with key value 'my_option' to be set
83 from the value in the environment variable named 'REPO_MY_OPTION'.
84
85 Note: This does not work properly for options that are explicitly
86 set to None by the user, or options that are defined with a
87 default value other than None.
88
89 """
90 return {}
91
52 def Usage(self): 92 def Usage(self):
53 """Display usage and terminate. 93 """Display usage and terminate.
54 """ 94 """
@@ -60,7 +100,33 @@ class Command(object):
60 """ 100 """
61 raise NotImplementedError 101 raise NotImplementedError
62 102
63 def GetProjects(self, args, missing_ok=False): 103 def _ResetPathToProjectMap(self, projects):
104 self._by_path = dict((p.worktree, p) for p in projects)
105
106 def _UpdatePathToProjectMap(self, project):
107 self._by_path[project.worktree] = project
108
109 def _GetProjectByPath(self, path):
110 project = None
111 if os.path.exists(path):
112 oldpath = None
113 while path \
114 and path != oldpath \
115 and path != self.manifest.topdir:
116 try:
117 project = self._by_path[path]
118 break
119 except KeyError:
120 oldpath = path
121 path = os.path.dirname(path)
122 else:
123 try:
124 project = self._by_path[path]
125 except KeyError:
126 pass
127 return project
128
129 def GetProjects(self, args, missing_ok=False, submodules_ok=False):
64 """A list of projects that match the arguments. 130 """A list of projects that match the arguments.
65 """ 131 """
66 all_projects = self.manifest.projects 132 all_projects = self.manifest.projects
@@ -71,43 +137,40 @@ class Command(object):
71 groups = mp.config.GetString('manifest.groups') 137 groups = mp.config.GetString('manifest.groups')
72 if not groups: 138 if not groups:
73 groups = 'all,-notdefault,platform-' + platform.system().lower() 139 groups = 'all,-notdefault,platform-' + platform.system().lower()
74 groups = [x for x in re.split('[,\s]+', groups) if x] 140 groups = [x for x in re.split(r'[,\s]+', groups) if x]
75 141
76 if not args: 142 if not args:
77 for project in all_projects.values(): 143 all_projects_list = all_projects.values()
144 derived_projects = {}
145 for project in all_projects_list:
146 if submodules_ok or project.sync_s:
147 derived_projects.update((p.name, p)
148 for p in project.GetDerivedSubprojects())
149 all_projects_list.extend(derived_projects.values())
150 for project in all_projects_list:
78 if ((missing_ok or project.Exists) and 151 if ((missing_ok or project.Exists) and
79 project.MatchesGroups(groups)): 152 project.MatchesGroups(groups)):
80 result.append(project) 153 result.append(project)
81 else: 154 else:
82 by_path = None 155 self._ResetPathToProjectMap(all_projects.values())
83 156
84 for arg in args: 157 for arg in args:
85 project = all_projects.get(arg) 158 project = all_projects.get(arg)
86 159
87 if not project: 160 if not project:
88 path = os.path.abspath(arg).replace('\\', '/') 161 path = os.path.abspath(arg).replace('\\', '/')
89 162 project = self._GetProjectByPath(path)
90 if not by_path: 163
91 by_path = dict() 164 # If it's not a derived project, update path->project mapping and
92 for p in all_projects.values(): 165 # search again, as arg might actually point to a derived subproject.
93 by_path[p.worktree] = p 166 if (project and not project.Derived and
94 167 (submodules_ok or project.sync_s)):
95 if os.path.exists(path): 168 search_again = False
96 oldpath = None 169 for subproject in project.GetDerivedSubprojects():
97 while path \ 170 self._UpdatePathToProjectMap(subproject)
98 and path != oldpath \ 171 search_again = True
99 and path != self.manifest.topdir: 172 if search_again:
100 try: 173 project = self._GetProjectByPath(path) or project
101 project = by_path[path]
102 break
103 except KeyError:
104 oldpath = path
105 path = os.path.dirname(path)
106 else:
107 try:
108 project = by_path[path]
109 except KeyError:
110 pass
111 174
112 if not project: 175 if not project:
113 raise NoSuchProjectError(arg) 176 raise NoSuchProjectError(arg)
diff --git a/docs/manifest-format.txt b/docs/manifest-format.txt
index f499868c..0bf09f6f 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest-format.txt
@@ -41,17 +41,21 @@ following DTD:
41 <!ATTLIST default revision CDATA #IMPLIED> 41 <!ATTLIST default revision CDATA #IMPLIED>
42 <!ATTLIST default sync-j CDATA #IMPLIED> 42 <!ATTLIST default sync-j CDATA #IMPLIED>
43 <!ATTLIST default sync-c CDATA #IMPLIED> 43 <!ATTLIST default sync-c CDATA #IMPLIED>
44 <!ATTLIST default sync-s CDATA #IMPLIED>
44 45
45 <!ELEMENT manifest-server (EMPTY)> 46 <!ELEMENT manifest-server (EMPTY)>
46 <!ATTLIST url CDATA #REQUIRED> 47 <!ATTLIST url CDATA #REQUIRED>
47 48
48 <!ELEMENT project (annotation?)> 49 <!ELEMENT project (annotation?,
50 project*)>
49 <!ATTLIST project name CDATA #REQUIRED> 51 <!ATTLIST project name CDATA #REQUIRED>
50 <!ATTLIST project path CDATA #IMPLIED> 52 <!ATTLIST project path CDATA #IMPLIED>
51 <!ATTLIST project remote IDREF #IMPLIED> 53 <!ATTLIST project remote IDREF #IMPLIED>
52 <!ATTLIST project revision CDATA #IMPLIED> 54 <!ATTLIST project revision CDATA #IMPLIED>
53 <!ATTLIST project groups CDATA #IMPLIED> 55 <!ATTLIST project groups CDATA #IMPLIED>
54 <!ATTLIST project sync-c CDATA #IMPLIED> 56 <!ATTLIST project sync-c CDATA #IMPLIED>
57 <!ATTLIST project sync-s CDATA #IMPLIED>
58 <!ATTLIST project upstream CDATA #IMPLIED>
55 59
56 <!ELEMENT annotation (EMPTY)> 60 <!ELEMENT annotation (EMPTY)>
57 <!ATTLIST annotation name CDATA #REQUIRED> 61 <!ATTLIST annotation name CDATA #REQUIRED>
@@ -119,6 +123,15 @@ Attribute `revision`: Name of a Git branch (e.g. `master` or
119`refs/heads/master`). Project elements lacking their own 123`refs/heads/master`). Project elements lacking their own
120revision attribute will use this revision. 124revision attribute will use this revision.
121 125
126Attribute `sync_j`: Number of parallel jobs to use when synching.
127
128Attribute `sync_c`: Set to true to only sync the given Git
129branch (specified in the `revision` attribute) rather than the
130whole ref space. Project elements lacking a sync_c element of
131their own will use this value.
132
133Attribute `sync_s`: Set to true to also sync sub-projects.
134
122 135
123Element manifest-server 136Element manifest-server
124----------------------- 137-----------------------
@@ -152,7 +165,10 @@ Element project
152 165
153One or more project elements may be specified. Each element 166One or more project elements may be specified. Each element
154describes a single Git repository to be cloned into the repo 167describes a single Git repository to be cloned into the repo
155client workspace. 168client workspace. You may specify Git-submodules by creating a
169nested project. Git-submodules will be automatically
170recognized and inherit their parent's attributes, but those
171may be overridden by an explicitly specified project element.
156 172
157Attribute `name`: A unique name for this project. The project's 173Attribute `name`: A unique name for this project. The project's
158name is appended onto its remote's fetch URL to generate the actual 174name is appended onto its remote's fetch URL to generate the actual
@@ -163,7 +179,8 @@ URL to configure the Git remote with. The URL gets formed as:
163where ${remote_fetch} is the remote's fetch attribute and 179where ${remote_fetch} is the remote's fetch attribute and
164${project_name} is the project's name attribute. The suffix ".git" 180${project_name} is the project's name attribute. The suffix ".git"
165is always appended as repo assumes the upstream is a forest of 181is always appended as repo assumes the upstream is a forest of
166bare Git repositories. 182bare Git repositories. If the project has a parent element, its
183name will be prefixed by the parent's.
167 184
168The project name must match the name Gerrit knows, if Gerrit is 185The project name must match the name Gerrit knows, if Gerrit is
169being used for code reviews. 186being used for code reviews.
@@ -171,6 +188,8 @@ being used for code reviews.
171Attribute `path`: An optional path relative to the top directory 188Attribute `path`: An optional path relative to the top directory
172of the repo client where the Git working directory for this project 189of the repo client where the Git working directory for this project
173should be placed. If not supplied the project name is used. 190should be placed. If not supplied the project name is used.
191If the project has a parent element, its path will be prefixed
192by the parent's.
174 193
175Attribute `remote`: Name of a previously defined remote element. 194Attribute `remote`: Name of a previously defined remote element.
176If not supplied the remote given by the default element is used. 195If not supplied the remote given by the default element is used.
@@ -190,6 +209,18 @@ its name:`name` and path:`path`. E.g. for
190definition is implicitly in the following manifest groups: 209definition is implicitly in the following manifest groups:
191default, name:monkeys, and path:barrel-of. If you place a project in the 210default, name:monkeys, and path:barrel-of. If you place a project in the
192group "notdefault", it will not be automatically downloaded by repo. 211group "notdefault", it will not be automatically downloaded by repo.
212If the project has a parent element, the `name` and `path` here
213are the prefixed ones.
214
215Attribute `sync_c`: Set to true to only sync the given Git
216branch (specified in the `revision` attribute) rather than the
217whole ref space.
218
219Attribute `sync_s`: Set to true to also sync sub-projects.
220
221Attribute `upstream`: Name of the Git branch in which a sha1
222can be found. Used when syncing a revision locked manifest in
223-c mode to avoid having to sync the entire ref space.
193 224
194Element annotation 225Element annotation
195------------------ 226------------------
@@ -209,7 +240,7 @@ Deletes the named project from the internal manifest table, possibly
209allowing a subsequent project element in the same manifest file to 240allowing a subsequent project element in the same manifest file to
210replace the project with a different source. 241replace the project with a different source.
211 242
212This element is mostly useful in the local_manifest.xml, where 243This element is mostly useful in a local manifest file, where
213the user can remove a project, and possibly replace it with their 244the user can remove a project, and possibly replace it with their
214own definition. 245own definition.
215 246
@@ -218,21 +249,25 @@ Element include
218 249
219This element provides the capability of including another manifest 250This element provides the capability of including another manifest
220file into the originating manifest. Normal rules apply for the 251file into the originating manifest. Normal rules apply for the
221target manifest to include- it must be a usable manifest on it's own. 252target manifest to include - it must be a usable manifest on its own.
222 253
223Attribute `name`; the manifest to include, specified relative to 254Attribute `name`: the manifest to include, specified relative to
224the manifest repositories root. 255the manifest repository's root.
225 256
226 257
227Local Manifest 258Local Manifests
228============== 259===============
229 260
230Additional remotes and projects may be added through a local 261Additional remotes and projects may be added through local manifest
231manifest, stored in `$TOP_DIR/.repo/local_manifest.xml`. 262files stored in `$TOP_DIR/.repo/local_manifests/*.xml`.
232 263
233For example: 264For example:
234 265
235 $ cat .repo/local_manifest.xml 266 $ ls .repo/local_manifests
267 local_manifest.xml
268 another_local_manifest.xml
269
270 $ cat .repo/local_manifests/local_manifest.xml
236 <?xml version="1.0" encoding="UTF-8"?> 271 <?xml version="1.0" encoding="UTF-8"?>
237 <manifest> 272 <manifest>
238 <project path="manifest" 273 <project path="manifest"
@@ -241,6 +276,17 @@ For example:
241 name="platform/manifest" /> 276 name="platform/manifest" />
242 </manifest> 277 </manifest>
243 278
244Users may add projects to the local manifest prior to a `repo sync` 279Users may add projects to the local manifest(s) prior to a `repo sync`
245invocation, instructing repo to automatically download and manage 280invocation, instructing repo to automatically download and manage
246these extra projects. 281these extra projects.
282
283Manifest files stored in `$TOP_DIR/.repo/local_manifests/*.xml` will
284be loaded in alphabetical order.
285
286Additional remotes and projects may also be added through a local
287manifest, stored in `$TOP_DIR/.repo/local_manifest.xml`. This method
288is deprecated in favor of using multiple manifest files as mentioned
289above.
290
291If `$TOP_DIR/.repo/local_manifest.xml` exists, it will be loaded before
292any manifest files stored in `$TOP_DIR/.repo/local_manifests/*.xml`.
diff --git a/editor.py b/editor.py
index 489c6cd3..883a1a83 100644
--- a/editor.py
+++ b/editor.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import os 17import os
17import re 18import re
18import sys 19import sys
@@ -53,10 +54,10 @@ class Editor(object):
53 return e 54 return e
54 55
55 if os.getenv('TERM') == 'dumb': 56 if os.getenv('TERM') == 'dumb':
56 print >>sys.stderr,\ 57 print(
57"""No editor specified in GIT_EDITOR, core.editor, VISUAL or EDITOR. 58"""No editor specified in GIT_EDITOR, core.editor, VISUAL or EDITOR.
58Tried to fall back to vi but terminal is dumb. Please configure at 59Tried to fall back to vi but terminal is dumb. Please configure at
59least one of these before using this command.""" 60least one of these before using this command.""", file=sys.stderr)
60 sys.exit(1) 61 sys.exit(1)
61 62
62 return 'vi' 63 return 'vi'
@@ -67,7 +68,7 @@ least one of these before using this command."""
67 68
68 Args: 69 Args:
69 data : the text to edit 70 data : the text to edit
70 71
71 Returns: 72 Returns:
72 new value of edited text; None if editing did not succeed 73 new value of edited text; None if editing did not succeed
73 """ 74 """
diff --git a/error.py b/error.py
index 21482486..7e52b016 100644
--- a/error.py
+++ b/error.py
@@ -21,6 +21,10 @@ class ManifestInvalidRevisionError(Exception):
21 """The revision value in a project is incorrect. 21 """The revision value in a project is incorrect.
22 """ 22 """
23 23
24class NoManifestException(Exception):
25 """The required manifest does not exist.
26 """
27
24class EditorError(Exception): 28class EditorError(Exception):
25 """Unspecified error from the user's text editor. 29 """Unspecified error from the user's text editor.
26 """ 30 """
diff --git a/git_command.py b/git_command.py
index a40e6c05..d347dd61 100644
--- a/git_command.py
+++ b/git_command.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import os 17import os
17import sys 18import sys
18import subprocess 19import subprocess
@@ -88,11 +89,11 @@ class _GitCall(object):
88 ver_str = git.version() 89 ver_str = git.version()
89 if ver_str.startswith('git version '): 90 if ver_str.startswith('git version '):
90 _git_version = tuple( 91 _git_version = tuple(
91 map(lambda x: int(x), 92 map(int,
92 ver_str[len('git version '):].strip().split('-')[0].split('.')[0:3] 93 ver_str[len('git version '):].strip().split('-')[0].split('.')[0:3]
93 )) 94 ))
94 else: 95 else:
95 print >>sys.stderr, 'fatal: "%s" unsupported' % ver_str 96 print('fatal: "%s" unsupported' % ver_str, file=sys.stderr)
96 sys.exit(1) 97 sys.exit(1)
97 return _git_version 98 return _git_version
98 99
@@ -110,8 +111,8 @@ def git_require(min_version, fail=False):
110 if min_version <= git_version: 111 if min_version <= git_version:
111 return True 112 return True
112 if fail: 113 if fail:
113 need = '.'.join(map(lambda x: str(x), min_version)) 114 need = '.'.join(map(str, min_version))
114 print >>sys.stderr, 'fatal: git %s or later required' % need 115 print('fatal: git %s or later required' % need, file=sys.stderr)
115 sys.exit(1) 116 sys.exit(1)
116 return False 117 return False
117 118
@@ -132,15 +133,15 @@ class GitCommand(object):
132 gitdir = None): 133 gitdir = None):
133 env = os.environ.copy() 134 env = os.environ.copy()
134 135
135 for e in [REPO_TRACE, 136 for key in [REPO_TRACE,
136 GIT_DIR, 137 GIT_DIR,
137 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 138 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
138 'GIT_OBJECT_DIRECTORY', 139 'GIT_OBJECT_DIRECTORY',
139 'GIT_WORK_TREE', 140 'GIT_WORK_TREE',
140 'GIT_GRAFT_FILE', 141 'GIT_GRAFT_FILE',
141 'GIT_INDEX_FILE']: 142 'GIT_INDEX_FILE']:
142 if e in env: 143 if key in env:
143 del env[e] 144 del env[key]
144 145
145 if disable_editor: 146 if disable_editor:
146 _setenv(env, 'GIT_EDITOR', ':') 147 _setenv(env, 'GIT_EDITOR', ':')
diff --git a/git_config.py b/git_config.py
index ae288558..56cc6a24 100644
--- a/git_config.py
+++ b/git_config.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import cPickle 17import cPickle
17import os 18import os
18import re 19import re
@@ -23,7 +24,18 @@ try:
23except ImportError: 24except ImportError:
24 import dummy_threading as _threading 25 import dummy_threading as _threading
25import time 26import time
26import urllib2 27try:
28 import urllib2
29except ImportError:
30 # For python3
31 import urllib.request
32 import urllib.error
33else:
34 # For python2
35 import imp
36 urllib = imp.new_module('urllib')
37 urllib.request = urllib2
38 urllib.error = urllib2
27 39
28from signal import SIGTERM 40from signal import SIGTERM
29from error import GitError, UploadError 41from error import GitError, UploadError
@@ -35,7 +47,7 @@ from git_command import terminate_ssh_clients
35 47
36R_HEADS = 'refs/heads/' 48R_HEADS = 'refs/heads/'
37R_TAGS = 'refs/tags/' 49R_TAGS = 'refs/tags/'
38ID_RE = re.compile('^[0-9a-f]{40}$') 50ID_RE = re.compile(r'^[0-9a-f]{40}$')
39 51
40REVIEW_CACHE = dict() 52REVIEW_CACHE = dict()
41 53
@@ -157,7 +169,7 @@ class GitConfig(object):
157 elif old != value: 169 elif old != value:
158 self._cache[key] = list(value) 170 self._cache[key] = list(value)
159 self._do('--replace-all', name, value[0]) 171 self._do('--replace-all', name, value[0])
160 for i in xrange(1, len(value)): 172 for i in range(1, len(value)):
161 self._do('--add', name, value[i]) 173 self._do('--add', name, value[i])
162 174
163 elif len(old) != 1 or old[0] != value: 175 elif len(old) != 1 or old[0] != value:
@@ -288,12 +300,13 @@ class GitConfig(object):
288 d = self._do('--null', '--list') 300 d = self._do('--null', '--list')
289 if d is None: 301 if d is None:
290 return c 302 return c
291 for line in d.rstrip('\0').split('\0'): 303 for line in d.rstrip('\0').split('\0'): # pylint: disable=W1401
304 # Backslash is not anomalous
292 if '\n' in line: 305 if '\n' in line:
293 key, val = line.split('\n', 1) 306 key, val = line.split('\n', 1)
294 else: 307 else:
295 key = line 308 key = line
296 val = None 309 val = None
297 310
298 if key in c: 311 if key in c:
299 c[key].append(val) 312 c[key].append(val)
@@ -418,7 +431,7 @@ def _open_ssh(host, port=None):
418 '-o','ControlPath %s' % ssh_sock(), 431 '-o','ControlPath %s' % ssh_sock(),
419 host] 432 host]
420 if port is not None: 433 if port is not None:
421 command_base[1:1] = ['-p',str(port)] 434 command_base[1:1] = ['-p', str(port)]
422 435
423 # Since the key wasn't in _master_keys, we think that master isn't running. 436 # Since the key wasn't in _master_keys, we think that master isn't running.
424 # ...but before actually starting a master, we'll double-check. This can 437 # ...but before actually starting a master, we'll double-check. This can
@@ -451,9 +464,8 @@ def _open_ssh(host, port=None):
451 p = subprocess.Popen(command) 464 p = subprocess.Popen(command)
452 except Exception as e: 465 except Exception as e:
453 _ssh_master = False 466 _ssh_master = False
454 print >>sys.stderr, \ 467 print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
455 '\nwarn: cannot enable ssh control master for %s:%s\n%s' \ 468 % (host,port, str(e)), file=sys.stderr)
456 % (host,port, str(e))
457 return False 469 return False
458 470
459 _master_processes.append(p) 471 _master_processes.append(p)
@@ -525,7 +537,7 @@ class Remote(object):
525 self.url = self._Get('url') 537 self.url = self._Get('url')
526 self.review = self._Get('review') 538 self.review = self._Get('review')
527 self.projectname = self._Get('projectname') 539 self.projectname = self._Get('projectname')
528 self.fetch = map(lambda x: RefSpec.FromString(x), 540 self.fetch = map(RefSpec.FromString,
529 self._Get('fetch', all_keys=True)) 541 self._Get('fetch', all_keys=True))
530 self._review_url = None 542 self._review_url = None
531 543
@@ -579,7 +591,7 @@ class Remote(object):
579 else: 591 else:
580 try: 592 try:
581 info_url = u + 'ssh_info' 593 info_url = u + 'ssh_info'
582 info = urllib2.urlopen(info_url).read() 594 info = urllib.request.urlopen(info_url).read()
583 if '<' in info: 595 if '<' in info:
584 # Assume the server gave us some sort of HTML 596 # Assume the server gave us some sort of HTML
585 # response back, like maybe a login page. 597 # response back, like maybe a login page.
@@ -592,9 +604,9 @@ class Remote(object):
592 else: 604 else:
593 host, port = info.split() 605 host, port = info.split()
594 self._review_url = self._SshReviewUrl(userEmail, host, port) 606 self._review_url = self._SshReviewUrl(userEmail, host, port)
595 except urllib2.HTTPError as e: 607 except urllib.error.HTTPError as e:
596 raise UploadError('%s: %s' % (self.review, str(e))) 608 raise UploadError('%s: %s' % (self.review, str(e)))
597 except urllib2.URLError as e: 609 except urllib.error.URLError as e:
598 raise UploadError('%s: %s' % (self.review, str(e))) 610 raise UploadError('%s: %s' % (self.review, str(e)))
599 611
600 REVIEW_CACHE[u] = self._review_url 612 REVIEW_CACHE[u] = self._review_url
@@ -645,7 +657,7 @@ class Remote(object):
645 self._Set('url', self.url) 657 self._Set('url', self.url)
646 self._Set('review', self.review) 658 self._Set('review', self.review)
647 self._Set('projectname', self.projectname) 659 self._Set('projectname', self.projectname)
648 self._Set('fetch', map(lambda x: str(x), self.fetch)) 660 self._Set('fetch', map(str, self.fetch))
649 661
650 def _Set(self, key, value): 662 def _Set(self, key, value):
651 key = 'remote.%s.%s' % (self.name, key) 663 key = 'remote.%s.%s' % (self.name, key)
diff --git a/git_refs.py b/git_refs.py
index 18c9230c..cfeffba9 100644
--- a/git_refs.py
+++ b/git_refs.py
@@ -138,14 +138,14 @@ class GitRefs(object):
138 def _ReadLoose1(self, path, name): 138 def _ReadLoose1(self, path, name):
139 try: 139 try:
140 fd = open(path, 'rb') 140 fd = open(path, 'rb')
141 except: 141 except IOError:
142 return 142 return
143 143
144 try: 144 try:
145 try: 145 try:
146 mtime = os.path.getmtime(path) 146 mtime = os.path.getmtime(path)
147 ref_id = fd.readline() 147 ref_id = fd.readline()
148 except: 148 except (IOError, OSError):
149 return 149 return
150 finally: 150 finally:
151 fd.close() 151 fd.close()
diff --git a/hooks/commit-msg b/hooks/commit-msg
index 172a1781..b37dfaa4 100755
--- a/hooks/commit-msg
+++ b/hooks/commit-msg
@@ -1,5 +1,5 @@
1#!/bin/sh 1#!/bin/sh
2# From Gerrit Code Review 2.5-rc0 2# From Gerrit Code Review 2.5.2
3# 3#
4# Part of Gerrit Code Review (http://code.google.com/p/gerrit/) 4# Part of Gerrit Code Review (http://code.google.com/p/gerrit/)
5# 5#
@@ -18,6 +18,8 @@
18# limitations under the License. 18# limitations under the License.
19# 19#
20 20
21unset GREP_OPTIONS
22
21CHANGE_ID_AFTER="Bug|Issue" 23CHANGE_ID_AFTER="Bug|Issue"
22MSG="$1" 24MSG="$1"
23 25
diff --git a/main.py b/main.py
index ba40d56b..9cc2639a 100755
--- a/main.py
+++ b/main.py
@@ -1,4 +1,4 @@
1#!/bin/sh 1#!/usr/bin/env python
2# 2#
3# Copyright (C) 2008 The Android Open Source Project 3# Copyright (C) 2008 The Android Open Source Project
4# 4#
@@ -14,23 +14,23 @@
14# See the License for the specific language governing permissions and 14# See the License for the specific language governing permissions and
15# limitations under the License. 15# limitations under the License.
16 16
17magic='--calling-python-from-/bin/sh--' 17from __future__ import print_function
18"""exec" python -E "$0" "$@" """#$magic"
19if __name__ == '__main__':
20 import sys
21 if sys.argv[-1] == '#%s' % magic:
22 del sys.argv[-1]
23del magic
24
25import getpass 18import getpass
26import imp 19import imp
27import netrc 20import netrc
28import optparse 21import optparse
29import os 22import os
30import re
31import sys 23import sys
32import time 24import time
33import urllib2 25try:
26 import urllib2
27except ImportError:
28 # For python3
29 import urllib.request
30else:
31 # For python2
32 urllib = imp.new_module('urllib')
33 urllib.request = urllib2
34 34
35from trace import SetTrace 35from trace import SetTrace
36from git_command import git, GitCommand 36from git_command import git, GitCommand
@@ -41,6 +41,8 @@ from subcmds.version import Version
41from editor import Editor 41from editor import Editor
42from error import DownloadError 42from error import DownloadError
43from error import ManifestInvalidRevisionError 43from error import ManifestInvalidRevisionError
44from error import ManifestParseError
45from error import NoManifestException
44from error import NoSuchProjectError 46from error import NoSuchProjectError
45from error import RepoChangedException 47from error import RepoChangedException
46from manifest_xml import XmlManifest 48from manifest_xml import XmlManifest
@@ -79,7 +81,7 @@ class _Repo(object):
79 name = None 81 name = None
80 glob = [] 82 glob = []
81 83
82 for i in xrange(0, len(argv)): 84 for i in range(len(argv)):
83 if not argv[i].startswith('-'): 85 if not argv[i].startswith('-'):
84 name = argv[i] 86 name = argv[i]
85 if i > 0: 87 if i > 0:
@@ -98,15 +100,14 @@ class _Repo(object):
98 if name == 'help': 100 if name == 'help':
99 name = 'version' 101 name = 'version'
100 else: 102 else:
101 print >>sys.stderr, 'fatal: invalid usage of --version' 103 print('fatal: invalid usage of --version', file=sys.stderr)
102 return 1 104 return 1
103 105
104 try: 106 try:
105 cmd = self.commands[name] 107 cmd = self.commands[name]
106 except KeyError: 108 except KeyError:
107 print >>sys.stderr,\ 109 print("repo: '%s' is not a repo command. See 'repo help'." % name,
108 "repo: '%s' is not a repo command. See 'repo help'."\ 110 file=sys.stderr)
109 % name
110 return 1 111 return 1
111 112
112 cmd.repodir = self.repodir 113 cmd.repodir = self.repodir
@@ -114,12 +115,12 @@ class _Repo(object):
114 Editor.globalConfig = cmd.manifest.globalConfig 115 Editor.globalConfig = cmd.manifest.globalConfig
115 116
116 if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror: 117 if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror:
117 print >>sys.stderr, \ 118 print("fatal: '%s' requires a working directory" % name,
118 "fatal: '%s' requires a working directory"\ 119 file=sys.stderr)
119 % name
120 return 1 120 return 1
121 121
122 copts, cargs = cmd.OptionParser.parse_args(argv) 122 copts, cargs = cmd.OptionParser.parse_args(argv)
123 copts = cmd.ReadEnvironmentOptions(copts)
123 124
124 if not gopts.no_pager and not isinstance(cmd, InteractiveCommand): 125 if not gopts.no_pager and not isinstance(cmd, InteractiveCommand):
125 config = cmd.manifest.globalConfig 126 config = cmd.manifest.globalConfig
@@ -132,33 +133,35 @@ class _Repo(object):
132 if use_pager: 133 if use_pager:
133 RunPager(config) 134 RunPager(config)
134 135
136 start = time.time()
135 try: 137 try:
136 start = time.time() 138 result = cmd.Execute(copts, cargs)
137 try:
138 result = cmd.Execute(copts, cargs)
139 finally:
140 elapsed = time.time() - start
141 hours, remainder = divmod(elapsed, 3600)
142 minutes, seconds = divmod(remainder, 60)
143 if gopts.time:
144 if hours == 0:
145 print >>sys.stderr, 'real\t%dm%.3fs' \
146 % (minutes, seconds)
147 else:
148 print >>sys.stderr, 'real\t%dh%dm%.3fs' \
149 % (hours, minutes, seconds)
150 except DownloadError as e: 139 except DownloadError as e:
151 print >>sys.stderr, 'error: %s' % str(e) 140 print('error: %s' % str(e), file=sys.stderr)
152 return 1 141 result = 1
153 except ManifestInvalidRevisionError as e: 142 except ManifestInvalidRevisionError as e:
154 print >>sys.stderr, 'error: %s' % str(e) 143 print('error: %s' % str(e), file=sys.stderr)
155 return 1 144 result = 1
145 except NoManifestException as e:
146 print('error: manifest required for this command -- please run init',
147 file=sys.stderr)
148 result = 1
156 except NoSuchProjectError as e: 149 except NoSuchProjectError as e:
157 if e.name: 150 if e.name:
158 print >>sys.stderr, 'error: project %s not found' % e.name 151 print('error: project %s not found' % e.name, file=sys.stderr)
159 else: 152 else:
160 print >>sys.stderr, 'error: no project in current directory' 153 print('error: no project in current directory', file=sys.stderr)
161 return 1 154 result = 1
155 finally:
156 elapsed = time.time() - start
157 hours, remainder = divmod(elapsed, 3600)
158 minutes, seconds = divmod(remainder, 60)
159 if gopts.time:
160 if hours == 0:
161 print('real\t%dm%.3fs' % (minutes, seconds), file=sys.stderr)
162 else:
163 print('real\t%dh%dm%.3fs' % (hours, minutes, seconds),
164 file=sys.stderr)
162 165
163 return result 166 return result
164 167
@@ -183,36 +186,35 @@ def _CheckWrapperVersion(ver, repo_path):
183 repo_path = '~/bin/repo' 186 repo_path = '~/bin/repo'
184 187
185 if not ver: 188 if not ver:
186 print >>sys.stderr, 'no --wrapper-version argument' 189 print('no --wrapper-version argument', file=sys.stderr)
187 sys.exit(1) 190 sys.exit(1)
188 191
189 exp = _CurrentWrapperVersion() 192 exp = _CurrentWrapperVersion()
190 ver = tuple(map(lambda x: int(x), ver.split('.'))) 193 ver = tuple(map(int, ver.split('.')))
191 if len(ver) == 1: 194 if len(ver) == 1:
192 ver = (0, ver[0]) 195 ver = (0, ver[0])
193 196
197 exp_str = '.'.join(map(str, exp))
194 if exp[0] > ver[0] or ver < (0, 4): 198 if exp[0] > ver[0] or ver < (0, 4):
195 exp_str = '.'.join(map(lambda x: str(x), exp)) 199 print("""
196 print >>sys.stderr, """
197!!! A new repo command (%5s) is available. !!! 200!!! A new repo command (%5s) is available. !!!
198!!! You must upgrade before you can continue: !!! 201!!! You must upgrade before you can continue: !!!
199 202
200 cp %s %s 203 cp %s %s
201""" % (exp_str, _MyWrapperPath(), repo_path) 204""" % (exp_str, _MyWrapperPath(), repo_path), file=sys.stderr)
202 sys.exit(1) 205 sys.exit(1)
203 206
204 if exp > ver: 207 if exp > ver:
205 exp_str = '.'.join(map(lambda x: str(x), exp)) 208 print("""
206 print >>sys.stderr, """
207... A new repo command (%5s) is available. 209... A new repo command (%5s) is available.
208... You should upgrade soon: 210... You should upgrade soon:
209 211
210 cp %s %s 212 cp %s %s
211""" % (exp_str, _MyWrapperPath(), repo_path) 213""" % (exp_str, _MyWrapperPath(), repo_path), file=sys.stderr)
212 214
213def _CheckRepoDir(repo_dir): 215def _CheckRepoDir(repo_dir):
214 if not repo_dir: 216 if not repo_dir:
215 print >>sys.stderr, 'no --repo-dir argument' 217 print('no --repo-dir argument', file=sys.stderr)
216 sys.exit(1) 218 sys.exit(1)
217 219
218def _PruneOptions(argv, opt): 220def _PruneOptions(argv, opt):
@@ -264,11 +266,11 @@ def _UserAgent():
264 _user_agent = 'git-repo/%s (%s) git/%s Python/%d.%d.%d' % ( 266 _user_agent = 'git-repo/%s (%s) git/%s Python/%d.%d.%d' % (
265 repo_version, 267 repo_version,
266 os_name, 268 os_name,
267 '.'.join(map(lambda d: str(d), git.version_tuple())), 269 '.'.join(map(str, git.version_tuple())),
268 py_version[0], py_version[1], py_version[2]) 270 py_version[0], py_version[1], py_version[2])
269 return _user_agent 271 return _user_agent
270 272
271class _UserAgentHandler(urllib2.BaseHandler): 273class _UserAgentHandler(urllib.request.BaseHandler):
272 def http_request(self, req): 274 def http_request(self, req):
273 req.add_header('User-Agent', _UserAgent()) 275 req.add_header('User-Agent', _UserAgent())
274 return req 276 return req
@@ -278,22 +280,22 @@ class _UserAgentHandler(urllib2.BaseHandler):
278 return req 280 return req
279 281
280def _AddPasswordFromUserInput(handler, msg, req): 282def _AddPasswordFromUserInput(handler, msg, req):
281 # If repo could not find auth info from netrc, try to get it from user input 283 # If repo could not find auth info from netrc, try to get it from user input
282 url = req.get_full_url() 284 url = req.get_full_url()
283 user, password = handler.passwd.find_user_password(None, url) 285 user, password = handler.passwd.find_user_password(None, url)
284 if user is None: 286 if user is None:
285 print msg 287 print(msg)
286 try: 288 try:
287 user = raw_input('User: ') 289 user = raw_input('User: ')
288 password = getpass.getpass() 290 password = getpass.getpass()
289 except KeyboardInterrupt: 291 except KeyboardInterrupt:
290 return 292 return
291 handler.passwd.add_password(None, url, user, password) 293 handler.passwd.add_password(None, url, user, password)
292 294
293class _BasicAuthHandler(urllib2.HTTPBasicAuthHandler): 295class _BasicAuthHandler(urllib.request.HTTPBasicAuthHandler):
294 def http_error_401(self, req, fp, code, msg, headers): 296 def http_error_401(self, req, fp, code, msg, headers):
295 _AddPasswordFromUserInput(self, msg, req) 297 _AddPasswordFromUserInput(self, msg, req)
296 return urllib2.HTTPBasicAuthHandler.http_error_401( 298 return urllib.request.HTTPBasicAuthHandler.http_error_401(
297 self, req, fp, code, msg, headers) 299 self, req, fp, code, msg, headers)
298 300
299 def http_error_auth_reqed(self, authreq, host, req, headers): 301 def http_error_auth_reqed(self, authreq, host, req, headers):
@@ -303,7 +305,7 @@ class _BasicAuthHandler(urllib2.HTTPBasicAuthHandler):
303 val = val.replace('\n', '') 305 val = val.replace('\n', '')
304 old_add_header(name, val) 306 old_add_header(name, val)
305 req.add_header = _add_header 307 req.add_header = _add_header
306 return urllib2.AbstractBasicAuthHandler.http_error_auth_reqed( 308 return urllib.request.AbstractBasicAuthHandler.http_error_auth_reqed(
307 self, authreq, host, req, headers) 309 self, authreq, host, req, headers)
308 except: 310 except:
309 reset = getattr(self, 'reset_retry_count', None) 311 reset = getattr(self, 'reset_retry_count', None)
@@ -313,10 +315,10 @@ class _BasicAuthHandler(urllib2.HTTPBasicAuthHandler):
313 self.retried = 0 315 self.retried = 0
314 raise 316 raise
315 317
316class _DigestAuthHandler(urllib2.HTTPDigestAuthHandler): 318class _DigestAuthHandler(urllib.request.HTTPDigestAuthHandler):
317 def http_error_401(self, req, fp, code, msg, headers): 319 def http_error_401(self, req, fp, code, msg, headers):
318 _AddPasswordFromUserInput(self, msg, req) 320 _AddPasswordFromUserInput(self, msg, req)
319 return urllib2.HTTPDigestAuthHandler.http_error_401( 321 return urllib.request.HTTPDigestAuthHandler.http_error_401(
320 self, req, fp, code, msg, headers) 322 self, req, fp, code, msg, headers)
321 323
322 def http_error_auth_reqed(self, auth_header, host, req, headers): 324 def http_error_auth_reqed(self, auth_header, host, req, headers):
@@ -326,7 +328,7 @@ class _DigestAuthHandler(urllib2.HTTPDigestAuthHandler):
326 val = val.replace('\n', '') 328 val = val.replace('\n', '')
327 old_add_header(name, val) 329 old_add_header(name, val)
328 req.add_header = _add_header 330 req.add_header = _add_header
329 return urllib2.AbstractDigestAuthHandler.http_error_auth_reqed( 331 return urllib.request.AbstractDigestAuthHandler.http_error_auth_reqed(
330 self, auth_header, host, req, headers) 332 self, auth_header, host, req, headers)
331 except: 333 except:
332 reset = getattr(self, 'reset_retry_count', None) 334 reset = getattr(self, 'reset_retry_count', None)
@@ -339,7 +341,7 @@ class _DigestAuthHandler(urllib2.HTTPDigestAuthHandler):
339def init_http(): 341def init_http():
340 handlers = [_UserAgentHandler()] 342 handlers = [_UserAgentHandler()]
341 343
342 mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() 344 mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
343 try: 345 try:
344 n = netrc.netrc() 346 n = netrc.netrc()
345 for host in n.hosts: 347 for host in n.hosts:
@@ -355,11 +357,11 @@ def init_http():
355 357
356 if 'http_proxy' in os.environ: 358 if 'http_proxy' in os.environ:
357 url = os.environ['http_proxy'] 359 url = os.environ['http_proxy']
358 handlers.append(urllib2.ProxyHandler({'http': url, 'https': url})) 360 handlers.append(urllib.request.ProxyHandler({'http': url, 'https': url}))
359 if 'REPO_CURL_VERBOSE' in os.environ: 361 if 'REPO_CURL_VERBOSE' in os.environ:
360 handlers.append(urllib2.HTTPHandler(debuglevel=1)) 362 handlers.append(urllib.request.HTTPHandler(debuglevel=1))
361 handlers.append(urllib2.HTTPSHandler(debuglevel=1)) 363 handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
362 urllib2.install_opener(urllib2.build_opener(*handlers)) 364 urllib.request.install_opener(urllib.request.build_opener(*handlers))
363 365
364def _Main(argv): 366def _Main(argv):
365 result = 0 367 result = 0
@@ -389,6 +391,10 @@ def _Main(argv):
389 finally: 391 finally:
390 close_ssh() 392 close_ssh()
391 except KeyboardInterrupt: 393 except KeyboardInterrupt:
394 print('aborted by user', file=sys.stderr)
395 result = 1
396 except ManifestParseError as mpe:
397 print('fatal: %s' % mpe, file=sys.stderr)
392 result = 1 398 result = 1
393 except RepoChangedException as rce: 399 except RepoChangedException as rce:
394 # If repo changed, re-exec ourselves. 400 # If repo changed, re-exec ourselves.
@@ -398,8 +404,8 @@ def _Main(argv):
398 try: 404 try:
399 os.execv(__file__, argv) 405 os.execv(__file__, argv)
400 except OSError as e: 406 except OSError as e:
401 print >>sys.stderr, 'fatal: cannot restart repo after upgrade' 407 print('fatal: cannot restart repo after upgrade', file=sys.stderr)
402 print >>sys.stderr, 'fatal: %s' % e 408 print('fatal: %s' % e, file=sys.stderr)
403 result = 128 409 result = 128
404 410
405 sys.exit(result) 411 sys.exit(result)
diff --git a/manifest_xml.py b/manifest_xml.py
index dd163bed..53f33537 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import itertools 17import itertools
17import os 18import os
18import re 19import re
@@ -27,6 +28,7 @@ from error import ManifestParseError
27 28
28MANIFEST_FILE_NAME = 'manifest.xml' 29MANIFEST_FILE_NAME = 'manifest.xml'
29LOCAL_MANIFEST_NAME = 'local_manifest.xml' 30LOCAL_MANIFEST_NAME = 'local_manifest.xml'
31LOCAL_MANIFESTS_DIR_NAME = 'local_manifests'
30 32
31urlparse.uses_relative.extend(['ssh', 'git']) 33urlparse.uses_relative.extend(['ssh', 'git'])
32urlparse.uses_netloc.extend(['ssh', 'git']) 34urlparse.uses_netloc.extend(['ssh', 'git'])
@@ -38,6 +40,7 @@ class _Default(object):
38 remote = None 40 remote = None
39 sync_j = 1 41 sync_j = 1
40 sync_c = False 42 sync_c = False
43 sync_s = False
41 44
42class _XmlRemote(object): 45class _XmlRemote(object):
43 def __init__(self, 46 def __init__(self,
@@ -53,15 +56,28 @@ class _XmlRemote(object):
53 self.reviewUrl = review 56 self.reviewUrl = review
54 self.resolvedFetchUrl = self._resolveFetchUrl() 57 self.resolvedFetchUrl = self._resolveFetchUrl()
55 58
59 def __eq__(self, other):
60 return self.__dict__ == other.__dict__
61
62 def __ne__(self, other):
63 return self.__dict__ != other.__dict__
64
56 def _resolveFetchUrl(self): 65 def _resolveFetchUrl(self):
57 url = self.fetchUrl.rstrip('/') 66 url = self.fetchUrl.rstrip('/')
58 manifestUrl = self.manifestUrl.rstrip('/') 67 manifestUrl = self.manifestUrl.rstrip('/')
68 p = manifestUrl.startswith('persistent-http')
69 if p:
70 manifestUrl = manifestUrl[len('persistent-'):]
71
59 # urljoin will get confused if there is no scheme in the base url 72 # urljoin will get confused if there is no scheme in the base url
60 # ie, if manifestUrl is of the form <hostname:port> 73 # ie, if manifestUrl is of the form <hostname:port>
61 if manifestUrl.find(':') != manifestUrl.find('/') - 1: 74 if manifestUrl.find(':') != manifestUrl.find('/') - 1:
62 manifestUrl = 'gopher://' + manifestUrl 75 manifestUrl = 'gopher://' + manifestUrl
63 url = urlparse.urljoin(manifestUrl, url) 76 url = urlparse.urljoin(manifestUrl, url)
64 return re.sub(r'^gopher://', '', url) 77 url = re.sub(r'^gopher://', '', url)
78 if p:
79 url = 'persistent-' + url
80 return url
65 81
66 def ToRemoteSpec(self, projectName): 82 def ToRemoteSpec(self, projectName):
67 url = self.resolvedFetchUrl.rstrip('/') + '/' + projectName 83 url = self.resolvedFetchUrl.rstrip('/') + '/' + projectName
@@ -110,11 +126,11 @@ class XmlManifest(object):
110 self.Override(name) 126 self.Override(name)
111 127
112 try: 128 try:
113 if os.path.exists(self.manifestFile): 129 if os.path.lexists(self.manifestFile):
114 os.remove(self.manifestFile) 130 os.remove(self.manifestFile)
115 os.symlink('manifests/%s' % name, self.manifestFile) 131 os.symlink('manifests/%s' % name, self.manifestFile)
116 except OSError: 132 except OSError as e:
117 raise ManifestParseError('cannot link manifest %s' % name) 133 raise ManifestParseError('cannot link manifest %s: %s' % (name, str(e)))
118 134
119 def _RemoteToXml(self, r, doc, root): 135 def _RemoteToXml(self, r, doc, root):
120 e = doc.createElement('remote') 136 e = doc.createElement('remote')
@@ -130,9 +146,8 @@ class XmlManifest(object):
130 mp = self.manifestProject 146 mp = self.manifestProject
131 147
132 groups = mp.config.GetString('manifest.groups') 148 groups = mp.config.GetString('manifest.groups')
133 if not groups: 149 if groups:
134 groups = 'all' 150 groups = [x for x in re.split(r'[,\s]+', groups) if x]
135 groups = [x for x in re.split(r'[,\s]+', groups) if x]
136 151
137 doc = xml.dom.minidom.Document() 152 doc = xml.dom.minidom.Document()
138 root = doc.createElement('manifest') 153 root = doc.createElement('manifest')
@@ -170,6 +185,9 @@ class XmlManifest(object):
170 if d.sync_c: 185 if d.sync_c:
171 have_default = True 186 have_default = True
172 e.setAttribute('sync-c', 'true') 187 e.setAttribute('sync-c', 'true')
188 if d.sync_s:
189 have_default = True
190 e.setAttribute('sync-s', 'true')
173 if have_default: 191 if have_default:
174 root.appendChild(e) 192 root.appendChild(e)
175 root.appendChild(doc.createTextNode('')) 193 root.appendChild(doc.createTextNode(''))
@@ -180,20 +198,25 @@ class XmlManifest(object):
180 root.appendChild(e) 198 root.appendChild(e)
181 root.appendChild(doc.createTextNode('')) 199 root.appendChild(doc.createTextNode(''))
182 200
183 sort_projects = list(self.projects.keys()) 201 def output_projects(parent, parent_node, projects):
184 sort_projects.sort() 202 for p in projects:
185 203 output_project(parent, parent_node, self.projects[p])
186 for p in sort_projects:
187 p = self.projects[p]
188 204
205 def output_project(parent, parent_node, p):
189 if not p.MatchesGroups(groups): 206 if not p.MatchesGroups(groups):
190 continue 207 return
208
209 name = p.name
210 relpath = p.relpath
211 if parent:
212 name = self._UnjoinName(parent.name, name)
213 relpath = self._UnjoinRelpath(parent.relpath, relpath)
191 214
192 e = doc.createElement('project') 215 e = doc.createElement('project')
193 root.appendChild(e) 216 parent_node.appendChild(e)
194 e.setAttribute('name', p.name) 217 e.setAttribute('name', name)
195 if p.relpath != p.name: 218 if relpath != name:
196 e.setAttribute('path', p.relpath) 219 e.setAttribute('path', relpath)
197 if not d.remote or p.remote.name != d.remote.name: 220 if not d.remote or p.remote.name != d.remote.name:
198 e.setAttribute('remote', p.remote.name) 221 e.setAttribute('remote', p.remote.name)
199 if peg_rev: 222 if peg_rev:
@@ -231,6 +254,19 @@ class XmlManifest(object):
231 if p.sync_c: 254 if p.sync_c:
232 e.setAttribute('sync-c', 'true') 255 e.setAttribute('sync-c', 'true')
233 256
257 if p.sync_s:
258 e.setAttribute('sync-s', 'true')
259
260 if p.subprojects:
261 sort_projects = [subp.name for subp in p.subprojects]
262 sort_projects.sort()
263 output_projects(p, e, sort_projects)
264
265 sort_projects = [key for key in self.projects.keys()
266 if not self.projects[key].parent]
267 sort_projects.sort()
268 output_projects(None, root, sort_projects)
269
234 if self._repo_hooks_project: 270 if self._repo_hooks_project:
235 root.appendChild(doc.createTextNode('')) 271 root.appendChild(doc.createTextNode(''))
236 e = doc.createElement('repo-hooks') 272 e = doc.createElement('repo-hooks')
@@ -299,9 +335,30 @@ class XmlManifest(object):
299 335
300 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME) 336 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
301 if os.path.exists(local): 337 if os.path.exists(local):
338 print('warning: %s is deprecated; put local manifests in %s instead'
339 % (LOCAL_MANIFEST_NAME, LOCAL_MANIFESTS_DIR_NAME),
340 file=sys.stderr)
302 nodes.append(self._ParseManifestXml(local, self.repodir)) 341 nodes.append(self._ParseManifestXml(local, self.repodir))
303 342
304 self._ParseManifest(nodes) 343 local_dir = os.path.abspath(os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME))
344 try:
345 for local_file in sorted(os.listdir(local_dir)):
346 if local_file.endswith('.xml'):
347 try:
348 local = os.path.join(local_dir, local_file)
349 nodes.append(self._ParseManifestXml(local, self.repodir))
350 except ManifestParseError as e:
351 print('%s' % str(e), file=sys.stderr)
352 except OSError:
353 pass
354
355 try:
356 self._ParseManifest(nodes)
357 except ManifestParseError as e:
358 # There was a problem parsing, unload ourselves in case they catch
359 # this error and try again later, we will show the correct error
360 self._Unload()
361 raise e
305 362
306 if self.IsMirror: 363 if self.IsMirror:
307 self._AddMetaProjectMirror(self.repoProject) 364 self._AddMetaProjectMirror(self.repoProject)
@@ -310,7 +367,11 @@ class XmlManifest(object):
310 self._loaded = True 367 self._loaded = True
311 368
312 def _ParseManifestXml(self, path, include_root): 369 def _ParseManifestXml(self, path, include_root):
313 root = xml.dom.minidom.parse(path) 370 try:
371 root = xml.dom.minidom.parse(path)
372 except (OSError, xml.parsers.expat.ExpatError) as e:
373 raise ManifestParseError("error parsing manifest %s: %s" % (path, e))
374
314 if not root or not root.childNodes: 375 if not root or not root.childNodes:
315 raise ManifestParseError("no root node in %s" % (path,)) 376 raise ManifestParseError("no root node in %s" % (path,))
316 377
@@ -323,35 +384,38 @@ class XmlManifest(object):
323 nodes = [] 384 nodes = []
324 for node in manifest.childNodes: # pylint:disable=W0631 385 for node in manifest.childNodes: # pylint:disable=W0631
325 # We only get here if manifest is initialised 386 # We only get here if manifest is initialised
326 if node.nodeName == 'include': 387 if node.nodeName == 'include':
327 name = self._reqatt(node, 'name') 388 name = self._reqatt(node, 'name')
328 fp = os.path.join(include_root, name) 389 fp = os.path.join(include_root, name)
329 if not os.path.isfile(fp): 390 if not os.path.isfile(fp):
330 raise ManifestParseError, \ 391 raise ManifestParseError, \
331 "include %s doesn't exist or isn't a file" % \ 392 "include %s doesn't exist or isn't a file" % \
332 (name,) 393 (name,)
333 try: 394 try:
334 nodes.extend(self._ParseManifestXml(fp, include_root)) 395 nodes.extend(self._ParseManifestXml(fp, include_root))
335 # should isolate this to the exact exception, but that's 396 # should isolate this to the exact exception, but that's
336 # tricky. actual parsing implementation may vary. 397 # tricky. actual parsing implementation may vary.
337 except (KeyboardInterrupt, RuntimeError, SystemExit): 398 except (KeyboardInterrupt, RuntimeError, SystemExit):
338 raise 399 raise
339 except Exception as e: 400 except Exception as e:
340 raise ManifestParseError( 401 raise ManifestParseError(
341 "failed parsing included manifest %s: %s", (name, e)) 402 "failed parsing included manifest %s: %s", (name, e))
342 else: 403 else:
343 nodes.append(node) 404 nodes.append(node)
344 return nodes 405 return nodes
345 406
346 def _ParseManifest(self, node_list): 407 def _ParseManifest(self, node_list):
347 for node in itertools.chain(*node_list): 408 for node in itertools.chain(*node_list):
348 if node.nodeName == 'remote': 409 if node.nodeName == 'remote':
349 remote = self._ParseRemote(node) 410 remote = self._ParseRemote(node)
350 if self._remotes.get(remote.name): 411 if remote:
351 raise ManifestParseError( 412 if remote.name in self._remotes:
352 'duplicate remote %s in %s' % 413 if remote != self._remotes[remote.name]:
353 (remote.name, self.manifestFile)) 414 raise ManifestParseError(
354 self._remotes[remote.name] = remote 415 'remote %s already exists with different attributes' %
416 (remote.name))
417 else:
418 self._remotes[remote.name] = remote
355 419
356 for node in itertools.chain(*node_list): 420 for node in itertools.chain(*node_list):
357 if node.nodeName == 'default': 421 if node.nodeName == 'default':
@@ -375,19 +439,24 @@ class XmlManifest(object):
375 if node.nodeName == 'manifest-server': 439 if node.nodeName == 'manifest-server':
376 url = self._reqatt(node, 'url') 440 url = self._reqatt(node, 'url')
377 if self._manifest_server is not None: 441 if self._manifest_server is not None:
378 raise ManifestParseError( 442 raise ManifestParseError(
379 'duplicate manifest-server in %s' % 443 'duplicate manifest-server in %s' %
380 (self.manifestFile)) 444 (self.manifestFile))
381 self._manifest_server = url 445 self._manifest_server = url
382 446
447 def recursively_add_projects(project):
448 if self._projects.get(project.name):
449 raise ManifestParseError(
450 'duplicate project %s in %s' %
451 (project.name, self.manifestFile))
452 self._projects[project.name] = project
453 for subproject in project.subprojects:
454 recursively_add_projects(subproject)
455
383 for node in itertools.chain(*node_list): 456 for node in itertools.chain(*node_list):
384 if node.nodeName == 'project': 457 if node.nodeName == 'project':
385 project = self._ParseProject(node) 458 project = self._ParseProject(node)
386 if self._projects.get(project.name): 459 recursively_add_projects(project)
387 raise ManifestParseError(
388 'duplicate project %s in %s' %
389 (project.name, self.manifestFile))
390 self._projects[project.name] = project
391 if node.nodeName == 'repo-hooks': 460 if node.nodeName == 'repo-hooks':
392 # Get the name of the project and the (space-separated) list of enabled. 461 # Get the name of the project and the (space-separated) list of enabled.
393 repo_hooks_project = self._reqatt(node, 'in-project') 462 repo_hooks_project = self._reqatt(node, 'in-project')
@@ -414,9 +483,8 @@ class XmlManifest(object):
414 try: 483 try:
415 del self._projects[name] 484 del self._projects[name]
416 except KeyError: 485 except KeyError:
417 raise ManifestParseError( 486 raise ManifestParseError('remove-project element specifies non-existent '
418 'project %s not found' % 487 'project: %s' % name)
419 (name))
420 488
421 # If the manifest removes the hooks project, treat it as if it deleted 489 # If the manifest removes the hooks project, treat it as if it deleted
422 # the repo-hooks element too. 490 # the repo-hooks element too.
@@ -496,6 +564,12 @@ class XmlManifest(object):
496 d.sync_c = False 564 d.sync_c = False
497 else: 565 else:
498 d.sync_c = sync_c.lower() in ("yes", "true", "1") 566 d.sync_c = sync_c.lower() in ("yes", "true", "1")
567
568 sync_s = node.getAttribute('sync-s')
569 if not sync_s:
570 d.sync_s = False
571 else:
572 d.sync_s = sync_s.lower() in ("yes", "true", "1")
499 return d 573 return d
500 574
501 def _ParseNotice(self, node): 575 def _ParseNotice(self, node):
@@ -537,11 +611,19 @@ class XmlManifest(object):
537 611
538 return '\n'.join(cleanLines) 612 return '\n'.join(cleanLines)
539 613
540 def _ParseProject(self, node): 614 def _JoinName(self, parent_name, name):
615 return os.path.join(parent_name, name)
616
617 def _UnjoinName(self, parent_name, name):
618 return os.path.relpath(name, parent_name)
619
620 def _ParseProject(self, node, parent = None):
541 """ 621 """
542 reads a <project> element from the manifest file 622 reads a <project> element from the manifest file
543 """ 623 """
544 name = self._reqatt(node, 'name') 624 name = self._reqatt(node, 'name')
625 if parent:
626 name = self._JoinName(parent.name, name)
545 627
546 remote = self._get_remote(node) 628 remote = self._get_remote(node)
547 if remote is None: 629 if remote is None:
@@ -579,44 +661,80 @@ class XmlManifest(object):
579 else: 661 else:
580 sync_c = sync_c.lower() in ("yes", "true", "1") 662 sync_c = sync_c.lower() in ("yes", "true", "1")
581 663
664 sync_s = node.getAttribute('sync-s')
665 if not sync_s:
666 sync_s = self._default.sync_s
667 else:
668 sync_s = sync_s.lower() in ("yes", "true", "1")
669
582 upstream = node.getAttribute('upstream') 670 upstream = node.getAttribute('upstream')
583 671
584 groups = '' 672 groups = ''
585 if node.hasAttribute('groups'): 673 if node.hasAttribute('groups'):
586 groups = node.getAttribute('groups') 674 groups = node.getAttribute('groups')
587 groups = [x for x in re.split('[,\s]+', groups) if x] 675 groups = [x for x in re.split(r'[,\s]+', groups) if x]
588
589 default_groups = ['all', 'name:%s' % name, 'path:%s' % path]
590 groups.extend(set(default_groups).difference(groups))
591 676
592 if self.IsMirror: 677 if parent is None:
593 worktree = None 678 relpath, worktree, gitdir = self.GetProjectPaths(name, path)
594 gitdir = os.path.join(self.topdir, '%s.git' % name)
595 else: 679 else:
596 worktree = os.path.join(self.topdir, path).replace('\\', '/') 680 relpath, worktree, gitdir = self.GetSubprojectPaths(parent, path)
597 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path) 681
682 default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath]
683 groups.extend(set(default_groups).difference(groups))
598 684
599 project = Project(manifest = self, 685 project = Project(manifest = self,
600 name = name, 686 name = name,
601 remote = remote.ToRemoteSpec(name), 687 remote = remote.ToRemoteSpec(name),
602 gitdir = gitdir, 688 gitdir = gitdir,
603 worktree = worktree, 689 worktree = worktree,
604 relpath = path, 690 relpath = relpath,
605 revisionExpr = revisionExpr, 691 revisionExpr = revisionExpr,
606 revisionId = None, 692 revisionId = None,
607 rebase = rebase, 693 rebase = rebase,
608 groups = groups, 694 groups = groups,
609 sync_c = sync_c, 695 sync_c = sync_c,
610 upstream = upstream) 696 sync_s = sync_s,
697 upstream = upstream,
698 parent = parent)
611 699
612 for n in node.childNodes: 700 for n in node.childNodes:
613 if n.nodeName == 'copyfile': 701 if n.nodeName == 'copyfile':
614 self._ParseCopyFile(project, n) 702 self._ParseCopyFile(project, n)
615 if n.nodeName == 'annotation': 703 if n.nodeName == 'annotation':
616 self._ParseAnnotation(project, n) 704 self._ParseAnnotation(project, n)
705 if n.nodeName == 'project':
706 project.subprojects.append(self._ParseProject(n, parent = project))
617 707
618 return project 708 return project
619 709
710 def GetProjectPaths(self, name, path):
711 relpath = path
712 if self.IsMirror:
713 worktree = None
714 gitdir = os.path.join(self.topdir, '%s.git' % name)
715 else:
716 worktree = os.path.join(self.topdir, path).replace('\\', '/')
717 gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path)
718 return relpath, worktree, gitdir
719
720 def GetSubprojectName(self, parent, submodule_path):
721 return os.path.join(parent.name, submodule_path)
722
723 def _JoinRelpath(self, parent_relpath, relpath):
724 return os.path.join(parent_relpath, relpath)
725
726 def _UnjoinRelpath(self, parent_relpath, relpath):
727 return os.path.relpath(relpath, parent_relpath)
728
729 def GetSubprojectPaths(self, parent, path):
730 relpath = self._JoinRelpath(parent.relpath, path)
731 gitdir = os.path.join(parent.gitdir, 'subprojects', '%s.git' % path)
732 if self.IsMirror:
733 worktree = None
734 else:
735 worktree = os.path.join(parent.worktree, path).replace('\\', '/')
736 return relpath, worktree, gitdir
737
620 def _ParseCopyFile(self, project, node): 738 def _ParseCopyFile(self, project, node):
621 src = self._reqatt(node, 'src') 739 src = self._reqatt(node, 'src')
622 dest = self._reqatt(node, 'dest') 740 dest = self._reqatt(node, 'dest')
diff --git a/pager.py b/pager.py
index 888b108b..c6211419 100755
--- a/pager.py
+++ b/pager.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import os 17import os
17import select 18import select
18import sys 19import sys
@@ -49,7 +50,7 @@ def RunPager(globalConfig):
49 50
50 _BecomePager(pager) 51 _BecomePager(pager)
51 except Exception: 52 except Exception:
52 print >>sys.stderr, "fatal: cannot start pager '%s'" % pager 53 print("fatal: cannot start pager '%s'" % pager, file=sys.stderr)
53 sys.exit(255) 54 sys.exit(255)
54 55
55def _SelectPager(globalConfig): 56def _SelectPager(globalConfig):
diff --git a/project.py b/project.py
index 2f471692..ba7898ed 100644
--- a/project.py
+++ b/project.py
@@ -12,6 +12,7 @@
12# See the License for the specific language governing permissions and 12# See the License for the specific language governing permissions and
13# limitations under the License. 13# limitations under the License.
14 14
15from __future__ import print_function
15import traceback 16import traceback
16import errno 17import errno
17import filecmp 18import filecmp
@@ -22,13 +23,15 @@ import shutil
22import stat 23import stat
23import subprocess 24import subprocess
24import sys 25import sys
26import tempfile
25import time 27import time
26 28
27from color import Coloring 29from color import Coloring
28from git_command import GitCommand 30from git_command import GitCommand, git_require
29from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE 31from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
30from error import GitError, HookError, UploadError 32from error import GitError, HookError, UploadError
31from error import ManifestInvalidRevisionError 33from error import ManifestInvalidRevisionError
34from error import NoManifestException
32from trace import IsTrace, Trace 35from trace import IsTrace, Trace
33 36
34from 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
@@ -50,7 +53,7 @@ def _lwrite(path, content):
50 53
51def _error(fmt, *args): 54def _error(fmt, *args):
52 msg = fmt % args 55 msg = fmt % args
53 print >>sys.stderr, 'error: %s' % msg 56 print('error: %s' % msg, file=sys.stderr)
54 57
55def not_rev(r): 58def not_rev(r):
56 return '^' + r 59 return '^' + r
@@ -359,7 +362,7 @@ class RepoHook(object):
359 '(yes/yes-never-ask-again/NO)? ') % ( 362 '(yes/yes-never-ask-again/NO)? ') % (
360 self._GetMustVerb(), self._script_fullpath) 363 self._GetMustVerb(), self._script_fullpath)
361 response = raw_input(prompt).lower() 364 response = raw_input(prompt).lower()
362 print 365 print()
363 366
364 # User is doing a one-time approval. 367 # User is doing a one-time approval.
365 if response in ('y', 'yes'): 368 if response in ('y', 'yes'):
@@ -484,7 +487,30 @@ class Project(object):
484 rebase = True, 487 rebase = True,
485 groups = None, 488 groups = None,
486 sync_c = False, 489 sync_c = False,
487 upstream = None): 490 sync_s = False,
491 upstream = None,
492 parent = None,
493 is_derived = False):
494 """Init a Project object.
495
496 Args:
497 manifest: The XmlManifest object.
498 name: The `name` attribute of manifest.xml's project element.
499 remote: RemoteSpec object specifying its remote's properties.
500 gitdir: Absolute path of git directory.
501 worktree: Absolute path of git working tree.
502 relpath: Relative path of git working tree to repo's top directory.
503 revisionExpr: The `revision` attribute of manifest.xml's project element.
504 revisionId: git commit id for checking out.
505 rebase: The `rebase` attribute of manifest.xml's project element.
506 groups: The `groups` attribute of manifest.xml's project element.
507 sync_c: The `sync-c` attribute of manifest.xml's project element.
508 sync_s: The `sync-s` attribute of manifest.xml's project element.
509 upstream: The `upstream` attribute of manifest.xml's project element.
510 parent: The parent Project object.
511 is_derived: False if the project was explicitly defined in the manifest;
512 True if the project is a discovered submodule.
513 """
488 self.manifest = manifest 514 self.manifest = manifest
489 self.name = name 515 self.name = name
490 self.remote = remote 516 self.remote = remote
@@ -506,7 +532,11 @@ class Project(object):
506 self.rebase = rebase 532 self.rebase = rebase
507 self.groups = groups 533 self.groups = groups
508 self.sync_c = sync_c 534 self.sync_c = sync_c
535 self.sync_s = sync_s
509 self.upstream = upstream 536 self.upstream = upstream
537 self.parent = parent
538 self.is_derived = is_derived
539 self.subprojects = []
510 540
511 self.snapshots = {} 541 self.snapshots = {}
512 self.copyfiles = [] 542 self.copyfiles = []
@@ -527,6 +557,10 @@ class Project(object):
527 self.enabled_repo_hooks = [] 557 self.enabled_repo_hooks = []
528 558
529 @property 559 @property
560 def Derived(self):
561 return self.is_derived
562
563 @property
530 def Exists(self): 564 def Exists(self):
531 return os.path.isdir(self.gitdir) 565 return os.path.isdir(self.gitdir)
532 566
@@ -555,7 +589,7 @@ class Project(object):
555 '--unmerged', 589 '--unmerged',
556 '--ignore-missing', 590 '--ignore-missing',
557 '--refresh') 591 '--refresh')
558 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD): 592 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
559 return True 593 return True
560 if self.work_git.DiffZ('diff-files'): 594 if self.work_git.DiffZ('diff-files'):
561 return True 595 return True
@@ -584,14 +618,14 @@ class Project(object):
584 return self._userident_email 618 return self._userident_email
585 619
586 def _LoadUserIdentity(self): 620 def _LoadUserIdentity(self):
587 u = self.bare_git.var('GIT_COMMITTER_IDENT') 621 u = self.bare_git.var('GIT_COMMITTER_IDENT')
588 m = re.compile("^(.*) <([^>]*)> ").match(u) 622 m = re.compile("^(.*) <([^>]*)> ").match(u)
589 if m: 623 if m:
590 self._userident_name = m.group(1) 624 self._userident_name = m.group(1)
591 self._userident_email = m.group(2) 625 self._userident_email = m.group(2)
592 else: 626 else:
593 self._userident_name = '' 627 self._userident_name = ''
594 self._userident_email = '' 628 self._userident_email = ''
595 629
596 def GetRemote(self, name): 630 def GetRemote(self, name):
597 """Get the configuration for a single remote. 631 """Get the configuration for a single remote.
@@ -683,9 +717,9 @@ class Project(object):
683 if not os.path.isdir(self.worktree): 717 if not os.path.isdir(self.worktree):
684 if output_redir == None: 718 if output_redir == None:
685 output_redir = sys.stdout 719 output_redir = sys.stdout
686 print >>output_redir, '' 720 print(file=output_redir)
687 print >>output_redir, 'project %s/' % self.relpath 721 print('project %s/' % self.relpath, file=output_redir)
688 print >>output_redir, ' missing (run "repo sync")' 722 print(' missing (run "repo sync")', file=output_redir)
689 return 723 return
690 724
691 self.work_git.update_index('-q', 725 self.work_git.update_index('-q',
@@ -785,7 +819,7 @@ class Project(object):
785 out.project('project %s/' % self.relpath) 819 out.project('project %s/' % self.relpath)
786 out.nl() 820 out.nl()
787 has_diff = True 821 has_diff = True
788 print line[:-1] 822 print(line[:-1])
789 p.Wait() 823 p.Wait()
790 824
791 825
@@ -1012,6 +1046,10 @@ class Project(object):
1012 self.CleanPublishedCache(all_refs) 1046 self.CleanPublishedCache(all_refs)
1013 revid = self.GetRevisionId(all_refs) 1047 revid = self.GetRevisionId(all_refs)
1014 1048
1049 def _doff():
1050 self._FastForward(revid)
1051 self._CopyFiles()
1052
1015 self._InitWorkTree() 1053 self._InitWorkTree()
1016 head = self.work_git.GetHead() 1054 head = self.work_git.GetHead()
1017 if head.startswith(R_HEADS): 1055 if head.startswith(R_HEADS):
@@ -1090,9 +1128,6 @@ class Project(object):
1090 # All published commits are merged, and thus we are a 1128 # All published commits are merged, and thus we are a
1091 # strict subset. We can fast-forward safely. 1129 # strict subset. We can fast-forward safely.
1092 # 1130 #
1093 def _doff():
1094 self._FastForward(revid)
1095 self._CopyFiles()
1096 syncbuf.later1(self, _doff) 1131 syncbuf.later1(self, _doff)
1097 return 1132 return
1098 1133
@@ -1155,9 +1190,6 @@ class Project(object):
1155 syncbuf.fail(self, e) 1190 syncbuf.fail(self, e)
1156 return 1191 return
1157 else: 1192 else:
1158 def _doff():
1159 self._FastForward(revid)
1160 self._CopyFiles()
1161 syncbuf.later1(self, _doff) 1193 syncbuf.later1(self, _doff)
1162 1194
1163 def AddCopyFile(self, src, dest, absdest): 1195 def AddCopyFile(self, src, dest, absdest):
@@ -1177,7 +1209,7 @@ class Project(object):
1177 cmd = ['fetch', remote.name] 1209 cmd = ['fetch', remote.name]
1178 cmd.append('refs/changes/%2.2d/%d/%d' \ 1210 cmd.append('refs/changes/%2.2d/%d/%d' \
1179 % (change_id % 100, change_id, patch_id)) 1211 % (change_id % 100, change_id, patch_id))
1180 cmd.extend(map(lambda x: str(x), remote.fetch)) 1212 cmd.extend(map(str, remote.fetch))
1181 if GitCommand(self, cmd, bare=True).Wait() != 0: 1213 if GitCommand(self, cmd, bare=True).Wait() != 0:
1182 return None 1214 return None
1183 return DownloadedChange(self, 1215 return DownloadedChange(self,
@@ -1370,6 +1402,149 @@ class Project(object):
1370 return kept 1402 return kept
1371 1403
1372 1404
1405## Submodule Management ##
1406
1407 def GetRegisteredSubprojects(self):
1408 result = []
1409 def rec(subprojects):
1410 if not subprojects:
1411 return
1412 result.extend(subprojects)
1413 for p in subprojects:
1414 rec(p.subprojects)
1415 rec(self.subprojects)
1416 return result
1417
1418 def _GetSubmodules(self):
1419 # Unfortunately we cannot call `git submodule status --recursive` here
1420 # because the working tree might not exist yet, and it cannot be used
1421 # without a working tree in its current implementation.
1422
1423 def get_submodules(gitdir, rev):
1424 # Parse .gitmodules for submodule sub_paths and sub_urls
1425 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1426 if not sub_paths:
1427 return []
1428 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1429 # revision of submodule repository
1430 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1431 submodules = []
1432 for sub_path, sub_url in zip(sub_paths, sub_urls):
1433 try:
1434 sub_rev = sub_revs[sub_path]
1435 except KeyError:
1436 # Ignore non-exist submodules
1437 continue
1438 submodules.append((sub_rev, sub_path, sub_url))
1439 return submodules
1440
1441 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1442 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1443 def parse_gitmodules(gitdir, rev):
1444 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1445 try:
1446 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1447 bare = True, gitdir = gitdir)
1448 except GitError:
1449 return [], []
1450 if p.Wait() != 0:
1451 return [], []
1452
1453 gitmodules_lines = []
1454 fd, temp_gitmodules_path = tempfile.mkstemp()
1455 try:
1456 os.write(fd, p.stdout)
1457 os.close(fd)
1458 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1459 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1460 bare = True, gitdir = gitdir)
1461 if p.Wait() != 0:
1462 return [], []
1463 gitmodules_lines = p.stdout.split('\n')
1464 except GitError:
1465 return [], []
1466 finally:
1467 os.remove(temp_gitmodules_path)
1468
1469 names = set()
1470 paths = {}
1471 urls = {}
1472 for line in gitmodules_lines:
1473 if not line:
1474 continue
1475 m = re_path.match(line)
1476 if m:
1477 names.add(m.group(1))
1478 paths[m.group(1)] = m.group(2)
1479 continue
1480 m = re_url.match(line)
1481 if m:
1482 names.add(m.group(1))
1483 urls[m.group(1)] = m.group(2)
1484 continue
1485 names = sorted(names)
1486 return ([paths.get(name, '') for name in names],
1487 [urls.get(name, '') for name in names])
1488
1489 def git_ls_tree(gitdir, rev, paths):
1490 cmd = ['ls-tree', rev, '--']
1491 cmd.extend(paths)
1492 try:
1493 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1494 bare = True, gitdir = gitdir)
1495 except GitError:
1496 return []
1497 if p.Wait() != 0:
1498 return []
1499 objects = {}
1500 for line in p.stdout.split('\n'):
1501 if not line.strip():
1502 continue
1503 object_rev, object_path = line.split()[2:4]
1504 objects[object_path] = object_rev
1505 return objects
1506
1507 try:
1508 rev = self.GetRevisionId()
1509 except GitError:
1510 return []
1511 return get_submodules(self.gitdir, rev)
1512
1513 def GetDerivedSubprojects(self):
1514 result = []
1515 if not self.Exists:
1516 # If git repo does not exist yet, querying its submodules will
1517 # mess up its states; so return here.
1518 return result
1519 for rev, path, url in self._GetSubmodules():
1520 name = self.manifest.GetSubprojectName(self, path)
1521 project = self.manifest.projects.get(name)
1522 if project:
1523 result.extend(project.GetDerivedSubprojects())
1524 continue
1525 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1526 remote = RemoteSpec(self.remote.name,
1527 url = url,
1528 review = self.remote.review)
1529 subproject = Project(manifest = self.manifest,
1530 name = name,
1531 remote = remote,
1532 gitdir = gitdir,
1533 worktree = worktree,
1534 relpath = relpath,
1535 revisionExpr = self.revisionExpr,
1536 revisionId = rev,
1537 rebase = self.rebase,
1538 groups = self.groups,
1539 sync_c = self.sync_c,
1540 sync_s = self.sync_s,
1541 parent = self,
1542 is_derived = True)
1543 result.append(subproject)
1544 result.extend(subproject.GetDerivedSubprojects())
1545 return result
1546
1547
1373## Direct Git Commands ## 1548## Direct Git Commands ##
1374 1549
1375 def _RemoteFetch(self, name=None, 1550 def _RemoteFetch(self, name=None,
@@ -1382,14 +1557,14 @@ class Project(object):
1382 tag_name = None 1557 tag_name = None
1383 1558
1384 def CheckForSha1(): 1559 def CheckForSha1():
1385 try: 1560 try:
1386 # if revision (sha or tag) is not present then following function 1561 # if revision (sha or tag) is not present then following function
1387 # throws an error. 1562 # throws an error.
1388 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr) 1563 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1389 return True 1564 return True
1390 except GitError: 1565 except GitError:
1391 # There is no such persistent revision. We have to fetch it. 1566 # There is no such persistent revision. We have to fetch it.
1392 return False 1567 return False
1393 1568
1394 if current_branch_only: 1569 if current_branch_only:
1395 if ID_RE.match(self.revisionExpr) is not None: 1570 if ID_RE.match(self.revisionExpr) is not None:
@@ -1571,6 +1746,9 @@ class Project(object):
1571 os.remove(tmpPath) 1746 os.remove(tmpPath)
1572 if 'http_proxy' in os.environ and 'darwin' == sys.platform: 1747 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1573 cmd += ['--proxy', os.environ['http_proxy']] 1748 cmd += ['--proxy', os.environ['http_proxy']]
1749 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
1750 if cookiefile:
1751 cmd += ['--cookie', cookiefile]
1574 cmd += [srcUrl] 1752 cmd += [srcUrl]
1575 1753
1576 if IsTrace(): 1754 if IsTrace():
@@ -1588,7 +1766,8 @@ class Project(object):
1588 # returned another error with the HTTP error code being 400 or above. 1766 # returned another error with the HTTP error code being 400 or above.
1589 # This return code only appears if -f, --fail is used. 1767 # This return code only appears if -f, --fail is used.
1590 if not quiet: 1768 if not quiet:
1591 print >> sys.stderr, "Server does not provide clone.bundle; ignoring." 1769 print("Server does not provide clone.bundle; ignoring.",
1770 file=sys.stderr)
1592 return False 1771 return False
1593 1772
1594 if os.path.exists(tmpPath): 1773 if os.path.exists(tmpPath):
@@ -1836,7 +2015,8 @@ class Project(object):
1836 if p.Wait() == 0: 2015 if p.Wait() == 0:
1837 out = p.stdout 2016 out = p.stdout
1838 if out: 2017 if out:
1839 return out[:-1].split("\0") 2018 return out[:-1].split('\0') # pylint: disable=W1401
2019 # Backslash is not anomalous
1840 return [] 2020 return []
1841 2021
1842 def DiffZ(self, name, *args): 2022 def DiffZ(self, name, *args):
@@ -1852,7 +2032,7 @@ class Project(object):
1852 out = p.process.stdout.read() 2032 out = p.process.stdout.read()
1853 r = {} 2033 r = {}
1854 if out: 2034 if out:
1855 out = iter(out[:-1].split('\0')) 2035 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
1856 while out: 2036 while out:
1857 try: 2037 try:
1858 info = out.next() 2038 info = out.next()
@@ -1879,7 +2059,7 @@ class Project(object):
1879 self.level = self.level[1:] 2059 self.level = self.level[1:]
1880 2060
1881 info = info[1:].split(' ') 2061 info = info[1:].split(' ')
1882 info =_Info(path, *info) 2062 info = _Info(path, *info)
1883 if info.status in ('R', 'C'): 2063 if info.status in ('R', 'C'):
1884 info.src_path = info.path 2064 info.src_path = info.path
1885 info.path = out.next() 2065 info.path = out.next()
@@ -1893,7 +2073,10 @@ class Project(object):
1893 path = os.path.join(self._project.gitdir, HEAD) 2073 path = os.path.join(self._project.gitdir, HEAD)
1894 else: 2074 else:
1895 path = os.path.join(self._project.worktree, '.git', HEAD) 2075 path = os.path.join(self._project.worktree, '.git', HEAD)
1896 fd = open(path, 'rb') 2076 try:
2077 fd = open(path, 'rb')
2078 except IOError:
2079 raise NoManifestException(path)
1897 try: 2080 try:
1898 line = fd.read() 2081 line = fd.read()
1899 finally: 2082 finally:
@@ -1988,6 +2171,9 @@ class Project(object):
1988 raise TypeError('%s() got an unexpected keyword argument %r' 2171 raise TypeError('%s() got an unexpected keyword argument %r'
1989 % (name, k)) 2172 % (name, k))
1990 if config is not None: 2173 if config is not None:
2174 if not git_require((1, 7, 2)):
2175 raise ValueError('cannot set config on command line for %s()'
2176 % name)
1991 for k, v in config.iteritems(): 2177 for k, v in config.iteritems():
1992 cmdv.append('-c') 2178 cmdv.append('-c')
1993 cmdv.append('%s=%s' % (k, v)) 2179 cmdv.append('%s=%s' % (k, v))
diff --git a/repo b/repo
index 70f41572..6b374f72 100755
--- a/repo
+++ b/repo
@@ -1,9 +1,10 @@
1#!/bin/sh 1#!/usr/bin/env python
2 2
3## repo default configuration 3## repo default configuration
4## 4##
5REPO_URL='https://gerrit.googlesource.com/git-repo' 5from __future__ import print_function
6REPO_REV='stable' 6REPO_URL = 'https://gerrit.googlesource.com/git-repo'
7REPO_REV = 'stable'
7 8
8# Copyright (C) 2008 Google Inc. 9# Copyright (C) 2008 Google Inc.
9# 10#
@@ -19,19 +20,11 @@ REPO_REV='stable'
19# See the License for the specific language governing permissions and 20# See the License for the specific language governing permissions and
20# limitations under the License. 21# limitations under the License.
21 22
22magic='--calling-python-from-/bin/sh--'
23"""exec" python -E "$0" "$@" """#$magic"
24if __name__ == '__main__':
25 import sys
26 if sys.argv[-1] == '#%s' % magic:
27 del sys.argv[-1]
28del magic
29
30# increment this whenever we make important changes to this script 23# increment this whenever we make important changes to this script
31VERSION = (1, 18) 24VERSION = (1, 19)
32 25
33# increment this if the MAINTAINER_KEYS block is modified 26# increment this if the MAINTAINER_KEYS block is modified
34KEYRING_VERSION = (1,1) 27KEYRING_VERSION = (1, 1)
35MAINTAINER_KEYS = """ 28MAINTAINER_KEYS = """
36 29
37 Repo Maintainer <repo@android.kernel.org> 30 Repo Maintainer <repo@android.kernel.org>
@@ -110,7 +103,7 @@ V6pfUgqKLWa/aK7/N1ZHnPdFLD8Xt0Dmy4BPwrKC
110""" 103"""
111 104
112GIT = 'git' # our git command 105GIT = 'git' # our git command
113MIN_GIT_VERSION = (1, 5, 4) # minimum supported git version 106MIN_GIT_VERSION = (1, 7, 2) # minimum supported git version
114repodir = '.repo' # name of repo's private directory 107repodir = '.repo' # name of repo's private directory
115S_repo = 'repo' # special repo repository 108S_repo = 'repo' # special repo repository
116S_manifests = 'manifests' # special manifest repository 109S_manifests = 'manifests' # special manifest repository
@@ -120,9 +113,21 @@ REPO_MAIN = S_repo + '/main.py' # main script
120import optparse 113import optparse
121import os 114import os
122import re 115import re
116import stat
123import subprocess 117import subprocess
124import sys 118import sys
125import urllib2 119try:
120 import urllib2
121except ImportError:
122 # For python3
123 import urllib.request
124 import urllib.error
125else:
126 # For python2
127 import imp
128 urllib = imp.new_module('urllib')
129 urllib.request = urllib2
130 urllib.error = urllib2
126 131
127home_dot_repo = os.path.expanduser('~/.repoconfig') 132home_dot_repo = os.path.expanduser('~/.repoconfig')
128gpg_dir = os.path.join(home_dot_repo, 'gnupg') 133gpg_dir = os.path.join(home_dot_repo, 'gnupg')
@@ -149,7 +154,8 @@ group.add_option('-m', '--manifest-name',
149 help='initial manifest file', metavar='NAME.xml') 154 help='initial manifest file', metavar='NAME.xml')
150group.add_option('--mirror', 155group.add_option('--mirror',
151 dest='mirror', action='store_true', 156 dest='mirror', action='store_true',
152 help='mirror the forrest') 157 help='create a replica of the remote repositories '
158 'rather than a client working directory')
153group.add_option('--reference', 159group.add_option('--reference',
154 dest='reference', 160 dest='reference',
155 help='location of mirror directory', metavar='DIR') 161 help='location of mirror directory', metavar='DIR')
@@ -211,17 +217,16 @@ def _Init(args):
211 if branch.startswith('refs/heads/'): 217 if branch.startswith('refs/heads/'):
212 branch = branch[len('refs/heads/'):] 218 branch = branch[len('refs/heads/'):]
213 if branch.startswith('refs/'): 219 if branch.startswith('refs/'):
214 print >>sys.stderr, "fatal: invalid branch name '%s'" % branch 220 print("fatal: invalid branch name '%s'" % branch, file=sys.stderr)
215 raise CloneFailure() 221 raise CloneFailure()
216 222
217 if not os.path.isdir(repodir): 223 if not os.path.isdir(repodir):
218 try: 224 try:
219 os.mkdir(repodir) 225 os.mkdir(repodir)
220 except OSError as e: 226 except OSError as e:
221 print >>sys.stderr, \ 227 print('fatal: cannot make %s directory: %s'
222 'fatal: cannot make %s directory: %s' % ( 228 % (repodir, e.strerror), file=sys.stderr)
223 repodir, e.strerror) 229 # Don't raise CloneFailure; that would delete the
224 # Don't faise CloneFailure; that would delete the
225 # name. Instead exit immediately. 230 # name. Instead exit immediately.
226 # 231 #
227 sys.exit(1) 232 sys.exit(1)
@@ -244,8 +249,8 @@ def _Init(args):
244 _Checkout(dst, branch, rev, opt.quiet) 249 _Checkout(dst, branch, rev, opt.quiet)
245 except CloneFailure: 250 except CloneFailure:
246 if opt.quiet: 251 if opt.quiet:
247 print >>sys.stderr, \ 252 print('fatal: repo init failed; run without --quiet to see why',
248 'fatal: repo init failed; run without --quiet to see why' 253 file=sys.stderr)
249 raise 254 raise
250 255
251 256
@@ -254,12 +259,12 @@ def _CheckGitVersion():
254 try: 259 try:
255 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) 260 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
256 except OSError as e: 261 except OSError as e:
257 print >>sys.stderr 262 print(file=sys.stderr)
258 print >>sys.stderr, "fatal: '%s' is not available" % GIT 263 print("fatal: '%s' is not available" % GIT, file=sys.stderr)
259 print >>sys.stderr, 'fatal: %s' % e 264 print('fatal: %s' % e, file=sys.stderr)
260 print >>sys.stderr 265 print(file=sys.stderr)
261 print >>sys.stderr, 'Please make sure %s is installed'\ 266 print('Please make sure %s is installed and in your path.' % GIT,
262 ' and in your path.' % GIT 267 file=sys.stderr)
263 raise CloneFailure() 268 raise CloneFailure()
264 269
265 ver_str = proc.stdout.read().strip() 270 ver_str = proc.stdout.read().strip()
@@ -267,14 +272,14 @@ def _CheckGitVersion():
267 proc.wait() 272 proc.wait()
268 273
269 if not ver_str.startswith('git version '): 274 if not ver_str.startswith('git version '):
270 print >>sys.stderr, 'error: "%s" unsupported' % ver_str 275 print('error: "%s" unsupported' % ver_str, file=sys.stderr)
271 raise CloneFailure() 276 raise CloneFailure()
272 277
273 ver_str = ver_str[len('git version '):].strip() 278 ver_str = ver_str[len('git version '):].strip()
274 ver_act = tuple(map(lambda x: int(x), ver_str.split('.')[0:3])) 279 ver_act = tuple(map(int, ver_str.split('.')[0:3]))
275 if ver_act < MIN_GIT_VERSION: 280 if ver_act < MIN_GIT_VERSION:
276 need = '.'.join(map(lambda x: str(x), MIN_GIT_VERSION)) 281 need = '.'.join(map(str, MIN_GIT_VERSION))
277 print >>sys.stderr, 'fatal: git %s or later required' % need 282 print('fatal: git %s or later required' % need, file=sys.stderr)
278 raise CloneFailure() 283 raise CloneFailure()
279 284
280 285
@@ -290,7 +295,7 @@ def NeedSetupGnuPG():
290 if not kv: 295 if not kv:
291 return True 296 return True
292 297
293 kv = tuple(map(lambda x: int(x), kv.split('.'))) 298 kv = tuple(map(int, kv.split('.')))
294 if kv < KEYRING_VERSION: 299 if kv < KEYRING_VERSION:
295 return True 300 return True
296 return False 301 return False
@@ -301,18 +306,16 @@ def SetupGnuPG(quiet):
301 try: 306 try:
302 os.mkdir(home_dot_repo) 307 os.mkdir(home_dot_repo)
303 except OSError as e: 308 except OSError as e:
304 print >>sys.stderr, \ 309 print('fatal: cannot make %s directory: %s'
305 'fatal: cannot make %s directory: %s' % ( 310 % (home_dot_repo, e.strerror), file=sys.stderr)
306 home_dot_repo, e.strerror)
307 sys.exit(1) 311 sys.exit(1)
308 312
309 if not os.path.isdir(gpg_dir): 313 if not os.path.isdir(gpg_dir):
310 try: 314 try:
311 os.mkdir(gpg_dir, 0700) 315 os.mkdir(gpg_dir, stat.S_IRWXU)
312 except OSError as e: 316 except OSError as e:
313 print >>sys.stderr, \ 317 print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror),
314 'fatal: cannot make %s directory: %s' % ( 318 file=sys.stderr)
315 gpg_dir, e.strerror)
316 sys.exit(1) 319 sys.exit(1)
317 320
318 env = os.environ.copy() 321 env = os.environ.copy()
@@ -325,21 +328,21 @@ def SetupGnuPG(quiet):
325 stdin = subprocess.PIPE) 328 stdin = subprocess.PIPE)
326 except OSError as e: 329 except OSError as e:
327 if not quiet: 330 if not quiet:
328 print >>sys.stderr, 'warning: gpg (GnuPG) is not available.' 331 print('warning: gpg (GnuPG) is not available.', file=sys.stderr)
329 print >>sys.stderr, 'warning: Installing it is strongly encouraged.' 332 print('warning: Installing it is strongly encouraged.', file=sys.stderr)
330 print >>sys.stderr 333 print(file=sys.stderr)
331 return False 334 return False
332 335
333 proc.stdin.write(MAINTAINER_KEYS) 336 proc.stdin.write(MAINTAINER_KEYS)
334 proc.stdin.close() 337 proc.stdin.close()
335 338
336 if proc.wait() != 0: 339 if proc.wait() != 0:
337 print >>sys.stderr, 'fatal: registering repo maintainer keys failed' 340 print('fatal: registering repo maintainer keys failed', file=sys.stderr)
338 sys.exit(1) 341 sys.exit(1)
339 print 342 print()
340 343
341 fd = open(os.path.join(home_dot_repo, 'keyring-version'), 'w') 344 fd = open(os.path.join(home_dot_repo, 'keyring-version'), 'w')
342 fd.write('.'.join(map(lambda x: str(x), KEYRING_VERSION)) + '\n') 345 fd.write('.'.join(map(str, KEYRING_VERSION)) + '\n')
343 fd.close() 346 fd.close()
344 return True 347 return True
345 348
@@ -355,7 +358,7 @@ def _SetConfig(local, name, value):
355def _InitHttp(): 358def _InitHttp():
356 handlers = [] 359 handlers = []
357 360
358 mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() 361 mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
359 try: 362 try:
360 import netrc 363 import netrc
361 n = netrc.netrc() 364 n = netrc.netrc()
@@ -365,20 +368,20 @@ def _InitHttp():
365 mgr.add_password(p[1], 'https://%s/' % host, p[0], p[2]) 368 mgr.add_password(p[1], 'https://%s/' % host, p[0], p[2])
366 except: 369 except:
367 pass 370 pass
368 handlers.append(urllib2.HTTPBasicAuthHandler(mgr)) 371 handlers.append(urllib.request.HTTPBasicAuthHandler(mgr))
369 handlers.append(urllib2.HTTPDigestAuthHandler(mgr)) 372 handlers.append(urllib.request.HTTPDigestAuthHandler(mgr))
370 373
371 if 'http_proxy' in os.environ: 374 if 'http_proxy' in os.environ:
372 url = os.environ['http_proxy'] 375 url = os.environ['http_proxy']
373 handlers.append(urllib2.ProxyHandler({'http': url, 'https': url})) 376 handlers.append(urllib.request.ProxyHandler({'http': url, 'https': url}))
374 if 'REPO_CURL_VERBOSE' in os.environ: 377 if 'REPO_CURL_VERBOSE' in os.environ:
375 handlers.append(urllib2.HTTPHandler(debuglevel=1)) 378 handlers.append(urllib.request.HTTPHandler(debuglevel=1))
376 handlers.append(urllib2.HTTPSHandler(debuglevel=1)) 379 handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
377 urllib2.install_opener(urllib2.build_opener(*handlers)) 380 urllib.request.install_opener(urllib.request.build_opener(*handlers))
378 381
379def _Fetch(url, local, src, quiet): 382def _Fetch(url, local, src, quiet):
380 if not quiet: 383 if not quiet:
381 print >>sys.stderr, 'Get %s' % url 384 print('Get %s' % url, file=sys.stderr)
382 385
383 cmd = [GIT, 'fetch'] 386 cmd = [GIT, 'fetch']
384 if quiet: 387 if quiet:
@@ -423,20 +426,20 @@ def _DownloadBundle(url, local, quiet):
423 dest = open(os.path.join(local, '.git', 'clone.bundle'), 'w+b') 426 dest = open(os.path.join(local, '.git', 'clone.bundle'), 'w+b')
424 try: 427 try:
425 try: 428 try:
426 r = urllib2.urlopen(url) 429 r = urllib.request.urlopen(url)
427 except urllib2.HTTPError as e: 430 except urllib.error.HTTPError as e:
428 if e.code == 404: 431 if e.code in [403, 404]:
429 return False 432 return False
430 print >>sys.stderr, 'fatal: Cannot get %s' % url 433 print('fatal: Cannot get %s' % url, file=sys.stderr)
431 print >>sys.stderr, 'fatal: HTTP error %s' % e.code 434 print('fatal: HTTP error %s' % e.code, file=sys.stderr)
432 raise CloneFailure() 435 raise CloneFailure()
433 except urllib2.URLError as e: 436 except urllib.error.URLError as e:
434 print >>sys.stderr, 'fatal: Cannot get %s' % url 437 print('fatal: Cannot get %s' % url, file=sys.stderr)
435 print >>sys.stderr, 'fatal: error %s' % e.reason 438 print('fatal: error %s' % e.reason, file=sys.stderr)
436 raise CloneFailure() 439 raise CloneFailure()
437 try: 440 try:
438 if not quiet: 441 if not quiet:
439 print >>sys.stderr, 'Get %s' % url 442 print('Get %s' % url, file=sys.stderr)
440 while True: 443 while True:
441 buf = r.read(8192) 444 buf = r.read(8192)
442 if buf == '': 445 if buf == '':
@@ -460,24 +463,23 @@ def _Clone(url, local, quiet):
460 try: 463 try:
461 os.mkdir(local) 464 os.mkdir(local)
462 except OSError as e: 465 except OSError as e:
463 print >>sys.stderr, \ 466 print('fatal: cannot make %s directory: %s' % (local, e.strerror),
464 'fatal: cannot make %s directory: %s' \ 467 file=sys.stderr)
465 % (local, e.strerror)
466 raise CloneFailure() 468 raise CloneFailure()
467 469
468 cmd = [GIT, 'init', '--quiet'] 470 cmd = [GIT, 'init', '--quiet']
469 try: 471 try:
470 proc = subprocess.Popen(cmd, cwd = local) 472 proc = subprocess.Popen(cmd, cwd = local)
471 except OSError as e: 473 except OSError as e:
472 print >>sys.stderr 474 print(file=sys.stderr)
473 print >>sys.stderr, "fatal: '%s' is not available" % GIT 475 print("fatal: '%s' is not available" % GIT, file=sys.stderr)
474 print >>sys.stderr, 'fatal: %s' % e 476 print('fatal: %s' % e, file=sys.stderr)
475 print >>sys.stderr 477 print(file=sys.stderr)
476 print >>sys.stderr, 'Please make sure %s is installed'\ 478 print('Please make sure %s is installed and in your path.' % GIT,
477 ' and in your path.' % GIT 479 file=sys.stderr)
478 raise CloneFailure() 480 raise CloneFailure()
479 if proc.wait() != 0: 481 if proc.wait() != 0:
480 print >>sys.stderr, 'fatal: could not create %s' % local 482 print('fatal: could not create %s' % local, file=sys.stderr)
481 raise CloneFailure() 483 raise CloneFailure()
482 484
483 _InitHttp() 485 _InitHttp()
@@ -505,21 +507,18 @@ def _Verify(cwd, branch, quiet):
505 proc.stderr.close() 507 proc.stderr.close()
506 508
507 if proc.wait() != 0 or not cur: 509 if proc.wait() != 0 or not cur:
508 print >>sys.stderr 510 print(file=sys.stderr)
509 print >>sys.stderr,\ 511 print("fatal: branch '%s' has not been signed" % branch, file=sys.stderr)
510 "fatal: branch '%s' has not been signed" \
511 % branch
512 raise CloneFailure() 512 raise CloneFailure()
513 513
514 m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur) 514 m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur)
515 if m: 515 if m:
516 cur = m.group(1) 516 cur = m.group(1)
517 if not quiet: 517 if not quiet:
518 print >>sys.stderr 518 print(file=sys.stderr)
519 print >>sys.stderr, \ 519 print("info: Ignoring branch '%s'; using tagged release '%s'"
520 "info: Ignoring branch '%s'; using tagged release '%s'" \ 520 % (branch, cur), file=sys.stderr)
521 % (branch, cur) 521 print(file=sys.stderr)
522 print >>sys.stderr
523 522
524 env = os.environ.copy() 523 env = os.environ.copy()
525 env['GNUPGHOME'] = gpg_dir.encode() 524 env['GNUPGHOME'] = gpg_dir.encode()
@@ -537,10 +536,10 @@ def _Verify(cwd, branch, quiet):
537 proc.stderr.close() 536 proc.stderr.close()
538 537
539 if proc.wait() != 0: 538 if proc.wait() != 0:
540 print >>sys.stderr 539 print(file=sys.stderr)
541 print >>sys.stderr, out 540 print(out, file=sys.stderr)
542 print >>sys.stderr, err 541 print(err, file=sys.stderr)
543 print >>sys.stderr 542 print(file=sys.stderr)
544 raise CloneFailure() 543 raise CloneFailure()
545 return '%s^0' % cur 544 return '%s^0' % cur
546 545
@@ -594,7 +593,7 @@ def _ParseArguments(args):
594 opt = _Options() 593 opt = _Options()
595 arg = [] 594 arg = []
596 595
597 for i in xrange(0, len(args)): 596 for i in range(len(args)):
598 a = args[i] 597 a = args[i]
599 if a == '-h' or a == '--help': 598 if a == '-h' or a == '--help':
600 opt.help = True 599 opt.help = True
@@ -607,7 +606,7 @@ def _ParseArguments(args):
607 606
608 607
609def _Usage(): 608def _Usage():
610 print >>sys.stderr,\ 609 print(
611"""usage: repo COMMAND [ARGS] 610"""usage: repo COMMAND [ARGS]
612 611
613repo is not yet installed. Use "repo init" to install it here. 612repo is not yet installed. Use "repo init" to install it here.
@@ -618,7 +617,7 @@ The most commonly used repo commands are:
618 help Display detailed help on a command 617 help Display detailed help on a command
619 618
620For access to the full online help, install repo ("repo init"). 619For access to the full online help, install repo ("repo init").
621""" 620""", file=sys.stderr)
622 sys.exit(1) 621 sys.exit(1)
623 622
624 623
@@ -628,25 +627,23 @@ def _Help(args):
628 init_optparse.print_help() 627 init_optparse.print_help()
629 sys.exit(0) 628 sys.exit(0)
630 else: 629 else:
631 print >>sys.stderr,\ 630 print("error: '%s' is not a bootstrap command.\n"
632 "error: '%s' is not a bootstrap command.\n"\ 631 ' For access to online help, install repo ("repo init").'
633 ' For access to online help, install repo ("repo init").'\ 632 % args[0], file=sys.stderr)
634 % args[0]
635 else: 633 else:
636 _Usage() 634 _Usage()
637 sys.exit(1) 635 sys.exit(1)
638 636
639 637
640def _NotInstalled(): 638def _NotInstalled():
641 print >>sys.stderr,\ 639 print('error: repo is not installed. Use "repo init" to install it here.',
642'error: repo is not installed. Use "repo init" to install it here.' 640 file=sys.stderr)
643 sys.exit(1) 641 sys.exit(1)
644 642
645 643
646def _NoCommands(cmd): 644def _NoCommands(cmd):
647 print >>sys.stderr,\ 645 print("""error: command '%s' requires repo to be installed first.
648"""error: command '%s' requires repo to be installed first. 646 Use "repo init" to install it here.""" % cmd, file=sys.stderr)
649 Use "repo init" to install it here.""" % cmd
650 sys.exit(1) 647 sys.exit(1)
651 648
652 649
@@ -683,7 +680,7 @@ def _SetDefaultsTo(gitdir):
683 proc.stderr.close() 680 proc.stderr.close()
684 681
685 if proc.wait() != 0: 682 if proc.wait() != 0:
686 print >>sys.stderr, 'fatal: %s has no current branch' % gitdir 683 print('fatal: %s has no current branch' % gitdir, file=sys.stderr)
687 sys.exit(1) 684 sys.exit(1)
688 685
689 686
@@ -721,7 +718,7 @@ def main(orig_args):
721 if my_main: 718 if my_main:
722 repo_main = my_main 719 repo_main = my_main
723 720
724 ver_str = '.'.join(map(lambda x: str(x), VERSION)) 721 ver_str = '.'.join(map(str, VERSION))
725 me = [repo_main, 722 me = [repo_main,
726 '--repo-dir=%s' % rel_repo_dir, 723 '--repo-dir=%s' % rel_repo_dir,
727 '--wrapper-version=%s' % ver_str, 724 '--wrapper-version=%s' % ver_str,
@@ -732,8 +729,8 @@ def main(orig_args):
732 try: 729 try:
733 os.execv(repo_main, me) 730 os.execv(repo_main, me)
734 except OSError as e: 731 except OSError as e:
735 print >>sys.stderr, "fatal: unable to start %s" % repo_main 732 print("fatal: unable to start %s" % repo_main, file=sys.stderr)
736 print >>sys.stderr, "fatal: %s" % e 733 print("fatal: %s" % e, file=sys.stderr)
737 sys.exit(148) 734 sys.exit(148)
738 735
739 736
diff --git a/subcmds/abandon.py b/subcmds/abandon.py
index e17ab2b6..b94ccdd3 100644
--- a/subcmds/abandon.py
+++ b/subcmds/abandon.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import sys 17import sys
17from command import Command 18from command import Command
18from git_command import git 19from git_command import git
@@ -36,7 +37,7 @@ It is equivalent to "git branch -D <branchname>".
36 37
37 nb = args[0] 38 nb = args[0]
38 if not git.check_ref_format('heads/%s' % nb): 39 if not git.check_ref_format('heads/%s' % nb):
39 print >>sys.stderr, "error: '%s' is not a valid name" % nb 40 print("error: '%s' is not a valid name" % nb, file=sys.stderr)
40 sys.exit(1) 41 sys.exit(1)
41 42
42 nb = args[0] 43 nb = args[0]
@@ -58,13 +59,13 @@ It is equivalent to "git branch -D <branchname>".
58 59
59 if err: 60 if err:
60 for p in err: 61 for p in err:
61 print >>sys.stderr,\ 62 print("error: %s/: cannot abandon %s" % (p.relpath, nb),
62 "error: %s/: cannot abandon %s" \ 63 file=sys.stderr)
63 % (p.relpath, nb)
64 sys.exit(1) 64 sys.exit(1)
65 elif not success: 65 elif not success:
66 print >>sys.stderr, 'error: no project has branch %s' % nb 66 print('error: no project has branch %s' % nb, file=sys.stderr)
67 sys.exit(1) 67 sys.exit(1)
68 else: 68 else:
69 print >>sys.stderr, 'Abandoned in %d project(s):\n %s' % ( 69 print('Abandoned in %d project(s):\n %s'
70 len(success), '\n '.join(p.relpath for p in success)) 70 % (len(success), '\n '.join(p.relpath for p in success)),
71 file=sys.stderr)
diff --git a/subcmds/branches.py b/subcmds/branches.py
index a7ba3d6d..06d45abe 100644
--- a/subcmds/branches.py
+++ b/subcmds/branches.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import sys 17import sys
17from color import Coloring 18from color import Coloring
18from command import Command 19from command import Command
@@ -107,7 +108,7 @@ is shown, then the branch appears in all projects.
107 names.sort() 108 names.sort()
108 109
109 if not names: 110 if not names:
110 print >>sys.stderr, ' (no branches)' 111 print(' (no branches)', file=sys.stderr)
111 return 112 return
112 113
113 width = 25 114 width = 25
diff --git a/subcmds/checkout.py b/subcmds/checkout.py
index bfbe9921..cbbca106 100644
--- a/subcmds/checkout.py
+++ b/subcmds/checkout.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import sys 17import sys
17from command import Command 18from command import Command
18from progress import Progress 19from progress import Progress
@@ -55,10 +56,9 @@ The command is equivalent to:
55 56
56 if err: 57 if err:
57 for p in err: 58 for p in err:
58 print >>sys.stderr,\ 59 print("error: %s/: cannot checkout %s" % (p.relpath, nb),
59 "error: %s/: cannot checkout %s" \ 60 file=sys.stderr)
60 % (p.relpath, nb)
61 sys.exit(1) 61 sys.exit(1)
62 elif not success: 62 elif not success:
63 print >>sys.stderr, 'error: no project has branch %s' % nb 63 print('error: no project has branch %s' % nb, file=sys.stderr)
64 sys.exit(1) 64 sys.exit(1)
diff --git a/subcmds/cherry_pick.py b/subcmds/cherry_pick.py
index 7a6d4c20..01b97e07 100644
--- a/subcmds/cherry_pick.py
+++ b/subcmds/cherry_pick.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import re 17import re
17import sys 18import sys
18from command import Command 19from command import Command
@@ -46,13 +47,13 @@ change id will be added.
46 capture_stdout = True, 47 capture_stdout = True,
47 capture_stderr = True) 48 capture_stderr = True)
48 if p.Wait() != 0: 49 if p.Wait() != 0:
49 print >>sys.stderr, p.stderr 50 print(p.stderr, file=sys.stderr)
50 sys.exit(1) 51 sys.exit(1)
51 sha1 = p.stdout.strip() 52 sha1 = p.stdout.strip()
52 53
53 p = GitCommand(None, ['cat-file', 'commit', sha1], capture_stdout=True) 54 p = GitCommand(None, ['cat-file', 'commit', sha1], capture_stdout=True)
54 if p.Wait() != 0: 55 if p.Wait() != 0:
55 print >>sys.stderr, "error: Failed to retrieve old commit message" 56 print("error: Failed to retrieve old commit message", file=sys.stderr)
56 sys.exit(1) 57 sys.exit(1)
57 old_msg = self._StripHeader(p.stdout) 58 old_msg = self._StripHeader(p.stdout)
58 59
@@ -62,8 +63,8 @@ change id will be added.
62 capture_stderr = True) 63 capture_stderr = True)
63 status = p.Wait() 64 status = p.Wait()
64 65
65 print >>sys.stdout, p.stdout 66 print(p.stdout, file=sys.stdout)
66 print >>sys.stderr, p.stderr 67 print(p.stderr, file=sys.stderr)
67 68
68 if status == 0: 69 if status == 0:
69 # The cherry-pick was applied correctly. We just need to edit the 70 # The cherry-pick was applied correctly. We just need to edit the
@@ -76,16 +77,14 @@ change id will be added.
76 capture_stderr = True) 77 capture_stderr = True)
77 p.stdin.write(new_msg) 78 p.stdin.write(new_msg)
78 if p.Wait() != 0: 79 if p.Wait() != 0:
79 print >>sys.stderr, "error: Failed to update commit message" 80 print("error: Failed to update commit message", file=sys.stderr)
80 sys.exit(1) 81 sys.exit(1)
81 82
82 else: 83 else:
83 print >>sys.stderr, """\ 84 print('NOTE: When committing (please see above) and editing the commit'
84NOTE: When committing (please see above) and editing the commit message, 85 'message, please remove the old Change-Id-line and add:')
85please remove the old Change-Id-line and add: 86 print(self._GetReference(sha1), file=sys.stderr)
86""" 87 print(file=sys.stderr)
87 print >>sys.stderr, self._GetReference(sha1)
88 print >>sys.stderr
89 88
90 def _IsChangeId(self, line): 89 def _IsChangeId(self, line):
91 return CHANGE_ID_RE.match(line) 90 return CHANGE_ID_RE.match(line)
diff --git a/subcmds/download.py b/subcmds/download.py
index 0abe90d0..471e88b5 100644
--- a/subcmds/download.py
+++ b/subcmds/download.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import re 17import re
17import sys 18import sys
18 19
@@ -32,13 +33,13 @@ makes it available in your project's local working directory.
32""" 33"""
33 34
34 def _Options(self, p): 35 def _Options(self, p):
35 p.add_option('-c','--cherry-pick', 36 p.add_option('-c', '--cherry-pick',
36 dest='cherrypick', action='store_true', 37 dest='cherrypick', action='store_true',
37 help="cherry-pick instead of checkout") 38 help="cherry-pick instead of checkout")
38 p.add_option('-r','--revert', 39 p.add_option('-r', '--revert',
39 dest='revert', action='store_true', 40 dest='revert', action='store_true',
40 help="revert instead of checkout") 41 help="revert instead of checkout")
41 p.add_option('-f','--ff-only', 42 p.add_option('-f', '--ff-only',
42 dest='ffonly', action='store_true', 43 dest='ffonly', action='store_true',
43 help="force fast-forward merge") 44 help="force fast-forward merge")
44 45
@@ -68,23 +69,23 @@ makes it available in your project's local working directory.
68 for project, change_id, ps_id in self._ParseChangeIds(args): 69 for project, change_id, ps_id in self._ParseChangeIds(args):
69 dl = project.DownloadPatchSet(change_id, ps_id) 70 dl = project.DownloadPatchSet(change_id, ps_id)
70 if not dl: 71 if not dl:
71 print >>sys.stderr, \ 72 print('[%s] change %d/%d not found'
72 '[%s] change %d/%d not found' \ 73 % (project.name, change_id, ps_id),
73 % (project.name, change_id, ps_id) 74 file=sys.stderr)
74 sys.exit(1) 75 sys.exit(1)
75 76
76 if not opt.revert and not dl.commits: 77 if not opt.revert and not dl.commits:
77 print >>sys.stderr, \ 78 print('[%s] change %d/%d has already been merged'
78 '[%s] change %d/%d has already been merged' \ 79 % (project.name, change_id, ps_id),
79 % (project.name, change_id, ps_id) 80 file=sys.stderr)
80 continue 81 continue
81 82
82 if len(dl.commits) > 1: 83 if len(dl.commits) > 1:
83 print >>sys.stderr, \ 84 print('[%s] %d/%d depends on %d unmerged changes:' \
84 '[%s] %d/%d depends on %d unmerged changes:' \ 85 % (project.name, change_id, ps_id, len(dl.commits)),
85 % (project.name, change_id, ps_id, len(dl.commits)) 86 file=sys.stderr)
86 for c in dl.commits: 87 for c in dl.commits:
87 print >>sys.stderr, ' %s' % (c) 88 print(' %s' % (c), file=sys.stderr)
88 if opt.cherrypick: 89 if opt.cherrypick:
89 project._CherryPick(dl.commit) 90 project._CherryPick(dl.commit)
90 elif opt.revert: 91 elif opt.revert:
diff --git a/subcmds/forall.py b/subcmds/forall.py
index b633b7d4..4c1c9ff8 100644
--- a/subcmds/forall.py
+++ b/subcmds/forall.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import fcntl 17import fcntl
17import re 18import re
18import os 19import os
@@ -92,6 +93,9 @@ following <command>.
92 93
93Unless -p is used, stdin, stdout, stderr are inherited from the 94Unless -p is used, stdin, stdout, stderr are inherited from the
94terminal and are not redirected. 95terminal and are not redirected.
96
97If -e is used, when a command exits unsuccessfully, '%prog' will abort
98without iterating through the remaining projects.
95""" 99"""
96 100
97 def _Options(self, p): 101 def _Options(self, p):
@@ -104,6 +108,9 @@ terminal and are not redirected.
104 dest='command', 108 dest='command',
105 action='callback', 109 action='callback',
106 callback=cmd) 110 callback=cmd)
111 p.add_option('-e', '--abort-on-errors',
112 dest='abort_on_errors', action='store_true',
113 help='Abort if a command exits unsuccessfully')
107 114
108 g = p.add_option_group('Output') 115 g = p.add_option_group('Output')
109 g.add_option('-p', 116 g.add_option('-p',
@@ -183,7 +190,7 @@ terminal and are not redirected.
183 if not os.path.exists(cwd): 190 if not os.path.exists(cwd):
184 if (opt.project_header and opt.verbose) \ 191 if (opt.project_header and opt.verbose) \
185 or not opt.project_header: 192 or not opt.project_header:
186 print >>sys.stderr, 'skipping %s/' % project.relpath 193 print('skipping %s/' % project.relpath, file=sys.stderr)
187 continue 194 continue
188 195
189 if opt.project_header: 196 if opt.project_header:
@@ -254,7 +261,12 @@ terminal and are not redirected.
254 s.dest.flush() 261 s.dest.flush()
255 262
256 r = p.wait() 263 r = p.wait()
257 if r != 0 and r != rc: 264 if r != 0:
258 rc = r 265 if r != rc:
266 rc = r
267 if opt.abort_on_errors:
268 print("error: %s: Aborting due to previous error" % project.relpath,
269 file=sys.stderr)
270 sys.exit(r)
259 if rc != 0: 271 if rc != 0:
260 sys.exit(rc) 272 sys.exit(rc)
diff --git a/subcmds/grep.py b/subcmds/grep.py
index 0dc8f9f6..dd391cfa 100644
--- a/subcmds/grep.py
+++ b/subcmds/grep.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import sys 17import sys
17from color import Coloring 18from color import Coloring
18from command import PagedCommand 19from command import PagedCommand
@@ -51,7 +52,7 @@ Examples
51 52
52Look for a line that has '#define' and either 'MAX_PATH or 'PATH_MAX': 53Look for a line that has '#define' and either 'MAX_PATH or 'PATH_MAX':
53 54
54 repo grep -e '#define' --and -\( -e MAX_PATH -e PATH_MAX \) 55 repo grep -e '#define' --and -\\( -e MAX_PATH -e PATH_MAX \\)
55 56
56Look for a line that has 'NODE' or 'Unexpected' in files that 57Look for a line that has 'NODE' or 'Unexpected' in files that
57contain a line that matches both expressions: 58contain a line that matches both expressions:
@@ -84,7 +85,7 @@ contain a line that matches both expressions:
84 g.add_option('--cached', 85 g.add_option('--cached',
85 action='callback', callback=carry, 86 action='callback', callback=carry,
86 help='Search the index, instead of the work tree') 87 help='Search the index, instead of the work tree')
87 g.add_option('-r','--revision', 88 g.add_option('-r', '--revision',
88 dest='revision', action='append', metavar='TREEish', 89 dest='revision', action='append', metavar='TREEish',
89 help='Search TREEish, instead of the work tree') 90 help='Search TREEish, instead of the work tree')
90 91
@@ -96,7 +97,7 @@ contain a line that matches both expressions:
96 g.add_option('-i', '--ignore-case', 97 g.add_option('-i', '--ignore-case',
97 action='callback', callback=carry, 98 action='callback', callback=carry,
98 help='Ignore case differences') 99 help='Ignore case differences')
99 g.add_option('-a','--text', 100 g.add_option('-a', '--text',
100 action='callback', callback=carry, 101 action='callback', callback=carry,
101 help="Process binary files as if they were text") 102 help="Process binary files as if they were text")
102 g.add_option('-I', 103 g.add_option('-I',
@@ -125,7 +126,7 @@ contain a line that matches both expressions:
125 g.add_option('--and', '--or', '--not', 126 g.add_option('--and', '--or', '--not',
126 action='callback', callback=carry, 127 action='callback', callback=carry,
127 help='Boolean operators to combine patterns') 128 help='Boolean operators to combine patterns')
128 g.add_option('-(','-)', 129 g.add_option('-(', '-)',
129 action='callback', callback=carry, 130 action='callback', callback=carry,
130 help='Boolean operator grouping') 131 help='Boolean operator grouping')
131 132
@@ -145,10 +146,10 @@ contain a line that matches both expressions:
145 action='callback', callback=carry, 146 action='callback', callback=carry,
146 metavar='CONTEXT', type='str', 147 metavar='CONTEXT', type='str',
147 help='Show CONTEXT lines after match') 148 help='Show CONTEXT lines after match')
148 g.add_option('-l','--name-only','--files-with-matches', 149 g.add_option('-l', '--name-only', '--files-with-matches',
149 action='callback', callback=carry, 150 action='callback', callback=carry,
150 help='Show only file names containing matching lines') 151 help='Show only file names containing matching lines')
151 g.add_option('-L','--files-without-match', 152 g.add_option('-L', '--files-without-match',
152 action='callback', callback=carry, 153 action='callback', callback=carry,
153 help='Show only file names not containing matching lines') 154 help='Show only file names not containing matching lines')
154 155
@@ -157,9 +158,9 @@ contain a line that matches both expressions:
157 out = GrepColoring(self.manifest.manifestProject.config) 158 out = GrepColoring(self.manifest.manifestProject.config)
158 159
159 cmd_argv = ['grep'] 160 cmd_argv = ['grep']
160 if out.is_on and git_require((1,6,3)): 161 if out.is_on and git_require((1, 6, 3)):
161 cmd_argv.append('--color') 162 cmd_argv.append('--color')
162 cmd_argv.extend(getattr(opt,'cmd_argv',[])) 163 cmd_argv.extend(getattr(opt, 'cmd_argv', []))
163 164
164 if '-e' not in cmd_argv: 165 if '-e' not in cmd_argv:
165 if not args: 166 if not args:
@@ -178,8 +179,7 @@ contain a line that matches both expressions:
178 have_rev = False 179 have_rev = False
179 if opt.revision: 180 if opt.revision:
180 if '--cached' in cmd_argv: 181 if '--cached' in cmd_argv:
181 print >>sys.stderr,\ 182 print('fatal: cannot combine --cached and --revision', file=sys.stderr)
182 'fatal: cannot combine --cached and --revision'
183 sys.exit(1) 183 sys.exit(1)
184 have_rev = True 184 have_rev = True
185 cmd_argv.extend(opt.revision) 185 cmd_argv.extend(opt.revision)
@@ -230,13 +230,13 @@ contain a line that matches both expressions:
230 out.nl() 230 out.nl()
231 else: 231 else:
232 for line in r: 232 for line in r:
233 print line 233 print(line)
234 234
235 if have_match: 235 if have_match:
236 sys.exit(0) 236 sys.exit(0)
237 elif have_rev and bad_rev: 237 elif have_rev and bad_rev:
238 for r in opt.revision: 238 for r in opt.revision:
239 print >>sys.stderr, "error: can't search revision %s" % r 239 print("error: can't search revision %s" % r, file=sys.stderr)
240 sys.exit(1) 240 sys.exit(1)
241 else: 241 else:
242 sys.exit(1) 242 sys.exit(1)
diff --git a/subcmds/help.py b/subcmds/help.py
index 375d04d2..15aab7f9 100644
--- a/subcmds/help.py
+++ b/subcmds/help.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import re 17import re
17import sys 18import sys
18from formatter import AbstractFormatter, DumbWriter 19from formatter import AbstractFormatter, DumbWriter
@@ -31,10 +32,8 @@ Displays detailed usage information about a command.
31""" 32"""
32 33
33 def _PrintAllCommands(self): 34 def _PrintAllCommands(self):
34 print 'usage: repo COMMAND [ARGS]' 35 print('usage: repo COMMAND [ARGS]')
35 print """ 36 print('The complete list of recognized repo commands are:')
36The complete list of recognized repo commands are:
37"""
38 commandNames = self.commands.keys() 37 commandNames = self.commands.keys()
39 commandNames.sort() 38 commandNames.sort()
40 39
@@ -49,17 +48,14 @@ The complete list of recognized repo commands are:
49 summary = command.helpSummary.strip() 48 summary = command.helpSummary.strip()
50 except AttributeError: 49 except AttributeError:
51 summary = '' 50 summary = ''
52 print fmt % (name, summary) 51 print(fmt % (name, summary))
53 print """ 52 print("See 'repo help <command>' for more information on a"
54See 'repo help <command>' for more information on a specific command. 53 'specific command.')
55"""
56 54
57 def _PrintCommonCommands(self): 55 def _PrintCommonCommands(self):
58 print 'usage: repo COMMAND [ARGS]' 56 print('usage: repo COMMAND [ARGS]')
59 print """ 57 print('The most commonly used repo commands are:')
60The most commonly used repo commands are: 58 commandNames = [name
61"""
62 commandNames = [name
63 for name in self.commands.keys() 59 for name in self.commands.keys()
64 if self.commands[name].common] 60 if self.commands[name].common]
65 commandNames.sort() 61 commandNames.sort()
@@ -75,11 +71,10 @@ The most commonly used repo commands are:
75 summary = command.helpSummary.strip() 71 summary = command.helpSummary.strip()
76 except AttributeError: 72 except AttributeError:
77 summary = '' 73 summary = ''
78 print fmt % (name, summary) 74 print(fmt % (name, summary))
79 print """ 75 print(
80See 'repo help <command>' for more information on a specific command. 76"See 'repo help <command>' for more information on a specific command.\n"
81See 'repo help --all' for a complete list of recognized commands. 77"See 'repo help --all' for a complete list of recognized commands.")
82"""
83 78
84 def _PrintCommandHelp(self, cmd): 79 def _PrintCommandHelp(self, cmd):
85 class _Out(Coloring): 80 class _Out(Coloring):
@@ -131,7 +126,7 @@ See 'repo help --all' for a complete list of recognized commands.
131 126
132 p('%s', title) 127 p('%s', title)
133 self.nl() 128 self.nl()
134 p('%s', ''.ljust(len(title),section_type[0])) 129 p('%s', ''.ljust(len(title), section_type[0]))
135 self.nl() 130 self.nl()
136 continue 131 continue
137 132
@@ -162,7 +157,7 @@ See 'repo help --all' for a complete list of recognized commands.
162 try: 157 try:
163 cmd = self.commands[name] 158 cmd = self.commands[name]
164 except KeyError: 159 except KeyError:
165 print >>sys.stderr, "repo: '%s' is not a repo command." % name 160 print("repo: '%s' is not a repo command." % name, file=sys.stderr)
166 sys.exit(1) 161 sys.exit(1)
167 162
168 cmd.manifest = self.manifest 163 cmd.manifest = self.manifest
diff --git a/subcmds/info.py b/subcmds/info.py
new file mode 100644
index 00000000..a6eba889
--- /dev/null
+++ b/subcmds/info.py
@@ -0,0 +1,195 @@
1#
2# Copyright (C) 2012 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
16from command import PagedCommand
17from color import Coloring
18from error import NoSuchProjectError
19from git_refs import R_M
20
21class _Coloring(Coloring):
22 def __init__(self, config):
23 Coloring.__init__(self, config, "status")
24
25class Info(PagedCommand):
26 common = True
27 helpSummary = "Get info on the manifest branch, current branch or unmerged branches"
28 helpUsage = "%prog [-dl] [-o [-b]] [<project>...]"
29
30 def _Options(self, p, show_smart=True):
31 p.add_option('-d', '--diff',
32 dest='all', action='store_true',
33 help="show full info and commit diff including remote branches")
34 p.add_option('-o', '--overview',
35 dest='overview', action='store_true',
36 help='show overview of all local commits')
37 p.add_option('-b', '--current-branch',
38 dest="current_branch", action="store_true",
39 help="consider only checked out branches")
40 p.add_option('-l', '--local-only',
41 dest="local", action="store_true",
42 help="Disable all remote operations")
43
44
45 def Execute(self, opt, args):
46 self.out = _Coloring(self.manifest.globalConfig)
47 self.heading = self.out.printer('heading', attr = 'bold')
48 self.headtext = self.out.printer('headtext', fg = 'yellow')
49 self.redtext = self.out.printer('redtext', fg = 'red')
50 self.sha = self.out.printer("sha", fg = 'yellow')
51 self.text = self.out.printer('text')
52 self.dimtext = self.out.printer('dimtext', attr = 'dim')
53
54 self.opt = opt
55
56 mergeBranch = self.manifest.manifestProject.config.GetBranch("default").merge
57
58 self.heading("Manifest branch: ")
59 self.headtext(self.manifest.default.revisionExpr)
60 self.out.nl()
61 self.heading("Manifest merge branch: ")
62 self.headtext(mergeBranch)
63 self.out.nl()
64
65 self.printSeparator()
66
67 if not opt.overview:
68 self.printDiffInfo(args)
69 else:
70 self.printCommitOverview(args)
71
72 def printSeparator(self):
73 self.text("----------------------------")
74 self.out.nl()
75
76 def printDiffInfo(self, args):
77 try:
78 projs = self.GetProjects(args)
79 except NoSuchProjectError:
80 return
81
82 for p in projs:
83 self.heading("Project: ")
84 self.headtext(p.name)
85 self.out.nl()
86
87 self.heading("Mount path: ")
88 self.headtext(p.worktree)
89 self.out.nl()
90
91 self.heading("Current revision: ")
92 self.headtext(p.revisionExpr)
93 self.out.nl()
94
95 localBranches = p.GetBranches().keys()
96 self.heading("Local Branches: ")
97 self.redtext(str(len(localBranches)))
98 if len(localBranches) > 0:
99 self.text(" [")
100 self.text(", ".join(localBranches))
101 self.text("]")
102 self.out.nl()
103
104 if self.opt.all:
105 self.findRemoteLocalDiff(p)
106
107 self.printSeparator()
108
109 def findRemoteLocalDiff(self, project):
110 #Fetch all the latest commits
111 if not self.opt.local:
112 project.Sync_NetworkHalf(quiet=True, current_branch_only=True)
113
114 logTarget = R_M + self.manifest.manifestProject.config.GetBranch("default").merge
115
116 bareTmp = project.bare_git._bare
117 project.bare_git._bare = False
118 localCommits = project.bare_git.rev_list(
119 '--abbrev=8',
120 '--abbrev-commit',
121 '--pretty=oneline',
122 logTarget + "..",
123 '--')
124
125 originCommits = project.bare_git.rev_list(
126 '--abbrev=8',
127 '--abbrev-commit',
128 '--pretty=oneline',
129 ".." + logTarget,
130 '--')
131 project.bare_git._bare = bareTmp
132
133 self.heading("Local Commits: ")
134 self.redtext(str(len(localCommits)))
135 self.dimtext(" (on current branch)")
136 self.out.nl()
137
138 for c in localCommits:
139 split = c.split()
140 self.sha(split[0] + " ")
141 self.text(" ".join(split[1:]))
142 self.out.nl()
143
144 self.printSeparator()
145
146 self.heading("Remote Commits: ")
147 self.redtext(str(len(originCommits)))
148 self.out.nl()
149
150 for c in originCommits:
151 split = c.split()
152 self.sha(split[0] + " ")
153 self.text(" ".join(split[1:]))
154 self.out.nl()
155
156 def printCommitOverview(self, args):
157 all_branches = []
158 for project in self.GetProjects(args):
159 br = [project.GetUploadableBranch(x)
160 for x in project.GetBranches().keys()]
161 br = [x for x in br if x]
162 if self.opt.current_branch:
163 br = [x for x in br if x.name == project.CurrentBranch]
164 all_branches.extend(br)
165
166 if not all_branches:
167 return
168
169 self.out.nl()
170 self.heading('Projects Overview')
171 project = None
172
173 for branch in all_branches:
174 if project != branch.project:
175 project = branch.project
176 self.out.nl()
177 self.headtext(project.relpath)
178 self.out.nl()
179
180 commits = branch.commits
181 date = branch.date
182 self.text('%s %-33s (%2d commit%s, %s)' % (
183 branch.name == project.CurrentBranch and '*' or ' ',
184 branch.name,
185 len(commits),
186 len(commits) != 1 and 's' or '',
187 date))
188 self.out.nl()
189
190 for commit in commits:
191 split = commit.split()
192 self.text('{0:38}{1} '.format('','-'))
193 self.sha(split[0] + " ")
194 self.text(" ".join(split[1:]))
195 self.out.nl()
diff --git a/subcmds/init.py b/subcmds/init.py
index b6b98076..11312601 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import os 17import os
17import platform 18import platform
18import re 19import re
@@ -117,18 +118,22 @@ to update the working directory files.
117 dest='config_name', action="store_true", default=False, 118 dest='config_name', action="store_true", default=False,
118 help='Always prompt for name/e-mail') 119 help='Always prompt for name/e-mail')
119 120
121 def _RegisteredEnvironmentOptions(self):
122 return {'REPO_MANIFEST_URL': 'manifest_url',
123 'REPO_MIRROR_LOCATION': 'reference'}
124
120 def _SyncManifest(self, opt): 125 def _SyncManifest(self, opt):
121 m = self.manifest.manifestProject 126 m = self.manifest.manifestProject
122 is_new = not m.Exists 127 is_new = not m.Exists
123 128
124 if is_new: 129 if is_new:
125 if not opt.manifest_url: 130 if not opt.manifest_url:
126 print >>sys.stderr, 'fatal: manifest url (-u) is required.' 131 print('fatal: manifest url (-u) is required.', file=sys.stderr)
127 sys.exit(1) 132 sys.exit(1)
128 133
129 if not opt.quiet: 134 if not opt.quiet:
130 print >>sys.stderr, 'Get %s' \ 135 print('Get %s' % GitConfig.ForUser().UrlInsteadOf(opt.manifest_url),
131 % GitConfig.ForUser().UrlInsteadOf(opt.manifest_url) 136 file=sys.stderr)
132 m._InitGitDir() 137 m._InitGitDir()
133 138
134 if opt.manifest_branch: 139 if opt.manifest_branch:
@@ -147,7 +152,7 @@ to update the working directory files.
147 r.ResetFetch() 152 r.ResetFetch()
148 r.Save() 153 r.Save()
149 154
150 groups = re.split('[,\s]+', opt.groups) 155 groups = re.split(r'[,\s]+', opt.groups)
151 all_platforms = ['linux', 'darwin'] 156 all_platforms = ['linux', 'darwin']
152 platformize = lambda x: 'platform-' + x 157 platformize = lambda x: 'platform-' + x
153 if opt.platform == 'auto': 158 if opt.platform == 'auto':
@@ -159,7 +164,7 @@ to update the working directory files.
159 elif opt.platform in all_platforms: 164 elif opt.platform in all_platforms:
160 groups.extend(platformize(opt.platform)) 165 groups.extend(platformize(opt.platform))
161 elif opt.platform != 'none': 166 elif opt.platform != 'none':
162 print >>sys.stderr, 'fatal: invalid platform flag' 167 print('fatal: invalid platform flag', file=sys.stderr)
163 sys.exit(1) 168 sys.exit(1)
164 169
165 groups = [x for x in groups if x] 170 groups = [x for x in groups if x]
@@ -175,12 +180,15 @@ to update the working directory files.
175 if is_new: 180 if is_new:
176 m.config.SetString('repo.mirror', 'true') 181 m.config.SetString('repo.mirror', 'true')
177 else: 182 else:
178 print >>sys.stderr, 'fatal: --mirror not supported on existing client' 183 print('fatal: --mirror is only supported when initializing a new '
184 'workspace.', file=sys.stderr)
185 print('Either delete the .repo folder in this workspace, or initialize '
186 'in another location.', file=sys.stderr)
179 sys.exit(1) 187 sys.exit(1)
180 188
181 if not m.Sync_NetworkHalf(is_new=is_new): 189 if not m.Sync_NetworkHalf(is_new=is_new):
182 r = m.GetRemote(m.remote.name) 190 r = m.GetRemote(m.remote.name)
183 print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url 191 print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
184 192
185 # Better delete the manifest git dir if we created it; otherwise next 193 # Better delete the manifest git dir if we created it; otherwise next
186 # time (when user fixes problems) we won't go through the "is_new" logic. 194 # time (when user fixes problems) we won't go through the "is_new" logic.
@@ -197,19 +205,19 @@ to update the working directory files.
197 205
198 if is_new or m.CurrentBranch is None: 206 if is_new or m.CurrentBranch is None:
199 if not m.StartBranch('default'): 207 if not m.StartBranch('default'):
200 print >>sys.stderr, 'fatal: cannot create default in manifest' 208 print('fatal: cannot create default in manifest', file=sys.stderr)
201 sys.exit(1) 209 sys.exit(1)
202 210
203 def _LinkManifest(self, name): 211 def _LinkManifest(self, name):
204 if not name: 212 if not name:
205 print >>sys.stderr, 'fatal: manifest name (-m) is required.' 213 print('fatal: manifest name (-m) is required.', file=sys.stderr)
206 sys.exit(1) 214 sys.exit(1)
207 215
208 try: 216 try:
209 self.manifest.Link(name) 217 self.manifest.Link(name)
210 except ManifestParseError as e: 218 except ManifestParseError as e:
211 print >>sys.stderr, "fatal: manifest '%s' not available" % name 219 print("fatal: manifest '%s' not available" % name, file=sys.stderr)
212 print >>sys.stderr, 'fatal: %s' % str(e) 220 print('fatal: %s' % str(e), file=sys.stderr)
213 sys.exit(1) 221 sys.exit(1)
214 222
215 def _Prompt(self, prompt, value): 223 def _Prompt(self, prompt, value):
@@ -231,24 +239,24 @@ to update the working directory files.
231 mp.config.SetString('user.name', gc.GetString('user.name')) 239 mp.config.SetString('user.name', gc.GetString('user.name'))
232 mp.config.SetString('user.email', gc.GetString('user.email')) 240 mp.config.SetString('user.email', gc.GetString('user.email'))
233 241
234 print '' 242 print()
235 print 'Your identity is: %s <%s>' % (mp.config.GetString('user.name'), 243 print('Your identity is: %s <%s>' % (mp.config.GetString('user.name'),
236 mp.config.GetString('user.email')) 244 mp.config.GetString('user.email')))
237 print 'If you want to change this, please re-run \'repo init\' with --config-name' 245 print('If you want to change this, please re-run \'repo init\' with --config-name')
238 return False 246 return False
239 247
240 def _ConfigureUser(self): 248 def _ConfigureUser(self):
241 mp = self.manifest.manifestProject 249 mp = self.manifest.manifestProject
242 250
243 while True: 251 while True:
244 print '' 252 print()
245 name = self._Prompt('Your Name', mp.UserName) 253 name = self._Prompt('Your Name', mp.UserName)
246 email = self._Prompt('Your Email', mp.UserEmail) 254 email = self._Prompt('Your Email', mp.UserEmail)
247 255
248 print '' 256 print()
249 print 'Your identity is: %s <%s>' % (name, email) 257 print('Your identity is: %s <%s>' % (name, email))
250 sys.stdout.write('is this correct [y/N]? ') 258 sys.stdout.write('is this correct [y/N]? ')
251 a = sys.stdin.readline().strip() 259 a = sys.stdin.readline().strip().lower()
252 if a in ('yes', 'y', 't', 'true'): 260 if a in ('yes', 'y', 't', 'true'):
253 break 261 break
254 262
@@ -274,17 +282,17 @@ to update the working directory files.
274 self._on = True 282 self._on = True
275 out = _Test() 283 out = _Test()
276 284
277 print '' 285 print()
278 print "Testing colorized output (for 'repo diff', 'repo status'):" 286 print("Testing colorized output (for 'repo diff', 'repo status'):")
279 287
280 for c in ['black','red','green','yellow','blue','magenta','cyan']: 288 for c in ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan']:
281 out.write(' ') 289 out.write(' ')
282 out.printer(fg=c)(' %-6s ', c) 290 out.printer(fg=c)(' %-6s ', c)
283 out.write(' ') 291 out.write(' ')
284 out.printer(fg='white', bg='black')(' %s ' % 'white') 292 out.printer(fg='white', bg='black')(' %s ' % 'white')
285 out.nl() 293 out.nl()
286 294
287 for c in ['bold','dim','ul','reverse']: 295 for c in ['bold', 'dim', 'ul', 'reverse']:
288 out.write(' ') 296 out.write(' ')
289 out.printer(fg='black', attr=c)(' %-6s ', c) 297 out.printer(fg='black', attr=c)(' %-6s ', c)
290 out.nl() 298 out.nl()
@@ -313,6 +321,23 @@ to update the working directory files.
313 # We store the depth in the main manifest project. 321 # We store the depth in the main manifest project.
314 self.manifest.manifestProject.config.SetString('repo.depth', depth) 322 self.manifest.manifestProject.config.SetString('repo.depth', depth)
315 323
324 def _DisplayResult(self):
325 if self.manifest.IsMirror:
326 init_type = 'mirror '
327 else:
328 init_type = ''
329
330 print()
331 print('repo %shas been initialized in %s'
332 % (init_type, self.manifest.topdir))
333
334 current_dir = os.getcwd()
335 if current_dir != self.manifest.topdir:
336 print('If this is not the directory in which you want to initialize '
337 'repo, please run:')
338 print(' rm -r %s/.repo' % self.manifest.topdir)
339 print('and try again.')
340
316 def Execute(self, opt, args): 341 def Execute(self, opt, args):
317 git_require(MIN_GIT_VERSION, fail=True) 342 git_require(MIN_GIT_VERSION, fail=True)
318 343
@@ -329,10 +354,4 @@ to update the working directory files.
329 354
330 self._ConfigureDepth(opt) 355 self._ConfigureDepth(opt)
331 356
332 if self.manifest.IsMirror: 357 self._DisplayResult()
333 init_type = 'mirror '
334 else:
335 init_type = ''
336
337 print ''
338 print 'repo %sinitialized in %s' % (init_type, self.manifest.topdir)
diff --git a/subcmds/list.py b/subcmds/list.py
index 6058a755..0d5c27f7 100644
--- a/subcmds/list.py
+++ b/subcmds/list.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import re 17import re
17 18
18from command import Command, MirrorSafeCommand 19from command import Command, MirrorSafeCommand
@@ -64,7 +65,7 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
64 lines.append("%s : %s" % (_getpath(project), project.name)) 65 lines.append("%s : %s" % (_getpath(project), project.name))
65 66
66 lines.sort() 67 lines.sort()
67 print '\n'.join(lines) 68 print('\n'.join(lines))
68 69
69 def FindProjects(self, args): 70 def FindProjects(self, args):
70 result = [] 71 result = []
diff --git a/subcmds/manifest.py b/subcmds/manifest.py
index 5592a37d..5ceeb12f 100644
--- a/subcmds/manifest.py
+++ b/subcmds/manifest.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import os 17import os
17import sys 18import sys
18 19
@@ -69,7 +70,7 @@ in a Git repository for use during future 'repo init' invocations.
69 peg_rev_upstream = opt.peg_rev_upstream) 70 peg_rev_upstream = opt.peg_rev_upstream)
70 fd.close() 71 fd.close()
71 if opt.output_file != '-': 72 if opt.output_file != '-':
72 print >>sys.stderr, 'Saved manifest to %s' % opt.output_file 73 print('Saved manifest to %s' % opt.output_file, file=sys.stderr)
73 74
74 def Execute(self, opt, args): 75 def Execute(self, opt, args):
75 if args: 76 if args:
@@ -79,6 +80,6 @@ in a Git repository for use during future 'repo init' invocations.
79 self._Output(opt) 80 self._Output(opt)
80 return 81 return
81 82
82 print >>sys.stderr, 'error: no operation to perform' 83 print('error: no operation to perform', file=sys.stderr)
83 print >>sys.stderr, 'error: see repo help manifest' 84 print('error: see repo help manifest', file=sys.stderr)
84 sys.exit(1) 85 sys.exit(1)
diff --git a/subcmds/overview.py b/subcmds/overview.py
index a509bd9a..418459ae 100644
--- a/subcmds/overview.py
+++ b/subcmds/overview.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16from color import Coloring 17from color import Coloring
17from command import PagedCommand 18from command import PagedCommand
18 19
@@ -54,8 +55,11 @@ are displayed.
54 def __init__(self, config): 55 def __init__(self, config):
55 Coloring.__init__(self, config, 'status') 56 Coloring.__init__(self, config, 'status')
56 self.project = self.printer('header', attr='bold') 57 self.project = self.printer('header', attr='bold')
58 self.text = self.printer('text')
57 59
58 out = Report(all_branches[0].project.config) 60 out = Report(all_branches[0].project.config)
61 out.text("Deprecated. See repo info -o.")
62 out.nl()
59 out.project('Projects Overview') 63 out.project('Projects Overview')
60 out.nl() 64 out.nl()
61 65
@@ -70,11 +74,11 @@ are displayed.
70 74
71 commits = branch.commits 75 commits = branch.commits
72 date = branch.date 76 date = branch.date
73 print '%s %-33s (%2d commit%s, %s)' % ( 77 print('%s %-33s (%2d commit%s, %s)' % (
74 branch.name == project.CurrentBranch and '*' or ' ', 78 branch.name == project.CurrentBranch and '*' or ' ',
75 branch.name, 79 branch.name,
76 len(commits), 80 len(commits),
77 len(commits) != 1 and 's' or ' ', 81 len(commits) != 1 and 's' or ' ',
78 date) 82 date))
79 for commit in commits: 83 for commit in commits:
80 print '%-35s - %s' % ('', commit) 84 print('%-35s - %s' % ('', commit))
diff --git a/subcmds/prune.py b/subcmds/prune.py
index c50a5507..39c571a4 100644
--- a/subcmds/prune.py
+++ b/subcmds/prune.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16from color import Coloring 17from color import Coloring
17from command import PagedCommand 18from command import PagedCommand
18 19
@@ -51,9 +52,9 @@ class Prune(PagedCommand):
51 52
52 commits = branch.commits 53 commits = branch.commits
53 date = branch.date 54 date = branch.date
54 print '%s %-33s (%2d commit%s, %s)' % ( 55 print('%s %-33s (%2d commit%s, %s)' % (
55 branch.name == project.CurrentBranch and '*' or ' ', 56 branch.name == project.CurrentBranch and '*' or ' ',
56 branch.name, 57 branch.name,
57 len(commits), 58 len(commits),
58 len(commits) != 1 and 's' or ' ', 59 len(commits) != 1 and 's' or ' ',
59 date) 60 date))
diff --git a/subcmds/rebase.py b/subcmds/rebase.py
index a8d58cdb..06cda22c 100644
--- a/subcmds/rebase.py
+++ b/subcmds/rebase.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import sys 17import sys
17 18
18from command import Command 19from command import Command
@@ -59,14 +60,16 @@ branch but need to incorporate new upstream changes "underneath" them.
59 one_project = len(all_projects) == 1 60 one_project = len(all_projects) == 1
60 61
61 if opt.interactive and not one_project: 62 if opt.interactive and not one_project:
62 print >>sys.stderr, 'error: interactive rebase not supported with multiple projects' 63 print('error: interactive rebase not supported with multiple projects',
64 file=sys.stderr)
63 return -1 65 return -1
64 66
65 for project in all_projects: 67 for project in all_projects:
66 cb = project.CurrentBranch 68 cb = project.CurrentBranch
67 if not cb: 69 if not cb:
68 if one_project: 70 if one_project:
69 print >>sys.stderr, "error: project %s has a detatched HEAD" % project.relpath 71 print("error: project %s has a detatched HEAD" % project.relpath,
72 file=sys.stderr)
70 return -1 73 return -1
71 # ignore branches with detatched HEADs 74 # ignore branches with detatched HEADs
72 continue 75 continue
@@ -74,7 +77,8 @@ branch but need to incorporate new upstream changes "underneath" them.
74 upbranch = project.GetBranch(cb) 77 upbranch = project.GetBranch(cb)
75 if not upbranch.LocalMerge: 78 if not upbranch.LocalMerge:
76 if one_project: 79 if one_project:
77 print >>sys.stderr, "error: project %s does not track any remote branches" % project.relpath 80 print("error: project %s does not track any remote branches"
81 % project.relpath, file=sys.stderr)
78 return -1 82 return -1
79 # ignore branches without remotes 83 # ignore branches without remotes
80 continue 84 continue
@@ -101,8 +105,8 @@ branch but need to incorporate new upstream changes "underneath" them.
101 105
102 args.append(upbranch.LocalMerge) 106 args.append(upbranch.LocalMerge)
103 107
104 print >>sys.stderr, '# %s: rebasing %s -> %s' % \ 108 print('# %s: rebasing %s -> %s'
105 (project.relpath, cb, upbranch.LocalMerge) 109 % (project.relpath, cb, upbranch.LocalMerge), file=sys.stderr)
106 110
107 needs_stash = False 111 needs_stash = False
108 if opt.auto_stash: 112 if opt.auto_stash:
diff --git a/subcmds/selfupdate.py b/subcmds/selfupdate.py
index 46aa3a19..d12e08d0 100644
--- a/subcmds/selfupdate.py
+++ b/subcmds/selfupdate.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16from optparse import SUPPRESS_HELP 17from optparse import SUPPRESS_HELP
17import sys 18import sys
18 19
@@ -52,7 +53,7 @@ need to be performed by an end-user.
52 53
53 else: 54 else:
54 if not rp.Sync_NetworkHalf(): 55 if not rp.Sync_NetworkHalf():
55 print >>sys.stderr, "error: can't update repo" 56 print("error: can't update repo", file=sys.stderr)
56 sys.exit(1) 57 sys.exit(1)
57 58
58 rp.bare_git.gc('--auto') 59 rp.bare_git.gc('--auto')
diff --git a/subcmds/stage.py b/subcmds/stage.py
index 2ec48069..ff15ee0c 100644
--- a/subcmds/stage.py
+++ b/subcmds/stage.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import sys 17import sys
17 18
18from color import Coloring 19from color import Coloring
@@ -50,7 +51,7 @@ The '%prog' command stages files to prepare the next commit.
50 def _Interactive(self, opt, args): 51 def _Interactive(self, opt, args):
51 all_projects = filter(lambda x: x.IsDirty(), self.GetProjects(args)) 52 all_projects = filter(lambda x: x.IsDirty(), self.GetProjects(args))
52 if not all_projects: 53 if not all_projects:
53 print >>sys.stderr,'no projects have uncommitted modifications' 54 print('no projects have uncommitted modifications', file=sys.stderr)
54 return 55 return
55 56
56 out = _ProjectList(self.manifest.manifestProject.config) 57 out = _ProjectList(self.manifest.manifestProject.config)
@@ -58,7 +59,7 @@ The '%prog' command stages files to prepare the next commit.
58 out.header(' %s', 'project') 59 out.header(' %s', 'project')
59 out.nl() 60 out.nl()
60 61
61 for i in xrange(0, len(all_projects)): 62 for i in range(len(all_projects)):
62 p = all_projects[i] 63 p = all_projects[i]
63 out.write('%3d: %s', i + 1, p.relpath + '/') 64 out.write('%3d: %s', i + 1, p.relpath + '/')
64 out.nl() 65 out.nl()
@@ -101,7 +102,7 @@ The '%prog' command stages files to prepare the next commit.
101 if len(p) == 1: 102 if len(p) == 1:
102 _AddI(p[0]) 103 _AddI(p[0])
103 continue 104 continue
104 print 'Bye.' 105 print('Bye.')
105 106
106def _AddI(project): 107def _AddI(project):
107 p = GitCommand(project, ['add', '--interactive'], bare=False) 108 p = GitCommand(project, ['add', '--interactive'], bare=False)
diff --git a/subcmds/start.py b/subcmds/start.py
index be645314..2d723fc2 100644
--- a/subcmds/start.py
+++ b/subcmds/start.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import sys 17import sys
17from command import Command 18from command import Command
18from git_config import IsId 19from git_config import IsId
@@ -41,7 +42,7 @@ revision specified in the manifest.
41 42
42 nb = args[0] 43 nb = args[0]
43 if not git.check_ref_format('heads/%s' % nb): 44 if not git.check_ref_format('heads/%s' % nb):
44 print >>sys.stderr, "error: '%s' is not a valid name" % nb 45 print("error: '%s' is not a valid name" % nb, file=sys.stderr)
45 sys.exit(1) 46 sys.exit(1)
46 47
47 err = [] 48 err = []
@@ -49,7 +50,7 @@ revision specified in the manifest.
49 if not opt.all: 50 if not opt.all:
50 projects = args[1:] 51 projects = args[1:]
51 if len(projects) < 1: 52 if len(projects) < 1:
52 print >>sys.stderr, "error: at least one project must be specified" 53 print("error: at least one project must be specified", file=sys.stderr)
53 sys.exit(1) 54 sys.exit(1)
54 55
55 all_projects = self.GetProjects(projects) 56 all_projects = self.GetProjects(projects)
@@ -67,7 +68,6 @@ revision specified in the manifest.
67 68
68 if err: 69 if err:
69 for p in err: 70 for p in err:
70 print >>sys.stderr,\ 71 print("error: %s/: cannot start %s" % (p.relpath, nb),
71 "error: %s/: cannot start %s" \ 72 file=sys.stderr)
72 % (p.relpath, nb)
73 sys.exit(1) 73 sys.exit(1)
diff --git a/subcmds/status.py b/subcmds/status.py
index 7611621e..cce00c81 100644
--- a/subcmds/status.py
+++ b/subcmds/status.py
@@ -20,10 +20,14 @@ try:
20except ImportError: 20except ImportError:
21 import dummy_threading as _threading 21 import dummy_threading as _threading
22 22
23import glob
23import itertools 24import itertools
25import os
24import sys 26import sys
25import StringIO 27import StringIO
26 28
29from color import Coloring
30
27class Status(PagedCommand): 31class Status(PagedCommand):
28 common = True 32 common = True
29 helpSummary = "Show the working tree status" 33 helpSummary = "Show the working tree status"
@@ -39,6 +43,13 @@ is a difference between these three states.
39The -j/--jobs option can be used to run multiple status queries 43The -j/--jobs option can be used to run multiple status queries
40in parallel. 44in parallel.
41 45
46The -o/--orphans option can be used to show objects that are in
47the working directory, but not associated with a repo project.
48This includes unmanaged top-level files and directories, but also
49includes deeper items. For example, if dir/subdir/proj1 and
50dir/subdir/proj2 are repo projects, dir/subdir/proj3 will be shown
51if it is not known to repo.
52
42Status Display 53Status Display
43-------------- 54--------------
44 55
@@ -76,6 +87,9 @@ the following meanings:
76 p.add_option('-j', '--jobs', 87 p.add_option('-j', '--jobs',
77 dest='jobs', action='store', type='int', default=2, 88 dest='jobs', action='store', type='int', default=2,
78 help="number of projects to check simultaneously") 89 help="number of projects to check simultaneously")
90 p.add_option('-o', '--orphans',
91 dest='orphans', action='store_true',
92 help="include objects in working directory outside of repo projects")
79 93
80 def _StatusHelper(self, project, clean_counter, sem, output): 94 def _StatusHelper(self, project, clean_counter, sem, output):
81 """Obtains the status for a specific project. 95 """Obtains the status for a specific project.
@@ -97,6 +111,22 @@ the following meanings:
97 finally: 111 finally:
98 sem.release() 112 sem.release()
99 113
114 def _FindOrphans(self, dirs, proj_dirs, proj_dirs_parents, outstring):
115 """find 'dirs' that are present in 'proj_dirs_parents' but not in 'proj_dirs'"""
116 status_header = ' --\t'
117 for item in dirs:
118 if not os.path.isdir(item):
119 outstring.write(''.join([status_header, item]))
120 continue
121 if item in proj_dirs:
122 continue
123 if item in proj_dirs_parents:
124 self._FindOrphans(glob.glob('%s/.*' % item) + \
125 glob.glob('%s/*' % item), \
126 proj_dirs, proj_dirs_parents, outstring)
127 continue
128 outstring.write(''.join([status_header, item, '/']))
129
100 def Execute(self, opt, args): 130 def Execute(self, opt, args):
101 all_projects = self.GetProjects(args) 131 all_projects = self.GetProjects(args)
102 counter = itertools.count() 132 counter = itertools.count()
@@ -129,4 +159,46 @@ the following meanings:
129 output.dump(sys.stdout) 159 output.dump(sys.stdout)
130 output.close() 160 output.close()
131 if len(all_projects) == counter.next(): 161 if len(all_projects) == counter.next():
132 print 'nothing to commit (working directory clean)' 162 print('nothing to commit (working directory clean)')
163
164 if opt.orphans:
165 proj_dirs = set()
166 proj_dirs_parents = set()
167 for project in self.GetProjects(None, missing_ok=True):
168 proj_dirs.add(project.relpath)
169 (head, _tail) = os.path.split(project.relpath)
170 while head != "":
171 proj_dirs_parents.add(head)
172 (head, _tail) = os.path.split(head)
173 proj_dirs.add('.repo')
174
175 class StatusColoring(Coloring):
176 def __init__(self, config):
177 Coloring.__init__(self, config, 'status')
178 self.project = self.printer('header', attr = 'bold')
179 self.untracked = self.printer('untracked', fg = 'red')
180
181 orig_path = os.getcwd()
182 try:
183 os.chdir(self.manifest.topdir)
184
185 outstring = StringIO.StringIO()
186 self._FindOrphans(glob.glob('.*') + \
187 glob.glob('*'), \
188 proj_dirs, proj_dirs_parents, outstring)
189
190 if outstring.buflist:
191 output = StatusColoring(self.manifest.globalConfig)
192 output.project('Objects not within a project (orphans)')
193 output.nl()
194 for entry in outstring.buflist:
195 output.untracked(entry)
196 output.nl()
197 else:
198 print('No orphan files or directories')
199
200 outstring.close()
201
202 finally:
203 # Restore CWD.
204 os.chdir(orig_path)
diff --git a/subcmds/sync.py b/subcmds/sync.py
index d4637d0c..228a279a 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import netrc 17import netrc
17from optparse import SUPPRESS_HELP 18from optparse import SUPPRESS_HELP
18import os 19import os
@@ -44,13 +45,13 @@ try:
44except ImportError: 45except ImportError:
45 multiprocessing = None 46 multiprocessing = None
46 47
47from git_command import GIT 48from git_command import GIT, git_require
48from git_refs import R_HEADS, HEAD 49from git_refs import R_HEADS, HEAD
49from main import WrapperModule 50from main import WrapperModule
50from project import Project 51from project import Project
51from project import RemoteSpec 52from project import RemoteSpec
52from command import Command, MirrorSafeCommand 53from command import Command, MirrorSafeCommand
53from error import RepoChangedException, GitError 54from error import RepoChangedException, GitError, ManifestParseError
54from project import SyncBuffer 55from project import SyncBuffer
55from progress import Progress 56from progress import Progress
56 57
@@ -113,6 +114,9 @@ resumeable bundle file on a content delivery network. This
113may be necessary if there are problems with the local Python 114may be necessary if there are problems with the local Python
114HTTP client or proxy configuration, but the Git binary works. 115HTTP client or proxy configuration, but the Git binary works.
115 116
117The --fetch-submodules option enables fetching Git submodules
118of a project from server.
119
116SSH Connections 120SSH Connections
117--------------- 121---------------
118 122
@@ -144,27 +148,30 @@ later is required to fix a server side protocol bug.
144""" 148"""
145 149
146 def _Options(self, p, show_smart=True): 150 def _Options(self, p, show_smart=True):
147 self.jobs = self.manifest.default.sync_j 151 try:
152 self.jobs = self.manifest.default.sync_j
153 except ManifestParseError:
154 self.jobs = 1
148 155
149 p.add_option('-f', '--force-broken', 156 p.add_option('-f', '--force-broken',
150 dest='force_broken', action='store_true', 157 dest='force_broken', action='store_true',
151 help="continue sync even if a project fails to sync") 158 help="continue sync even if a project fails to sync")
152 p.add_option('-l','--local-only', 159 p.add_option('-l', '--local-only',
153 dest='local_only', action='store_true', 160 dest='local_only', action='store_true',
154 help="only update working tree, don't fetch") 161 help="only update working tree, don't fetch")
155 p.add_option('-n','--network-only', 162 p.add_option('-n', '--network-only',
156 dest='network_only', action='store_true', 163 dest='network_only', action='store_true',
157 help="fetch only, don't update working tree") 164 help="fetch only, don't update working tree")
158 p.add_option('-d','--detach', 165 p.add_option('-d', '--detach',
159 dest='detach_head', action='store_true', 166 dest='detach_head', action='store_true',
160 help='detach projects back to manifest revision') 167 help='detach projects back to manifest revision')
161 p.add_option('-c','--current-branch', 168 p.add_option('-c', '--current-branch',
162 dest='current_branch_only', action='store_true', 169 dest='current_branch_only', action='store_true',
163 help='fetch only current branch from server') 170 help='fetch only current branch from server')
164 p.add_option('-q','--quiet', 171 p.add_option('-q', '--quiet',
165 dest='quiet', action='store_true', 172 dest='quiet', action='store_true',
166 help='be more quiet') 173 help='be more quiet')
167 p.add_option('-j','--jobs', 174 p.add_option('-j', '--jobs',
168 dest='jobs', action='store', type='int', 175 dest='jobs', action='store', type='int',
169 help="projects to fetch simultaneously (default %d)" % self.jobs) 176 help="projects to fetch simultaneously (default %d)" % self.jobs)
170 p.add_option('-m', '--manifest-name', 177 p.add_option('-m', '--manifest-name',
@@ -173,6 +180,15 @@ later is required to fix a server side protocol bug.
173 p.add_option('--no-clone-bundle', 180 p.add_option('--no-clone-bundle',
174 dest='no_clone_bundle', action='store_true', 181 dest='no_clone_bundle', action='store_true',
175 help='disable use of /clone.bundle on HTTP/HTTPS') 182 help='disable use of /clone.bundle on HTTP/HTTPS')
183 p.add_option('-u', '--manifest-server-username', action='store',
184 dest='manifest_server_username',
185 help='username to authenticate with the manifest server')
186 p.add_option('-p', '--manifest-server-password', action='store',
187 dest='manifest_server_password',
188 help='password to authenticate with the manifest server')
189 p.add_option('--fetch-submodules',
190 dest='fetch_submodules', action='store_true',
191 help='fetch submodules from server')
176 if show_smart: 192 if show_smart:
177 p.add_option('-s', '--smart-sync', 193 p.add_option('-s', '--smart-sync',
178 dest='smart_sync', action='store_true', 194 dest='smart_sync', action='store_true',
@@ -180,12 +196,6 @@ later is required to fix a server side protocol bug.
180 p.add_option('-t', '--smart-tag', 196 p.add_option('-t', '--smart-tag',
181 dest='smart_tag', action='store', 197 dest='smart_tag', action='store',
182 help='smart sync using manifest from a known tag') 198 help='smart sync using manifest from a known tag')
183 p.add_option('-u', '--manifest-server-username', action='store',
184 dest='manifest_server_username',
185 help='username to authenticate with the manifest server')
186 p.add_option('-p', '--manifest-server-password', action='store',
187 dest='manifest_server_password',
188 help='password to authenticate with the manifest server')
189 199
190 g = p.add_option_group('repo Version options') 200 g = p.add_option_group('repo Version options')
191 g.add_option('--no-repo-verify', 201 g.add_option('--no-repo-verify',
@@ -196,61 +206,62 @@ later is required to fix a server side protocol bug.
196 help=SUPPRESS_HELP) 206 help=SUPPRESS_HELP)
197 207
198 def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event): 208 def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event):
199 """Main function of the fetch threads when jobs are > 1. 209 """Main function of the fetch threads when jobs are > 1.
200 210
201 Args: 211 Args:
202 opt: Program options returned from optparse. See _Options(). 212 opt: Program options returned from optparse. See _Options().
203 project: Project object for the project to fetch. 213 project: Project object for the project to fetch.
204 lock: Lock for accessing objects that are shared amongst multiple 214 lock: Lock for accessing objects that are shared amongst multiple
205 _FetchHelper() threads. 215 _FetchHelper() threads.
206 fetched: set object that we will add project.gitdir to when we're done 216 fetched: set object that we will add project.gitdir to when we're done
207 (with our lock held). 217 (with our lock held).
208 pm: Instance of a Project object. We will call pm.update() (with our 218 pm: Instance of a Project object. We will call pm.update() (with our
209 lock held). 219 lock held).
210 sem: We'll release() this semaphore when we exit so that another thread 220 sem: We'll release() this semaphore when we exit so that another thread
211 can be started up. 221 can be started up.
212 err_event: We'll set this event in the case of an error (after printing 222 err_event: We'll set this event in the case of an error (after printing
213 out info about the error). 223 out info about the error).
214 """ 224 """
215 # We'll set to true once we've locked the lock. 225 # We'll set to true once we've locked the lock.
216 did_lock = False 226 did_lock = False
217 227
218 # Encapsulate everything in a try/except/finally so that: 228 # Encapsulate everything in a try/except/finally so that:
219 # - We always set err_event in the case of an exception. 229 # - We always set err_event in the case of an exception.
220 # - We always make sure we call sem.release(). 230 # - We always make sure we call sem.release().
221 # - We always make sure we unlock the lock if we locked it. 231 # - We always make sure we unlock the lock if we locked it.
232 try:
222 try: 233 try:
223 try: 234 start = time.time()
224 start = time.time() 235 success = project.Sync_NetworkHalf(
225 success = project.Sync_NetworkHalf( 236 quiet=opt.quiet,
226 quiet=opt.quiet, 237 current_branch_only=opt.current_branch_only,
227 current_branch_only=opt.current_branch_only, 238 clone_bundle=not opt.no_clone_bundle)
228 clone_bundle=not opt.no_clone_bundle) 239 self._fetch_times.Set(project, time.time() - start)
229 self._fetch_times.Set(project, time.time() - start) 240
230 241 # Lock around all the rest of the code, since printing, updating a set
231 # Lock around all the rest of the code, since printing, updating a set 242 # and Progress.update() are not thread safe.
232 # and Progress.update() are not thread safe. 243 lock.acquire()
233 lock.acquire() 244 did_lock = True
234 did_lock = True 245
235 246 if not success:
236 if not success: 247 print('error: Cannot fetch %s' % project.name, file=sys.stderr)
237 print >>sys.stderr, 'error: Cannot fetch %s' % project.name 248 if opt.force_broken:
238 if opt.force_broken: 249 print('warn: --force-broken, continuing to sync',
239 print >>sys.stderr, 'warn: --force-broken, continuing to sync' 250 file=sys.stderr)
240 else: 251 else:
241 raise _FetchError() 252 raise _FetchError()
242 253
243 fetched.add(project.gitdir) 254 fetched.add(project.gitdir)
244 pm.update() 255 pm.update()
245 except _FetchError: 256 except _FetchError:
246 err_event.set() 257 err_event.set()
247 except: 258 except:
248 err_event.set() 259 err_event.set()
249 raise 260 raise
250 finally: 261 finally:
251 if did_lock: 262 if did_lock:
252 lock.release() 263 lock.release()
253 sem.release() 264 sem.release()
254 265
255 def _Fetch(self, projects, opt): 266 def _Fetch(self, projects, opt):
256 fetched = set() 267 fetched = set()
@@ -265,9 +276,9 @@ later is required to fix a server side protocol bug.
265 clone_bundle=not opt.no_clone_bundle): 276 clone_bundle=not opt.no_clone_bundle):
266 fetched.add(project.gitdir) 277 fetched.add(project.gitdir)
267 else: 278 else:
268 print >>sys.stderr, 'error: Cannot fetch %s' % project.name 279 print('error: Cannot fetch %s' % project.name, file=sys.stderr)
269 if opt.force_broken: 280 if opt.force_broken:
270 print >>sys.stderr, 'warn: --force-broken, continuing to sync' 281 print('warn: --force-broken, continuing to sync', file=sys.stderr)
271 else: 282 else:
272 sys.exit(1) 283 sys.exit(1)
273 else: 284 else:
@@ -300,7 +311,7 @@ later is required to fix a server side protocol bug.
300 311
301 # If we saw an error, exit with code 1 so that other scripts can check. 312 # If we saw an error, exit with code 1 so that other scripts can check.
302 if err_event.isSet(): 313 if err_event.isSet():
303 print >>sys.stderr, '\nerror: Exited sync due to fetch errors' 314 print('\nerror: Exited sync due to fetch errors', file=sys.stderr)
304 sys.exit(1) 315 sys.exit(1)
305 316
306 pm.end() 317 pm.end()
@@ -310,7 +321,8 @@ later is required to fix a server side protocol bug.
310 return fetched 321 return fetched
311 322
312 def _GCProjects(self, projects): 323 def _GCProjects(self, projects):
313 if multiprocessing: 324 has_dash_c = git_require((1, 7, 2))
325 if multiprocessing and has_dash_c:
314 cpu_count = multiprocessing.cpu_count() 326 cpu_count = multiprocessing.cpu_count()
315 else: 327 else:
316 cpu_count = 1 328 cpu_count = 1
@@ -352,7 +364,7 @@ later is required to fix a server side protocol bug.
352 t.join() 364 t.join()
353 365
354 if err_event.isSet(): 366 if err_event.isSet():
355 print >>sys.stderr, '\nerror: Exited sync due to gc errors' 367 print('\nerror: Exited sync due to gc errors', file=sys.stderr)
356 sys.exit(1) 368 sys.exit(1)
357 369
358 def UpdateProjectList(self): 370 def UpdateProjectList(self):
@@ -376,34 +388,36 @@ later is required to fix a server side protocol bug.
376 if path not in new_project_paths: 388 if path not in new_project_paths:
377 # If the path has already been deleted, we don't need to do it 389 # If the path has already been deleted, we don't need to do it
378 if os.path.exists(self.manifest.topdir + '/' + path): 390 if os.path.exists(self.manifest.topdir + '/' + path):
379 project = Project( 391 project = Project(
380 manifest = self.manifest, 392 manifest = self.manifest,
381 name = path, 393 name = path,
382 remote = RemoteSpec('origin'), 394 remote = RemoteSpec('origin'),
383 gitdir = os.path.join(self.manifest.topdir, 395 gitdir = os.path.join(self.manifest.topdir,
384 path, '.git'), 396 path, '.git'),
385 worktree = os.path.join(self.manifest.topdir, path), 397 worktree = os.path.join(self.manifest.topdir, path),
386 relpath = path, 398 relpath = path,
387 revisionExpr = 'HEAD', 399 revisionExpr = 'HEAD',
388 revisionId = None, 400 revisionId = None,
389 groups = None) 401 groups = None)
390 402
391 if project.IsDirty(): 403 if project.IsDirty():
392 print >>sys.stderr, 'error: Cannot remove project "%s": \ 404 print('error: Cannot remove project "%s": uncommitted changes'
393uncommitted changes are present' % project.relpath 405 'are present' % project.relpath, file=sys.stderr)
394 print >>sys.stderr, ' commit changes, then run sync again' 406 print(' commit changes, then run sync again',
395 return -1 407 file=sys.stderr)
396 else: 408 return -1
397 print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree 409 else:
398 shutil.rmtree(project.worktree) 410 print('Deleting obsolete path %s' % project.worktree,
399 # Try deleting parent subdirs if they are empty 411 file=sys.stderr)
400 project_dir = os.path.dirname(project.worktree) 412 shutil.rmtree(project.worktree)
401 while project_dir != self.manifest.topdir: 413 # Try deleting parent subdirs if they are empty
402 try: 414 project_dir = os.path.dirname(project.worktree)
403 os.rmdir(project_dir) 415 while project_dir != self.manifest.topdir:
404 except OSError: 416 try:
405 break 417 os.rmdir(project_dir)
406 project_dir = os.path.dirname(project_dir) 418 except OSError:
419 break
420 project_dir = os.path.dirname(project_dir)
407 421
408 new_project_paths.sort() 422 new_project_paths.sort()
409 fd = open(file_path, 'w') 423 fd = open(file_path, 'w')
@@ -422,24 +436,24 @@ uncommitted changes are present' % project.relpath
422 self.jobs = min(self.jobs, (soft_limit - 5) / 3) 436 self.jobs = min(self.jobs, (soft_limit - 5) / 3)
423 437
424 if opt.network_only and opt.detach_head: 438 if opt.network_only and opt.detach_head:
425 print >>sys.stderr, 'error: cannot combine -n and -d' 439 print('error: cannot combine -n and -d', file=sys.stderr)
426 sys.exit(1) 440 sys.exit(1)
427 if opt.network_only and opt.local_only: 441 if opt.network_only and opt.local_only:
428 print >>sys.stderr, 'error: cannot combine -n and -l' 442 print('error: cannot combine -n and -l', file=sys.stderr)
429 sys.exit(1) 443 sys.exit(1)
430 if opt.manifest_name and opt.smart_sync: 444 if opt.manifest_name and opt.smart_sync:
431 print >>sys.stderr, 'error: cannot combine -m and -s' 445 print('error: cannot combine -m and -s', file=sys.stderr)
432 sys.exit(1) 446 sys.exit(1)
433 if opt.manifest_name and opt.smart_tag: 447 if opt.manifest_name and opt.smart_tag:
434 print >>sys.stderr, 'error: cannot combine -m and -t' 448 print('error: cannot combine -m and -t', file=sys.stderr)
435 sys.exit(1) 449 sys.exit(1)
436 if opt.manifest_server_username or opt.manifest_server_password: 450 if opt.manifest_server_username or opt.manifest_server_password:
437 if not (opt.smart_sync or opt.smart_tag): 451 if not (opt.smart_sync or opt.smart_tag):
438 print >>sys.stderr, 'error: -u and -p may only be combined with ' \ 452 print('error: -u and -p may only be combined with -s or -t',
439 '-s or -t' 453 file=sys.stderr)
440 sys.exit(1) 454 sys.exit(1)
441 if None in [opt.manifest_server_username, opt.manifest_server_password]: 455 if None in [opt.manifest_server_username, opt.manifest_server_password]:
442 print >>sys.stderr, 'error: both -u and -p must be given' 456 print('error: both -u and -p must be given', file=sys.stderr)
443 sys.exit(1) 457 sys.exit(1)
444 458
445 if opt.manifest_name: 459 if opt.manifest_name:
@@ -447,8 +461,8 @@ uncommitted changes are present' % project.relpath
447 461
448 if opt.smart_sync or opt.smart_tag: 462 if opt.smart_sync or opt.smart_tag:
449 if not self.manifest.manifest_server: 463 if not self.manifest.manifest_server:
450 print >>sys.stderr, \ 464 print('error: cannot smart sync: no manifest server defined in'
451 'error: cannot smart sync: no manifest server defined in manifest' 465 'manifest', file=sys.stderr)
452 sys.exit(1) 466 sys.exit(1)
453 467
454 manifest_server = self.manifest.manifest_server 468 manifest_server = self.manifest.manifest_server
@@ -463,7 +477,8 @@ uncommitted changes are present' % project.relpath
463 try: 477 try:
464 info = netrc.netrc() 478 info = netrc.netrc()
465 except IOError: 479 except IOError:
466 print >>sys.stderr, '.netrc file does not exist or could not be opened' 480 print('.netrc file does not exist or could not be opened',
481 file=sys.stderr)
467 else: 482 else:
468 try: 483 try:
469 parse_result = urlparse.urlparse(manifest_server) 484 parse_result = urlparse.urlparse(manifest_server)
@@ -473,10 +488,10 @@ uncommitted changes are present' % project.relpath
473 except TypeError: 488 except TypeError:
474 # TypeError is raised when the given hostname is not present 489 # TypeError is raised when the given hostname is not present
475 # in the .netrc file. 490 # in the .netrc file.
476 print >>sys.stderr, 'No credentials found for %s in .netrc' % \ 491 print('No credentials found for %s in .netrc'
477 parse_result.hostname 492 % parse_result.hostname, file=sys.stderr)
478 except netrc.NetrcParseError as e: 493 except netrc.NetrcParseError as e:
479 print >>sys.stderr, 'Error parsing .netrc file: %s' % e 494 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
480 495
481 if (username and password): 496 if (username and password):
482 manifest_server = manifest_server.replace('://', '://%s:%s@' % 497 manifest_server = manifest_server.replace('://', '://%s:%s@' %
@@ -515,20 +530,21 @@ uncommitted changes are present' % project.relpath
515 finally: 530 finally:
516 f.close() 531 f.close()
517 except IOError: 532 except IOError:
518 print >>sys.stderr, 'error: cannot write manifest to %s' % \ 533 print('error: cannot write manifest to %s' % manifest_path,
519 manifest_path 534 file=sys.stderr)
520 sys.exit(1) 535 sys.exit(1)
521 self.manifest.Override(manifest_name) 536 self.manifest.Override(manifest_name)
522 else: 537 else:
523 print >>sys.stderr, 'error: %s' % manifest_str 538 print('error: %s' % manifest_str, file=sys.stderr)
524 sys.exit(1) 539 sys.exit(1)
525 except (socket.error, IOError, xmlrpclib.Fault) as e: 540 except (socket.error, IOError, xmlrpclib.Fault) as e:
526 print >>sys.stderr, 'error: cannot connect to manifest server %s:\n%s' % ( 541 print('error: cannot connect to manifest server %s:\n%s'
527 self.manifest.manifest_server, e) 542 % (self.manifest.manifest_server, e), file=sys.stderr)
528 sys.exit(1) 543 sys.exit(1)
529 except xmlrpclib.ProtocolError as e: 544 except xmlrpclib.ProtocolError as e:
530 print >>sys.stderr, 'error: cannot connect to manifest server %s:\n%d %s' % ( 545 print('error: cannot connect to manifest server %s:\n%d %s'
531 self.manifest.manifest_server, e.errcode, e.errmsg) 546 % (self.manifest.manifest_server, e.errcode, e.errmsg),
547 file=sys.stderr)
532 sys.exit(1) 548 sys.exit(1)
533 549
534 rp = self.manifest.repoProject 550 rp = self.manifest.repoProject
@@ -552,7 +568,9 @@ uncommitted changes are present' % project.relpath
552 self.manifest._Unload() 568 self.manifest._Unload()
553 if opt.jobs is None: 569 if opt.jobs is None:
554 self.jobs = self.manifest.default.sync_j 570 self.jobs = self.manifest.default.sync_j
555 all_projects = self.GetProjects(args, missing_ok=True) 571 all_projects = self.GetProjects(args,
572 missing_ok=True,
573 submodules_ok=opt.fetch_submodules)
556 574
557 self._fetch_times = _FetchTimes(self.manifest) 575 self._fetch_times = _FetchTimes(self.manifest)
558 if not opt.local_only: 576 if not opt.local_only:
@@ -563,12 +581,33 @@ uncommitted changes are present' % project.relpath
563 to_fetch.extend(all_projects) 581 to_fetch.extend(all_projects)
564 to_fetch.sort(key=self._fetch_times.Get, reverse=True) 582 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
565 583
566 self._Fetch(to_fetch, opt) 584 fetched = self._Fetch(to_fetch, opt)
567 _PostRepoFetch(rp, opt.no_repo_verify) 585 _PostRepoFetch(rp, opt.no_repo_verify)
568 if opt.network_only: 586 if opt.network_only:
569 # bail out now; the rest touches the working tree 587 # bail out now; the rest touches the working tree
570 return 588 return
571 589
590 # Iteratively fetch missing and/or nested unregistered submodules
591 previously_missing_set = set()
592 while True:
593 self.manifest._Unload()
594 all_projects = self.GetProjects(args,
595 missing_ok=True,
596 submodules_ok=opt.fetch_submodules)
597 missing = []
598 for project in all_projects:
599 if project.gitdir not in fetched:
600 missing.append(project)
601 if not missing:
602 break
603 # Stop us from non-stopped fetching actually-missing repos: If set of
604 # missing repos has not been changed from last fetch, we break.
605 missing_set = set(p.name for p in missing)
606 if previously_missing_set == missing_set:
607 break
608 previously_missing_set = missing_set
609 fetched.update(self._Fetch(missing, opt))
610
572 if self.manifest.IsMirror: 611 if self.manifest.IsMirror:
573 # bail out now, we have no working tree 612 # bail out now, we have no working tree
574 return 613 return
@@ -584,14 +623,14 @@ uncommitted changes are present' % project.relpath
584 if project.worktree: 623 if project.worktree:
585 project.Sync_LocalHalf(syncbuf) 624 project.Sync_LocalHalf(syncbuf)
586 pm.end() 625 pm.end()
587 print >>sys.stderr 626 print(file=sys.stderr)
588 if not syncbuf.Finish(): 627 if not syncbuf.Finish():
589 sys.exit(1) 628 sys.exit(1)
590 629
591 # If there's a notice that's supposed to print at the end of the sync, print 630 # If there's a notice that's supposed to print at the end of the sync, print
592 # it now... 631 # it now...
593 if self.manifest.notice: 632 if self.manifest.notice:
594 print self.manifest.notice 633 print(self.manifest.notice)
595 634
596def _PostRepoUpgrade(manifest, quiet=False): 635def _PostRepoUpgrade(manifest, quiet=False):
597 wrapper = WrapperModule() 636 wrapper = WrapperModule()
@@ -603,27 +642,28 @@ def _PostRepoUpgrade(manifest, quiet=False):
603 642
604def _PostRepoFetch(rp, no_repo_verify=False, verbose=False): 643def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
605 if rp.HasChanges: 644 if rp.HasChanges:
606 print >>sys.stderr, 'info: A new version of repo is available' 645 print('info: A new version of repo is available', file=sys.stderr)
607 print >>sys.stderr, '' 646 print(file=sys.stderr)
608 if no_repo_verify or _VerifyTag(rp): 647 if no_repo_verify or _VerifyTag(rp):
609 syncbuf = SyncBuffer(rp.config) 648 syncbuf = SyncBuffer(rp.config)
610 rp.Sync_LocalHalf(syncbuf) 649 rp.Sync_LocalHalf(syncbuf)
611 if not syncbuf.Finish(): 650 if not syncbuf.Finish():
612 sys.exit(1) 651 sys.exit(1)
613 print >>sys.stderr, 'info: Restarting repo with latest version' 652 print('info: Restarting repo with latest version', file=sys.stderr)
614 raise RepoChangedException(['--repo-upgraded']) 653 raise RepoChangedException(['--repo-upgraded'])
615 else: 654 else:
616 print >>sys.stderr, 'warning: Skipped upgrade to unverified version' 655 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
617 else: 656 else:
618 if verbose: 657 if verbose:
619 print >>sys.stderr, 'repo version %s is current' % rp.work_git.describe(HEAD) 658 print('repo version %s is current' % rp.work_git.describe(HEAD),
659 file=sys.stderr)
620 660
621def _VerifyTag(project): 661def _VerifyTag(project):
622 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg') 662 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
623 if not os.path.exists(gpg_dir): 663 if not os.path.exists(gpg_dir):
624 print >>sys.stderr,\ 664 print('warning: GnuPG was not available during last "repo init"\n'
625"""warning: GnuPG was not available during last "repo init" 665 'warning: Cannot automatically authenticate repo."""',
626warning: Cannot automatically authenticate repo.""" 666 file=sys.stderr)
627 return True 667 return True
628 668
629 try: 669 try:
@@ -637,10 +677,9 @@ warning: Cannot automatically authenticate repo."""
637 if rev.startswith(R_HEADS): 677 if rev.startswith(R_HEADS):
638 rev = rev[len(R_HEADS):] 678 rev = rev[len(R_HEADS):]
639 679
640 print >>sys.stderr 680 print(file=sys.stderr)
641 print >>sys.stderr,\ 681 print("warning: project '%s' branch '%s' is not signed"
642 "warning: project '%s' branch '%s' is not signed" \ 682 % (project.name, rev), file=sys.stderr)
643 % (project.name, rev)
644 return False 683 return False
645 684
646 env = os.environ.copy() 685 env = os.environ.copy()
@@ -659,10 +698,10 @@ warning: Cannot automatically authenticate repo."""
659 proc.stderr.close() 698 proc.stderr.close()
660 699
661 if proc.wait() != 0: 700 if proc.wait() != 0:
662 print >>sys.stderr 701 print(file=sys.stderr)
663 print >>sys.stderr, out 702 print(out, file=sys.stderr)
664 print >>sys.stderr, err 703 print(err, file=sys.stderr)
665 print >>sys.stderr 704 print(file=sys.stderr)
666 return False 705 return False
667 return True 706 return True
668 707
@@ -696,7 +735,7 @@ class _FetchTimes(object):
696 try: 735 try:
697 try: 736 try:
698 self._times = pickle.load(f) 737 self._times = pickle.load(f)
699 except: 738 except IOError:
700 try: 739 try:
701 os.remove(self._path) 740 os.remove(self._path)
702 except OSError: 741 except OSError:
diff --git a/subcmds/upload.py b/subcmds/upload.py
index 84a5e440..e314032a 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import copy 17import copy
17import re 18import re
18import sys 19import sys
@@ -26,16 +27,18 @@ UNUSUAL_COMMIT_THRESHOLD = 5
26 27
27def _ConfirmManyUploads(multiple_branches=False): 28def _ConfirmManyUploads(multiple_branches=False):
28 if multiple_branches: 29 if multiple_branches:
29 print "ATTENTION: One or more branches has an unusually high number of commits." 30 print('ATTENTION: One or more branches has an unusually high number'
31 'of commits.')
30 else: 32 else:
31 print "ATTENTION: You are uploading an unusually high number of commits." 33 print('ATTENTION: You are uploading an unusually high number of commits.')
32 print "YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across branches?)" 34 print('YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across'
35 'branches?)')
33 answer = raw_input("If you are sure you intend to do this, type 'yes': ").strip() 36 answer = raw_input("If you are sure you intend to do this, type 'yes': ").strip()
34 return answer == "yes" 37 return answer == "yes"
35 38
36def _die(fmt, *args): 39def _die(fmt, *args):
37 msg = fmt % args 40 msg = fmt % args
38 print >>sys.stderr, 'error: %s' % msg 41 print('error: %s' % msg, file=sys.stderr)
39 sys.exit(1) 42 sys.exit(1)
40 43
41def _SplitEmails(values): 44def _SplitEmails(values):
@@ -47,7 +50,7 @@ def _SplitEmails(values):
47class Upload(InteractiveCommand): 50class Upload(InteractiveCommand):
48 common = True 51 common = True
49 helpSummary = "Upload changes for code review" 52 helpSummary = "Upload changes for code review"
50 helpUsage=""" 53 helpUsage = """
51%prog [--re --cc] [<project>]... 54%prog [--re --cc] [<project>]...
52""" 55"""
53 helpDescription = """ 56 helpDescription = """
@@ -176,18 +179,18 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
176 date = branch.date 179 date = branch.date
177 commit_list = branch.commits 180 commit_list = branch.commits
178 181
179 print 'Upload project %s/ to remote branch %s:' % (project.relpath, project.revisionExpr) 182 print('Upload project %s/ to remote branch %s:' % (project.relpath, project.revisionExpr))
180 print ' branch %s (%2d commit%s, %s):' % ( 183 print(' branch %s (%2d commit%s, %s):' % (
181 name, 184 name,
182 len(commit_list), 185 len(commit_list),
183 len(commit_list) != 1 and 's' or '', 186 len(commit_list) != 1 and 's' or '',
184 date) 187 date))
185 for commit in commit_list: 188 for commit in commit_list:
186 print ' %s' % commit 189 print(' %s' % commit)
187 190
188 sys.stdout.write('to %s (y/N)? ' % remote.review) 191 sys.stdout.write('to %s (y/N)? ' % remote.review)
189 answer = sys.stdin.readline().strip() 192 answer = sys.stdin.readline().strip().lower()
190 answer = answer in ('y', 'Y', 'yes', '1', 'true', 't') 193 answer = answer in ('y', 'yes', '1', 'true', 't')
191 194
192 if answer: 195 if answer:
193 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD: 196 if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
@@ -297,7 +300,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
297 try: 300 try:
298 # refs/changes/XYZ/N --> XYZ 301 # refs/changes/XYZ/N --> XYZ
299 return refs.get(last_pub).split('/')[-2] 302 return refs.get(last_pub).split('/')[-2]
300 except: 303 except (AttributeError, IndexError):
301 return "" 304 return ""
302 305
303 def _UploadAndReport(self, opt, todo, original_people): 306 def _UploadAndReport(self, opt, todo, original_people):
@@ -309,23 +312,23 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
309 312
310 # Check if there are local changes that may have been forgotten 313 # Check if there are local changes that may have been forgotten
311 if branch.project.HasChanges(): 314 if branch.project.HasChanges():
312 key = 'review.%s.autoupload' % branch.project.remote.review 315 key = 'review.%s.autoupload' % branch.project.remote.review
313 answer = branch.project.config.GetBoolean(key) 316 answer = branch.project.config.GetBoolean(key)
314 317
315 # if they want to auto upload, let's not ask because it could be automated 318 # if they want to auto upload, let's not ask because it could be automated
316 if answer is None: 319 if answer is None:
317 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/N) ') 320 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/N) ')
318 a = sys.stdin.readline().strip().lower() 321 a = sys.stdin.readline().strip().lower()
319 if a not in ('y', 'yes', 't', 'true', 'on'): 322 if a not in ('y', 'yes', 't', 'true', 'on'):
320 print >>sys.stderr, "skipping upload" 323 print("skipping upload", file=sys.stderr)
321 branch.uploaded = False 324 branch.uploaded = False
322 branch.error = 'User aborted' 325 branch.error = 'User aborted'
323 continue 326 continue
324 327
325 # Check if topic branches should be sent to the server during upload 328 # Check if topic branches should be sent to the server during upload
326 if opt.auto_topic is not True: 329 if opt.auto_topic is not True:
327 key = 'review.%s.uploadtopic' % branch.project.remote.review 330 key = 'review.%s.uploadtopic' % branch.project.remote.review
328 opt.auto_topic = branch.project.config.GetBoolean(key) 331 opt.auto_topic = branch.project.config.GetBoolean(key)
329 332
330 branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft) 333 branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft)
331 branch.uploaded = True 334 branch.uploaded = True
@@ -334,8 +337,8 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
334 branch.uploaded = False 337 branch.uploaded = False
335 have_errors = True 338 have_errors = True
336 339
337 print >>sys.stderr, '' 340 print(file=sys.stderr)
338 print >>sys.stderr, '----------------------------------------------------------------------' 341 print('----------------------------------------------------------------------', file=sys.stderr)
339 342
340 if have_errors: 343 if have_errors:
341 for branch in todo: 344 for branch in todo:
@@ -344,17 +347,19 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
344 fmt = ' (%s)' 347 fmt = ' (%s)'
345 else: 348 else:
346 fmt = '\n (%s)' 349 fmt = '\n (%s)'
347 print >>sys.stderr, ('[FAILED] %-15s %-15s' + fmt) % ( 350 print(('[FAILED] %-15s %-15s' + fmt) % (
348 branch.project.relpath + '/', \ 351 branch.project.relpath + '/', \
349 branch.name, \ 352 branch.name, \
350 str(branch.error)) 353 str(branch.error)),
351 print >>sys.stderr, '' 354 file=sys.stderr)
355 print()
352 356
353 for branch in todo: 357 for branch in todo:
354 if branch.uploaded: 358 if branch.uploaded:
355 print >>sys.stderr, '[OK ] %-15s %s' % ( 359 print('[OK ] %-15s %s' % (
356 branch.project.relpath + '/', 360 branch.project.relpath + '/',
357 branch.name) 361 branch.name),
362 file=sys.stderr)
358 363
359 if have_errors: 364 if have_errors:
360 sys.exit(1) 365 sys.exit(1)
@@ -385,17 +390,17 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
385 try: 390 try:
386 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names) 391 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
387 except HookError as e: 392 except HookError as e:
388 print >>sys.stderr, "ERROR: %s" % str(e) 393 print("ERROR: %s" % str(e), file=sys.stderr)
389 return 394 return
390 395
391 if opt.reviewers: 396 if opt.reviewers:
392 reviewers = _SplitEmails(opt.reviewers) 397 reviewers = _SplitEmails(opt.reviewers)
393 if opt.cc: 398 if opt.cc:
394 cc = _SplitEmails(opt.cc) 399 cc = _SplitEmails(opt.cc)
395 people = (reviewers,cc) 400 people = (reviewers, cc)
396 401
397 if not pending: 402 if not pending:
398 print >>sys.stdout, "no branches ready for upload" 403 print("no branches ready for upload", file=sys.stderr)
399 elif len(pending) == 1 and len(pending[0][1]) == 1: 404 elif len(pending) == 1 and len(pending[0][1]) == 1:
400 self._SingleBranch(opt, pending[0][1][0], people) 405 self._SingleBranch(opt, pending[0][1][0], people)
401 else: 406 else:
diff --git a/subcmds/version.py b/subcmds/version.py
index 243e3676..01b7fd8c 100644
--- a/subcmds/version.py
+++ b/subcmds/version.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import sys 17import sys
17from command import Command, MirrorSafeCommand 18from command import Command, MirrorSafeCommand
18from git_command import git 19from git_command import git
@@ -32,12 +33,12 @@ class Version(Command, MirrorSafeCommand):
32 rp = self.manifest.repoProject 33 rp = self.manifest.repoProject
33 rem = rp.GetRemote(rp.remote.name) 34 rem = rp.GetRemote(rp.remote.name)
34 35
35 print 'repo version %s' % rp.work_git.describe(HEAD) 36 print('repo version %s' % rp.work_git.describe(HEAD))
36 print ' (from %s)' % rem.url 37 print(' (from %s)' % rem.url)
37 38
38 if Version.wrapper_path is not None: 39 if Version.wrapper_path is not None:
39 print 'repo launcher version %s' % Version.wrapper_version 40 print('repo launcher version %s' % Version.wrapper_version)
40 print ' (from %s)' % Version.wrapper_path 41 print(' (from %s)' % Version.wrapper_path)
41 42
42 print git.version().strip() 43 print(git.version().strip())
43 print 'Python %s' % sys.version 44 print('Python %s' % sys.version)
diff --git a/tests/test_git_config.py b/tests/test_git_config.py
index 5b1770e7..3d4b9970 100644
--- a/tests/test_git_config.py
+++ b/tests/test_git_config.py
@@ -4,49 +4,49 @@ import unittest
4import git_config 4import git_config
5 5
6def fixture(*paths): 6def fixture(*paths):
7 """Return a path relative to test/fixtures. 7 """Return a path relative to test/fixtures.
8 """ 8 """
9 return os.path.join(os.path.dirname(__file__), 'fixtures', *paths) 9 return os.path.join(os.path.dirname(__file__), 'fixtures', *paths)
10 10
11class GitConfigUnitTest(unittest.TestCase): 11class GitConfigUnitTest(unittest.TestCase):
12 """Tests the GitConfig class. 12 """Tests the GitConfig class.
13 """
14 def setUp(self):
15 """Create a GitConfig object using the test.gitconfig fixture.
16 """
17 config_fixture = fixture('test.gitconfig')
18 self.config = git_config.GitConfig(config_fixture)
19
20 def test_GetString_with_empty_config_values(self):
21 """
22 Test config entries with no value.
23
24 [section]
25 empty
26
27 """
28 val = self.config.GetString('section.empty')
29 self.assertEqual(val, None)
30
31 def test_GetString_with_true_value(self):
32 """
33 Test config entries with a string value.
34
35 [section]
36 nonempty = true
37
38 """
39 val = self.config.GetString('section.nonempty')
40 self.assertEqual(val, 'true')
41
42 def test_GetString_from_missing_file(self):
43 """
44 Test missing config file
13 """ 45 """
14 def setUp(self): 46 config_fixture = fixture('not.present.gitconfig')
15 """Create a GitConfig object using the test.gitconfig fixture. 47 config = git_config.GitConfig(config_fixture)
16 """ 48 val = config.GetString('empty')
17 config_fixture = fixture('test.gitconfig') 49 self.assertEqual(val, None)
18 self.config = git_config.GitConfig(config_fixture)
19
20 def test_GetString_with_empty_config_values(self):
21 """
22 Test config entries with no value.
23
24 [section]
25 empty
26
27 """
28 val = self.config.GetString('section.empty')
29 self.assertEqual(val, None)
30
31 def test_GetString_with_true_value(self):
32 """
33 Test config entries with a string value.
34
35 [section]
36 nonempty = true
37
38 """
39 val = self.config.GetString('section.nonempty')
40 self.assertEqual(val, 'true')
41
42 def test_GetString_from_missing_file(self):
43 """
44 Test missing config file
45 """
46 config_fixture = fixture('not.present.gitconfig')
47 config = git_config.GitConfig(config_fixture)
48 val = config.GetString('empty')
49 self.assertEqual(val, None)
50 50
51if __name__ == '__main__': 51if __name__ == '__main__':
52 unittest.main() 52 unittest.main()
diff --git a/trace.py b/trace.py
index 0376d2b4..db42a684 100644
--- a/trace.py
+++ b/trace.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import sys 17import sys
17import os 18import os
18REPO_TRACE = 'REPO_TRACE' 19REPO_TRACE = 'REPO_TRACE'
@@ -31,4 +32,4 @@ def SetTrace():
31 32
32def Trace(fmt, *args): 33def Trace(fmt, *args):
33 if IsTrace(): 34 if IsTrace():
34 print >>sys.stderr, fmt % args 35 print(fmt % args, file=sys.stderr)