blob: 604de76f23d270c17bfc30031d2b011c81c75cc9 [file] [log] [blame]
Mike Frysingera488af52020-09-06 13:33:45 -04001#!/usr/bin/env python3
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Mike Frysinger87fb5a12019-06-13 01:54:46 -040017"""The repo tool.
18
19People shouldn't run this directly; instead, they should use the `repo` wrapper
20which takes care of execing this entry point.
21"""
22
JoonCheol Parke9860722012-10-11 02:31:44 +090023import getpass
Mike Frysinger64477332023-08-21 21:20:32 -040024import json
Shawn O. Pearcebd0312a2011-09-19 10:04:23 -070025import netrc
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070026import optparse
27import os
Mike Frysinger949bc342020-02-18 21:37:00 -050028import shlex
Jason Changc6578442023-06-22 15:04:06 -070029import signal
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070030import sys
Mike Frysinger7c321f12019-12-02 16:49:44 -050031import textwrap
Shawn O. Pearce3a0e7822011-09-22 17:06:41 -070032import time
Mike Frysingeracf63b22019-06-13 02:24:21 -040033import urllib.request
Mike Frysinger64477332023-08-21 21:20:32 -040034
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +000035from repo_logging import RepoLogger
36
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037
Carlos Aguado1242e602014-02-03 13:48:47 +010038try:
Gavin Makea2e3302023-03-11 06:46:20 +000039 import kerberos
Carlos Aguado1242e602014-02-03 13:48:47 +010040except ImportError:
Gavin Makea2e3302023-03-11 06:46:20 +000041 kerberos = None
Carlos Aguado1242e602014-02-03 13:48:47 +010042
Mike Frysinger902665b2014-12-22 15:17:59 -050043from color import SetDefaultColoring
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080044from command import InteractiveCommand
45from command import MirrorSafeCommand
Shawn O. Pearce7965f9f2008-10-29 15:20:02 -070046from editor import Editor
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070047from error import DownloadError
Mike Frysinger64477332023-08-21 21:20:32 -040048from error import GitcUnsupportedError
Jarkko Pöyry87ea5912015-06-19 15:39:25 -070049from error import InvalidProjectGroupsError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080050from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080051from error import NoManifestException
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070052from error import NoSuchProjectError
53from error import RepoChangedException
Mike Frysinger64477332023-08-21 21:20:32 -040054from error import RepoError
Jason Chang32b59562023-07-14 16:45:35 -070055from error import RepoExitError
56from error import RepoUnhandledExceptionError
Jason Chang1a3612f2023-08-08 14:12:53 -070057from error import SilentRepoExitError
Mike Frysinger64477332023-08-21 21:20:32 -040058import event_log
59from git_command import user_agent
60from git_config import RepoConfig
61from git_trace2_event_log import EventLog
Jason Chang8914b1f2023-05-26 12:44:50 -070062from manifest_xml import RepoClient
Mike Frysinger64477332023-08-21 21:20:32 -040063from pager import RunPager
64from pager import TerminatePager
65from repo_trace import SetTrace
66from repo_trace import SetTraceToStderr
67from repo_trace import Trace
David Pursehouse5c6eeac2012-10-11 16:44:48 +090068from subcmds import all_commands
Mike Frysinger64477332023-08-21 21:20:32 -040069from subcmds.version import Version
70from wrapper import Wrapper
71from wrapper import WrapperPath
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070072
Chirayu Desai217ea7d2013-03-01 19:14:38 +053073
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +000074logger = RepoLogger(__file__)
75
76
Mike Frysinger37f28f12020-02-16 15:15:53 -050077# NB: These do not need to be kept in sync with the repo launcher script.
78# These may be much newer as it allows the repo launcher to roll between
79# different repo releases while source versions might require a newer python.
80#
81# The soft version is when we start warning users that the version is old and
82# we'll be dropping support for it. We'll refuse to work with versions older
83# than the hard version.
84#
85# python-3.6 is in Ubuntu Bionic.
86MIN_PYTHON_VERSION_SOFT = (3, 6)
Peter Kjellerstedta3b2edf2021-04-15 01:32:40 +020087MIN_PYTHON_VERSION_HARD = (3, 6)
Mike Frysinger37f28f12020-02-16 15:15:53 -050088
Mike Frysinger8f4f9852023-10-14 01:10:29 +054589if sys.version_info < MIN_PYTHON_VERSION_HARD:
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +000090 logger.error(
Mike Frysinger8f4f9852023-10-14 01:10:29 +054591 "repo: error: Python version is too old; "
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +000092 "Please upgrade to Python %d.%d+.",
93 *MIN_PYTHON_VERSION_SOFT,
Gavin Makea2e3302023-03-11 06:46:20 +000094 )
Mike Frysinger37f28f12020-02-16 15:15:53 -050095 sys.exit(1)
Mike Frysinger8f4f9852023-10-14 01:10:29 +054596elif sys.version_info < MIN_PYTHON_VERSION_SOFT:
97 logger.error(
98 "repo: warning: your Python version is no longer supported; "
99 "Please upgrade to Python %d.%d+.",
100 *MIN_PYTHON_VERSION_SOFT,
101 )
Mike Frysinger37f28f12020-02-16 15:15:53 -0500102
Jason Changc6578442023-06-22 15:04:06 -0700103KEYBOARD_INTERRUPT_EXIT = 128 + signal.SIGINT
Jason Chang32b59562023-07-14 16:45:35 -0700104MAX_PRINT_ERRORS = 5
Mike Frysinger37f28f12020-02-16 15:15:53 -0500105
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700106global_options = optparse.OptionParser(
Gavin Makea2e3302023-03-11 06:46:20 +0000107 usage="repo [-p|--paginate|--no-pager] COMMAND [ARGS]",
108 add_help_option=False,
109)
110global_options.add_option(
111 "-h", "--help", action="store_true", help="show this help message and exit"
112)
113global_options.add_option(
114 "--help-all",
115 action="store_true",
116 help="show this help message with all subcommands and exit",
117)
118global_options.add_option(
119 "-p",
120 "--paginate",
121 dest="pager",
122 action="store_true",
123 help="display command output in the pager",
124)
125global_options.add_option(
126 "--no-pager", dest="pager", action="store_false", help="disable the pager"
127)
128global_options.add_option(
129 "--color",
130 choices=("auto", "always", "never"),
131 default=None,
132 help="control color usage: auto, always, never",
133)
134global_options.add_option(
135 "--trace",
136 dest="trace",
137 action="store_true",
138 help="trace git command execution (REPO_TRACE=1)",
139)
140global_options.add_option(
141 "--trace-to-stderr",
142 dest="trace_to_stderr",
143 action="store_true",
144 help="trace outputs go to stderr in addition to .repo/TRACE_FILE",
145)
146global_options.add_option(
147 "--trace-python",
148 dest="trace_python",
149 action="store_true",
150 help="trace python command execution",
151)
152global_options.add_option(
153 "--time",
154 dest="time",
155 action="store_true",
156 help="time repo command execution",
157)
158global_options.add_option(
159 "--version",
160 dest="show_version",
161 action="store_true",
162 help="display this version of repo",
163)
164global_options.add_option(
165 "--show-toplevel",
166 action="store_true",
167 help="display the path of the top-level directory of "
168 "the repo client checkout",
169)
170global_options.add_option(
171 "--event-log",
172 dest="event_log",
173 action="store",
174 help="filename of event log to append timeline to",
175)
176global_options.add_option(
177 "--git-trace2-event-log",
178 action="store",
179 help="directory to write git trace2 event log to",
180)
181global_options.add_option(
182 "--submanifest-path",
183 action="store",
184 metavar="REL_PATH",
185 help="submanifest path",
186)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700187
David Pursehouse819827a2020-02-12 15:20:19 +0900188
Mike Frysingerd4aee652023-10-19 05:13:32 -0400189class _Repo:
Gavin Makea2e3302023-03-11 06:46:20 +0000190 def __init__(self, repodir):
191 self.repodir = repodir
192 self.commands = all_commands
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700193
Gavin Makea2e3302023-03-11 06:46:20 +0000194 def _PrintHelp(self, short: bool = False, all_commands: bool = False):
195 """Show --help screen."""
196 global_options.print_help()
197 print()
198 if short:
199 commands = " ".join(sorted(self.commands))
200 wrapped_commands = textwrap.wrap(commands, width=77)
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400201 help_commands = "".join(f"\n {x}" for x in wrapped_commands)
202 print(f"Available commands:{help_commands}")
Gavin Makea2e3302023-03-11 06:46:20 +0000203 print("\nRun `repo help <command>` for command-specific details.")
204 print("Bug reports:", Wrapper().BUG_URL)
Conley Owens7ba25be2012-11-14 14:18:06 -0800205 else:
Gavin Makea2e3302023-03-11 06:46:20 +0000206 cmd = self.commands["help"]()
207 if all_commands:
208 cmd.PrintAllCommandsBody()
209 else:
210 cmd.PrintCommonCommandsBody()
Daniel Sandler3ce2a6b2011-04-29 09:59:12 -0400211
Gavin Makea2e3302023-03-11 06:46:20 +0000212 def _ParseArgs(self, argv):
213 """Parse the main `repo` command line options."""
214 for i, arg in enumerate(argv):
215 if not arg.startswith("-"):
216 name = arg
217 glob = argv[:i]
218 argv = argv[i + 1 :]
219 break
220 else:
221 name = None
222 glob = argv
223 argv = []
224 gopts, _gargs = global_options.parse_args(glob)
Ian Kasprzak30bc3542020-12-23 10:08:20 -0800225
Gavin Makea2e3302023-03-11 06:46:20 +0000226 if name:
227 name, alias_args = self._ExpandAlias(name)
228 argv = alias_args + argv
David Rileye0684ad2017-04-05 00:02:59 -0700229
Gavin Makea2e3302023-03-11 06:46:20 +0000230 return (name, gopts, argv)
231
232 def _ExpandAlias(self, name):
233 """Look up user registered aliases."""
234 # We don't resolve aliases for existing subcommands. This matches git.
235 if name in self.commands:
236 return name, []
237
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400238 key = f"alias.{name}"
Gavin Makea2e3302023-03-11 06:46:20 +0000239 alias = RepoConfig.ForRepository(self.repodir).GetString(key)
240 if alias is None:
241 alias = RepoConfig.ForUser().GetString(key)
242 if alias is None:
243 return name, []
244
245 args = alias.strip().split(" ", 1)
246 name = args[0]
247 if len(args) == 2:
248 args = shlex.split(args[1])
249 else:
250 args = []
251 return name, args
252
253 def _Run(self, name, gopts, argv):
254 """Execute the requested subcommand."""
255 result = 0
256
257 # Handle options that terminate quickly first.
258 if gopts.help or gopts.help_all:
259 self._PrintHelp(short=False, all_commands=gopts.help_all)
260 return 0
261 elif gopts.show_version:
262 # Always allow global --version regardless of subcommand validity.
263 name = "version"
264 elif gopts.show_toplevel:
265 print(os.path.dirname(self.repodir))
266 return 0
267 elif not name:
268 # No subcommand specified, so show the help/subcommand.
269 self._PrintHelp(short=True)
270 return 1
271
272 run = lambda: self._RunLong(name, gopts, argv) or 0
273 with Trace(
274 "starting new command: %s",
275 ", ".join([name] + argv),
276 first_trace=True,
277 ):
278 if gopts.trace_python:
279 import trace
280
281 tracer = trace.Trace(
282 count=False,
283 trace=True,
284 timing=True,
285 ignoredirs=set(sys.path[1:]),
286 )
287 result = tracer.runfunc(run)
288 else:
289 result = run()
290 return result
291
292 def _RunLong(self, name, gopts, argv):
293 """Execute the (longer running) requested subcommand."""
294 result = 0
295 SetDefaultColoring(gopts.color)
296
297 git_trace2_event_log = EventLog()
298 outer_client = RepoClient(self.repodir)
299 repo_client = outer_client
300 if gopts.submanifest_path:
301 repo_client = RepoClient(
302 self.repodir,
303 submanifest_path=gopts.submanifest_path,
304 outer_client=outer_client,
305 )
Jason Chang8914b1f2023-05-26 12:44:50 -0700306
307 if Wrapper().gitc_parse_clientdir(os.getcwd()):
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000308 logger.error("GITC is not supported.")
Jason Chang8914b1f2023-05-26 12:44:50 -0700309 raise GitcUnsupportedError()
Gavin Makea2e3302023-03-11 06:46:20 +0000310
311 try:
312 cmd = self.commands[name](
313 repodir=self.repodir,
314 client=repo_client,
315 manifest=repo_client.manifest,
316 outer_client=outer_client,
317 outer_manifest=outer_client.manifest,
Gavin Makea2e3302023-03-11 06:46:20 +0000318 git_event_log=git_trace2_event_log,
319 )
320 except KeyError:
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000321 logger.error(
322 "repo: '%s' is not a repo command. See 'repo help'.", name
Gavin Makea2e3302023-03-11 06:46:20 +0000323 )
324 return 1
325
326 Editor.globalConfig = cmd.client.globalConfig
327
328 if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror:
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000329 logger.error("fatal: '%s' requires a working directory", name)
Gavin Makea2e3302023-03-11 06:46:20 +0000330 return 1
331
Gavin Makea2e3302023-03-11 06:46:20 +0000332 try:
333 copts, cargs = cmd.OptionParser.parse_args(argv)
334 copts = cmd.ReadEnvironmentOptions(copts)
335 except NoManifestException as e:
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000336 logger.error("error: in `%s`: %s", " ".join([name] + argv), e)
337 logger.error(
338 "error: manifest missing or unreadable -- please run init"
Gavin Makea2e3302023-03-11 06:46:20 +0000339 )
340 return 1
341
342 if gopts.pager is not False and not isinstance(cmd, InteractiveCommand):
343 config = cmd.client.globalConfig
344 if gopts.pager:
345 use_pager = True
346 else:
347 use_pager = config.GetBoolean("pager.%s" % name)
348 if use_pager is None:
349 use_pager = cmd.WantPager(copts)
350 if use_pager:
351 RunPager(config)
352
353 start = time.time()
354 cmd_event = cmd.event_log.Add(name, event_log.TASK_COMMAND, start)
355 cmd.event_log.SetParent(cmd_event)
356 git_trace2_event_log.StartEvent()
357 git_trace2_event_log.CommandEvent(name="repo", subcommands=[name])
358
Jason Changc6578442023-06-22 15:04:06 -0700359 def execute_command_helper():
360 """
361 Execute the subcommand.
362 """
363 nonlocal result
Gavin Makea2e3302023-03-11 06:46:20 +0000364 cmd.CommonValidateOptions(copts, cargs)
365 cmd.ValidateOptions(copts, cargs)
366
367 this_manifest_only = copts.this_manifest_only
368 outer_manifest = copts.outer_manifest
369 if cmd.MULTI_MANIFEST_SUPPORT or this_manifest_only:
370 result = cmd.Execute(copts, cargs)
371 elif outer_manifest and repo_client.manifest.is_submanifest:
372 # The command does not support multi-manifest, we are using a
373 # submanifest, and the command line is for the outermost
374 # manifest. Re-run using the outermost manifest, which will
375 # recurse through the submanifests.
376 gopts.submanifest_path = ""
377 result = self._Run(name, gopts, argv)
378 else:
379 # No multi-manifest support. Run the command in the current
380 # (sub)manifest, and then any child submanifests.
381 result = cmd.Execute(copts, cargs)
382 for submanifest in repo_client.manifest.submanifests.values():
383 spec = submanifest.ToSubmanifestSpec()
384 gopts.submanifest_path = submanifest.repo_client.path_prefix
385 child_argv = argv[:]
386 child_argv.append("--no-outer-manifest")
387 # Not all subcommands support the 3 manifest options, so
388 # only add them if the original command includes them.
389 if hasattr(copts, "manifest_url"):
390 child_argv.extend(["--manifest-url", spec.manifestUrl])
391 if hasattr(copts, "manifest_name"):
392 child_argv.extend(
393 ["--manifest-name", spec.manifestName]
394 )
395 if hasattr(copts, "manifest_branch"):
396 child_argv.extend(["--manifest-branch", spec.revision])
397 result = self._Run(name, gopts, child_argv) or result
Jason Changc6578442023-06-22 15:04:06 -0700398
399 def execute_command():
400 """
401 Execute the command and log uncaught exceptions.
402 """
403 try:
404 execute_command_helper()
Jason Chang32b59562023-07-14 16:45:35 -0700405 except (
406 KeyboardInterrupt,
407 SystemExit,
408 Exception,
409 RepoExitError,
410 ) as e:
Jason Changc6578442023-06-22 15:04:06 -0700411 ok = isinstance(e, SystemExit) and not e.code
Jason Chang32b59562023-07-14 16:45:35 -0700412 exception_name = type(e).__name__
413 if isinstance(e, RepoUnhandledExceptionError):
414 exception_name = type(e.error).__name__
415 if isinstance(e, RepoExitError):
416 aggregated_errors = e.aggregate_errors or []
417 for error in aggregated_errors:
418 project = None
419 if isinstance(error, RepoError):
420 project = error.project
421 error_info = json.dumps(
422 {
423 "ErrorType": type(error).__name__,
424 "Project": project,
425 "Message": str(error),
426 }
427 )
428 git_trace2_event_log.ErrorEvent(
429 f"AggregateExitError:{error_info}"
430 )
Jason Changc6578442023-06-22 15:04:06 -0700431 if not ok:
Jason Changc6578442023-06-22 15:04:06 -0700432 git_trace2_event_log.ErrorEvent(
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000433 f"RepoExitError:{exception_name}"
434 )
Jason Changc6578442023-06-22 15:04:06 -0700435 raise
436
437 try:
438 execute_command()
Gavin Makea2e3302023-03-11 06:46:20 +0000439 except (
440 DownloadError,
441 ManifestInvalidRevisionError,
442 NoManifestException,
443 ) as e:
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000444 logger.error("error: in `%s`: %s", " ".join([name] + argv), e)
Gavin Makea2e3302023-03-11 06:46:20 +0000445 if isinstance(e, NoManifestException):
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000446 logger.error(
447 "error: manifest missing or unreadable -- please run init"
Gavin Makea2e3302023-03-11 06:46:20 +0000448 )
Jason Chang32b59562023-07-14 16:45:35 -0700449 result = e.exit_code
Gavin Makea2e3302023-03-11 06:46:20 +0000450 except NoSuchProjectError as e:
451 if e.name:
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000452 logger.error("error: project %s not found", e.name)
Gavin Makea2e3302023-03-11 06:46:20 +0000453 else:
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000454 logger.error("error: no project in current directory")
Jason Chang32b59562023-07-14 16:45:35 -0700455 result = e.exit_code
Gavin Makea2e3302023-03-11 06:46:20 +0000456 except InvalidProjectGroupsError as e:
457 if e.name:
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000458 logger.error(
459 "error: project group must be enabled for project %s",
460 e.name,
Gavin Makea2e3302023-03-11 06:46:20 +0000461 )
462 else:
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000463 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +0000464 "error: project group must be enabled for the project in "
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000465 "the current directory"
Gavin Makea2e3302023-03-11 06:46:20 +0000466 )
Jason Chang32b59562023-07-14 16:45:35 -0700467 result = e.exit_code
Gavin Makea2e3302023-03-11 06:46:20 +0000468 except SystemExit as e:
469 if e.code:
470 result = e.code
471 raise
Jason Changc6578442023-06-22 15:04:06 -0700472 except KeyboardInterrupt:
473 result = KEYBOARD_INTERRUPT_EXIT
474 raise
Jason Chang32b59562023-07-14 16:45:35 -0700475 except RepoExitError as e:
476 result = e.exit_code
477 raise
Jason Changc6578442023-06-22 15:04:06 -0700478 except Exception:
479 result = 1
480 raise
Gavin Makea2e3302023-03-11 06:46:20 +0000481 finally:
482 finish = time.time()
483 elapsed = finish - start
484 hours, remainder = divmod(elapsed, 3600)
485 minutes, seconds = divmod(remainder, 60)
486 if gopts.time:
487 if hours == 0:
488 print(
489 "real\t%dm%.3fs" % (minutes, seconds), file=sys.stderr
490 )
491 else:
492 print(
493 "real\t%dh%dm%.3fs" % (hours, minutes, seconds),
494 file=sys.stderr,
495 )
496
497 cmd.event_log.FinishEvent(
498 cmd_event, finish, result is None or result == 0
499 )
500 git_trace2_event_log.DefParamRepoEvents(
501 cmd.manifest.manifestProject.config.DumpConfigDict()
502 )
503 git_trace2_event_log.ExitEvent(result)
504
505 if gopts.event_log:
506 cmd.event_log.Write(
507 os.path.abspath(os.path.expanduser(gopts.event_log))
508 )
509
510 git_trace2_event_log.Write(gopts.git_trace2_event_log)
511 return result
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700512
Conley Owens094cdbe2014-01-30 15:09:59 -0800513
Mike Frysinger3285e4b2020-02-10 17:34:49 -0500514def _CheckWrapperVersion(ver_str, repo_path):
Gavin Makea2e3302023-03-11 06:46:20 +0000515 """Verify the repo launcher is new enough for this checkout.
Mike Frysinger3285e4b2020-02-10 17:34:49 -0500516
Gavin Makea2e3302023-03-11 06:46:20 +0000517 Args:
518 ver_str: The version string passed from the repo launcher when it ran
519 us.
520 repo_path: The path to the repo launcher that loaded us.
521 """
522 # Refuse to work with really old wrapper versions. We don't test these,
523 # so might as well require a somewhat recent sane version.
524 # v1.15 of the repo launcher was released in ~Mar 2012.
525 MIN_REPO_VERSION = (1, 15)
526 min_str = ".".join(str(x) for x in MIN_REPO_VERSION)
Mike Frysinger3285e4b2020-02-10 17:34:49 -0500527
Gavin Makea2e3302023-03-11 06:46:20 +0000528 if not repo_path:
529 repo_path = "~/bin/repo"
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700530
Gavin Makea2e3302023-03-11 06:46:20 +0000531 if not ver_str:
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000532 logger.error("no --wrapper-version argument")
Gavin Makea2e3302023-03-11 06:46:20 +0000533 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700534
Gavin Makea2e3302023-03-11 06:46:20 +0000535 # Pull out the version of the repo launcher we know about to compare.
536 exp = Wrapper().VERSION
537 ver = tuple(map(int, ver_str.split(".")))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700538
Gavin Makea2e3302023-03-11 06:46:20 +0000539 exp_str = ".".join(map(str, exp))
540 if ver < MIN_REPO_VERSION:
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000541 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +0000542 """
Mike Frysinger3285e4b2020-02-10 17:34:49 -0500543repo: error:
544!!! Your version of repo %s is too old.
545!!! We need at least version %s.
David Pursehouse7838e382020-02-13 09:54:49 +0900546!!! A new version of repo (%s) is available.
Mike Frysinger3285e4b2020-02-10 17:34:49 -0500547!!! You must upgrade before you can continue:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700548
549 cp %s %s
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000550""",
551 ver_str,
552 min_str,
553 exp_str,
554 WrapperPath(),
555 repo_path,
Gavin Makea2e3302023-03-11 06:46:20 +0000556 )
557 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700558
Gavin Makea2e3302023-03-11 06:46:20 +0000559 if exp > ver:
Aravind Vasudevan8bc50002023-10-13 19:22:47 +0000560 logger.warning(
561 "\n... A new version of repo (%s) is available.", exp_str
562 )
Gavin Makea2e3302023-03-11 06:46:20 +0000563 if os.access(repo_path, os.W_OK):
Aravind Vasudevan8bc50002023-10-13 19:22:47 +0000564 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +0000565 """\
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700566... You should upgrade soon:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700567 cp %s %s
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000568""",
569 WrapperPath(),
570 repo_path,
Gavin Makea2e3302023-03-11 06:46:20 +0000571 )
572 else:
Aravind Vasudevan8bc50002023-10-13 19:22:47 +0000573 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +0000574 """\
Mike Frysingereea23b42020-02-26 16:21:08 -0500575... New version is available at: %s
576... The launcher is run from: %s
577!!! The launcher is not writable. Please talk to your sysadmin or distro
578!!! to get an update installed.
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000579""",
580 WrapperPath(),
581 repo_path,
Gavin Makea2e3302023-03-11 06:46:20 +0000582 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700583
David Pursehouse819827a2020-02-12 15:20:19 +0900584
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200585def _CheckRepoDir(repo_dir):
Gavin Makea2e3302023-03-11 06:46:20 +0000586 if not repo_dir:
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000587 logger.error("no --repo-dir argument")
Gavin Makea2e3302023-03-11 06:46:20 +0000588 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700589
David Pursehouse819827a2020-02-12 15:20:19 +0900590
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700591def _PruneOptions(argv, opt):
Gavin Makea2e3302023-03-11 06:46:20 +0000592 i = 0
593 while i < len(argv):
594 a = argv[i]
595 if a == "--":
596 break
597 if a.startswith("--"):
598 eq = a.find("=")
599 if eq > 0:
600 a = a[0:eq]
601 if not opt.has_option(a):
602 del argv[i]
603 continue
604 i += 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700605
David Pursehouse819827a2020-02-12 15:20:19 +0900606
Sarah Owens1f7627f2012-10-31 09:21:55 -0700607class _UserAgentHandler(urllib.request.BaseHandler):
Gavin Makea2e3302023-03-11 06:46:20 +0000608 def http_request(self, req):
609 req.add_header("User-Agent", user_agent.repo)
610 return req
Shawn O. Pearce334851e2011-09-19 08:05:31 -0700611
Gavin Makea2e3302023-03-11 06:46:20 +0000612 def https_request(self, req):
613 req.add_header("User-Agent", user_agent.repo)
614 return req
Shawn O. Pearce334851e2011-09-19 08:05:31 -0700615
David Pursehouse819827a2020-02-12 15:20:19 +0900616
JoonCheol Parke9860722012-10-11 02:31:44 +0900617def _AddPasswordFromUserInput(handler, msg, req):
Gavin Makea2e3302023-03-11 06:46:20 +0000618 # If repo could not find auth info from netrc, try to get it from user input
619 url = req.get_full_url()
620 user, password = handler.passwd.find_user_password(None, url)
621 if user is None:
622 print(msg)
623 try:
624 user = input("User: ")
625 password = getpass.getpass()
626 except KeyboardInterrupt:
627 return
628 handler.passwd.add_password(None, url, user, password)
JoonCheol Parke9860722012-10-11 02:31:44 +0900629
David Pursehouse819827a2020-02-12 15:20:19 +0900630
Sarah Owens1f7627f2012-10-31 09:21:55 -0700631class _BasicAuthHandler(urllib.request.HTTPBasicAuthHandler):
Gavin Makea2e3302023-03-11 06:46:20 +0000632 def http_error_401(self, req, fp, code, msg, headers):
633 _AddPasswordFromUserInput(self, msg, req)
634 return urllib.request.HTTPBasicAuthHandler.http_error_401(
635 self, req, fp, code, msg, headers
636 )
JoonCheol Parke9860722012-10-11 02:31:44 +0900637
Gavin Makea2e3302023-03-11 06:46:20 +0000638 def http_error_auth_reqed(self, authreq, host, req, headers):
639 try:
640 old_add_header = req.add_header
David Pursehouse819827a2020-02-12 15:20:19 +0900641
Gavin Makea2e3302023-03-11 06:46:20 +0000642 def _add_header(name, val):
643 val = val.replace("\n", "")
644 old_add_header(name, val)
645
646 req.add_header = _add_header
647 return (
648 urllib.request.AbstractBasicAuthHandler.http_error_auth_reqed(
649 self, authreq, host, req, headers
650 )
651 )
652 except Exception:
653 reset = getattr(self, "reset_retry_count", None)
654 if reset is not None:
655 reset()
656 elif getattr(self, "retried", None):
657 self.retried = 0
658 raise
Shawn O. Pearcefab96c62011-10-11 12:00:38 -0700659
David Pursehouse819827a2020-02-12 15:20:19 +0900660
Sarah Owens1f7627f2012-10-31 09:21:55 -0700661class _DigestAuthHandler(urllib.request.HTTPDigestAuthHandler):
Gavin Makea2e3302023-03-11 06:46:20 +0000662 def http_error_401(self, req, fp, code, msg, headers):
663 _AddPasswordFromUserInput(self, msg, req)
664 return urllib.request.HTTPDigestAuthHandler.http_error_401(
665 self, req, fp, code, msg, headers
666 )
JoonCheol Parke9860722012-10-11 02:31:44 +0900667
Gavin Makea2e3302023-03-11 06:46:20 +0000668 def http_error_auth_reqed(self, auth_header, host, req, headers):
669 try:
670 old_add_header = req.add_header
David Pursehouse819827a2020-02-12 15:20:19 +0900671
Gavin Makea2e3302023-03-11 06:46:20 +0000672 def _add_header(name, val):
673 val = val.replace("\n", "")
674 old_add_header(name, val)
675
676 req.add_header = _add_header
677 return (
678 urllib.request.AbstractDigestAuthHandler.http_error_auth_reqed(
679 self, auth_header, host, req, headers
680 )
681 )
682 except Exception:
683 reset = getattr(self, "reset_retry_count", None)
684 if reset is not None:
685 reset()
686 elif getattr(self, "retried", None):
687 self.retried = 0
688 raise
Xiaodong Xuae0a36c2012-01-31 11:10:09 +0800689
David Pursehouse819827a2020-02-12 15:20:19 +0900690
Carlos Aguado1242e602014-02-03 13:48:47 +0100691class _KerberosAuthHandler(urllib.request.BaseHandler):
Gavin Makea2e3302023-03-11 06:46:20 +0000692 def __init__(self):
693 self.retried = 0
694 self.context = None
695 self.handler_order = urllib.request.BaseHandler.handler_order - 50
Carlos Aguado1242e602014-02-03 13:48:47 +0100696
Gavin Makea2e3302023-03-11 06:46:20 +0000697 def http_error_401(self, req, fp, code, msg, headers):
698 host = req.get_host()
699 retry = self.http_error_auth_reqed(
700 "www-authenticate", host, req, headers
701 )
702 return retry
Carlos Aguado1242e602014-02-03 13:48:47 +0100703
Gavin Makea2e3302023-03-11 06:46:20 +0000704 def http_error_auth_reqed(self, auth_header, host, req, headers):
705 try:
706 spn = "HTTP@%s" % host
707 authdata = self._negotiate_get_authdata(auth_header, headers)
Carlos Aguado1242e602014-02-03 13:48:47 +0100708
Gavin Makea2e3302023-03-11 06:46:20 +0000709 if self.retried > 3:
710 raise urllib.request.HTTPError(
711 req.get_full_url(),
712 401,
713 "Negotiate auth failed",
714 headers,
715 None,
716 )
717 else:
718 self.retried += 1
Carlos Aguado1242e602014-02-03 13:48:47 +0100719
Gavin Makea2e3302023-03-11 06:46:20 +0000720 neghdr = self._negotiate_get_svctk(spn, authdata)
721 if neghdr is None:
722 return None
723
724 req.add_unredirected_header("Authorization", neghdr)
725 response = self.parent.open(req)
726
727 srvauth = self._negotiate_get_authdata(auth_header, response.info())
728 if self._validate_response(srvauth):
729 return response
730 except kerberos.GSSError:
731 return None
732 except Exception:
733 self.reset_retry_count()
734 raise
735 finally:
736 self._clean_context()
737
738 def reset_retry_count(self):
739 self.retried = 0
740
741 def _negotiate_get_authdata(self, auth_header, headers):
742 authhdr = headers.get(auth_header, None)
743 if authhdr is not None:
744 for mech_tuple in authhdr.split(","):
745 mech, __, authdata = mech_tuple.strip().partition(" ")
746 if mech.lower() == "negotiate":
747 return authdata.strip()
Carlos Aguado1242e602014-02-03 13:48:47 +0100748 return None
749
Gavin Makea2e3302023-03-11 06:46:20 +0000750 def _negotiate_get_svctk(self, spn, authdata):
751 if authdata is None:
752 return None
Carlos Aguado1242e602014-02-03 13:48:47 +0100753
Gavin Makea2e3302023-03-11 06:46:20 +0000754 result, self.context = kerberos.authGSSClientInit(spn)
755 if result < kerberos.AUTH_GSS_COMPLETE:
756 return None
Carlos Aguado1242e602014-02-03 13:48:47 +0100757
Gavin Makea2e3302023-03-11 06:46:20 +0000758 result = kerberos.authGSSClientStep(self.context, authdata)
759 if result < kerberos.AUTH_GSS_CONTINUE:
760 return None
Carlos Aguado1242e602014-02-03 13:48:47 +0100761
Gavin Makea2e3302023-03-11 06:46:20 +0000762 response = kerberos.authGSSClientResponse(self.context)
763 return "Negotiate %s" % response
Carlos Aguado1242e602014-02-03 13:48:47 +0100764
Gavin Makea2e3302023-03-11 06:46:20 +0000765 def _validate_response(self, authdata):
766 if authdata is None:
767 return None
768 result = kerberos.authGSSClientStep(self.context, authdata)
769 if result == kerberos.AUTH_GSS_COMPLETE:
770 return True
771 return None
Carlos Aguado1242e602014-02-03 13:48:47 +0100772
Gavin Makea2e3302023-03-11 06:46:20 +0000773 def _clean_context(self):
774 if self.context is not None:
775 kerberos.authGSSClientClean(self.context)
776 self.context = None
Carlos Aguado1242e602014-02-03 13:48:47 +0100777
David Pursehouse819827a2020-02-12 15:20:19 +0900778
Shawn O. Pearce014d0602011-09-11 12:57:15 -0700779def init_http():
Gavin Makea2e3302023-03-11 06:46:20 +0000780 handlers = [_UserAgentHandler()]
Shawn O. Pearce334851e2011-09-19 08:05:31 -0700781
Gavin Makea2e3302023-03-11 06:46:20 +0000782 mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
783 try:
784 n = netrc.netrc()
785 for host in n.hosts:
786 p = n.hosts[host]
787 mgr.add_password(p[1], "http://%s/" % host, p[0], p[2])
788 mgr.add_password(p[1], "https://%s/" % host, p[0], p[2])
789 except netrc.NetrcParseError:
790 pass
Jason R. Coombsae824fb2023-10-20 23:32:40 +0545791 except OSError:
Gavin Makea2e3302023-03-11 06:46:20 +0000792 pass
793 handlers.append(_BasicAuthHandler(mgr))
794 handlers.append(_DigestAuthHandler(mgr))
795 if kerberos:
796 handlers.append(_KerberosAuthHandler())
Shawn O. Pearcebd0312a2011-09-19 10:04:23 -0700797
Gavin Makea2e3302023-03-11 06:46:20 +0000798 if "http_proxy" in os.environ:
799 url = os.environ["http_proxy"]
800 handlers.append(
801 urllib.request.ProxyHandler({"http": url, "https": url})
802 )
803 if "REPO_CURL_VERBOSE" in os.environ:
804 handlers.append(urllib.request.HTTPHandler(debuglevel=1))
805 handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
806 urllib.request.install_opener(urllib.request.build_opener(*handlers))
Shawn O. Pearce014d0602011-09-11 12:57:15 -0700807
David Pursehouse819827a2020-02-12 15:20:19 +0900808
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700809def _Main(argv):
Gavin Makea2e3302023-03-11 06:46:20 +0000810 result = 0
Daniel Sandler3ce2a6b2011-04-29 09:59:12 -0400811
Gavin Makea2e3302023-03-11 06:46:20 +0000812 opt = optparse.OptionParser(usage="repo wrapperinfo -- ...")
813 opt.add_option("--repo-dir", dest="repodir", help="path to .repo/")
814 opt.add_option(
815 "--wrapper-version",
816 dest="wrapper_version",
817 help="version of the wrapper script",
818 )
819 opt.add_option(
820 "--wrapper-path",
821 dest="wrapper_path",
822 help="location of the wrapper script",
823 )
824 _PruneOptions(argv, opt)
825 opt, argv = opt.parse_args(argv)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700826
Gavin Makea2e3302023-03-11 06:46:20 +0000827 _CheckWrapperVersion(opt.wrapper_version, opt.wrapper_path)
828 _CheckRepoDir(opt.repodir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700829
Gavin Makea2e3302023-03-11 06:46:20 +0000830 Version.wrapper_version = opt.wrapper_version
831 Version.wrapper_path = opt.wrapper_path
Shawn O. Pearceecff4f12011-11-29 15:01:33 -0800832
Gavin Makea2e3302023-03-11 06:46:20 +0000833 repo = _Repo(opt.repodir)
Joanna Wanga6c52f52022-11-03 16:51:19 -0400834
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700835 try:
Gavin Makea2e3302023-03-11 06:46:20 +0000836 init_http()
837 name, gopts, argv = repo._ParseArgs(argv)
Daniel Sandler3ce2a6b2011-04-29 09:59:12 -0400838
Gavin Makea2e3302023-03-11 06:46:20 +0000839 if gopts.trace:
840 SetTrace()
841
842 if gopts.trace_to_stderr:
843 SetTraceToStderr()
844
845 result = repo._Run(name, gopts, argv) or 0
Jason Chang32b59562023-07-14 16:45:35 -0700846 except RepoExitError as e:
Jason Chang1a3612f2023-08-08 14:12:53 -0700847 if not isinstance(e, SilentRepoExitError):
Aravind Vasudevanb8fd1922023-09-14 22:54:04 +0000848 logger.log_aggregated_errors(e)
Jason Chang32b59562023-07-14 16:45:35 -0700849 result = e.exit_code
Gavin Makea2e3302023-03-11 06:46:20 +0000850 except KeyboardInterrupt:
851 print("aborted by user", file=sys.stderr)
Jason Changc6578442023-06-22 15:04:06 -0700852 result = KEYBOARD_INTERRUPT_EXIT
Gavin Makea2e3302023-03-11 06:46:20 +0000853 except RepoChangedException as rce:
854 # If repo changed, re-exec ourselves.
Gavin Makea2e3302023-03-11 06:46:20 +0000855 argv = list(sys.argv)
856 argv.extend(rce.extra_args)
857 try:
Gavin Makf1ddaaa2023-08-04 21:13:38 +0000858 os.execv(sys.executable, [sys.executable, __file__] + argv)
Gavin Makea2e3302023-03-11 06:46:20 +0000859 except OSError as e:
860 print("fatal: cannot restart repo after upgrade", file=sys.stderr)
861 print("fatal: %s" % e, file=sys.stderr)
862 result = 128
863
864 TerminatePager()
865 sys.exit(result)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700866
David Pursehouse819827a2020-02-12 15:20:19 +0900867
Gavin Makea2e3302023-03-11 06:46:20 +0000868if __name__ == "__main__":
869 _Main(sys.argv[1:])