OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 # Copyright (C) 2008 Evan Martin <martine@danga.com> | 6 # Copyright (C) 2008 Evan Martin <martine@danga.com> |
7 | 7 |
8 """A git-command for integrating reviews on Rietveld.""" | 8 """A git-command for integrating reviews on Rietveld.""" |
9 | 9 |
10 import json | 10 import json |
(...skipping 418 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
429 | 429 |
430 def GetRemoteUrl(self): | 430 def GetRemoteUrl(self): |
431 """Return the configured remote URL, e.g. 'git://example.org/foo.git/'. | 431 """Return the configured remote URL, e.g. 'git://example.org/foo.git/'. |
432 | 432 |
433 Returns None if there is no remote. | 433 Returns None if there is no remote. |
434 """ | 434 """ |
435 remote = self.GetRemote() | 435 remote = self.GetRemote() |
436 return RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip() | 436 return RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip() |
437 | 437 |
438 def GetIssue(self): | 438 def GetIssue(self): |
| 439 """Returns the issue number as a int or None if not set.""" |
439 if not self.has_issue: | 440 if not self.has_issue: |
440 issue = RunGit(['config', self._IssueSetting()], error_ok=True).strip() | 441 issue = RunGit(['config', self._IssueSetting()], error_ok=True).strip() |
441 if issue: | 442 if issue: |
442 self.issue = issue | 443 self.issue = int(issue) |
443 else: | 444 else: |
444 self.issue = None | 445 self.issue = None |
445 self.has_issue = True | 446 self.has_issue = True |
446 return self.issue | 447 return self.issue |
447 | 448 |
448 def GetRietveldServer(self): | 449 def GetRietveldServer(self): |
449 if not self.rietveld_server: | 450 if not self.rietveld_server: |
450 # If we're on a branch then get the server potentially associated | 451 # If we're on a branch then get the server potentially associated |
451 # with that branch. | 452 # with that branch. |
452 if self.GetIssue(): | 453 if self.GetIssue(): |
453 self.rietveld_server = gclient_utils.UpgradeToHttps(RunGit( | 454 self.rietveld_server = gclient_utils.UpgradeToHttps(RunGit( |
454 ['config', self._RietveldServer()], error_ok=True).strip()) | 455 ['config', self._RietveldServer()], error_ok=True).strip()) |
455 if not self.rietveld_server: | 456 if not self.rietveld_server: |
456 self.rietveld_server = settings.GetDefaultServerUrl() | 457 self.rietveld_server = settings.GetDefaultServerUrl() |
457 return self.rietveld_server | 458 return self.rietveld_server |
458 | 459 |
459 def GetIssueURL(self): | 460 def GetIssueURL(self): |
460 """Get the URL for a particular issue.""" | 461 """Get the URL for a particular issue.""" |
461 return '%s/%s' % (self.GetRietveldServer(), self.GetIssue()) | 462 return '%s/%s' % (self.GetRietveldServer(), self.GetIssue()) |
462 | 463 |
463 def GetDescription(self, pretty=False): | 464 def GetDescription(self, pretty=False): |
464 if not self.has_description: | 465 if not self.has_description: |
465 if self.GetIssue(): | 466 if self.GetIssue(): |
466 issue = int(self.GetIssue()) | 467 issue = self.GetIssue() |
467 try: | 468 try: |
468 self.description = self.RpcServer().get_description(issue).strip() | 469 self.description = self.RpcServer().get_description(issue).strip() |
469 except urllib2.HTTPError, e: | 470 except urllib2.HTTPError, e: |
470 if e.code == 404: | 471 if e.code == 404: |
471 DieWithError( | 472 DieWithError( |
472 ('\nWhile fetching the description for issue %d, received a ' | 473 ('\nWhile fetching the description for issue %d, received a ' |
473 '404 (not found)\n' | 474 '404 (not found)\n' |
474 'error. It is likely that you deleted this ' | 475 'error. It is likely that you deleted this ' |
475 'issue on the server. If this is the\n' | 476 'issue on the server. If this is the\n' |
476 'case, please run\n\n' | 477 'case, please run\n\n' |
477 ' git cl issue 0\n\n' | 478 ' git cl issue 0\n\n' |
478 'to clear the association with the deleted issue. Then run ' | 479 'to clear the association with the deleted issue. Then run ' |
479 'this command again.') % issue) | 480 'this command again.') % issue) |
480 else: | 481 else: |
481 DieWithError( | 482 DieWithError( |
482 '\nFailed to fetch issue description. HTTP error ' + e.code) | 483 '\nFailed to fetch issue description. HTTP error ' + e.code) |
483 self.has_description = True | 484 self.has_description = True |
484 if pretty: | 485 if pretty: |
485 wrapper = textwrap.TextWrapper() | 486 wrapper = textwrap.TextWrapper() |
486 wrapper.initial_indent = wrapper.subsequent_indent = ' ' | 487 wrapper.initial_indent = wrapper.subsequent_indent = ' ' |
487 return wrapper.fill(self.description) | 488 return wrapper.fill(self.description) |
488 return self.description | 489 return self.description |
489 | 490 |
490 def GetPatchset(self): | 491 def GetPatchset(self): |
| 492 """Returns the patchset number as a int or None if not set.""" |
491 if not self.has_patchset: | 493 if not self.has_patchset: |
492 patchset = RunGit(['config', self._PatchsetSetting()], | 494 patchset = RunGit(['config', self._PatchsetSetting()], |
493 error_ok=True).strip() | 495 error_ok=True).strip() |
494 if patchset: | 496 if patchset: |
495 self.patchset = patchset | 497 self.patchset = int(patchset) |
496 else: | 498 else: |
497 self.patchset = None | 499 self.patchset = None |
498 self.has_patchset = True | 500 self.has_patchset = True |
499 return self.patchset | 501 return self.patchset |
500 | 502 |
501 def SetPatchset(self, patchset): | 503 def SetPatchset(self, patchset): |
502 """Set this branch's patchset. If patchset=0, clears the patchset.""" | 504 """Set this branch's patchset. If patchset=0, clears the patchset.""" |
503 if patchset: | 505 if patchset: |
504 RunGit(['config', self._PatchsetSetting(), str(patchset)]) | 506 RunGit(['config', self._PatchsetSetting(), str(patchset)]) |
505 else: | 507 else: |
(...skipping 29 matching lines...) Expand all Loading... |
535 files = scm.GIT.CaptureStatus([root], '.', upstream_branch) | 537 files = scm.GIT.CaptureStatus([root], '.', upstream_branch) |
536 except subprocess2.CalledProcessError: | 538 except subprocess2.CalledProcessError: |
537 DieWithError( | 539 DieWithError( |
538 ('\nFailed to diff against upstream branch %s!\n\n' | 540 ('\nFailed to diff against upstream branch %s!\n\n' |
539 'This branch probably doesn\'t exist anymore. To reset the\n' | 541 'This branch probably doesn\'t exist anymore. To reset the\n' |
540 'tracking branch, please run\n' | 542 'tracking branch, please run\n' |
541 ' git branch --set-upstream %s trunk\n' | 543 ' git branch --set-upstream %s trunk\n' |
542 'replacing trunk with origin/master or the relevant branch') % | 544 'replacing trunk with origin/master or the relevant branch') % |
543 (upstream_branch, self.GetBranch())) | 545 (upstream_branch, self.GetBranch())) |
544 | 546 |
545 issue = ConvertToInteger(self.GetIssue()) | 547 issue = self.GetIssue() |
546 patchset = ConvertToInteger(self.GetPatchset()) | 548 patchset = self.GetPatchset() |
547 if issue: | 549 if issue: |
548 description = self.GetDescription() | 550 description = self.GetDescription() |
549 else: | 551 else: |
550 # If the change was never uploaded, use the log messages of all commits | 552 # If the change was never uploaded, use the log messages of all commits |
551 # up to the branch point, as git cl upload will prefill the description | 553 # up to the branch point, as git cl upload will prefill the description |
552 # with these log messages. | 554 # with these log messages. |
553 description = RunCommand(['git', 'log', '--pretty=format:%s%n%n%b', | 555 description = RunCommand(['git', 'log', '--pretty=format:%s%n%n%b', |
554 '%s...' % (upstream_branch)]).strip() | 556 '%s...' % (upstream_branch)]).strip() |
555 | 557 |
556 if not author: | 558 if not author: |
(...skipping 22 matching lines...) Expand all Loading... |
579 verbose=verbose, output_stream=sys.stdout, input_stream=sys.stdin, | 581 verbose=verbose, output_stream=sys.stdout, input_stream=sys.stdin, |
580 default_presubmit=None, may_prompt=may_prompt, | 582 default_presubmit=None, may_prompt=may_prompt, |
581 rietveld_obj=self.RpcServer()) | 583 rietveld_obj=self.RpcServer()) |
582 except presubmit_support.PresubmitFailure, e: | 584 except presubmit_support.PresubmitFailure, e: |
583 DieWithError( | 585 DieWithError( |
584 ('%s\nMaybe your depot_tools is out of date?\n' | 586 ('%s\nMaybe your depot_tools is out of date?\n' |
585 'If all fails, contact maruel@') % e) | 587 'If all fails, contact maruel@') % e) |
586 | 588 |
587 def CloseIssue(self): | 589 def CloseIssue(self): |
588 """Updates the description and closes the issue.""" | 590 """Updates the description and closes the issue.""" |
589 issue = int(self.GetIssue()) | 591 issue = self.GetIssue() |
590 self.RpcServer().update_description(issue, self.description) | 592 self.RpcServer().update_description(issue, self.description) |
591 return self.RpcServer().close_issue(issue) | 593 return self.RpcServer().close_issue(issue) |
592 | 594 |
593 def SetFlag(self, flag, value): | 595 def SetFlag(self, flag, value): |
594 """Patchset must match.""" | 596 """Patchset must match.""" |
595 if not self.GetPatchset(): | 597 if not self.GetPatchset(): |
596 DieWithError('The patchset needs to match. Send another patchset.') | 598 DieWithError('The patchset needs to match. Send another patchset.') |
597 try: | 599 try: |
598 return self.RpcServer().set_flag( | 600 return self.RpcServer().set_flag( |
599 int(self.GetIssue()), int(self.GetPatchset()), flag, value) | 601 self.GetIssue(), self.GetPatchset(), flag, value) |
600 except urllib2.HTTPError, e: | 602 except urllib2.HTTPError, e: |
601 if e.code == 404: | 603 if e.code == 404: |
602 DieWithError('The issue %s doesn\'t exist.' % self.GetIssue()) | 604 DieWithError('The issue %s doesn\'t exist.' % self.GetIssue()) |
603 if e.code == 403: | 605 if e.code == 403: |
604 DieWithError( | 606 DieWithError( |
605 ('Access denied to issue %s. Maybe the patchset %s doesn\'t ' | 607 ('Access denied to issue %s. Maybe the patchset %s doesn\'t ' |
606 'match?') % (self.GetIssue(), self.GetPatchset())) | 608 'match?') % (self.GetIssue(), self.GetPatchset())) |
607 raise | 609 raise |
608 | 610 |
609 def RpcServer(self): | 611 def RpcServer(self): |
(...skipping 247 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
857 url = cl.GetIssueURL() | 859 url = cl.GetIssueURL() |
858 if url: | 860 if url: |
859 print url | 861 print url |
860 else: | 862 else: |
861 print | 863 print |
862 print 'Current branch:', | 864 print 'Current branch:', |
863 if not cl.GetIssue(): | 865 if not cl.GetIssue(): |
864 print 'no issue assigned.' | 866 print 'no issue assigned.' |
865 return 0 | 867 return 0 |
866 print cl.GetBranch() | 868 print cl.GetBranch() |
867 print 'Issue number:', cl.GetIssue(), '(%s)' % cl.GetIssueURL() | 869 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()) |
868 print 'Issue description:' | 870 print 'Issue description:' |
869 print cl.GetDescription(pretty=True) | 871 print cl.GetDescription(pretty=True) |
870 return 0 | 872 return 0 |
871 | 873 |
872 | 874 |
873 @usage('[issue_number]') | 875 @usage('[issue_number]') |
874 def CMDissue(parser, args): | 876 def CMDissue(parser, args): |
875 """Set or display the current code review issue number. | 877 """Set or display the current code review issue number. |
876 | 878 |
877 Pass issue number 0 to clear the current issue. | 879 Pass issue number 0 to clear the current issue. |
878 """ | 880 """ |
879 _, args = parser.parse_args(args) | 881 _, args = parser.parse_args(args) |
880 | 882 |
881 cl = Changelist() | 883 cl = Changelist() |
882 if len(args) > 0: | 884 if len(args) > 0: |
883 try: | 885 try: |
884 issue = int(args[0]) | 886 issue = int(args[0]) |
885 except ValueError: | 887 except ValueError: |
886 DieWithError('Pass a number to set the issue or none to list it.\n' | 888 DieWithError('Pass a number to set the issue or none to list it.\n' |
887 'Maybe you want to run git cl status?') | 889 'Maybe you want to run git cl status?') |
888 cl.SetIssue(issue) | 890 cl.SetIssue(issue) |
889 print 'Issue number:', cl.GetIssue(), '(%s)' % cl.GetIssueURL() | 891 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()) |
890 return 0 | 892 return 0 |
891 | 893 |
892 | 894 |
893 def CMDcomments(parser, args): | 895 def CMDcomments(parser, args): |
894 """show review comments of the current changelist""" | 896 """show review comments of the current changelist""" |
895 (_, args) = parser.parse_args(args) | 897 (_, args) = parser.parse_args(args) |
896 if args: | 898 if args: |
897 parser.error('Unsupported argument: %s' % args) | 899 parser.error('Unsupported argument: %s' % args) |
898 | 900 |
899 cl = Changelist() | 901 cl = Changelist() |
(...skipping 13 matching lines...) Expand all Loading... |
913 log_args = [args[0] + '..'] | 915 log_args = [args[0] + '..'] |
914 elif len(args) == 1 and args[0].endswith('...'): | 916 elif len(args) == 1 and args[0].endswith('...'): |
915 log_args = [args[0][:-1]] | 917 log_args = [args[0][:-1]] |
916 elif len(args) == 2: | 918 elif len(args) == 2: |
917 log_args = [args[0] + '..' + args[1]] | 919 log_args = [args[0] + '..' + args[1]] |
918 else: | 920 else: |
919 log_args = args[:] # Hope for the best! | 921 log_args = args[:] # Hope for the best! |
920 return RunGit(['log', '--pretty=format:%s\n\n%b'] + log_args) | 922 return RunGit(['log', '--pretty=format:%s\n\n%b'] + log_args) |
921 | 923 |
922 | 924 |
923 def ConvertToInteger(inputval): | |
924 """Convert a string to integer, but returns either an int or None.""" | |
925 try: | |
926 return int(inputval) | |
927 except (TypeError, ValueError): | |
928 return None | |
929 | |
930 | |
931 def CMDpresubmit(parser, args): | 925 def CMDpresubmit(parser, args): |
932 """run presubmit tests on the current changelist""" | 926 """run presubmit tests on the current changelist""" |
933 parser.add_option('--upload', action='store_true', | 927 parser.add_option('--upload', action='store_true', |
934 help='Run upload hook instead of the push/dcommit hook') | 928 help='Run upload hook instead of the push/dcommit hook') |
935 (options, args) = parser.parse_args(args) | 929 (options, args) = parser.parse_args(args) |
936 | 930 |
937 # Make sure index is up-to-date before running diff-index. | 931 # Make sure index is up-to-date before running diff-index. |
938 RunGit(['update-index', '--refresh', '-q'], error_ok=True) | 932 RunGit(['update-index', '--refresh', '-q'], error_ok=True) |
939 if RunGit(['diff-index', 'HEAD']): | 933 if RunGit(['diff-index', 'HEAD']): |
940 # TODO(maruel): Is this really necessary? | 934 # TODO(maruel): Is this really necessary? |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1004 change_desc = None | 998 change_desc = None |
1005 | 999 |
1006 if cl.GetIssue(): | 1000 if cl.GetIssue(): |
1007 if options.title: | 1001 if options.title: |
1008 upload_args.extend(['--title', options.title]) | 1002 upload_args.extend(['--title', options.title]) |
1009 elif options.message: | 1003 elif options.message: |
1010 # TODO(rogerta): for now, the -m option will also set the --title option | 1004 # TODO(rogerta): for now, the -m option will also set the --title option |
1011 # for upload.py. Soon this will be changed to set the --message option. | 1005 # for upload.py. Soon this will be changed to set the --message option. |
1012 # Will wait until people are used to typing -t instead of -m. | 1006 # Will wait until people are used to typing -t instead of -m. |
1013 upload_args.extend(['--title', options.message]) | 1007 upload_args.extend(['--title', options.message]) |
1014 upload_args.extend(['--issue', cl.GetIssue()]) | 1008 upload_args.extend(['--issue', str(cl.GetIssue())]) |
1015 print ("This branch is associated with issue %s. " | 1009 print ("This branch is associated with issue %s. " |
1016 "Adding patch to that issue." % cl.GetIssue()) | 1010 "Adding patch to that issue." % cl.GetIssue()) |
1017 else: | 1011 else: |
1018 if options.title: | 1012 if options.title: |
1019 upload_args.extend(['--title', options.title]) | 1013 upload_args.extend(['--title', options.title]) |
1020 message = options.message or CreateDescriptionFromLog(args) | 1014 message = options.message or CreateDescriptionFromLog(args) |
1021 change_desc = ChangeDescription(message, options.reviewers) | 1015 change_desc = ChangeDescription(message, options.reviewers) |
1022 if not options.force: | 1016 if not options.force: |
1023 change_desc.Prompt() | 1017 change_desc.Prompt() |
1024 change_desc.ParseDescription() | 1018 change_desc.ParseDescription() |
(...skipping 373 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1398 help="don't commit after patch applies") | 1392 help="don't commit after patch applies") |
1399 (options, args) = parser.parse_args(args) | 1393 (options, args) = parser.parse_args(args) |
1400 if len(args) != 1: | 1394 if len(args) != 1: |
1401 parser.print_help() | 1395 parser.print_help() |
1402 return 1 | 1396 return 1 |
1403 issue_arg = args[0] | 1397 issue_arg = args[0] |
1404 | 1398 |
1405 # TODO(maruel): Use apply_issue.py | 1399 # TODO(maruel): Use apply_issue.py |
1406 # TODO(ukai): use gerrit-cherry-pick for gerrit repository? | 1400 # TODO(ukai): use gerrit-cherry-pick for gerrit repository? |
1407 | 1401 |
1408 if re.match(r'\d+', issue_arg): | 1402 if issue_arg.isdigit(): |
1409 # Input is an issue id. Figure out the URL. | 1403 # Input is an issue id. Figure out the URL. |
1410 issue = issue_arg | 1404 issue = int(issue_arg) |
1411 patch_data = Changelist().GetPatchSetDiff(issue) | 1405 patch_data = Changelist().GetPatchSetDiff(issue) |
1412 else: | 1406 else: |
1413 # Assume it's a URL to the patch. Default to https. | 1407 # Assume it's a URL to the patch. Default to https. |
1414 issue_url = gclient_utils.UpgradeToHttps(issue_arg) | 1408 issue_url = gclient_utils.UpgradeToHttps(issue_arg) |
1415 match = re.match(r'.*?/issue(\d+)_\d+.diff', issue_url) | 1409 match = re.match(r'.*?/issue(\d+)_\d+.diff', issue_url) |
1416 if not match: | 1410 if not match: |
1417 DieWithError('Must pass an issue ID or full URL for ' | 1411 DieWithError('Must pass an issue ID or full URL for ' |
1418 '\'Download raw patch set\'') | 1412 '\'Download raw patch set\'') |
1419 issue = match.group(1) | 1413 issue = int(match.group(1)) |
1420 patch_data = urllib2.urlopen(issue_arg).read() | 1414 patch_data = urllib2.urlopen(issue_arg).read() |
1421 | 1415 |
1422 if options.newbranch: | 1416 if options.newbranch: |
1423 if options.force: | 1417 if options.force: |
1424 RunGit(['branch', '-D', options.newbranch], | 1418 RunGit(['branch', '-D', options.newbranch], |
1425 stderr=subprocess2.PIPE, error_ok=True) | 1419 stderr=subprocess2.PIPE, error_ok=True) |
1426 RunGit(['checkout', '-b', options.newbranch, | 1420 RunGit(['checkout', '-b', options.newbranch, |
1427 Changelist().GetUpstreamBranch()]) | 1421 Changelist().GetUpstreamBranch()]) |
1428 | 1422 |
1429 # Switch up to the top-level directory, if necessary, in preparation for | 1423 # Switch up to the top-level directory, if necessary, in preparation for |
(...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1617 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 1611 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
1618 | 1612 |
1619 # Not a known command. Default to help. | 1613 # Not a known command. Default to help. |
1620 GenUsage(parser, 'help') | 1614 GenUsage(parser, 'help') |
1621 return CMDhelp(parser, argv) | 1615 return CMDhelp(parser, argv) |
1622 | 1616 |
1623 | 1617 |
1624 if __name__ == '__main__': | 1618 if __name__ == '__main__': |
1625 fix_encoding.fix_encoding() | 1619 fix_encoding.fix_encoding() |
1626 sys.exit(main(sys.argv[1:])) | 1620 sys.exit(main(sys.argv[1:])) |
OLD | NEW |