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