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 StringIO | 10 import StringIO |
11 import subprocess | 11 import subprocess |
12 import sys | 12 import sys |
13 import tempfile | 13 import tempfile |
14 import textwrap | 14 import textwrap |
15 import upload | |
16 import urlparse | 15 import urlparse |
17 import urllib2 | 16 import urllib2 |
18 | 17 |
19 try: | 18 try: |
20 import readline | 19 import readline # pylint: disable=W0611 |
21 except ImportError: | 20 except ImportError: |
22 pass | 21 pass |
23 | 22 |
23 # TODO(dpranke): don't use relative import. | |
24 import upload # pylint: disable=W0403 | |
24 try: | 25 try: |
25 # TODO(dpranke): We wrap this in a try block for a limited form of | 26 # TODO(dpranke): We wrap this in a try block for a limited form of |
26 # backwards-compatibility with older versions of git-cl that weren't | 27 # backwards-compatibility with older versions of git-cl that weren't |
27 # dependent on depot_tools. This version should still work outside of | 28 # dependent on depot_tools. This version should still work outside of |
28 # depot_tools as long as --bypass-hooks is used. We should remove this | 29 # depot_tools as long as --bypass-hooks is used. We should remove this |
29 # once this has baked for a while and things seem safe. | 30 # once this has baked for a while and things seem safe. |
30 depot_tools_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | 31 depot_tools_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
31 sys.path.append(depot_tools_path) | 32 sys.path.append(depot_tools_path) |
32 import breakpad | 33 import breakpad # pylint: disable=W0611 |
33 except ImportError: | 34 except ImportError: |
34 pass | 35 pass |
35 | 36 |
36 DEFAULT_SERVER = 'http://codereview.appspot.com' | 37 DEFAULT_SERVER = 'http://codereview.appspot.com' |
37 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' | 38 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' |
38 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' | 39 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' |
39 | 40 |
40 def DieWithError(message): | 41 def DieWithError(message): |
41 print >> sys.stderr, message | 42 print >> sys.stderr, message |
42 sys.exit(1) | 43 sys.exit(1) |
(...skipping 557 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
600 #PUSH_URL_CONFIG: url.ssh://gitrw.chromium.org.pushinsteadof | 601 #PUSH_URL_CONFIG: url.ssh://gitrw.chromium.org.pushinsteadof |
601 #ORIGIN_URL_CONFIG: http://src.chromium.org/git | 602 #ORIGIN_URL_CONFIG: http://src.chromium.org/git |
602 RunGit(['config', keyvals['PUSH_URL_CONFIG'], | 603 RunGit(['config', keyvals['PUSH_URL_CONFIG'], |
603 keyvals['ORIGIN_URL_CONFIG']]) | 604 keyvals['ORIGIN_URL_CONFIG']]) |
604 | 605 |
605 | 606 |
606 @usage('[repo root containing codereview.settings]') | 607 @usage('[repo root containing codereview.settings]') |
607 def CMDconfig(parser, args): | 608 def CMDconfig(parser, args): |
608 """edit configuration for this tree""" | 609 """edit configuration for this tree""" |
609 | 610 |
610 (options, args) = parser.parse_args(args) | 611 _, args = parser.parse_args(args) |
611 if len(args) == 0: | 612 if len(args) == 0: |
612 GetCodereviewSettingsInteractively() | 613 GetCodereviewSettingsInteractively() |
613 return 0 | 614 return 0 |
614 | 615 |
615 url = args[0] | 616 url = args[0] |
616 if not url.endswith('codereview.settings'): | 617 if not url.endswith('codereview.settings'): |
617 url = os.path.join(url, 'codereview.settings') | 618 url = os.path.join(url, 'codereview.settings') |
618 | 619 |
619 # Load code review settings and download hooks (if available). | 620 # Load code review settings and download hooks (if available). |
620 LoadCodereviewSettingsFromFile(urllib2.urlopen(url)) | 621 LoadCodereviewSettingsFromFile(urllib2.urlopen(url)) |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
666 print cl.GetDescription(pretty=True) | 667 print cl.GetDescription(pretty=True) |
667 return 0 | 668 return 0 |
668 | 669 |
669 | 670 |
670 @usage('[issue_number]') | 671 @usage('[issue_number]') |
671 def CMDissue(parser, args): | 672 def CMDissue(parser, args): |
672 """Set or display the current code review issue number. | 673 """Set or display the current code review issue number. |
673 | 674 |
674 Pass issue number 0 to clear the current issue. | 675 Pass issue number 0 to clear the current issue. |
675 """ | 676 """ |
676 (options, args) = parser.parse_args(args) | 677 _, args = parser.parse_args(args) |
677 | 678 |
678 cl = Changelist() | 679 cl = Changelist() |
679 if len(args) > 0: | 680 if len(args) > 0: |
680 try: | 681 try: |
681 issue = int(args[0]) | 682 issue = int(args[0]) |
682 except ValueError: | 683 except ValueError: |
683 DieWithError('Pass a number to set the issue or none to list it.\n' | 684 DieWithError('Pass a number to set the issue or none to list it.\n' |
684 'Maybe you want to run git cl status?') | 685 'Maybe you want to run git cl status?') |
685 cl.SetIssue(issue) | 686 cl.SetIssue(issue) |
686 print 'Issue number:', cl.GetIssue(), '(%s)' % cl.GetIssueURL() | 687 print 'Issue number:', cl.GetIssue(), '(%s)' % cl.GetIssueURL() |
(...skipping 511 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1198 parser.add_option('-f', action='store_true', dest='force', | 1199 parser.add_option('-f', action='store_true', dest='force', |
1199 help='with -b, clobber any existing branch') | 1200 help='with -b, clobber any existing branch') |
1200 parser.add_option('--reject', action='store_true', dest='reject', | 1201 parser.add_option('--reject', action='store_true', dest='reject', |
1201 help='allow failed patches and spew .rej files') | 1202 help='allow failed patches and spew .rej files') |
1202 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit', | 1203 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit', |
1203 help="don't commit after patch applies") | 1204 help="don't commit after patch applies") |
1204 (options, args) = parser.parse_args(args) | 1205 (options, args) = parser.parse_args(args) |
1205 if len(args) != 1: | 1206 if len(args) != 1: |
1206 parser.print_help() | 1207 parser.print_help() |
1207 return 1 | 1208 return 1 |
1208 input = args[0] | 1209 issue_arg = args[0] |
1209 | 1210 |
1210 if re.match(r'\d+', input): | 1211 if re.match(r'\d+', input): |
1211 # Input is an issue id. Figure out the URL. | 1212 # Input is an issue id. Figure out the URL. |
1212 issue = input | 1213 issue = issue_arg |
1213 server = settings.GetDefaultServerUrl() | 1214 server = settings.GetDefaultServerUrl() |
1214 fetch = urllib2.urlopen('%s/%s' % (server, issue)).read() | 1215 fetch = urllib2.urlopen('%s/%s' % (server, issue)).read() |
1215 m = re.search(r'/download/issue[0-9]+_[0-9]+.diff', fetch) | 1216 m = re.search(r'/download/issue[0-9]+_[0-9]+.diff', fetch) |
1216 if not m: | 1217 if not m: |
1217 DieWithError('Must pass an issue ID or full URL for ' | 1218 DieWithError('Must pass an issue ID or full URL for ' |
1218 '\'Download raw patch set\'') | 1219 '\'Download raw patch set\'') |
1219 url = '%s%s' % (server, m.group(0).strip()) | 1220 url = '%s%s' % (server, m.group(0).strip()) |
1220 else: | 1221 else: |
1221 # Assume it's a URL to the patch. Default to http. | 1222 # Assume it's a URL to the patch. Default to http. |
1222 input = FixUrl(input) | 1223 issue_url = FixUrl(issue_arg) |
1223 match = re.match(r'.*?/issue(\d+)_\d+.diff', input) | 1224 match = re.match(r'.*?/issue(\d+)_\d+.diff', issue_url) |
1224 if match: | 1225 if match: |
1225 issue = match.group(1) | 1226 issue = match.group(1) |
1226 url = input | 1227 url = input |
1227 else: | 1228 else: |
1228 DieWithError('Must pass an issue ID or full URL for ' | 1229 DieWithError('Must pass an issue ID or full URL for ' |
1229 '\'Download raw patch set\'') | 1230 '\'Download raw patch set\'') |
1230 | 1231 |
1231 if options.newbranch: | 1232 if options.newbranch: |
1232 if options.force: | 1233 if options.force: |
1233 RunGit(['branch', '-D', options.newbranch], | 1234 RunGit(['branch', '-D', options.newbranch], |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1298 return 'unset' | 1299 return 'unset' |
1299 | 1300 |
1300 | 1301 |
1301 def GetTreeStatusReason(): | 1302 def GetTreeStatusReason(): |
1302 """Fetches the tree status from a json url and returns the message | 1303 """Fetches the tree status from a json url and returns the message |
1303 with the reason for the tree to be opened or closed.""" | 1304 with the reason for the tree to be opened or closed.""" |
1304 # Don't import it at file level since simplejson is not installed by default | 1305 # Don't import it at file level since simplejson is not installed by default |
1305 # on python 2.5 and it is only used for git-cl tree which isn't often used, | 1306 # on python 2.5 and it is only used for git-cl tree which isn't often used, |
1306 # forcing everyone to install simplejson isn't efficient. | 1307 # forcing everyone to install simplejson isn't efficient. |
1307 try: | 1308 try: |
1308 import simplejson as json | 1309 import simplejson as json # pylint: disable=F0401 |
1309 except ImportError: | 1310 except ImportError: |
1310 try: | 1311 try: |
1311 import json | 1312 import json |
1312 # Some versions of python2.5 have an incomplete json module. Check to make | 1313 except (ImportError): |
M-A Ruel
2011/03/17 02:09:09
except ImportError:
| |
1313 # sure loads exists. | |
1314 json.loads | |
1315 except (ImportError, AttributeError): | |
1316 print >> sys.stderr, 'Please install simplejson' | 1314 print >> sys.stderr, 'Please install simplejson' |
1317 sys.exit(1) | 1315 sys.exit(1) |
1318 | 1316 |
1319 url = settings.GetTreeStatusUrl() | 1317 url = settings.GetTreeStatusUrl() |
1320 json_url = urlparse.urljoin(url, '/current?format=json') | 1318 json_url = urlparse.urljoin(url, '/current?format=json') |
1321 connection = urllib2.urlopen(json_url) | 1319 connection = urllib2.urlopen(json_url) |
1322 status = json.loads(connection.read()) | 1320 status = json.loads(connection.read()) |
1323 connection.close() | 1321 connection.close() |
1324 return status['message'] | 1322 return status['message'] |
1325 | 1323 |
1326 | 1324 |
1327 def CMDtree(parser, args): | 1325 def CMDtree(parser, args): |
1328 """show the status of the tree""" | 1326 """show the status of the tree""" |
1329 (options, args) = parser.parse_args(args) | 1327 _, args = parser.parse_args(args) |
1330 status = GetTreeStatus() | 1328 status = GetTreeStatus() |
1331 if 'unset' == status: | 1329 if 'unset' == status: |
1332 print 'You must configure your tree status URL by running "git cl config".' | 1330 print 'You must configure your tree status URL by running "git cl config".' |
1333 return 2 | 1331 return 2 |
1334 | 1332 |
1335 print "The tree is %s" % status | 1333 print "The tree is %s" % status |
1336 print | 1334 print |
1337 print GetTreeStatusReason() | 1335 print GetTreeStatusReason() |
1338 if status != 'open': | 1336 if status != 'open': |
1339 return 1 | 1337 return 1 |
1340 return 0 | 1338 return 0 |
1341 | 1339 |
1342 | 1340 |
1343 def CMDupstream(parser, args): | 1341 def CMDupstream(parser, args): |
1344 """print the name of the upstream branch, if any""" | 1342 """print the name of the upstream branch, if any""" |
1345 (options, args) = parser.parse_args(args) | 1343 _, args = parser.parse_args(args) |
1346 cl = Changelist() | 1344 cl = Changelist() |
1347 print cl.GetUpstreamBranch() | 1345 print cl.GetUpstreamBranch() |
1348 return 0 | 1346 return 0 |
1349 | 1347 |
1350 | 1348 |
1351 def Command(name): | 1349 def Command(name): |
1352 return getattr(sys.modules[__name__], 'CMD' + name, None) | 1350 return getattr(sys.modules[__name__], 'CMD' + name, None) |
1353 | 1351 |
1354 | 1352 |
1355 def CMDhelp(parser, args): | 1353 def CMDhelp(parser, args): |
1356 """print list of commands or help for a specific command""" | 1354 """print list of commands or help for a specific command""" |
1357 (options, args) = parser.parse_args(args) | 1355 _, args = parser.parse_args(args) |
1358 if len(args) == 1: | 1356 if len(args) == 1: |
1359 return main(args + ['--help']) | 1357 return main(args + ['--help']) |
1360 parser.print_help() | 1358 parser.print_help() |
1361 return 0 | 1359 return 0 |
1362 | 1360 |
1363 | 1361 |
1364 def GenUsage(parser, command): | 1362 def GenUsage(parser, command): |
1365 """Modify an OptParse object with the function's documentation.""" | 1363 """Modify an OptParse object with the function's documentation.""" |
1366 obj = Command(command) | 1364 obj = Command(command) |
1367 more = getattr(obj, 'usage_more', '') | 1365 more = getattr(obj, 'usage_more', '') |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1408 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' | 1406 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' |
1409 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 1407 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
1410 | 1408 |
1411 # Not a known command. Default to help. | 1409 # Not a known command. Default to help. |
1412 GenUsage(parser, 'help') | 1410 GenUsage(parser, 'help') |
1413 return CMDhelp(parser, argv) | 1411 return CMDhelp(parser, argv) |
1414 | 1412 |
1415 | 1413 |
1416 if __name__ == '__main__': | 1414 if __name__ == '__main__': |
1417 sys.exit(main(sys.argv[1:])) | 1415 sys.exit(main(sys.argv[1:])) |
OLD | NEW |