OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 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 errno | 10 import errno |
(...skipping 445 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
456 self.GetIssue() | 456 self.GetIssue() |
457 return self.rietveld_server | 457 return self.rietveld_server |
458 | 458 |
459 def GetIssueURL(self): | 459 def GetIssueURL(self): |
460 """Get the URL for a particular issue.""" | 460 """Get the URL for a particular issue.""" |
461 return '%s/%s' % (self.GetRietveldServer(), self.GetIssue()) | 461 return '%s/%s' % (self.GetRietveldServer(), self.GetIssue()) |
462 | 462 |
463 def GetDescription(self, pretty=False): | 463 def GetDescription(self, pretty=False): |
464 if not self.has_description: | 464 if not self.has_description: |
465 if self.GetIssue(): | 465 if self.GetIssue(): |
466 path = '/' + self.GetIssue() + '/description' | 466 self.description = self.RpcServer().get_description( |
467 rpc_server = self.RpcServer() | 467 int(self.GetIssue())).strip() |
468 self.description = rpc_server.Send(path).strip() | |
469 self.has_description = True | 468 self.has_description = True |
470 if pretty: | 469 if pretty: |
471 wrapper = textwrap.TextWrapper() | 470 wrapper = textwrap.TextWrapper() |
472 wrapper.initial_indent = wrapper.subsequent_indent = ' ' | 471 wrapper.initial_indent = wrapper.subsequent_indent = ' ' |
473 return wrapper.fill(self.description) | 472 return wrapper.fill(self.description) |
474 return self.description | 473 return self.description |
475 | 474 |
476 def GetPatchset(self): | 475 def GetPatchset(self): |
477 if not self.has_patchset: | 476 if not self.has_patchset: |
478 patchset = RunGit(['config', self._PatchsetSetting()], | 477 patchset = RunGit(['config', self._PatchsetSetting()], |
479 error_ok=True).strip() | 478 error_ok=True).strip() |
480 if patchset: | 479 if patchset: |
481 self.patchset = patchset | 480 self.patchset = patchset |
482 else: | 481 else: |
483 self.patchset = None | 482 self.patchset = None |
484 self.has_patchset = True | 483 self.has_patchset = True |
485 return self.patchset | 484 return self.patchset |
486 | 485 |
487 def SetPatchset(self, patchset): | 486 def SetPatchset(self, patchset): |
488 """Set this branch's patchset. If patchset=0, clears the patchset.""" | 487 """Set this branch's patchset. If patchset=0, clears the patchset.""" |
489 if patchset: | 488 if patchset: |
490 RunGit(['config', self._PatchsetSetting(), str(patchset)]) | 489 RunGit(['config', self._PatchsetSetting(), str(patchset)]) |
491 else: | 490 else: |
492 RunGit(['config', '--unset', self._PatchsetSetting()], | 491 RunGit(['config', '--unset', self._PatchsetSetting()], |
493 swallow_stderr=True, error_ok=True) | 492 swallow_stderr=True, error_ok=True) |
494 self.has_patchset = False | 493 self.has_patchset = False |
495 | 494 |
496 def GetPatchSetDiff(self, issue): | 495 def GetPatchSetDiff(self, issue): |
497 # Grab the last patchset of the issue first. | 496 patchset = self.RpcServer().get_issue_properties( |
498 data = json.loads(self.RpcServer().Send('/api/%s' % issue)) | 497 int(issue), False)['patchsets'][-1] |
499 patchset = data['patchsets'][-1] | 498 return self.RpcServer().get( |
500 return self.RpcServer().Send( | |
501 '/download/issue%s_%s.diff' % (issue, patchset)) | 499 '/download/issue%s_%s.diff' % (issue, patchset)) |
502 | 500 |
503 def SetIssue(self, issue): | 501 def SetIssue(self, issue): |
504 """Set this branch's issue. If issue=0, clears the issue.""" | 502 """Set this branch's issue. If issue=0, clears the issue.""" |
505 if issue: | 503 if issue: |
506 RunGit(['config', self._IssueSetting(), str(issue)]) | 504 RunGit(['config', self._IssueSetting(), str(issue)]) |
507 if self.rietveld_server: | 505 if self.rietveld_server: |
508 RunGit(['config', self._RietveldServer(), self.rietveld_server]) | 506 RunGit(['config', self._RietveldServer(), self.rietveld_server]) |
509 else: | 507 else: |
510 RunGit(['config', '--unset', self._IssueSetting()]) | 508 RunGit(['config', '--unset', self._IssueSetting()]) |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
557 'If all fails, contact maruel@') % e) | 555 'If all fails, contact maruel@') % e) |
558 | 556 |
559 # TODO(dpranke): We should propagate the error out instead of calling | 557 # TODO(dpranke): We should propagate the error out instead of calling |
560 # exit(). | 558 # exit(). |
561 if not output.should_continue(): | 559 if not output.should_continue(): |
562 sys.exit(1) | 560 sys.exit(1) |
563 | 561 |
564 return output | 562 return output |
565 | 563 |
566 def CloseIssue(self): | 564 def CloseIssue(self): |
567 rpc_server = self.RpcServer() | 565 return self.RpcServer().close_issue(int(self.GetIssue())) |
568 # Newer versions of Rietveld require us to pass an XSRF token to POST, so | |
569 # we fetch it from the server. (The version used by Chromium has been | |
570 # modified so the token isn't required when closing an issue.) | |
571 xsrf_token = rpc_server.Send('/xsrf_token', | |
572 extra_headers={'X-Requesting-XSRF-Token': '1'}) | |
573 | 566 |
574 # You cannot close an issue with a GET. | 567 def SetFlag(self, flag, value): |
575 # We pass an empty string for the data so it is a POST rather than a GET. | 568 """Patchset must match.""" |
576 data = [("description", self.description), | 569 if not self.GetPatchset(): |
577 ("xsrf_token", xsrf_token)] | 570 DieWithError('The patchset needs to match. Send another patchset.') |
578 ctype, body = upload.EncodeMultipartFormData(data, []) | 571 try: |
579 rpc_server.Send( | 572 return self.RpcServer().set_flag( |
580 '/' + self.GetIssue() + '/close', payload=body, content_type=ctype) | 573 int(self.GetIssue()), int(self.GetPatchset()), flag, value) |
| 574 except urllib2.HTTPError, e: |
| 575 if e.code == 404: |
| 576 DieWithError('The issue %s doesn\'t exist.' % self.GetIssue()) |
| 577 if e.code == 403: |
| 578 DieWithError( |
| 579 ('Access denied to issue %s. Maybe the patchset %s doesn\'t ' |
| 580 'match?') % (self.GetIssue(), self.GetPatchset())) |
| 581 raise |
581 | 582 |
582 def RpcServer(self): | 583 def RpcServer(self): |
583 """Returns an upload.RpcServer() to access this review's rietveld instance. | 584 """Returns an upload.RpcServer() to access this review's rietveld instance. |
584 """ | 585 """ |
585 if not self._rpc_server: | 586 if not self._rpc_server: |
586 self.GetIssue() | 587 self.GetIssue() |
587 self._rpc_server = rietveld.Rietveld(self.rietveld_server, None, None) | 588 self._rpc_server = rietveld.Rietveld(self.rietveld_server, None, None) |
588 return self._rpc_server | 589 return self._rpc_server |
589 | 590 |
590 def _IssueSetting(self): | 591 def _IssueSetting(self): |
(...skipping 315 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
906 help='cc email addresses') | 907 help='cc email addresses') |
907 parser.add_option('--send-mail', action='store_true', | 908 parser.add_option('--send-mail', action='store_true', |
908 help='send email to reviewer immediately') | 909 help='send email to reviewer immediately') |
909 parser.add_option("--emulate_svn_auto_props", action="store_true", | 910 parser.add_option("--emulate_svn_auto_props", action="store_true", |
910 dest="emulate_svn_auto_props", | 911 dest="emulate_svn_auto_props", |
911 help="Emulate Subversion's auto properties feature.") | 912 help="Emulate Subversion's auto properties feature.") |
912 parser.add_option("--desc_from_logs", action="store_true", | 913 parser.add_option("--desc_from_logs", action="store_true", |
913 dest="from_logs", | 914 dest="from_logs", |
914 help="""Squashes git commit logs into change description and | 915 help="""Squashes git commit logs into change description and |
915 uses message as subject""") | 916 uses message as subject""") |
| 917 parser.add_option('-c', '--use-commit-queue', action='store_true', |
| 918 help='tell the commit queue to commit this patchset') |
916 (options, args) = parser.parse_args(args) | 919 (options, args) = parser.parse_args(args) |
917 | 920 |
918 # Make sure index is up-to-date before running diff-index. | 921 # Make sure index is up-to-date before running diff-index. |
919 RunGit(['update-index', '--refresh', '-q'], error_ok=True) | 922 RunGit(['update-index', '--refresh', '-q'], error_ok=True) |
920 if RunGit(['diff-index', 'HEAD']): | 923 if RunGit(['diff-index', 'HEAD']): |
921 print 'Cannot upload with a dirty tree. You must commit locally first.' | 924 print 'Cannot upload with a dirty tree. You must commit locally first.' |
922 return 1 | 925 return 1 |
923 | 926 |
924 cl = Changelist() | 927 cl = Changelist() |
925 if args: | 928 if args: |
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1014 print '\nGot exception while uploading -- saving description to %s\n' \ | 1017 print '\nGot exception while uploading -- saving description to %s\n' \ |
1015 % backup_path | 1018 % backup_path |
1016 backup_file = open(backup_path, 'w') | 1019 backup_file = open(backup_path, 'w') |
1017 backup_file.write(change_desc.description) | 1020 backup_file.write(change_desc.description) |
1018 backup_file.close() | 1021 backup_file.close() |
1019 raise | 1022 raise |
1020 | 1023 |
1021 if not cl.GetIssue(): | 1024 if not cl.GetIssue(): |
1022 cl.SetIssue(issue) | 1025 cl.SetIssue(issue) |
1023 cl.SetPatchset(patchset) | 1026 cl.SetPatchset(patchset) |
| 1027 |
| 1028 if options.use_commit_queue: |
| 1029 cl.SetFlag('commit', '1') |
1024 return 0 | 1030 return 0 |
1025 | 1031 |
1026 | 1032 |
1027 def SendUpstream(parser, args, cmd): | 1033 def SendUpstream(parser, args, cmd): |
1028 """Common code for CmdPush and CmdDCommit | 1034 """Common code for CmdPush and CmdDCommit |
1029 | 1035 |
1030 Squashed commit into a single. | 1036 Squashed commit into a single. |
1031 Updates changelog with metadata (e.g. pointer to review). | 1037 Updates changelog with metadata (e.g. pointer to review). |
1032 Pushes/dcommits the code upstream. | 1038 Pushes/dcommits the code upstream. |
1033 Updates review and closes. | 1039 Updates review and closes. |
(...skipping 224 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1258 parser.add_option('--reject', action='store_true', dest='reject', | 1264 parser.add_option('--reject', action='store_true', dest='reject', |
1259 help='allow failed patches and spew .rej files') | 1265 help='allow failed patches and spew .rej files') |
1260 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit', | 1266 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit', |
1261 help="don't commit after patch applies") | 1267 help="don't commit after patch applies") |
1262 (options, args) = parser.parse_args(args) | 1268 (options, args) = parser.parse_args(args) |
1263 if len(args) != 1: | 1269 if len(args) != 1: |
1264 parser.print_help() | 1270 parser.print_help() |
1265 return 1 | 1271 return 1 |
1266 issue_arg = args[0] | 1272 issue_arg = args[0] |
1267 | 1273 |
| 1274 # TODO(maruel): Use apply_issue.py |
| 1275 |
1268 if re.match(r'\d+', issue_arg): | 1276 if re.match(r'\d+', issue_arg): |
1269 # Input is an issue id. Figure out the URL. | 1277 # Input is an issue id. Figure out the URL. |
1270 issue = issue_arg | 1278 issue = issue_arg |
1271 patch_data = Changelist().GetPatchSetDiff(issue) | 1279 patch_data = Changelist().GetPatchSetDiff(issue) |
1272 else: | 1280 else: |
1273 # Assume it's a URL to the patch. Default to http. | 1281 # Assume it's a URL to the patch. Default to http. |
1274 issue_url = FixUrl(issue_arg) | 1282 issue_url = FixUrl(issue_arg) |
1275 match = re.match(r'.*?/issue(\d+)_\d+.diff', issue_url) | 1283 match = re.match(r'.*?/issue(\d+)_\d+.diff', issue_url) |
1276 if not match: | 1284 if not match: |
1277 DieWithError('Must pass an issue ID or full URL for ' | 1285 DieWithError('Must pass an issue ID or full URL for ' |
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1371 print | 1379 print |
1372 print GetTreeStatusReason() | 1380 print GetTreeStatusReason() |
1373 if status != 'open': | 1381 if status != 'open': |
1374 return 1 | 1382 return 1 |
1375 return 0 | 1383 return 0 |
1376 | 1384 |
1377 | 1385 |
1378 def CMDupstream(parser, args): | 1386 def CMDupstream(parser, args): |
1379 """print the name of the upstream branch, if any""" | 1387 """print the name of the upstream branch, if any""" |
1380 _, args = parser.parse_args(args) | 1388 _, args = parser.parse_args(args) |
| 1389 if args: |
| 1390 parser.error('Unrecognized args: %s' % ' '.join(args)) |
1381 cl = Changelist() | 1391 cl = Changelist() |
1382 print cl.GetUpstreamBranch() | 1392 print cl.GetUpstreamBranch() |
1383 return 0 | 1393 return 0 |
1384 | 1394 |
1385 | 1395 |
| 1396 def CMDset_commit(parser, args): |
| 1397 """set the commit bit""" |
| 1398 _, args = parser.parse_args(args) |
| 1399 if args: |
| 1400 parser.error('Unrecognized args: %s' % ' '.join(args)) |
| 1401 cl = Changelist() |
| 1402 cl.SetFlag('commit', '1') |
| 1403 return 0 |
| 1404 |
| 1405 |
1386 def Command(name): | 1406 def Command(name): |
1387 return getattr(sys.modules[__name__], 'CMD' + name, None) | 1407 return getattr(sys.modules[__name__], 'CMD' + name, None) |
1388 | 1408 |
1389 | 1409 |
1390 def CMDhelp(parser, args): | 1410 def CMDhelp(parser, args): |
1391 """print list of commands or help for a specific command""" | 1411 """print list of commands or help for a specific command""" |
1392 _, args = parser.parse_args(args) | 1412 _, args = parser.parse_args(args) |
1393 if len(args) == 1: | 1413 if len(args) == 1: |
1394 return main(args + ['--help']) | 1414 return main(args + ['--help']) |
1395 parser.print_help() | 1415 parser.print_help() |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1448 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 1468 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
1449 | 1469 |
1450 # Not a known command. Default to help. | 1470 # Not a known command. Default to help. |
1451 GenUsage(parser, 'help') | 1471 GenUsage(parser, 'help') |
1452 return CMDhelp(parser, argv) | 1472 return CMDhelp(parser, argv) |
1453 | 1473 |
1454 | 1474 |
1455 if __name__ == '__main__': | 1475 if __name__ == '__main__': |
1456 fix_encoding.fix_encoding() | 1476 fix_encoding.fix_encoding() |
1457 sys.exit(main(sys.argv[1:])) | 1477 sys.exit(main(sys.argv[1:])) |
OLD | NEW |