| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # -*- coding: utf-8 -*- | 2 # -*- coding: utf-8 -*- |
| 3 # Copyright 2013 Google Inc. All Rights Reserved. | 3 # Copyright 2013 Google Inc. All Rights Reserved. |
| 4 # | 4 # |
| 5 # Licensed under the Apache License, Version 2.0 (the "License"); | 5 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 # you may not use this file except in compliance with 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 | 7 # You may obtain a copy of the License at |
| 8 # | 8 # |
| 9 # http://www.apache.org/licenses/LICENSE-2.0 | 9 # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 # | 10 # |
| (...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 96 DEBUG_WARNING = """ | 96 DEBUG_WARNING = """ |
| 97 ***************************** WARNING ***************************** | 97 ***************************** WARNING ***************************** |
| 98 *** You are running gsutil with debug output enabled. | 98 *** You are running gsutil with debug output enabled. |
| 99 *** Be aware that debug output includes authentication credentials. | 99 *** Be aware that debug output includes authentication credentials. |
| 100 *** Make sure to remove the value of the Authorization header for | 100 *** Make sure to remove the value of the Authorization header for |
| 101 *** each HTTP request printed to the console prior to posting to | 101 *** each HTTP request printed to the console prior to posting to |
| 102 *** a public medium such as a forum post or Stack Overflow. | 102 *** a public medium such as a forum post or Stack Overflow. |
| 103 ***************************** WARNING ***************************** | 103 ***************************** WARNING ***************************** |
| 104 """.lstrip() | 104 """.lstrip() |
| 105 | 105 |
| 106 TRACE_WARNING = """ |
| 107 ***************************** WARNING ***************************** |
| 108 *** You are running gsutil with trace output enabled. |
| 109 *** Be aware that trace output includes authentication credentials |
| 110 *** and may include the contents of any files accessed during the trace. |
| 111 ***************************** WARNING ***************************** |
| 112 """.lstrip() |
| 113 |
| 106 HTTP_WARNING = """ | 114 HTTP_WARNING = """ |
| 107 ***************************** WARNING ***************************** | 115 ***************************** WARNING ***************************** |
| 108 *** You are running gsutil with the "https_validate_certificates" config | 116 *** You are running gsutil with the "https_validate_certificates" config |
| 109 *** variable set to False. This option should always be set to True in | 117 *** variable set to False. This option should always be set to True in |
| 110 *** production environments to protect against man-in-the-middle attacks, | 118 *** production environments to protect against man-in-the-middle attacks, |
| 111 *** and leaking of user data. | 119 *** and leaking of user data. |
| 112 ***************************** WARNING ***************************** | 120 ***************************** WARNING ***************************** |
| 113 """.lstrip() | 121 """.lstrip() |
| 114 | 122 |
| 115 debug = 0 | 123 debug = 0 |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 180 # Any modules used in initializing multiprocessing variables must be | 188 # Any modules used in initializing multiprocessing variables must be |
| 181 # imported after importing gslib.__main__. | 189 # imported after importing gslib.__main__. |
| 182 # pylint: disable=redefined-outer-name,g-import-not-at-top | 190 # pylint: disable=redefined-outer-name,g-import-not-at-top |
| 183 import gslib.boto_translation | 191 import gslib.boto_translation |
| 184 import gslib.command | 192 import gslib.command |
| 185 import gslib.util | 193 import gslib.util |
| 186 from gslib.util import BOTO_IS_SECURE | 194 from gslib.util import BOTO_IS_SECURE |
| 187 from gslib.util import CERTIFICATE_VALIDATION_ENABLED | 195 from gslib.util import CERTIFICATE_VALIDATION_ENABLED |
| 188 # pylint: disable=unused-variable | 196 # pylint: disable=unused-variable |
| 189 from gcs_oauth2_boto_plugin import oauth2_client | 197 from gcs_oauth2_boto_plugin import oauth2_client |
| 198 from apitools.base.py import credentials_lib |
| 190 # pylint: enable=unused-variable | 199 # pylint: enable=unused-variable |
| 191 from gslib.util import MultiprocessingIsAvailable | 200 from gslib.util import CheckMultiprocessingAvailableAndInit |
| 192 if MultiprocessingIsAvailable()[0]: | 201 if CheckMultiprocessingAvailableAndInit().is_available: |
| 193 # These setup methods must be called, and, on Windows, they can only be | 202 # These setup methods must be called, and, on Windows, they can only be |
| 194 # called from within an "if __name__ == '__main__':" block. | 203 # called from within an "if __name__ == '__main__':" block. |
| 195 gslib.util.InitializeMultiprocessingVariables() | |
| 196 gslib.command.InitializeMultiprocessingVariables() | 204 gslib.command.InitializeMultiprocessingVariables() |
| 197 gslib.boto_translation.InitializeMultiprocessingVariables() | 205 gslib.boto_translation.InitializeMultiprocessingVariables() |
| 206 else: |
| 207 gslib.command.InitializeThreadingVariables() |
| 198 | 208 |
| 199 # This needs to be done after gslib.util.InitializeMultiprocessingVariables(), | 209 # This needs to be done after gslib.util.InitializeMultiprocessingVariables(), |
| 200 # since otherwise we can't call gslib.util.CreateLock. | 210 # since otherwise we can't call gslib.util.CreateLock. |
| 201 try: | 211 try: |
| 202 # pylint: disable=unused-import,g-import-not-at-top | 212 # pylint: disable=unused-import,g-import-not-at-top |
| 203 import gcs_oauth2_boto_plugin | 213 import gcs_oauth2_boto_plugin |
| 204 gcs_oauth2_boto_plugin.oauth2_helper.SetFallbackClientIdAndSecret( | 214 gcs_oauth2_boto_plugin.oauth2_helper.SetFallbackClientIdAndSecret( |
| 205 GSUTIL_CLIENT_ID, GSUTIL_CLIENT_NOTSOSECRET) | 215 GSUTIL_CLIENT_ID, GSUTIL_CLIENT_NOTSOSECRET) |
| 206 gcs_oauth2_boto_plugin.oauth2_helper.SetLock(CreateLock()) | 216 gcs_oauth2_boto_plugin.oauth2_helper.SetLock(CreateLock()) |
| 217 credentials_lib.SetCredentialsCacheFileLock(CreateLock()) |
| 207 except ImportError: | 218 except ImportError: |
| 208 pass | 219 pass |
| 209 | 220 |
| 210 global debug | 221 global debug |
| 211 global test_exception_traces | 222 global test_exception_traces |
| 212 | 223 |
| 213 if not (2, 6) <= sys.version_info[:3] < (3,): | 224 if not (2, 6) <= sys.version_info[:3] < (3,): |
| 214 raise gslib.exception.CommandException( | 225 raise gslib.exception.CommandException( |
| 215 'gsutil requires python 2.6 or 2.7.') | 226 'gsutil requires python 2.6 or 2.7.') |
| 216 | 227 |
| 217 # In gsutil 4.0 and beyond, we don't use the boto library for the JSON | 228 # In gsutil 4.0 and beyond, we don't use the boto library for the JSON |
| 218 # API. However, we still store gsutil configuration data in the .boto | 229 # API. However, we still store gsutil configuration data in the .boto |
| 219 # config file for compatibility with previous versions and user convenience. | 230 # config file for compatibility with previous versions and user convenience. |
| 220 # Many users have a .boto configuration file from previous versions, and it | 231 # Many users have a .boto configuration file from previous versions, and it |
| 221 # is useful to have all of the configuration for gsutil stored in one place. | 232 # is useful to have all of the configuration for gsutil stored in one place. |
| 222 command_runner = CommandRunner() | 233 command_runner = CommandRunner() |
| 223 if not BOTO_IS_SECURE: | 234 if not BOTO_IS_SECURE: |
| 224 raise CommandException('\n'.join(textwrap.wrap( | 235 raise CommandException('\n'.join(textwrap.wrap( |
| 225 'Your boto configuration has is_secure = False. Gsutil cannot be ' | 236 'Your boto configuration has is_secure = False. Gsutil cannot be ' |
| 226 'run this way, for security reasons.'))) | 237 'run this way, for security reasons.'))) |
| 227 | 238 |
| 228 headers = {} | 239 headers = {} |
| 229 parallel_operations = False | 240 parallel_operations = False |
| 230 quiet = False | 241 quiet = False |
| 231 version = False | 242 version = False |
| 232 debug = 0 | 243 debug = 0 |
| 244 trace_token = None |
| 233 test_exception_traces = False | 245 test_exception_traces = False |
| 234 | 246 |
| 235 # If user enters no commands just print the usage info. | 247 # If user enters no commands just print the usage info. |
| 236 if len(sys.argv) == 1: | 248 if len(sys.argv) == 1: |
| 237 sys.argv.append('help') | 249 sys.argv.append('help') |
| 238 | 250 |
| 239 # Change the default of the 'https_validate_certificates' boto option to | 251 # Change the default of the 'https_validate_certificates' boto option to |
| 240 # True (it is currently False in boto). | 252 # True (it is currently False in boto). |
| 241 if not boto.config.has_option('Boto', 'https_validate_certificates'): | 253 if not boto.config.has_option('Boto', 'https_validate_certificates'): |
| 242 if not boto.config.has_section('Boto'): | 254 if not boto.config.has_section('Boto'): |
| 243 boto.config.add_section('Boto') | 255 boto.config.add_section('Boto') |
| 244 boto.config.setbool('Boto', 'https_validate_certificates', True) | 256 boto.config.setbool('Boto', 'https_validate_certificates', True) |
| 245 | 257 |
| 246 gslib.util.certs_file_lock = CreateLock() | 258 gslib.util.certs_file_lock = CreateLock() |
| 247 for signal_num in GetCaughtSignals(): | 259 for signal_num in GetCaughtSignals(): |
| 248 RegisterSignalHandler(signal_num, _CleanupSignalHandler) | 260 RegisterSignalHandler(signal_num, _CleanupSignalHandler) |
| 249 GetCertsFile() | 261 GetCertsFile() |
| 250 | 262 |
| 251 try: | 263 try: |
| 252 try: | 264 try: |
| 253 opts, args = getopt.getopt(sys.argv[1:], 'dDvo:h:mq', | 265 opts, args = getopt.getopt(sys.argv[1:], 'dDvo:h:mq', |
| 254 ['debug', 'detailedDebug', 'version', 'option', | 266 ['debug', 'detailedDebug', 'version', 'option', |
| 255 'help', 'header', 'multithreaded', 'quiet', | 267 'help', 'header', 'multithreaded', 'quiet', |
| 256 'testexceptiontraces']) | 268 'testexceptiontraces', 'trace-token=']) |
| 257 except getopt.GetoptError as e: | 269 except getopt.GetoptError as e: |
| 258 _HandleCommandException(gslib.exception.CommandException(e.msg)) | 270 _HandleCommandException(gslib.exception.CommandException(e.msg)) |
| 259 for o, a in opts: | 271 for o, a in opts: |
| 260 if o in ('-d', '--debug'): | 272 if o in ('-d', '--debug'): |
| 261 # Passing debug=2 causes boto to include httplib header output. | 273 # Passing debug=2 causes boto to include httplib header output. |
| 262 debug = 3 | 274 debug = 3 |
| 263 elif o in ('-D', '--detailedDebug'): | 275 elif o in ('-D', '--detailedDebug'): |
| 264 # We use debug level 3 to ask gsutil code to output more detailed | 276 # We use debug level 3 to ask gsutil code to output more detailed |
| 265 # debug output. This is a bit of a hack since it overloads the same | 277 # debug output. This is a bit of a hack since it overloads the same |
| 266 # flag that was originally implemented for boto use. And we use -DD | 278 # flag that was originally implemented for boto use. And we use -DD |
| 267 # to ask for really detailed debugging (i.e., including HTTP payload). | 279 # to ask for really detailed debugging (i.e., including HTTP payload). |
| 268 if debug == 3: | 280 if debug == 3: |
| 269 debug = 4 | 281 debug = 4 |
| 270 else: | 282 else: |
| 271 debug = 3 | 283 debug = 3 |
| 272 elif o in ('-?', '--help'): | 284 elif o in ('-?', '--help'): |
| 273 _OutputUsageAndExit(command_runner) | 285 _OutputUsageAndExit(command_runner) |
| 274 elif o in ('-h', '--header'): | 286 elif o in ('-h', '--header'): |
| 275 (hdr_name, _, hdr_val) = a.partition(':') | 287 (hdr_name, _, hdr_val) = a.partition(':') |
| 276 if not hdr_name: | 288 if not hdr_name: |
| 277 _OutputUsageAndExit(command_runner) | 289 _OutputUsageAndExit(command_runner) |
| 278 headers[hdr_name.lower()] = hdr_val | 290 headers[hdr_name.lower()] = hdr_val |
| 279 elif o in ('-m', '--multithreaded'): | 291 elif o in ('-m', '--multithreaded'): |
| 280 parallel_operations = True | 292 parallel_operations = True |
| 281 elif o in ('-q', '--quiet'): | 293 elif o in ('-q', '--quiet'): |
| 282 quiet = True | 294 quiet = True |
| 283 elif o in ('-v', '--version'): | 295 elif o in ('-v', '--version'): |
| 284 version = True | 296 version = True |
| 297 elif o == '--trace-token': |
| 298 trace_token = a |
| 285 elif o == '--testexceptiontraces': # Hidden flag for integration tests. | 299 elif o == '--testexceptiontraces': # Hidden flag for integration tests. |
| 286 test_exception_traces = True | 300 test_exception_traces = True |
| 287 elif o in ('-o', '--option'): | 301 elif o in ('-o', '--option'): |
| 288 (opt_section_name, _, opt_value) = a.partition('=') | 302 (opt_section_name, _, opt_value) = a.partition('=') |
| 289 if not opt_section_name: | 303 if not opt_section_name: |
| 290 _OutputUsageAndExit(command_runner) | 304 _OutputUsageAndExit(command_runner) |
| 291 (opt_section, _, opt_name) = opt_section_name.partition(':') | 305 (opt_section, _, opt_name) = opt_section_name.partition(':') |
| 292 if not opt_section or not opt_name: | 306 if not opt_section or not opt_name: |
| 293 _OutputUsageAndExit(command_runner) | 307 _OutputUsageAndExit(command_runner) |
| 294 if not boto.config.has_section(opt_section): | 308 if not boto.config.has_section(opt_section): |
| 295 boto.config.add_section(opt_section) | 309 boto.config.add_section(opt_section) |
| 296 boto.config.set(opt_section, opt_name, opt_value) | 310 boto.config.set(opt_section, opt_name, opt_value) |
| 297 httplib2.debuglevel = debug | 311 httplib2.debuglevel = debug |
| 312 if trace_token: |
| 313 sys.stderr.write(TRACE_WARNING) |
| 298 if debug > 1: | 314 if debug > 1: |
| 299 sys.stderr.write(DEBUG_WARNING) | 315 sys.stderr.write(DEBUG_WARNING) |
| 300 if debug >= 2: | 316 if debug >= 2: |
| 301 _ConfigureLogging(level=logging.DEBUG) | 317 _ConfigureLogging(level=logging.DEBUG) |
| 302 command_runner.RunNamedCommand('ver', ['-l']) | 318 command_runner.RunNamedCommand('ver', ['-l']) |
| 303 config_items = [] | 319 config_items = [] |
| 304 try: | 320 try: |
| 305 config_items.extend(boto.config.items('Boto')) | 321 config_items.extend(boto.config.items('Boto')) |
| 306 config_items.extend(boto.config.items('GSUtil')) | 322 config_items.extend(boto.config.items('GSUtil')) |
| 307 except ConfigParser.NoSectionError: | 323 except ConfigParser.NoSectionError: |
| (...skipping 24 matching lines...) Expand all Loading... |
| 332 else: | 348 else: |
| 333 command_name = args[0] | 349 command_name = args[0] |
| 334 | 350 |
| 335 _CheckAndWarnForProxyDifferences() | 351 _CheckAndWarnForProxyDifferences() |
| 336 | 352 |
| 337 if os.environ.get('_ARGCOMPLETE', '0') == '1': | 353 if os.environ.get('_ARGCOMPLETE', '0') == '1': |
| 338 return _PerformTabCompletion(command_runner) | 354 return _PerformTabCompletion(command_runner) |
| 339 | 355 |
| 340 return _RunNamedCommandAndHandleExceptions( | 356 return _RunNamedCommandAndHandleExceptions( |
| 341 command_runner, command_name, args=args[1:], headers=headers, | 357 command_runner, command_name, args=args[1:], headers=headers, |
| 342 debug_level=debug, parallel_operations=parallel_operations) | 358 debug_level=debug, trace_token=trace_token, |
| 359 parallel_operations=parallel_operations) |
| 343 finally: | 360 finally: |
| 344 _Cleanup() | 361 _Cleanup() |
| 345 | 362 |
| 346 | 363 |
| 347 def _CheckAndWarnForProxyDifferences(): | 364 def _CheckAndWarnForProxyDifferences(): |
| 348 # If there are both boto config and environment variable config present for | 365 # If there are both boto config and environment variable config present for |
| 349 # proxies, unset the environment variable and warn if it differs. | 366 # proxies, unset the environment variable and warn if it differs. |
| 350 boto_port = boto.config.getint('Boto', 'proxy_port', 0) | 367 boto_port = boto.config.getint('Boto', 'proxy_port', 0) |
| 351 if boto.config.get('Boto', 'proxy', None) or boto_port: | 368 if boto.config.get('Boto', 'proxy', None) or boto_port: |
| 352 for proxy_env_var in ['http_proxy', 'https_proxy', 'HTTPS_PROXY']: | 369 for proxy_env_var in ['http_proxy', 'https_proxy', 'HTTPS_PROXY']: |
| (...skipping 29 matching lines...) Expand all Loading... |
| 382 'environment variable and boto configuration, but ' | 399 'environment variable and boto configuration, but ' |
| 383 'configuration differs. boto configuration proxy values will ' | 400 'configuration differs. boto configuration proxy values will ' |
| 384 'be used. Differences detected:' % proxy_env_var))) | 401 'be used. Differences detected:' % proxy_env_var))) |
| 385 sys.stderr.write('\n%s\n' % '\n'.join(differing_values)) | 402 sys.stderr.write('\n%s\n' % '\n'.join(differing_values)) |
| 386 # Regardless of whether the proxy configuration values matched, | 403 # Regardless of whether the proxy configuration values matched, |
| 387 # delete the environment variable so as not to confuse boto. | 404 # delete the environment variable so as not to confuse boto. |
| 388 del os.environ[proxy_env_var] | 405 del os.environ[proxy_env_var] |
| 389 | 406 |
| 390 | 407 |
| 391 def _HandleUnknownFailure(e): | 408 def _HandleUnknownFailure(e): |
| 392 # Called if we fall through all known/handled exceptions. Allows us to | 409 # Called if we fall through all known/handled exceptions. |
| 393 # print a stacktrace if -D option used. | 410 _OutputAndExit('Failure: %s.' % e) |
| 394 if debug >= 2: | |
| 395 stack_trace = traceback.format_exc() | |
| 396 sys.stderr.write('DEBUG: Exception stack trace:\n %s\n' % | |
| 397 re.sub('\\n', '\n ', stack_trace)) | |
| 398 else: | |
| 399 _OutputAndExit('Failure: %s.' % e) | |
| 400 | 411 |
| 401 | 412 |
| 402 def _HandleCommandException(e): | 413 def _HandleCommandException(e): |
| 403 if e.informational: | 414 if e.informational: |
| 404 _OutputAndExit(e.reason) | 415 _OutputAndExit(e.reason) |
| 405 else: | 416 else: |
| 406 _OutputAndExit('CommandException: %s' % e.reason) | 417 _OutputAndExit('CommandException: %s' % e.reason) |
| 407 | 418 |
| 408 | 419 |
| 409 # pylint: disable=unused-argument | 420 # pylint: disable=unused-argument |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 499 elif (e.reason and | 510 elif (e.reason and |
| 500 (e.reason == 'AccountProblem' or e.reason == 'Account disabled.' or | 511 (e.reason == 'AccountProblem' or e.reason == 'Account disabled.' or |
| 501 'account for the specified project has been disabled' in e.reason) | 512 'account for the specified project has been disabled' in e.reason) |
| 502 and ','.join(args).find('gs://') != -1): | 513 and ','.join(args).find('gs://') != -1): |
| 503 _OutputAndExit('\n'.join(textwrap.wrap( | 514 _OutputAndExit('\n'.join(textwrap.wrap( |
| 504 _ConstructAccountProblemHelp(e.reason)))) | 515 _ConstructAccountProblemHelp(e.reason)))) |
| 505 | 516 |
| 506 | 517 |
| 507 def _RunNamedCommandAndHandleExceptions(command_runner, command_name, args=None, | 518 def _RunNamedCommandAndHandleExceptions(command_runner, command_name, args=None, |
| 508 headers=None, debug_level=0, | 519 headers=None, debug_level=0, |
| 520 trace_token=None, |
| 509 parallel_operations=False): | 521 parallel_operations=False): |
| 510 """Runs the command with the given command runner and arguments.""" | 522 """Runs the command with the given command runner and arguments.""" |
| 511 # pylint: disable=g-import-not-at-top | 523 # pylint: disable=g-import-not-at-top |
| 512 from gslib.util import GetConfigFilePath | 524 from gslib.util import GetConfigFilePath |
| 513 from gslib.util import IS_WINDOWS | 525 from gslib.util import IS_WINDOWS |
| 514 from gslib.util import IsRunningInteractively | 526 from gslib.util import IsRunningInteractively |
| 515 try: | 527 try: |
| 516 # Catch ^C so we can print a brief message instead of the normal Python | 528 # Catch ^C so we can print a brief message instead of the normal Python |
| 517 # stack trace. Register as a final signal handler because this handler kills | 529 # stack trace. Register as a final signal handler because this handler kills |
| 518 # the main gsutil process (so it must run last). | 530 # the main gsutil process (so it must run last). |
| 519 RegisterSignalHandler(signal.SIGINT, _HandleControlC, is_final_handler=True) | 531 RegisterSignalHandler(signal.SIGINT, _HandleControlC, is_final_handler=True) |
| 520 # Catch ^\ so we can force a breakpoint in a running gsutil. | 532 # Catch ^\ so we can force a breakpoint in a running gsutil. |
| 521 if not IS_WINDOWS: | 533 if not IS_WINDOWS: |
| 522 RegisterSignalHandler(signal.SIGQUIT, _HandleSigQuit) | 534 RegisterSignalHandler(signal.SIGQUIT, _HandleSigQuit) |
| 523 return command_runner.RunNamedCommand(command_name, args, headers, | 535 return command_runner.RunNamedCommand(command_name, args, headers, |
| 524 debug_level, parallel_operations) | 536 debug_level, trace_token, |
| 537 parallel_operations) |
| 525 except AttributeError as e: | 538 except AttributeError as e: |
| 526 if str(e).find('secret_access_key') != -1: | 539 if str(e).find('secret_access_key') != -1: |
| 527 _OutputAndExit('Missing credentials for the given URI(s). Does your ' | 540 _OutputAndExit('Missing credentials for the given URI(s). Does your ' |
| 528 'boto config file contain all needed credentials?') | 541 'boto config file contain all needed credentials?') |
| 529 else: | 542 else: |
| 530 _OutputAndExit(str(e)) | 543 _OutputAndExit(str(e)) |
| 531 except gslib.exception.CommandException as e: | 544 except gslib.exception.CommandException as e: |
| 532 _HandleCommandException(e) | 545 _HandleCommandException(e) |
| 533 except getopt.GetoptError as e: | 546 except getopt.GetoptError as e: |
| 534 _HandleCommandException(gslib.exception.CommandException(e.msg)) | 547 _HandleCommandException(gslib.exception.CommandException(e.msg)) |
| (...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 622 ' not found.\nCause: %s' % e) | 635 ' not found.\nCause: %s' % e) |
| 623 parser = argparse.ArgumentParser(add_help=False) | 636 parser = argparse.ArgumentParser(add_help=False) |
| 624 subparsers = parser.add_subparsers() | 637 subparsers = parser.add_subparsers() |
| 625 command_runner.ConfigureCommandArgumentParsers(subparsers) | 638 command_runner.ConfigureCommandArgumentParsers(subparsers) |
| 626 argcomplete.autocomplete(parser, exit_method=sys.exit) | 639 argcomplete.autocomplete(parser, exit_method=sys.exit) |
| 627 | 640 |
| 628 return 0 | 641 return 0 |
| 629 | 642 |
| 630 if __name__ == '__main__': | 643 if __name__ == '__main__': |
| 631 sys.exit(main()) | 644 sys.exit(main()) |
| OLD | NEW |