OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # git-cl -- a git-command for integrating reviews on Rietveld | 2 # git-cl -- a git-command for integrating reviews on Rietveld |
3 # Copyright (C) 2008 Evan Martin <martine@danga.com> | 3 # Copyright (C) 2008 Evan Martin <martine@danga.com> |
4 | 4 |
5 import errno | 5 import errno |
6 import logging | 6 import logging |
7 import optparse | 7 import optparse |
8 import os | 8 import os |
9 import re | 9 import re |
10 import subprocess | 10 import subprocess |
(...skipping 319 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
330 else: | 330 else: |
331 self.branch = None | 331 self.branch = None |
332 self.rietveld_server = None | 332 self.rietveld_server = None |
333 self.upstream_branch = None | 333 self.upstream_branch = None |
334 self.has_issue = False | 334 self.has_issue = False |
335 self.issue = None | 335 self.issue = None |
336 self.has_description = False | 336 self.has_description = False |
337 self.description = None | 337 self.description = None |
338 self.has_patchset = False | 338 self.has_patchset = False |
339 self.patchset = None | 339 self.patchset = None |
| 340 self._rpc_server = None |
340 | 341 |
341 def GetBranch(self): | 342 def GetBranch(self): |
342 """Returns the short branch name, e.g. 'master'.""" | 343 """Returns the short branch name, e.g. 'master'.""" |
343 if not self.branch: | 344 if not self.branch: |
344 self.branchref = RunGit(['symbolic-ref', 'HEAD']).strip() | 345 self.branchref = RunGit(['symbolic-ref', 'HEAD']).strip() |
345 self.branch = ShortBranchName(self.branchref) | 346 self.branch = ShortBranchName(self.branchref) |
346 return self.branch | 347 return self.branch |
347 | 348 |
348 def GetBranchRef(self): | 349 def GetBranchRef(self): |
349 """Returns the full branch name, e.g. 'refs/heads/master'.""" | 350 """Returns the full branch name, e.g. 'refs/heads/master'.""" |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
457 | 458 |
458 def SetPatchset(self, patchset): | 459 def SetPatchset(self, patchset): |
459 """Set this branch's patchset. If patchset=0, clears the patchset.""" | 460 """Set this branch's patchset. If patchset=0, clears the patchset.""" |
460 if patchset: | 461 if patchset: |
461 RunGit(['config', self._PatchsetSetting(), str(patchset)]) | 462 RunGit(['config', self._PatchsetSetting(), str(patchset)]) |
462 else: | 463 else: |
463 RunGit(['config', '--unset', self._PatchsetSetting()], | 464 RunGit(['config', '--unset', self._PatchsetSetting()], |
464 swallow_stderr=True, error_ok=True) | 465 swallow_stderr=True, error_ok=True) |
465 self.has_patchset = False | 466 self.has_patchset = False |
466 | 467 |
| 468 def GetPatchSetDiff(self, issue): |
| 469 # Grab the last patchset of the issue first. |
| 470 data = json.loads(self._RpcServer().Send('/api/%s' % issue)) |
| 471 patchset = data['patchsets'][-1] |
| 472 return self._RpcServer().Send( |
| 473 '/download/issue%s_%s.diff' % (issue, patchset)) |
| 474 |
467 def SetIssue(self, issue): | 475 def SetIssue(self, issue): |
468 """Set this branch's issue. If issue=0, clears the issue.""" | 476 """Set this branch's issue. If issue=0, clears the issue.""" |
469 if issue: | 477 if issue: |
470 RunGit(['config', self._IssueSetting(), str(issue)]) | 478 RunGit(['config', self._IssueSetting(), str(issue)]) |
471 if self.rietveld_server: | 479 if self.rietveld_server: |
472 RunGit(['config', self._RietveldServer(), self.rietveld_server]) | 480 RunGit(['config', self._RietveldServer(), self.rietveld_server]) |
473 else: | 481 else: |
474 RunGit(['config', '--unset', self._IssueSetting()]) | 482 RunGit(['config', '--unset', self._IssueSetting()]) |
475 self.SetPatchset(0) | 483 self.SetPatchset(0) |
476 self.has_issue = False | 484 self.has_issue = False |
477 | 485 |
478 def CloseIssue(self): | 486 def CloseIssue(self): |
479 rpc_server = self._RpcServer() | 487 rpc_server = self._RpcServer() |
480 # Newer versions of Rietveld require us to pass an XSRF token to POST, so | 488 # Newer versions of Rietveld require us to pass an XSRF token to POST, so |
481 # we fetch it from the server. (The version used by Chromium has been | 489 # we fetch it from the server. (The version used by Chromium has been |
482 # modified so the token isn't required when closing an issue.) | 490 # modified so the token isn't required when closing an issue.) |
483 xsrf_token = rpc_server.Send('/xsrf_token', | 491 xsrf_token = rpc_server.Send('/xsrf_token', |
484 extra_headers={'X-Requesting-XSRF-Token': '1'}) | 492 extra_headers={'X-Requesting-XSRF-Token': '1'}) |
485 | 493 |
486 # You cannot close an issue with a GET. | 494 # You cannot close an issue with a GET. |
487 # We pass an empty string for the data so it is a POST rather than a GET. | 495 # We pass an empty string for the data so it is a POST rather than a GET. |
488 data = [("description", self.description), | 496 data = [("description", self.description), |
489 ("xsrf_token", xsrf_token)] | 497 ("xsrf_token", xsrf_token)] |
490 ctype, body = upload.EncodeMultipartFormData(data, []) | 498 ctype, body = upload.EncodeMultipartFormData(data, []) |
491 rpc_server.Send('/' + self.GetIssue() + '/close', body, ctype) | 499 rpc_server.Send('/' + self.GetIssue() + '/close', body, ctype) |
492 | 500 |
493 def _RpcServer(self): | 501 def _RpcServer(self): |
494 """Returns an upload.RpcServer() to access this review's rietveld instance. | 502 """Returns an upload.RpcServer() to access this review's rietveld instance. |
495 """ | 503 """ |
496 server = self.GetRietveldServer() | 504 if not self._rpc_server: |
497 return upload.GetRpcServer(server, save_cookies=True) | 505 server = self.GetRietveldServer() |
| 506 self._rpc_server = upload.GetRpcServer(server, save_cookies=True) |
| 507 return self._rpc_server |
498 | 508 |
499 def _IssueSetting(self): | 509 def _IssueSetting(self): |
500 """Return the git setting that stores this change's issue.""" | 510 """Return the git setting that stores this change's issue.""" |
501 return 'branch.%s.rietveldissue' % self.GetBranch() | 511 return 'branch.%s.rietveldissue' % self.GetBranch() |
502 | 512 |
503 def _PatchsetSetting(self): | 513 def _PatchsetSetting(self): |
504 """Return the git setting that stores this change's most recent patchset.""" | 514 """Return the git setting that stores this change's most recent patchset.""" |
505 return 'branch.%s.rietveldpatchset' % self.GetBranch() | 515 return 'branch.%s.rietveldpatchset' % self.GetBranch() |
506 | 516 |
507 def _RietveldServer(self): | 517 def _RietveldServer(self): |
(...skipping 705 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1213 help="don't commit after patch applies") | 1223 help="don't commit after patch applies") |
1214 (options, args) = parser.parse_args(args) | 1224 (options, args) = parser.parse_args(args) |
1215 if len(args) != 1: | 1225 if len(args) != 1: |
1216 parser.print_help() | 1226 parser.print_help() |
1217 return 1 | 1227 return 1 |
1218 issue_arg = args[0] | 1228 issue_arg = args[0] |
1219 | 1229 |
1220 if re.match(r'\d+', issue_arg): | 1230 if re.match(r'\d+', issue_arg): |
1221 # Input is an issue id. Figure out the URL. | 1231 # Input is an issue id. Figure out the URL. |
1222 issue = issue_arg | 1232 issue = issue_arg |
1223 server = settings.GetDefaultServerUrl() | 1233 patch_data = Changelist().GetPatchSetDiff(issue) |
1224 fetch = urllib2.urlopen('%s/%s' % (server, issue)).read() | |
1225 m = re.search(r'/download/issue[0-9]+_[0-9]+.diff', fetch) | |
1226 if not m: | |
1227 DieWithError('Must pass an issue ID or full URL for ' | |
1228 '\'Download raw patch set\'') | |
1229 url = '%s%s' % (server, m.group(0).strip()) | |
1230 else: | 1234 else: |
1231 # Assume it's a URL to the patch. Default to http. | 1235 # Assume it's a URL to the patch. Default to http. |
1232 issue_url = FixUrl(issue_arg) | 1236 issue_url = FixUrl(issue_arg) |
1233 match = re.match(r'.*?/issue(\d+)_\d+.diff', issue_url) | 1237 match = re.match(r'.*?/issue(\d+)_\d+.diff', issue_url) |
1234 if match: | 1238 if not match: |
1235 issue = match.group(1) | |
1236 url = issue_arg | |
1237 else: | |
1238 DieWithError('Must pass an issue ID or full URL for ' | 1239 DieWithError('Must pass an issue ID or full URL for ' |
1239 '\'Download raw patch set\'') | 1240 '\'Download raw patch set\'') |
| 1241 issue = match.group(1) |
| 1242 patch_data = urllib2.urlopen(issue_arg).read() |
1240 | 1243 |
1241 if options.newbranch: | 1244 if options.newbranch: |
1242 if options.force: | 1245 if options.force: |
1243 RunGit(['branch', '-D', options.newbranch], | 1246 RunGit(['branch', '-D', options.newbranch], |
1244 swallow_stderr=True, error_ok=True) | 1247 swallow_stderr=True, error_ok=True) |
1245 RunGit(['checkout', '-b', options.newbranch, | 1248 RunGit(['checkout', '-b', options.newbranch, |
1246 Changelist().GetUpstreamBranch()]) | 1249 Changelist().GetUpstreamBranch()]) |
1247 | 1250 |
1248 # Switch up to the top-level directory, if necessary, in preparation for | 1251 # Switch up to the top-level directory, if necessary, in preparation for |
1249 # applying the patch. | 1252 # applying the patch. |
1250 top = RunGit(['rev-parse', '--show-cdup']).strip() | 1253 top = RunGit(['rev-parse', '--show-cdup']).strip() |
1251 if top: | 1254 if top: |
1252 os.chdir(top) | 1255 os.chdir(top) |
1253 | 1256 |
1254 patch_data = urllib2.urlopen(url).read() | |
1255 # Git patches have a/ at the beginning of source paths. We strip that out | 1257 # Git patches have a/ at the beginning of source paths. We strip that out |
1256 # with a sed script rather than the -p flag to patch so we can feed either | 1258 # with a sed script rather than the -p flag to patch so we can feed either |
1257 # Git or svn-style patches into the same apply command. | 1259 # Git or svn-style patches into the same apply command. |
1258 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7. | 1260 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7. |
1259 sed_proc = Popen(['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], | 1261 sed_proc = Popen(['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], |
1260 stdin=subprocess.PIPE, stdout=subprocess.PIPE) | 1262 stdin=subprocess.PIPE, stdout=subprocess.PIPE) |
1261 patch_data = sed_proc.communicate(patch_data)[0] | 1263 patch_data = sed_proc.communicate(patch_data)[0] |
1262 if sed_proc.returncode: | 1264 if sed_proc.returncode: |
1263 DieWithError('Git patch mungling failed.') | 1265 DieWithError('Git patch mungling failed.') |
1264 logging.info(patch_data) | 1266 logging.info(patch_data) |
(...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1403 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' | 1405 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' |
1404 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 1406 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
1405 | 1407 |
1406 # Not a known command. Default to help. | 1408 # Not a known command. Default to help. |
1407 GenUsage(parser, 'help') | 1409 GenUsage(parser, 'help') |
1408 return CMDhelp(parser, argv) | 1410 return CMDhelp(parser, argv) |
1409 | 1411 |
1410 | 1412 |
1411 if __name__ == '__main__': | 1413 if __name__ == '__main__': |
1412 sys.exit(main(sys.argv[1:])) | 1414 sys.exit(main(sys.argv[1:])) |
OLD | NEW |