summaryrefslogtreecommitdiffstats
path: root/main.py
diff options
context:
space:
mode:
Diffstat (limited to 'main.py')
-rwxr-xr-xmain.py1268
1 files changed, 700 insertions, 568 deletions
diff --git a/main.py b/main.py
index f4b6e7ac..6dcb66f6 100755
--- a/main.py
+++ b/main.py
@@ -31,9 +31,9 @@ import time
31import urllib.request 31import urllib.request
32 32
33try: 33try:
34 import kerberos 34 import kerberos
35except ImportError: 35except ImportError:
36 kerberos = None 36 kerberos = None
37 37
38from color import SetDefaultColoring 38from color import SetDefaultColoring
39import event_log 39import event_log
@@ -74,347 +74,442 @@ MIN_PYTHON_VERSION_SOFT = (3, 6)
74MIN_PYTHON_VERSION_HARD = (3, 6) 74MIN_PYTHON_VERSION_HARD = (3, 6)
75 75
76if sys.version_info.major < 3: 76if sys.version_info.major < 3:
77 print('repo: error: Python 2 is no longer supported; ' 77 print(
78 'Please upgrade to Python {}.{}+.'.format(*MIN_PYTHON_VERSION_SOFT), 78 "repo: error: Python 2 is no longer supported; "
79 file=sys.stderr) 79 "Please upgrade to Python {}.{}+.".format(*MIN_PYTHON_VERSION_SOFT),
80 sys.exit(1) 80 file=sys.stderr,
81else: 81 )
82 if sys.version_info < MIN_PYTHON_VERSION_HARD:
83 print('repo: error: Python 3 version is too old; '
84 'Please upgrade to Python {}.{}+.'.format(*MIN_PYTHON_VERSION_SOFT),
85 file=sys.stderr)
86 sys.exit(1) 82 sys.exit(1)
87 elif sys.version_info < MIN_PYTHON_VERSION_SOFT: 83else:
88 print('repo: warning: your Python 3 version is no longer supported; ' 84 if sys.version_info < MIN_PYTHON_VERSION_HARD:
89 'Please upgrade to Python {}.{}+.'.format(*MIN_PYTHON_VERSION_SOFT), 85 print(
90 file=sys.stderr) 86 "repo: error: Python 3 version is too old; "
87 "Please upgrade to Python {}.{}+.".format(*MIN_PYTHON_VERSION_SOFT),
88 file=sys.stderr,
89 )
90 sys.exit(1)
91 elif sys.version_info < MIN_PYTHON_VERSION_SOFT:
92 print(
93 "repo: warning: your Python 3 version is no longer supported; "
94 "Please upgrade to Python {}.{}+.".format(*MIN_PYTHON_VERSION_SOFT),
95 file=sys.stderr,
96 )
91 97
92 98
93global_options = optparse.OptionParser( 99global_options = optparse.OptionParser(
94 usage='repo [-p|--paginate|--no-pager] COMMAND [ARGS]', 100 usage="repo [-p|--paginate|--no-pager] COMMAND [ARGS]",
95 add_help_option=False) 101 add_help_option=False,
96global_options.add_option('-h', '--help', action='store_true', 102)
97 help='show this help message and exit') 103global_options.add_option(
98global_options.add_option('--help-all', action='store_true', 104 "-h", "--help", action="store_true", help="show this help message and exit"
99 help='show this help message with all subcommands and exit') 105)
100global_options.add_option('-p', '--paginate', 106global_options.add_option(
101 dest='pager', action='store_true', 107 "--help-all",
102 help='display command output in the pager') 108 action="store_true",
103global_options.add_option('--no-pager', 109 help="show this help message with all subcommands and exit",
104 dest='pager', action='store_false', 110)
105 help='disable the pager') 111global_options.add_option(
106global_options.add_option('--color', 112 "-p",
107 choices=('auto', 'always', 'never'), default=None, 113 "--paginate",
108 help='control color usage: auto, always, never') 114 dest="pager",
109global_options.add_option('--trace', 115 action="store_true",
110 dest='trace', action='store_true', 116 help="display command output in the pager",
111 help='trace git command execution (REPO_TRACE=1)') 117)
112global_options.add_option('--trace-to-stderr', 118global_options.add_option(
113 dest='trace_to_stderr', action='store_true', 119 "--no-pager", dest="pager", action="store_false", help="disable the pager"
114 help='trace outputs go to stderr in addition to .repo/TRACE_FILE') 120)
115global_options.add_option('--trace-python', 121global_options.add_option(
116 dest='trace_python', action='store_true', 122 "--color",
117 help='trace python command execution') 123 choices=("auto", "always", "never"),
118global_options.add_option('--time', 124 default=None,
119 dest='time', action='store_true', 125 help="control color usage: auto, always, never",
120 help='time repo command execution') 126)
121global_options.add_option('--version', 127global_options.add_option(
122 dest='show_version', action='store_true', 128 "--trace",
123 help='display this version of repo') 129 dest="trace",
124global_options.add_option('--show-toplevel', 130 action="store_true",
125 action='store_true', 131 help="trace git command execution (REPO_TRACE=1)",
126 help='display the path of the top-level directory of ' 132)
127 'the repo client checkout') 133global_options.add_option(
128global_options.add_option('--event-log', 134 "--trace-to-stderr",
129 dest='event_log', action='store', 135 dest="trace_to_stderr",
130 help='filename of event log to append timeline to') 136 action="store_true",
131global_options.add_option('--git-trace2-event-log', action='store', 137 help="trace outputs go to stderr in addition to .repo/TRACE_FILE",
132 help='directory to write git trace2 event log to') 138)
133global_options.add_option('--submanifest-path', action='store', 139global_options.add_option(
134 metavar='REL_PATH', help='submanifest path') 140 "--trace-python",
141 dest="trace_python",
142 action="store_true",
143 help="trace python command execution",
144)
145global_options.add_option(
146 "--time",
147 dest="time",
148 action="store_true",
149 help="time repo command execution",
150)
151global_options.add_option(
152 "--version",
153 dest="show_version",
154 action="store_true",
155 help="display this version of repo",
156)
157global_options.add_option(
158 "--show-toplevel",
159 action="store_true",
160 help="display the path of the top-level directory of "
161 "the repo client checkout",
162)
163global_options.add_option(
164 "--event-log",
165 dest="event_log",
166 action="store",
167 help="filename of event log to append timeline to",
168)
169global_options.add_option(
170 "--git-trace2-event-log",
171 action="store",
172 help="directory to write git trace2 event log to",
173)
174global_options.add_option(
175 "--submanifest-path",
176 action="store",
177 metavar="REL_PATH",
178 help="submanifest path",
179)
135 180
136 181
137class _Repo(object): 182class _Repo(object):
138 def __init__(self, repodir): 183 def __init__(self, repodir):
139 self.repodir = repodir 184 self.repodir = repodir
140 self.commands = all_commands 185 self.commands = all_commands
141 186
142 def _PrintHelp(self, short: bool = False, all_commands: bool = False): 187 def _PrintHelp(self, short: bool = False, all_commands: bool = False):
143 """Show --help screen.""" 188 """Show --help screen."""
144 global_options.print_help() 189 global_options.print_help()
145 print() 190 print()
146 if short: 191 if short:
147 commands = ' '.join(sorted(self.commands)) 192 commands = " ".join(sorted(self.commands))
148 wrapped_commands = textwrap.wrap(commands, width=77) 193 wrapped_commands = textwrap.wrap(commands, width=77)
149 print('Available commands:\n %s' % ('\n '.join(wrapped_commands),)) 194 print(
150 print('\nRun `repo help <command>` for command-specific details.') 195 "Available commands:\n %s" % ("\n ".join(wrapped_commands),)
151 print('Bug reports:', Wrapper().BUG_URL) 196 )
152 else: 197 print("\nRun `repo help <command>` for command-specific details.")
153 cmd = self.commands['help']() 198 print("Bug reports:", Wrapper().BUG_URL)
154 if all_commands:
155 cmd.PrintAllCommandsBody()
156 else:
157 cmd.PrintCommonCommandsBody()
158
159 def _ParseArgs(self, argv):
160 """Parse the main `repo` command line options."""
161 for i, arg in enumerate(argv):
162 if not arg.startswith('-'):
163 name = arg
164 glob = argv[:i]
165 argv = argv[i + 1:]
166 break
167 else:
168 name = None
169 glob = argv
170 argv = []
171 gopts, _gargs = global_options.parse_args(glob)
172
173 if name:
174 name, alias_args = self._ExpandAlias(name)
175 argv = alias_args + argv
176
177 return (name, gopts, argv)
178
179 def _ExpandAlias(self, name):
180 """Look up user registered aliases."""
181 # We don't resolve aliases for existing subcommands. This matches git.
182 if name in self.commands:
183 return name, []
184
185 key = 'alias.%s' % (name,)
186 alias = RepoConfig.ForRepository(self.repodir).GetString(key)
187 if alias is None:
188 alias = RepoConfig.ForUser().GetString(key)
189 if alias is None:
190 return name, []
191
192 args = alias.strip().split(' ', 1)
193 name = args[0]
194 if len(args) == 2:
195 args = shlex.split(args[1])
196 else:
197 args = []
198 return name, args
199
200 def _Run(self, name, gopts, argv):
201 """Execute the requested subcommand."""
202 result = 0
203
204 # Handle options that terminate quickly first.
205 if gopts.help or gopts.help_all:
206 self._PrintHelp(short=False, all_commands=gopts.help_all)
207 return 0
208 elif gopts.show_version:
209 # Always allow global --version regardless of subcommand validity.
210 name = 'version'
211 elif gopts.show_toplevel:
212 print(os.path.dirname(self.repodir))
213 return 0
214 elif not name:
215 # No subcommand specified, so show the help/subcommand.
216 self._PrintHelp(short=True)
217 return 1
218
219 run = lambda: self._RunLong(name, gopts, argv) or 0
220 with Trace('starting new command: %s', ', '.join([name] + argv),
221 first_trace=True):
222 if gopts.trace_python:
223 import trace
224 tracer = trace.Trace(count=False, trace=True, timing=True,
225 ignoredirs=set(sys.path[1:]))
226 result = tracer.runfunc(run)
227 else:
228 result = run()
229 return result
230
231 def _RunLong(self, name, gopts, argv):
232 """Execute the (longer running) requested subcommand."""
233 result = 0
234 SetDefaultColoring(gopts.color)
235
236 git_trace2_event_log = EventLog()
237 outer_client = RepoClient(self.repodir)
238 repo_client = outer_client
239 if gopts.submanifest_path:
240 repo_client = RepoClient(self.repodir,
241 submanifest_path=gopts.submanifest_path,
242 outer_client=outer_client)
243 gitc_manifest = None
244 gitc_client_name = gitc_utils.parse_clientdir(os.getcwd())
245 if gitc_client_name:
246 gitc_manifest = GitcClient(self.repodir, gitc_client_name)
247 repo_client.isGitcClient = True
248
249 try:
250 cmd = self.commands[name](
251 repodir=self.repodir,
252 client=repo_client,
253 manifest=repo_client.manifest,
254 outer_client=outer_client,
255 outer_manifest=outer_client.manifest,
256 gitc_manifest=gitc_manifest,
257 git_event_log=git_trace2_event_log)
258 except KeyError:
259 print("repo: '%s' is not a repo command. See 'repo help'." % name,
260 file=sys.stderr)
261 return 1
262
263 Editor.globalConfig = cmd.client.globalConfig
264
265 if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror:
266 print("fatal: '%s' requires a working directory" % name,
267 file=sys.stderr)
268 return 1
269
270 if isinstance(cmd, GitcAvailableCommand) and not gitc_utils.get_gitc_manifest_dir():
271 print("fatal: '%s' requires GITC to be available" % name,
272 file=sys.stderr)
273 return 1
274
275 if isinstance(cmd, GitcClientCommand) and not gitc_client_name:
276 print("fatal: '%s' requires a GITC client" % name,
277 file=sys.stderr)
278 return 1
279
280 try:
281 copts, cargs = cmd.OptionParser.parse_args(argv)
282 copts = cmd.ReadEnvironmentOptions(copts)
283 except NoManifestException as e:
284 print('error: in `%s`: %s' % (' '.join([name] + argv), str(e)),
285 file=sys.stderr)
286 print('error: manifest missing or unreadable -- please run init',
287 file=sys.stderr)
288 return 1
289
290 if gopts.pager is not False and not isinstance(cmd, InteractiveCommand):
291 config = cmd.client.globalConfig
292 if gopts.pager:
293 use_pager = True
294 else:
295 use_pager = config.GetBoolean('pager.%s' % name)
296 if use_pager is None:
297 use_pager = cmd.WantPager(copts)
298 if use_pager:
299 RunPager(config)
300
301 start = time.time()
302 cmd_event = cmd.event_log.Add(name, event_log.TASK_COMMAND, start)
303 cmd.event_log.SetParent(cmd_event)
304 git_trace2_event_log.StartEvent()
305 git_trace2_event_log.CommandEvent(name='repo', subcommands=[name])
306
307 try:
308 cmd.CommonValidateOptions(copts, cargs)
309 cmd.ValidateOptions(copts, cargs)
310
311 this_manifest_only = copts.this_manifest_only
312 outer_manifest = copts.outer_manifest
313 if cmd.MULTI_MANIFEST_SUPPORT or this_manifest_only:
314 result = cmd.Execute(copts, cargs)
315 elif outer_manifest and repo_client.manifest.is_submanifest:
316 # The command does not support multi-manifest, we are using a
317 # submanifest, and the command line is for the outermost manifest.
318 # Re-run using the outermost manifest, which will recurse through the
319 # submanifests.
320 gopts.submanifest_path = ''
321 result = self._Run(name, gopts, argv)
322 else:
323 # No multi-manifest support. Run the command in the current
324 # (sub)manifest, and then any child submanifests.
325 result = cmd.Execute(copts, cargs)
326 for submanifest in repo_client.manifest.submanifests.values():
327 spec = submanifest.ToSubmanifestSpec()
328 gopts.submanifest_path = submanifest.repo_client.path_prefix
329 child_argv = argv[:]
330 child_argv.append('--no-outer-manifest')
331 # Not all subcommands support the 3 manifest options, so only add them
332 # if the original command includes them.
333 if hasattr(copts, 'manifest_url'):
334 child_argv.extend(['--manifest-url', spec.manifestUrl])
335 if hasattr(copts, 'manifest_name'):
336 child_argv.extend(['--manifest-name', spec.manifestName])
337 if hasattr(copts, 'manifest_branch'):
338 child_argv.extend(['--manifest-branch', spec.revision])
339 result = self._Run(name, gopts, child_argv) or result
340 except (DownloadError, ManifestInvalidRevisionError,
341 NoManifestException) as e:
342 print('error: in `%s`: %s' % (' '.join([name] + argv), str(e)),
343 file=sys.stderr)
344 if isinstance(e, NoManifestException):
345 print('error: manifest missing or unreadable -- please run init',
346 file=sys.stderr)
347 result = 1
348 except NoSuchProjectError as e:
349 if e.name:
350 print('error: project %s not found' % e.name, file=sys.stderr)
351 else:
352 print('error: no project in current directory', file=sys.stderr)
353 result = 1
354 except InvalidProjectGroupsError as e:
355 if e.name:
356 print('error: project group must be enabled for project %s' % e.name, file=sys.stderr)
357 else:
358 print('error: project group must be enabled for the project in the current directory',
359 file=sys.stderr)
360 result = 1
361 except SystemExit as e:
362 if e.code:
363 result = e.code
364 raise
365 finally:
366 finish = time.time()
367 elapsed = finish - start
368 hours, remainder = divmod(elapsed, 3600)
369 minutes, seconds = divmod(remainder, 60)
370 if gopts.time:
371 if hours == 0:
372 print('real\t%dm%.3fs' % (minutes, seconds), file=sys.stderr)
373 else: 199 else:
374 print('real\t%dh%dm%.3fs' % (hours, minutes, seconds), 200 cmd = self.commands["help"]()
375 file=sys.stderr) 201 if all_commands:
376 202 cmd.PrintAllCommandsBody()
377 cmd.event_log.FinishEvent(cmd_event, finish, 203 else:
378 result is None or result == 0) 204 cmd.PrintCommonCommandsBody()
379 git_trace2_event_log.DefParamRepoEvents( 205
380 cmd.manifest.manifestProject.config.DumpConfigDict()) 206 def _ParseArgs(self, argv):
381 git_trace2_event_log.ExitEvent(result) 207 """Parse the main `repo` command line options."""
382 208 for i, arg in enumerate(argv):
383 if gopts.event_log: 209 if not arg.startswith("-"):
384 cmd.event_log.Write(os.path.abspath( 210 name = arg
385 os.path.expanduser(gopts.event_log))) 211 glob = argv[:i]
386 212 argv = argv[i + 1 :]
387 git_trace2_event_log.Write(gopts.git_trace2_event_log) 213 break
388 return result 214 else:
215 name = None
216 glob = argv
217 argv = []
218 gopts, _gargs = global_options.parse_args(glob)
219
220 if name:
221 name, alias_args = self._ExpandAlias(name)
222 argv = alias_args + argv
223
224 return (name, gopts, argv)
225
226 def _ExpandAlias(self, name):
227 """Look up user registered aliases."""
228 # We don't resolve aliases for existing subcommands. This matches git.
229 if name in self.commands:
230 return name, []
231
232 key = "alias.%s" % (name,)
233 alias = RepoConfig.ForRepository(self.repodir).GetString(key)
234 if alias is None:
235 alias = RepoConfig.ForUser().GetString(key)
236 if alias is None:
237 return name, []
238
239 args = alias.strip().split(" ", 1)
240 name = args[0]
241 if len(args) == 2:
242 args = shlex.split(args[1])
243 else:
244 args = []
245 return name, args
246
247 def _Run(self, name, gopts, argv):
248 """Execute the requested subcommand."""
249 result = 0
250
251 # Handle options that terminate quickly first.
252 if gopts.help or gopts.help_all:
253 self._PrintHelp(short=False, all_commands=gopts.help_all)
254 return 0
255 elif gopts.show_version:
256 # Always allow global --version regardless of subcommand validity.
257 name = "version"
258 elif gopts.show_toplevel:
259 print(os.path.dirname(self.repodir))
260 return 0
261 elif not name:
262 # No subcommand specified, so show the help/subcommand.
263 self._PrintHelp(short=True)
264 return 1
265
266 run = lambda: self._RunLong(name, gopts, argv) or 0
267 with Trace(
268 "starting new command: %s",
269 ", ".join([name] + argv),
270 first_trace=True,
271 ):
272 if gopts.trace_python:
273 import trace
274
275 tracer = trace.Trace(
276 count=False,
277 trace=True,
278 timing=True,
279 ignoredirs=set(sys.path[1:]),
280 )
281 result = tracer.runfunc(run)
282 else:
283 result = run()
284 return result
285
286 def _RunLong(self, name, gopts, argv):
287 """Execute the (longer running) requested subcommand."""
288 result = 0
289 SetDefaultColoring(gopts.color)
290
291 git_trace2_event_log = EventLog()
292 outer_client = RepoClient(self.repodir)
293 repo_client = outer_client
294 if gopts.submanifest_path:
295 repo_client = RepoClient(
296 self.repodir,
297 submanifest_path=gopts.submanifest_path,
298 outer_client=outer_client,
299 )
300 gitc_manifest = None
301 gitc_client_name = gitc_utils.parse_clientdir(os.getcwd())
302 if gitc_client_name:
303 gitc_manifest = GitcClient(self.repodir, gitc_client_name)
304 repo_client.isGitcClient = True
305
306 try:
307 cmd = self.commands[name](
308 repodir=self.repodir,
309 client=repo_client,
310 manifest=repo_client.manifest,
311 outer_client=outer_client,
312 outer_manifest=outer_client.manifest,
313 gitc_manifest=gitc_manifest,
314 git_event_log=git_trace2_event_log,
315 )
316 except KeyError:
317 print(
318 "repo: '%s' is not a repo command. See 'repo help'." % name,
319 file=sys.stderr,
320 )
321 return 1
322
323 Editor.globalConfig = cmd.client.globalConfig
324
325 if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror:
326 print(
327 "fatal: '%s' requires a working directory" % name,
328 file=sys.stderr,
329 )
330 return 1
331
332 if (
333 isinstance(cmd, GitcAvailableCommand)
334 and not gitc_utils.get_gitc_manifest_dir()
335 ):
336 print(
337 "fatal: '%s' requires GITC to be available" % name,
338 file=sys.stderr,
339 )
340 return 1
341
342 if isinstance(cmd, GitcClientCommand) and not gitc_client_name:
343 print("fatal: '%s' requires a GITC client" % name, file=sys.stderr)
344 return 1
345
346 try:
347 copts, cargs = cmd.OptionParser.parse_args(argv)
348 copts = cmd.ReadEnvironmentOptions(copts)
349 except NoManifestException as e:
350 print(
351 "error: in `%s`: %s" % (" ".join([name] + argv), str(e)),
352 file=sys.stderr,
353 )
354 print(
355 "error: manifest missing or unreadable -- please run init",
356 file=sys.stderr,
357 )
358 return 1
359
360 if gopts.pager is not False and not isinstance(cmd, InteractiveCommand):
361 config = cmd.client.globalConfig
362 if gopts.pager:
363 use_pager = True
364 else:
365 use_pager = config.GetBoolean("pager.%s" % name)
366 if use_pager is None:
367 use_pager = cmd.WantPager(copts)
368 if use_pager:
369 RunPager(config)
370
371 start = time.time()
372 cmd_event = cmd.event_log.Add(name, event_log.TASK_COMMAND, start)
373 cmd.event_log.SetParent(cmd_event)
374 git_trace2_event_log.StartEvent()
375 git_trace2_event_log.CommandEvent(name="repo", subcommands=[name])
376
377 try:
378 cmd.CommonValidateOptions(copts, cargs)
379 cmd.ValidateOptions(copts, cargs)
380
381 this_manifest_only = copts.this_manifest_only
382 outer_manifest = copts.outer_manifest
383 if cmd.MULTI_MANIFEST_SUPPORT or this_manifest_only:
384 result = cmd.Execute(copts, cargs)
385 elif outer_manifest and repo_client.manifest.is_submanifest:
386 # The command does not support multi-manifest, we are using a
387 # submanifest, and the command line is for the outermost
388 # manifest. Re-run using the outermost manifest, which will
389 # recurse through the submanifests.
390 gopts.submanifest_path = ""
391 result = self._Run(name, gopts, argv)
392 else:
393 # No multi-manifest support. Run the command in the current
394 # (sub)manifest, and then any child submanifests.
395 result = cmd.Execute(copts, cargs)
396 for submanifest in repo_client.manifest.submanifests.values():
397 spec = submanifest.ToSubmanifestSpec()
398 gopts.submanifest_path = submanifest.repo_client.path_prefix
399 child_argv = argv[:]
400 child_argv.append("--no-outer-manifest")
401 # Not all subcommands support the 3 manifest options, so
402 # only add them if the original command includes them.
403 if hasattr(copts, "manifest_url"):
404 child_argv.extend(["--manifest-url", spec.manifestUrl])
405 if hasattr(copts, "manifest_name"):
406 child_argv.extend(
407 ["--manifest-name", spec.manifestName]
408 )
409 if hasattr(copts, "manifest_branch"):
410 child_argv.extend(["--manifest-branch", spec.revision])
411 result = self._Run(name, gopts, child_argv) or result
412 except (
413 DownloadError,
414 ManifestInvalidRevisionError,
415 NoManifestException,
416 ) as e:
417 print(
418 "error: in `%s`: %s" % (" ".join([name] + argv), str(e)),
419 file=sys.stderr,
420 )
421 if isinstance(e, NoManifestException):
422 print(
423 "error: manifest missing or unreadable -- please run init",
424 file=sys.stderr,
425 )
426 result = 1
427 except NoSuchProjectError as e:
428 if e.name:
429 print("error: project %s not found" % e.name, file=sys.stderr)
430 else:
431 print("error: no project in current directory", file=sys.stderr)
432 result = 1
433 except InvalidProjectGroupsError as e:
434 if e.name:
435 print(
436 "error: project group must be enabled for project %s"
437 % e.name,
438 file=sys.stderr,
439 )
440 else:
441 print(
442 "error: project group must be enabled for the project in "
443 "the current directory",
444 file=sys.stderr,
445 )
446 result = 1
447 except SystemExit as e:
448 if e.code:
449 result = e.code
450 raise
451 finally:
452 finish = time.time()
453 elapsed = finish - start
454 hours, remainder = divmod(elapsed, 3600)
455 minutes, seconds = divmod(remainder, 60)
456 if gopts.time:
457 if hours == 0:
458 print(
459 "real\t%dm%.3fs" % (minutes, seconds), file=sys.stderr
460 )
461 else:
462 print(
463 "real\t%dh%dm%.3fs" % (hours, minutes, seconds),
464 file=sys.stderr,
465 )
466
467 cmd.event_log.FinishEvent(
468 cmd_event, finish, result is None or result == 0
469 )
470 git_trace2_event_log.DefParamRepoEvents(
471 cmd.manifest.manifestProject.config.DumpConfigDict()
472 )
473 git_trace2_event_log.ExitEvent(result)
474
475 if gopts.event_log:
476 cmd.event_log.Write(
477 os.path.abspath(os.path.expanduser(gopts.event_log))
478 )
479
480 git_trace2_event_log.Write(gopts.git_trace2_event_log)
481 return result
389 482
390 483
391def _CheckWrapperVersion(ver_str, repo_path): 484def _CheckWrapperVersion(ver_str, repo_path):
392 """Verify the repo launcher is new enough for this checkout. 485 """Verify the repo launcher is new enough for this checkout.
393 486
394 Args: 487 Args:
395 ver_str: The version string passed from the repo launcher when it ran us. 488 ver_str: The version string passed from the repo launcher when it ran
396 repo_path: The path to the repo launcher that loaded us. 489 us.
397 """ 490 repo_path: The path to the repo launcher that loaded us.
398 # Refuse to work with really old wrapper versions. We don't test these, 491 """
399 # so might as well require a somewhat recent sane version. 492 # Refuse to work with really old wrapper versions. We don't test these,
400 # v1.15 of the repo launcher was released in ~Mar 2012. 493 # so might as well require a somewhat recent sane version.
401 MIN_REPO_VERSION = (1, 15) 494 # v1.15 of the repo launcher was released in ~Mar 2012.
402 min_str = '.'.join(str(x) for x in MIN_REPO_VERSION) 495 MIN_REPO_VERSION = (1, 15)
403 496 min_str = ".".join(str(x) for x in MIN_REPO_VERSION)
404 if not repo_path: 497
405 repo_path = '~/bin/repo' 498 if not repo_path:
406 499 repo_path = "~/bin/repo"
407 if not ver_str: 500
408 print('no --wrapper-version argument', file=sys.stderr) 501 if not ver_str:
409 sys.exit(1) 502 print("no --wrapper-version argument", file=sys.stderr)
410 503 sys.exit(1)
411 # Pull out the version of the repo launcher we know about to compare. 504
412 exp = Wrapper().VERSION 505 # Pull out the version of the repo launcher we know about to compare.
413 ver = tuple(map(int, ver_str.split('.'))) 506 exp = Wrapper().VERSION
414 507 ver = tuple(map(int, ver_str.split(".")))
415 exp_str = '.'.join(map(str, exp)) 508
416 if ver < MIN_REPO_VERSION: 509 exp_str = ".".join(map(str, exp))
417 print(""" 510 if ver < MIN_REPO_VERSION:
511 print(
512 """
418repo: error: 513repo: error:
419!!! Your version of repo %s is too old. 514!!! Your version of repo %s is too old.
420!!! We need at least version %s. 515!!! We need at least version %s.
@@ -422,284 +517,321 @@ repo: error:
422!!! You must upgrade before you can continue: 517!!! You must upgrade before you can continue:
423 518
424 cp %s %s 519 cp %s %s
425""" % (ver_str, min_str, exp_str, WrapperPath(), repo_path), file=sys.stderr) 520"""
426 sys.exit(1) 521 % (ver_str, min_str, exp_str, WrapperPath(), repo_path),
427 522 file=sys.stderr,
428 if exp > ver: 523 )
429 print('\n... A new version of repo (%s) is available.' % (exp_str,), 524 sys.exit(1)
430 file=sys.stderr) 525
431 if os.access(repo_path, os.W_OK): 526 if exp > ver:
432 print("""\ 527 print(
528 "\n... A new version of repo (%s) is available." % (exp_str,),
529 file=sys.stderr,
530 )
531 if os.access(repo_path, os.W_OK):
532 print(
533 """\
433... You should upgrade soon: 534... You should upgrade soon:
434 cp %s %s 535 cp %s %s
435""" % (WrapperPath(), repo_path), file=sys.stderr) 536"""
436 else: 537 % (WrapperPath(), repo_path),
437 print("""\ 538 file=sys.stderr,
539 )
540 else:
541 print(
542 """\
438... New version is available at: %s 543... New version is available at: %s
439... The launcher is run from: %s 544... The launcher is run from: %s
440!!! The launcher is not writable. Please talk to your sysadmin or distro 545!!! The launcher is not writable. Please talk to your sysadmin or distro
441!!! to get an update installed. 546!!! to get an update installed.
442""" % (WrapperPath(), repo_path), file=sys.stderr) 547"""
548 % (WrapperPath(), repo_path),
549 file=sys.stderr,
550 )
443 551
444 552
445def _CheckRepoDir(repo_dir): 553def _CheckRepoDir(repo_dir):
446 if not repo_dir: 554 if not repo_dir:
447 print('no --repo-dir argument', file=sys.stderr) 555 print("no --repo-dir argument", file=sys.stderr)
448 sys.exit(1) 556 sys.exit(1)
449 557
450 558
451def _PruneOptions(argv, opt): 559def _PruneOptions(argv, opt):
452 i = 0 560 i = 0
453 while i < len(argv): 561 while i < len(argv):
454 a = argv[i] 562 a = argv[i]
455 if a == '--': 563 if a == "--":
456 break 564 break
457 if a.startswith('--'): 565 if a.startswith("--"):
458 eq = a.find('=') 566 eq = a.find("=")
459 if eq > 0: 567 if eq > 0:
460 a = a[0:eq] 568 a = a[0:eq]
461 if not opt.has_option(a): 569 if not opt.has_option(a):
462 del argv[i] 570 del argv[i]
463 continue 571 continue
464 i += 1 572 i += 1
465 573
466 574
467class _UserAgentHandler(urllib.request.BaseHandler): 575class _UserAgentHandler(urllib.request.BaseHandler):
468 def http_request(self, req): 576 def http_request(self, req):
469 req.add_header('User-Agent', user_agent.repo) 577 req.add_header("User-Agent", user_agent.repo)
470 return req 578 return req
471 579
472 def https_request(self, req): 580 def https_request(self, req):
473 req.add_header('User-Agent', user_agent.repo) 581 req.add_header("User-Agent", user_agent.repo)
474 return req 582 return req
475 583
476 584
477def _AddPasswordFromUserInput(handler, msg, req): 585def _AddPasswordFromUserInput(handler, msg, req):
478 # If repo could not find auth info from netrc, try to get it from user input 586 # If repo could not find auth info from netrc, try to get it from user input
479 url = req.get_full_url() 587 url = req.get_full_url()
480 user, password = handler.passwd.find_user_password(None, url) 588 user, password = handler.passwd.find_user_password(None, url)
481 if user is None: 589 if user is None:
482 print(msg) 590 print(msg)
483 try: 591 try:
484 user = input('User: ') 592 user = input("User: ")
485 password = getpass.getpass() 593 password = getpass.getpass()
486 except KeyboardInterrupt: 594 except KeyboardInterrupt:
487 return 595 return
488 handler.passwd.add_password(None, url, user, password) 596 handler.passwd.add_password(None, url, user, password)
489 597
490 598
491class _BasicAuthHandler(urllib.request.HTTPBasicAuthHandler): 599class _BasicAuthHandler(urllib.request.HTTPBasicAuthHandler):
492 def http_error_401(self, req, fp, code, msg, headers): 600 def http_error_401(self, req, fp, code, msg, headers):
493 _AddPasswordFromUserInput(self, msg, req) 601 _AddPasswordFromUserInput(self, msg, req)
494 return urllib.request.HTTPBasicAuthHandler.http_error_401( 602 return urllib.request.HTTPBasicAuthHandler.http_error_401(
495 self, req, fp, code, msg, headers) 603 self, req, fp, code, msg, headers
496 604 )
497 def http_error_auth_reqed(self, authreq, host, req, headers): 605
498 try: 606 def http_error_auth_reqed(self, authreq, host, req, headers):
499 old_add_header = req.add_header 607 try:
500 608 old_add_header = req.add_header
501 def _add_header(name, val): 609
502 val = val.replace('\n', '') 610 def _add_header(name, val):
503 old_add_header(name, val) 611 val = val.replace("\n", "")
504 req.add_header = _add_header 612 old_add_header(name, val)
505 return urllib.request.AbstractBasicAuthHandler.http_error_auth_reqed( 613
506 self, authreq, host, req, headers) 614 req.add_header = _add_header
507 except Exception: 615 return (
508 reset = getattr(self, 'reset_retry_count', None) 616 urllib.request.AbstractBasicAuthHandler.http_error_auth_reqed(
509 if reset is not None: 617 self, authreq, host, req, headers
510 reset() 618 )
511 elif getattr(self, 'retried', None): 619 )
512 self.retried = 0 620 except Exception:
513 raise 621 reset = getattr(self, "reset_retry_count", None)
622 if reset is not None:
623 reset()
624 elif getattr(self, "retried", None):
625 self.retried = 0
626 raise
514 627
515 628
516class _DigestAuthHandler(urllib.request.HTTPDigestAuthHandler): 629class _DigestAuthHandler(urllib.request.HTTPDigestAuthHandler):
517 def http_error_401(self, req, fp, code, msg, headers): 630 def http_error_401(self, req, fp, code, msg, headers):
518 _AddPasswordFromUserInput(self, msg, req) 631 _AddPasswordFromUserInput(self, msg, req)
519 return urllib.request.HTTPDigestAuthHandler.http_error_401( 632 return urllib.request.HTTPDigestAuthHandler.http_error_401(
520 self, req, fp, code, msg, headers) 633 self, req, fp, code, msg, headers
634 )
635
636 def http_error_auth_reqed(self, auth_header, host, req, headers):
637 try:
638 old_add_header = req.add_header
639
640 def _add_header(name, val):
641 val = val.replace("\n", "")
642 old_add_header(name, val)
643
644 req.add_header = _add_header
645 return (
646 urllib.request.AbstractDigestAuthHandler.http_error_auth_reqed(
647 self, auth_header, host, req, headers
648 )
649 )
650 except Exception:
651 reset = getattr(self, "reset_retry_count", None)
652 if reset is not None:
653 reset()
654 elif getattr(self, "retried", None):
655 self.retried = 0
656 raise
521 657
522 def http_error_auth_reqed(self, auth_header, host, req, headers): 658
523 try: 659class _KerberosAuthHandler(urllib.request.BaseHandler):
524 old_add_header = req.add_header 660 def __init__(self):
525 661 self.retried = 0
526 def _add_header(name, val): 662 self.context = None
527 val = val.replace('\n', '') 663 self.handler_order = urllib.request.BaseHandler.handler_order - 50
528 old_add_header(name, val) 664
529 req.add_header = _add_header 665 def http_error_401(self, req, fp, code, msg, headers):
530 return urllib.request.AbstractDigestAuthHandler.http_error_auth_reqed( 666 host = req.get_host()
531 self, auth_header, host, req, headers) 667 retry = self.http_error_auth_reqed(
532 except Exception: 668 "www-authenticate", host, req, headers
533 reset = getattr(self, 'reset_retry_count', None) 669 )
534 if reset is not None: 670 return retry
535 reset() 671
536 elif getattr(self, 'retried', None): 672 def http_error_auth_reqed(self, auth_header, host, req, headers):
673 try:
674 spn = "HTTP@%s" % host
675 authdata = self._negotiate_get_authdata(auth_header, headers)
676
677 if self.retried > 3:
678 raise urllib.request.HTTPError(
679 req.get_full_url(),
680 401,
681 "Negotiate auth failed",
682 headers,
683 None,
684 )
685 else:
686 self.retried += 1
687
688 neghdr = self._negotiate_get_svctk(spn, authdata)
689 if neghdr is None:
690 return None
691
692 req.add_unredirected_header("Authorization", neghdr)
693 response = self.parent.open(req)
694
695 srvauth = self._negotiate_get_authdata(auth_header, response.info())
696 if self._validate_response(srvauth):
697 return response
698 except kerberos.GSSError:
699 return None
700 except Exception:
701 self.reset_retry_count()
702 raise
703 finally:
704 self._clean_context()
705
706 def reset_retry_count(self):
537 self.retried = 0 707 self.retried = 0
538 raise
539 708
709 def _negotiate_get_authdata(self, auth_header, headers):
710 authhdr = headers.get(auth_header, None)
711 if authhdr is not None:
712 for mech_tuple in authhdr.split(","):
713 mech, __, authdata = mech_tuple.strip().partition(" ")
714 if mech.lower() == "negotiate":
715 return authdata.strip()
716 return None
540 717
541class _KerberosAuthHandler(urllib.request.BaseHandler): 718 def _negotiate_get_svctk(self, spn, authdata):
542 def __init__(self): 719 if authdata is None:
543 self.retried = 0 720 return None
544 self.context = None
545 self.handler_order = urllib.request.BaseHandler.handler_order - 50
546 721
547 def http_error_401(self, req, fp, code, msg, headers): 722 result, self.context = kerberos.authGSSClientInit(spn)
548 host = req.get_host() 723 if result < kerberos.AUTH_GSS_COMPLETE:
549 retry = self.http_error_auth_reqed('www-authenticate', host, req, headers) 724 return None
550 return retry
551 725
552 def http_error_auth_reqed(self, auth_header, host, req, headers): 726 result = kerberos.authGSSClientStep(self.context, authdata)
553 try: 727 if result < kerberos.AUTH_GSS_CONTINUE:
554 spn = "HTTP@%s" % host 728 return None
555 authdata = self._negotiate_get_authdata(auth_header, headers)
556 729
557 if self.retried > 3: 730 response = kerberos.authGSSClientResponse(self.context)
558 raise urllib.request.HTTPError(req.get_full_url(), 401, 731 return "Negotiate %s" % response
559 "Negotiate auth failed", headers, None)
560 else:
561 self.retried += 1
562 732
563 neghdr = self._negotiate_get_svctk(spn, authdata) 733 def _validate_response(self, authdata):
564 if neghdr is None: 734 if authdata is None:
735 return None
736 result = kerberos.authGSSClientStep(self.context, authdata)
737 if result == kerberos.AUTH_GSS_COMPLETE:
738 return True
565 return None 739 return None
566 740
567 req.add_unredirected_header('Authorization', neghdr) 741 def _clean_context(self):
568 response = self.parent.open(req) 742 if self.context is not None:
569 743 kerberos.authGSSClientClean(self.context)
570 srvauth = self._negotiate_get_authdata(auth_header, response.info()) 744 self.context = None
571 if self._validate_response(srvauth):
572 return response
573 except kerberos.GSSError:
574 return None
575 except Exception:
576 self.reset_retry_count()
577 raise
578 finally:
579 self._clean_context()
580
581 def reset_retry_count(self):
582 self.retried = 0
583
584 def _negotiate_get_authdata(self, auth_header, headers):
585 authhdr = headers.get(auth_header, None)
586 if authhdr is not None:
587 for mech_tuple in authhdr.split(","):
588 mech, __, authdata = mech_tuple.strip().partition(" ")
589 if mech.lower() == "negotiate":
590 return authdata.strip()
591 return None
592
593 def _negotiate_get_svctk(self, spn, authdata):
594 if authdata is None:
595 return None
596
597 result, self.context = kerberos.authGSSClientInit(spn)
598 if result < kerberos.AUTH_GSS_COMPLETE:
599 return None
600
601 result = kerberos.authGSSClientStep(self.context, authdata)
602 if result < kerberos.AUTH_GSS_CONTINUE:
603 return None
604
605 response = kerberos.authGSSClientResponse(self.context)
606 return "Negotiate %s" % response
607
608 def _validate_response(self, authdata):
609 if authdata is None:
610 return None
611 result = kerberos.authGSSClientStep(self.context, authdata)
612 if result == kerberos.AUTH_GSS_COMPLETE:
613 return True
614 return None
615
616 def _clean_context(self):
617 if self.context is not None:
618 kerberos.authGSSClientClean(self.context)
619 self.context = None
620 745
621 746
622def init_http(): 747def init_http():
623 handlers = [_UserAgentHandler()] 748 handlers = [_UserAgentHandler()]
624 749
625 mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() 750 mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
626 try: 751 try:
627 n = netrc.netrc() 752 n = netrc.netrc()
628 for host in n.hosts: 753 for host in n.hosts:
629 p = n.hosts[host] 754 p = n.hosts[host]
630 mgr.add_password(p[1], 'http://%s/' % host, p[0], p[2]) 755 mgr.add_password(p[1], "http://%s/" % host, p[0], p[2])
631 mgr.add_password(p[1], 'https://%s/' % host, p[0], p[2]) 756 mgr.add_password(p[1], "https://%s/" % host, p[0], p[2])
632 except netrc.NetrcParseError: 757 except netrc.NetrcParseError:
633 pass 758 pass
634 except IOError: 759 except IOError:
635 pass 760 pass
636 handlers.append(_BasicAuthHandler(mgr)) 761 handlers.append(_BasicAuthHandler(mgr))
637 handlers.append(_DigestAuthHandler(mgr)) 762 handlers.append(_DigestAuthHandler(mgr))
638 if kerberos: 763 if kerberos:
639 handlers.append(_KerberosAuthHandler()) 764 handlers.append(_KerberosAuthHandler())
640 765
641 if 'http_proxy' in os.environ: 766 if "http_proxy" in os.environ:
642 url = os.environ['http_proxy'] 767 url = os.environ["http_proxy"]
643 handlers.append(urllib.request.ProxyHandler({'http': url, 'https': url})) 768 handlers.append(
644 if 'REPO_CURL_VERBOSE' in os.environ: 769 urllib.request.ProxyHandler({"http": url, "https": url})
645 handlers.append(urllib.request.HTTPHandler(debuglevel=1)) 770 )
646 handlers.append(urllib.request.HTTPSHandler(debuglevel=1)) 771 if "REPO_CURL_VERBOSE" in os.environ:
647 urllib.request.install_opener(urllib.request.build_opener(*handlers)) 772 handlers.append(urllib.request.HTTPHandler(debuglevel=1))
773 handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
774 urllib.request.install_opener(urllib.request.build_opener(*handlers))
648 775
649 776
650def _Main(argv): 777def _Main(argv):
651 result = 0 778 result = 0
652 779
653 opt = optparse.OptionParser(usage="repo wrapperinfo -- ...") 780 opt = optparse.OptionParser(usage="repo wrapperinfo -- ...")
654 opt.add_option("--repo-dir", dest="repodir", 781 opt.add_option("--repo-dir", dest="repodir", help="path to .repo/")
655 help="path to .repo/") 782 opt.add_option(
656 opt.add_option("--wrapper-version", dest="wrapper_version", 783 "--wrapper-version",
657 help="version of the wrapper script") 784 dest="wrapper_version",
658 opt.add_option("--wrapper-path", dest="wrapper_path", 785 help="version of the wrapper script",
659 help="location of the wrapper script") 786 )
660 _PruneOptions(argv, opt) 787 opt.add_option(
661 opt, argv = opt.parse_args(argv) 788 "--wrapper-path",
662 789 dest="wrapper_path",
663 _CheckWrapperVersion(opt.wrapper_version, opt.wrapper_path) 790 help="location of the wrapper script",
664 _CheckRepoDir(opt.repodir) 791 )
665 792 _PruneOptions(argv, opt)
666 Version.wrapper_version = opt.wrapper_version 793 opt, argv = opt.parse_args(argv)
667 Version.wrapper_path = opt.wrapper_path 794
668 795 _CheckWrapperVersion(opt.wrapper_version, opt.wrapper_path)
669 repo = _Repo(opt.repodir) 796 _CheckRepoDir(opt.repodir)
670 797
671 try: 798 Version.wrapper_version = opt.wrapper_version
672 init_http() 799 Version.wrapper_path = opt.wrapper_path
673 name, gopts, argv = repo._ParseArgs(argv) 800
674 801 repo = _Repo(opt.repodir)
675 if gopts.trace: 802
676 SetTrace()
677
678 if gopts.trace_to_stderr:
679 SetTraceToStderr()
680
681 result = repo._Run(name, gopts, argv) or 0
682 except KeyboardInterrupt:
683 print('aborted by user', file=sys.stderr)
684 result = 1
685 except ManifestParseError as mpe:
686 print('fatal: %s' % mpe, file=sys.stderr)
687 result = 1
688 except RepoChangedException as rce:
689 # If repo changed, re-exec ourselves.
690 #
691 argv = list(sys.argv)
692 argv.extend(rce.extra_args)
693 try: 803 try:
694 os.execv(sys.executable, [__file__] + argv) 804 init_http()
695 except OSError as e: 805 name, gopts, argv = repo._ParseArgs(argv)
696 print('fatal: cannot restart repo after upgrade', file=sys.stderr)
697 print('fatal: %s' % e, file=sys.stderr)
698 result = 128
699 806
700 TerminatePager() 807 if gopts.trace:
701 sys.exit(result) 808 SetTrace()
702 809
810 if gopts.trace_to_stderr:
811 SetTraceToStderr()
703 812
704if __name__ == '__main__': 813 result = repo._Run(name, gopts, argv) or 0
705 _Main(sys.argv[1:]) 814 except KeyboardInterrupt:
815 print("aborted by user", file=sys.stderr)
816 result = 1
817 except ManifestParseError as mpe:
818 print("fatal: %s" % mpe, file=sys.stderr)
819 result = 1
820 except RepoChangedException as rce:
821 # If repo changed, re-exec ourselves.
822 #
823 argv = list(sys.argv)
824 argv.extend(rce.extra_args)
825 try:
826 os.execv(sys.executable, [__file__] + argv)
827 except OSError as e:
828 print("fatal: cannot restart repo after upgrade", file=sys.stderr)
829 print("fatal: %s" % e, file=sys.stderr)
830 result = 128
831
832 TerminatePager()
833 sys.exit(result)
834
835
836if __name__ == "__main__":
837 _Main(sys.argv[1:])