| 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 |