| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 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 difflib | 10 import difflib |
| (...skipping 1015 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1026 urlretrieve(src, dst) | 1026 urlretrieve(src, dst) |
| 1027 os.chmod(dst, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) | 1027 os.chmod(dst, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) |
| 1028 except Exception: | 1028 except Exception: |
| 1029 if os.path.exists(dst): | 1029 if os.path.exists(dst): |
| 1030 os.remove(dst) | 1030 os.remove(dst) |
| 1031 DieWithError('\nFailed to download hooks from %s' % src) | 1031 DieWithError('\nFailed to download hooks from %s' % src) |
| 1032 | 1032 |
| 1033 | 1033 |
| 1034 @usage('[repo root containing codereview.settings]') | 1034 @usage('[repo root containing codereview.settings]') |
| 1035 def CMDconfig(parser, args): | 1035 def CMDconfig(parser, args): |
| 1036 """Edits configuration for this tree.""" | 1036 """edit configuration for this tree""" |
| 1037 | 1037 |
| 1038 _, args = parser.parse_args(args) | 1038 _, args = parser.parse_args(args) |
| 1039 if len(args) == 0: | 1039 if len(args) == 0: |
| 1040 GetCodereviewSettingsInteractively() | 1040 GetCodereviewSettingsInteractively() |
| 1041 DownloadHooks(True) | 1041 DownloadHooks(True) |
| 1042 return 0 | 1042 return 0 |
| 1043 | 1043 |
| 1044 url = args[0] | 1044 url = args[0] |
| 1045 if not url.endswith('codereview.settings'): | 1045 if not url.endswith('codereview.settings'): |
| 1046 url = os.path.join(url, 'codereview.settings') | 1046 url = os.path.join(url, 'codereview.settings') |
| 1047 | 1047 |
| 1048 # Load code review settings and download hooks (if available). | 1048 # Load code review settings and download hooks (if available). |
| 1049 LoadCodereviewSettingsFromFile(urllib2.urlopen(url)) | 1049 LoadCodereviewSettingsFromFile(urllib2.urlopen(url)) |
| 1050 DownloadHooks(True) | 1050 DownloadHooks(True) |
| 1051 return 0 | 1051 return 0 |
| 1052 | 1052 |
| 1053 | 1053 |
| 1054 def CMDbaseurl(parser, args): | 1054 def CMDbaseurl(parser, args): |
| 1055 """Gets or sets base-url for this branch.""" | 1055 """get or set base-url for this branch""" |
| 1056 branchref = RunGit(['symbolic-ref', 'HEAD']).strip() | 1056 branchref = RunGit(['symbolic-ref', 'HEAD']).strip() |
| 1057 branch = ShortBranchName(branchref) | 1057 branch = ShortBranchName(branchref) |
| 1058 _, args = parser.parse_args(args) | 1058 _, args = parser.parse_args(args) |
| 1059 if not args: | 1059 if not args: |
| 1060 print("Current base-url:") | 1060 print("Current base-url:") |
| 1061 return RunGit(['config', 'branch.%s.base-url' % branch], | 1061 return RunGit(['config', 'branch.%s.base-url' % branch], |
| 1062 error_ok=False).strip() | 1062 error_ok=False).strip() |
| 1063 else: | 1063 else: |
| 1064 print("Setting base-url to %s" % args[0]) | 1064 print("Setting base-url to %s" % args[0]) |
| 1065 return RunGit(['config', 'branch.%s.base-url' % branch, args[0]], | 1065 return RunGit(['config', 'branch.%s.base-url' % branch, args[0]], |
| 1066 error_ok=False).strip() | 1066 error_ok=False).strip() |
| 1067 | 1067 |
| 1068 | 1068 |
| 1069 def CMDstatus(parser, args): | 1069 def CMDstatus(parser, args): |
| 1070 """Show status of changelists. | 1070 """show status of changelists""" |
| 1071 | |
| 1072 Colors are used to tell the state of the CL unless --fast is used: | |
| 1073 - Green LGTM'ed | |
| 1074 - Blue waiting for review | |
| 1075 - Yellow waiting for you to reply to review | |
| 1076 - Red not sent for review or broken | |
| 1077 - Cyan was committed, branch can be deleted | |
| 1078 | |
| 1079 Also see 'git cl comments'. | |
| 1080 """ | |
| 1081 parser.add_option('--field', | 1071 parser.add_option('--field', |
| 1082 help='print only specific field (desc|id|patch|url)') | 1072 help='print only specific field (desc|id|patch|url)') |
| 1083 parser.add_option('-f', '--fast', action='store_true', | 1073 parser.add_option('-f', '--fast', action='store_true', |
| 1084 help='Do not retrieve review status') | 1074 help='Do not retrieve review status') |
| 1085 (options, args) = parser.parse_args(args) | 1075 (options, args) = parser.parse_args(args) |
| 1086 | 1076 |
| 1087 if options.field: | 1077 if options.field: |
| 1088 cl = Changelist() | 1078 cl = Changelist() |
| 1089 if options.field.startswith('desc'): | 1079 if options.field.startswith('desc'): |
| 1090 print cl.GetDescription() | 1080 print cl.GetDescription() |
| (...skipping 21 matching lines...) Expand all Loading... |
| 1112 alignment = max(5, max(len(b) for b in branches)) | 1102 alignment = max(5, max(len(b) for b in branches)) |
| 1113 print 'Branches associated with reviews:' | 1103 print 'Branches associated with reviews:' |
| 1114 # Adhoc thread pool to request data concurrently. | 1104 # Adhoc thread pool to request data concurrently. |
| 1115 output = Queue.Queue() | 1105 output = Queue.Queue() |
| 1116 | 1106 |
| 1117 # Silence upload.py otherwise it becomes unweldly. | 1107 # Silence upload.py otherwise it becomes unweldly. |
| 1118 upload.verbosity = 0 | 1108 upload.verbosity = 0 |
| 1119 | 1109 |
| 1120 if not options.fast: | 1110 if not options.fast: |
| 1121 def fetch(b): | 1111 def fetch(b): |
| 1122 """Fetches information for an issue and returns (branch, issue, color).""" | |
| 1123 c = Changelist(branchref=b) | 1112 c = Changelist(branchref=b) |
| 1124 i = c.GetIssueURL() | 1113 i = c.GetIssueURL() |
| 1125 props = {} | 1114 try: |
| 1126 r = None | 1115 props = c.GetIssueProperties() |
| 1127 if i: | 1116 r = c.GetApprovingReviewers() if i else None |
| 1128 try: | 1117 if not props.get('messages'): |
| 1129 props = c.GetIssueProperties() | 1118 r = None |
| 1130 r = c.GetApprovingReviewers() if i else None | 1119 except urllib2.HTTPError: |
| 1131 except urllib2.HTTPError: | 1120 # The issue probably doesn't exist anymore. |
| 1132 # The issue probably doesn't exist anymore. | 1121 i += ' (broken)' |
| 1133 i += ' (broken)' | 1122 r = None |
| 1134 | 1123 output.put((b, i, r)) |
| 1135 msgs = props.get('messages') or [] | |
| 1136 | |
| 1137 if not i: | |
| 1138 color = Fore.WHITE | |
| 1139 elif props.get('closed'): | |
| 1140 # Issue is closed. | |
| 1141 color = Fore.CYAN | |
| 1142 elif r: | |
| 1143 # Was LGTM'ed. | |
| 1144 color = Fore.GREEN | |
| 1145 elif not msgs: | |
| 1146 # No message was sent. | |
| 1147 color = Fore.RED | |
| 1148 elif msgs[-1]['sender'] != props.get('owner_email'): | |
| 1149 color = Fore.YELLOW | |
| 1150 else: | |
| 1151 color = Fore.BLUE | |
| 1152 output.put((b, i, color)) | |
| 1153 | 1124 |
| 1154 threads = [threading.Thread(target=fetch, args=(b,)) for b in branches] | 1125 threads = [threading.Thread(target=fetch, args=(b,)) for b in branches] |
| 1155 for t in threads: | 1126 for t in threads: |
| 1156 t.daemon = True | 1127 t.daemon = True |
| 1157 t.start() | 1128 t.start() |
| 1158 else: | 1129 else: |
| 1159 # Do not use GetApprovingReviewers(), since it requires an HTTP request. | 1130 # Do not use GetApprovingReviewers(), since it requires an HTTP request. |
| 1160 for b in branches: | 1131 for b in branches: |
| 1161 c = Changelist(branchref=b) | 1132 c = Changelist(branchref=b) |
| 1162 url = c.GetIssueURL() | 1133 output.put((b, c.GetIssue(), None)) |
| 1163 output.put((b, url, Fore.BLUE if url else Fore.WHITE)) | |
| 1164 | 1134 |
| 1165 tmp = {} | 1135 tmp = {} |
| 1166 alignment = max(5, max(len(ShortBranchName(b)) for b in branches)) | 1136 alignment = max(5, max(len(ShortBranchName(b)) for b in branches)) |
| 1167 for branch in sorted(branches): | 1137 for branch in sorted(branches): |
| 1168 while branch not in tmp: | 1138 while branch not in tmp: |
| 1169 b, i, color = output.get() | 1139 b, i, r = output.get() |
| 1170 tmp[b] = (i, color) | 1140 tmp[b] = (i, r) |
| 1171 issue, color = tmp.pop(branch) | 1141 issue, reviewers = tmp.pop(branch) |
| 1142 if not issue: |
| 1143 color = Fore.WHITE |
| 1144 elif reviewers: |
| 1145 # Was approved. |
| 1146 color = Fore.GREEN |
| 1147 elif reviewers is None: |
| 1148 # No message was sent. |
| 1149 color = Fore.RED |
| 1150 else: |
| 1151 color = Fore.BLUE |
| 1172 print ' %*s: %s%s%s' % ( | 1152 print ' %*s: %s%s%s' % ( |
| 1173 alignment, ShortBranchName(branch), color, issue, Fore.RESET) | 1153 alignment, ShortBranchName(branch), color, issue, Fore.RESET) |
| 1174 | 1154 |
| 1175 cl = Changelist() | 1155 cl = Changelist() |
| 1176 print | 1156 print |
| 1177 print 'Current branch:', | 1157 print 'Current branch:', |
| 1178 if not cl.GetIssue(): | 1158 if not cl.GetIssue(): |
| 1179 print 'no issue assigned.' | 1159 print 'no issue assigned.' |
| 1180 return 0 | 1160 return 0 |
| 1181 print cl.GetBranch() | 1161 print cl.GetBranch() |
| 1182 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()) | 1162 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()) |
| 1183 print 'Issue description:' | 1163 print 'Issue description:' |
| 1184 print cl.GetDescription(pretty=True) | 1164 print cl.GetDescription(pretty=True) |
| 1185 return 0 | 1165 return 0 |
| 1186 | 1166 |
| 1187 | 1167 |
| 1188 @usage('[issue_number]') | 1168 @usage('[issue_number]') |
| 1189 def CMDissue(parser, args): | 1169 def CMDissue(parser, args): |
| 1190 """Sets or displays the current code review issue number. | 1170 """Set or display the current code review issue number. |
| 1191 | 1171 |
| 1192 Pass issue number 0 to clear the current issue. | 1172 Pass issue number 0 to clear the current issue. |
| 1193 """ | 1173 """ |
| 1194 _, args = parser.parse_args(args) | 1174 _, args = parser.parse_args(args) |
| 1195 | 1175 |
| 1196 cl = Changelist() | 1176 cl = Changelist() |
| 1197 if len(args) > 0: | 1177 if len(args) > 0: |
| 1198 try: | 1178 try: |
| 1199 issue = int(args[0]) | 1179 issue = int(args[0]) |
| 1200 except ValueError: | 1180 except ValueError: |
| 1201 DieWithError('Pass a number to set the issue or none to list it.\n' | 1181 DieWithError('Pass a number to set the issue or none to list it.\n' |
| 1202 'Maybe you want to run git cl status?') | 1182 'Maybe you want to run git cl status?') |
| 1203 cl.SetIssue(issue) | 1183 cl.SetIssue(issue) |
| 1204 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()) | 1184 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()) |
| 1205 return 0 | 1185 return 0 |
| 1206 | 1186 |
| 1207 | 1187 |
| 1208 def CMDcomments(parser, args): | 1188 def CMDcomments(parser, args): |
| 1209 """Shows review comments of the current changelist.""" | 1189 """show review comments of the current changelist""" |
| 1210 (_, args) = parser.parse_args(args) | 1190 (_, args) = parser.parse_args(args) |
| 1211 if args: | 1191 if args: |
| 1212 parser.error('Unsupported argument: %s' % args) | 1192 parser.error('Unsupported argument: %s' % args) |
| 1213 | 1193 |
| 1214 cl = Changelist() | 1194 cl = Changelist() |
| 1215 if cl.GetIssue(): | 1195 if cl.GetIssue(): |
| 1216 data = cl.GetIssueProperties() | 1196 data = cl.GetIssueProperties() |
| 1217 for message in sorted(data['messages'], key=lambda x: x['date']): | 1197 for message in sorted(data['messages'], key=lambda x: x['date']): |
| 1218 if message['disapproval']: | 1198 if message['disapproval']: |
| 1219 color = Fore.RED | 1199 color = Fore.RED |
| 1220 elif message['approval']: | 1200 elif message['approval']: |
| 1221 color = Fore.GREEN | 1201 color = Fore.GREEN |
| 1222 elif message['sender'] == data['owner_email']: | 1202 elif message['sender'] == data['owner_email']: |
| 1223 color = Fore.MAGENTA | 1203 color = Fore.MAGENTA |
| 1224 else: | 1204 else: |
| 1225 color = Fore.BLUE | 1205 color = Fore.BLUE |
| 1226 print '\n%s%s %s%s' % ( | 1206 print '\n%s%s %s%s' % ( |
| 1227 color, message['date'].split('.', 1)[0], message['sender'], | 1207 color, message['date'].split('.', 1)[0], message['sender'], |
| 1228 Fore.RESET) | 1208 Fore.RESET) |
| 1229 if message['text'].strip(): | 1209 if message['text'].strip(): |
| 1230 print '\n'.join(' ' + l for l in message['text'].splitlines()) | 1210 print '\n'.join(' ' + l for l in message['text'].splitlines()) |
| 1231 return 0 | 1211 return 0 |
| 1232 | 1212 |
| 1233 | 1213 |
| 1234 def CMDdescription(parser, args): | 1214 def CMDdescription(parser, args): |
| 1235 """Brings up the editor for the current CL's description.""" | 1215 """brings up the editor for the current CL's description.""" |
| 1236 cl = Changelist() | 1216 cl = Changelist() |
| 1237 if not cl.GetIssue(): | 1217 if not cl.GetIssue(): |
| 1238 DieWithError('This branch has no associated changelist.') | 1218 DieWithError('This branch has no associated changelist.') |
| 1239 description = ChangeDescription(cl.GetDescription()) | 1219 description = ChangeDescription(cl.GetDescription()) |
| 1240 description.prompt() | 1220 description.prompt() |
| 1241 cl.UpdateDescription(description.description) | 1221 cl.UpdateDescription(description.description) |
| 1242 return 0 | 1222 return 0 |
| 1243 | 1223 |
| 1244 | 1224 |
| 1245 def CreateDescriptionFromLog(args): | 1225 def CreateDescriptionFromLog(args): |
| 1246 """Pulls out the commit log to use as a base for the CL description.""" | 1226 """Pulls out the commit log to use as a base for the CL description.""" |
| 1247 log_args = [] | 1227 log_args = [] |
| 1248 if len(args) == 1 and not args[0].endswith('.'): | 1228 if len(args) == 1 and not args[0].endswith('.'): |
| 1249 log_args = [args[0] + '..'] | 1229 log_args = [args[0] + '..'] |
| 1250 elif len(args) == 1 and args[0].endswith('...'): | 1230 elif len(args) == 1 and args[0].endswith('...'): |
| 1251 log_args = [args[0][:-1]] | 1231 log_args = [args[0][:-1]] |
| 1252 elif len(args) == 2: | 1232 elif len(args) == 2: |
| 1253 log_args = [args[0] + '..' + args[1]] | 1233 log_args = [args[0] + '..' + args[1]] |
| 1254 else: | 1234 else: |
| 1255 log_args = args[:] # Hope for the best! | 1235 log_args = args[:] # Hope for the best! |
| 1256 return RunGit(['log', '--pretty=format:%s\n\n%b'] + log_args) | 1236 return RunGit(['log', '--pretty=format:%s\n\n%b'] + log_args) |
| 1257 | 1237 |
| 1258 | 1238 |
| 1259 def CMDpresubmit(parser, args): | 1239 def CMDpresubmit(parser, args): |
| 1260 """Runs presubmit tests on the current changelist.""" | 1240 """run presubmit tests on the current changelist""" |
| 1261 parser.add_option('-u', '--upload', action='store_true', | 1241 parser.add_option('-u', '--upload', action='store_true', |
| 1262 help='Run upload hook instead of the push/dcommit hook') | 1242 help='Run upload hook instead of the push/dcommit hook') |
| 1263 parser.add_option('-f', '--force', action='store_true', | 1243 parser.add_option('-f', '--force', action='store_true', |
| 1264 help='Run checks even if tree is dirty') | 1244 help='Run checks even if tree is dirty') |
| 1265 (options, args) = parser.parse_args(args) | 1245 (options, args) = parser.parse_args(args) |
| 1266 | 1246 |
| 1267 if not options.force and is_dirty_git_tree('presubmit'): | 1247 if not options.force and is_dirty_git_tree('presubmit'): |
| 1268 print 'use --force to check even if tree is dirty.' | 1248 print 'use --force to check even if tree is dirty.' |
| 1269 return 1 | 1249 return 1 |
| 1270 | 1250 |
| (...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1437 So that "--reviewers joe@c,john@c --reviewers joa@c" results in | 1417 So that "--reviewers joe@c,john@c --reviewers joa@c" results in |
| 1438 options.reviewers == sorted(['joe@c', 'john@c', 'joa@c']). | 1418 options.reviewers == sorted(['joe@c', 'john@c', 'joa@c']). |
| 1439 """ | 1419 """ |
| 1440 items = sum((i.split(',') for i in l), []) | 1420 items = sum((i.split(',') for i in l), []) |
| 1441 stripped_items = (i.strip() for i in items) | 1421 stripped_items = (i.strip() for i in items) |
| 1442 return sorted(filter(None, stripped_items)) | 1422 return sorted(filter(None, stripped_items)) |
| 1443 | 1423 |
| 1444 | 1424 |
| 1445 @usage('[args to "git diff"]') | 1425 @usage('[args to "git diff"]') |
| 1446 def CMDupload(parser, args): | 1426 def CMDupload(parser, args): |
| 1447 """Uploads the current changelist to codereview.""" | 1427 """upload the current changelist to codereview""" |
| 1448 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', | 1428 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', |
| 1449 help='bypass upload presubmit hook') | 1429 help='bypass upload presubmit hook') |
| 1450 parser.add_option('--bypass-watchlists', action='store_true', | 1430 parser.add_option('--bypass-watchlists', action='store_true', |
| 1451 dest='bypass_watchlists', | 1431 dest='bypass_watchlists', |
| 1452 help='bypass watchlists auto CC-ing reviewers') | 1432 help='bypass watchlists auto CC-ing reviewers') |
| 1453 parser.add_option('-f', action='store_true', dest='force', | 1433 parser.add_option('-f', action='store_true', dest='force', |
| 1454 help="force yes to questions (don't prompt)") | 1434 help="force yes to questions (don't prompt)") |
| 1455 parser.add_option('-m', dest='message', help='message for patchset') | 1435 parser.add_option('-m', dest='message', help='message for patchset') |
| 1456 parser.add_option('-t', dest='title', help='title for patchset') | 1436 parser.add_option('-t', dest='title', help='title for patchset') |
| 1457 parser.add_option('-r', '--reviewers', | 1437 parser.add_option('-r', '--reviewers', |
| (...skipping 302 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1760 if retcode == 0: | 1740 if retcode == 0: |
| 1761 hook = POSTUPSTREAM_HOOK_PATTERN % cmd | 1741 hook = POSTUPSTREAM_HOOK_PATTERN % cmd |
| 1762 if os.path.isfile(hook): | 1742 if os.path.isfile(hook): |
| 1763 RunCommand([hook, base_branch], error_ok=True) | 1743 RunCommand([hook, base_branch], error_ok=True) |
| 1764 | 1744 |
| 1765 return 0 | 1745 return 0 |
| 1766 | 1746 |
| 1767 | 1747 |
| 1768 @usage('[upstream branch to apply against]') | 1748 @usage('[upstream branch to apply against]') |
| 1769 def CMDdcommit(parser, args): | 1749 def CMDdcommit(parser, args): |
| 1770 """Commits the current changelist via git-svn.""" | 1750 """commit the current changelist via git-svn""" |
| 1771 if not settings.GetIsGitSvn(): | 1751 if not settings.GetIsGitSvn(): |
| 1772 message = """This doesn't appear to be an SVN repository. | 1752 message = """This doesn't appear to be an SVN repository. |
| 1773 If your project has a git mirror with an upstream SVN master, you probably need | 1753 If your project has a git mirror with an upstream SVN master, you probably need |
| 1774 to run 'git svn init', see your project's git mirror documentation. | 1754 to run 'git svn init', see your project's git mirror documentation. |
| 1775 If your project has a true writeable upstream repository, you probably want | 1755 If your project has a true writeable upstream repository, you probably want |
| 1776 to run 'git cl push' instead. | 1756 to run 'git cl push' instead. |
| 1777 Choose wisely, if you get this wrong, your commit might appear to succeed but | 1757 Choose wisely, if you get this wrong, your commit might appear to succeed but |
| 1778 will instead be silently ignored.""" | 1758 will instead be silently ignored.""" |
| 1779 print(message) | 1759 print(message) |
| 1780 ask_for_data('[Press enter to dcommit or ctrl-C to quit]') | 1760 ask_for_data('[Press enter to dcommit or ctrl-C to quit]') |
| 1781 return SendUpstream(parser, args, 'dcommit') | 1761 return SendUpstream(parser, args, 'dcommit') |
| 1782 | 1762 |
| 1783 | 1763 |
| 1784 @usage('[upstream branch to apply against]') | 1764 @usage('[upstream branch to apply against]') |
| 1785 def CMDpush(parser, args): | 1765 def CMDpush(parser, args): |
| 1786 """Commits the current changelist via git.""" | 1766 """commit the current changelist via git""" |
| 1787 if settings.GetIsGitSvn(): | 1767 if settings.GetIsGitSvn(): |
| 1788 print('This appears to be an SVN repository.') | 1768 print('This appears to be an SVN repository.') |
| 1789 print('Are you sure you didn\'t mean \'git cl dcommit\'?') | 1769 print('Are you sure you didn\'t mean \'git cl dcommit\'?') |
| 1790 ask_for_data('[Press enter to push or ctrl-C to quit]') | 1770 ask_for_data('[Press enter to push or ctrl-C to quit]') |
| 1791 return SendUpstream(parser, args, 'push') | 1771 return SendUpstream(parser, args, 'push') |
| 1792 | 1772 |
| 1793 | 1773 |
| 1794 @usage('<patch url or issue id>') | 1774 @usage('<patch url or issue id>') |
| 1795 def CMDpatch(parser, args): | 1775 def CMDpatch(parser, args): |
| 1796 """Patchs in a code review.""" | 1776 """patch in a code review""" |
| 1797 parser.add_option('-b', dest='newbranch', | 1777 parser.add_option('-b', dest='newbranch', |
| 1798 help='create a new branch off trunk for the patch') | 1778 help='create a new branch off trunk for the patch') |
| 1799 parser.add_option('-f', action='store_true', dest='force', | 1779 parser.add_option('-f', action='store_true', dest='force', |
| 1800 help='with -b, clobber any existing branch') | 1780 help='with -b, clobber any existing branch') |
| 1801 parser.add_option('--reject', action='store_true', dest='reject', | 1781 parser.add_option('--reject', action='store_true', dest='reject', |
| 1802 help='failed patches spew .rej files rather than ' | 1782 help='failed patches spew .rej files rather than ' |
| 1803 'attempting a 3-way merge') | 1783 'attempting a 3-way merge') |
| 1804 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit', | 1784 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit', |
| 1805 help="don't commit after patch applies") | 1785 help="don't commit after patch applies") |
| 1806 (options, args) = parser.parse_args(args) | 1786 (options, args) = parser.parse_args(args) |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1876 cl = Changelist() | 1856 cl = Changelist() |
| 1877 cl.SetIssue(issue) | 1857 cl.SetIssue(issue) |
| 1878 cl.SetPatchset(patchset) | 1858 cl.SetPatchset(patchset) |
| 1879 print "Committed patch locally." | 1859 print "Committed patch locally." |
| 1880 else: | 1860 else: |
| 1881 print "Patch applied to index." | 1861 print "Patch applied to index." |
| 1882 return 0 | 1862 return 0 |
| 1883 | 1863 |
| 1884 | 1864 |
| 1885 def CMDrebase(parser, args): | 1865 def CMDrebase(parser, args): |
| 1886 """Rebases current branch on top of svn repo.""" | 1866 """rebase current branch on top of svn repo""" |
| 1887 # Provide a wrapper for git svn rebase to help avoid accidental | 1867 # Provide a wrapper for git svn rebase to help avoid accidental |
| 1888 # git svn dcommit. | 1868 # git svn dcommit. |
| 1889 # It's the only command that doesn't use parser at all since we just defer | 1869 # It's the only command that doesn't use parser at all since we just defer |
| 1890 # execution to git-svn. | 1870 # execution to git-svn. |
| 1891 env = os.environ.copy() | 1871 env = os.environ.copy() |
| 1892 # 'cat' is a magical git string that disables pagers on all platforms. | 1872 # 'cat' is a magical git string that disables pagers on all platforms. |
| 1893 env['GIT_PAGER'] = 'cat' | 1873 env['GIT_PAGER'] = 'cat' |
| 1894 | 1874 |
| 1895 return subprocess2.call(['git', 'svn', 'rebase'] + args, env=env) | 1875 return subprocess2.call(['git', 'svn', 'rebase'] + args, env=env) |
| 1896 | 1876 |
| (...skipping 17 matching lines...) Expand all Loading... |
| 1914 with the reason for the tree to be opened or closed.""" | 1894 with the reason for the tree to be opened or closed.""" |
| 1915 url = settings.GetTreeStatusUrl() | 1895 url = settings.GetTreeStatusUrl() |
| 1916 json_url = urlparse.urljoin(url, '/current?format=json') | 1896 json_url = urlparse.urljoin(url, '/current?format=json') |
| 1917 connection = urllib2.urlopen(json_url) | 1897 connection = urllib2.urlopen(json_url) |
| 1918 status = json.loads(connection.read()) | 1898 status = json.loads(connection.read()) |
| 1919 connection.close() | 1899 connection.close() |
| 1920 return status['message'] | 1900 return status['message'] |
| 1921 | 1901 |
| 1922 | 1902 |
| 1923 def CMDtree(parser, args): | 1903 def CMDtree(parser, args): |
| 1924 """Shows the status of the tree.""" | 1904 """show the status of the tree""" |
| 1925 _, args = parser.parse_args(args) | 1905 _, args = parser.parse_args(args) |
| 1926 status = GetTreeStatus() | 1906 status = GetTreeStatus() |
| 1927 if 'unset' == status: | 1907 if 'unset' == status: |
| 1928 print 'You must configure your tree status URL by running "git cl config".' | 1908 print 'You must configure your tree status URL by running "git cl config".' |
| 1929 return 2 | 1909 return 2 |
| 1930 | 1910 |
| 1931 print "The tree is %s" % status | 1911 print "The tree is %s" % status |
| 1932 print | 1912 print |
| 1933 print GetTreeStatusReason() | 1913 print GetTreeStatusReason() |
| 1934 if status != 'open': | 1914 if status != 'open': |
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2030 builders_and_tests) | 2010 builders_and_tests) |
| 2031 print('Tried jobs on:') | 2011 print('Tried jobs on:') |
| 2032 length = max(len(builder) for builder in builders_and_tests) | 2012 length = max(len(builder) for builder in builders_and_tests) |
| 2033 for builder in sorted(builders_and_tests): | 2013 for builder in sorted(builders_and_tests): |
| 2034 print ' %*s: %s' % (length, builder, ','.join(builders_and_tests[builder])) | 2014 print ' %*s: %s' % (length, builder, ','.join(builders_and_tests[builder])) |
| 2035 return 0 | 2015 return 0 |
| 2036 | 2016 |
| 2037 | 2017 |
| 2038 @usage('[new upstream branch]') | 2018 @usage('[new upstream branch]') |
| 2039 def CMDupstream(parser, args): | 2019 def CMDupstream(parser, args): |
| 2040 """Prints or sets the name of the upstream branch, if any.""" | 2020 """prints or sets the name of the upstream branch, if any""" |
| 2041 _, args = parser.parse_args(args) | 2021 _, args = parser.parse_args(args) |
| 2042 if len(args) > 1: | 2022 if len(args) > 1: |
| 2043 parser.error('Unrecognized args: %s' % ' '.join(args)) | 2023 parser.error('Unrecognized args: %s' % ' '.join(args)) |
| 2044 return 0 | 2024 return 0 |
| 2045 | 2025 |
| 2046 cl = Changelist() | 2026 cl = Changelist() |
| 2047 if args: | 2027 if args: |
| 2048 # One arg means set upstream branch. | 2028 # One arg means set upstream branch. |
| 2049 RunGit(['branch', '--set-upstream', cl.GetBranch(), args[0]]) | 2029 RunGit(['branch', '--set-upstream', cl.GetBranch(), args[0]]) |
| 2050 cl = Changelist() | 2030 cl = Changelist() |
| 2051 print "Upstream branch set to " + cl.GetUpstreamBranch() | 2031 print "Upstream branch set to " + cl.GetUpstreamBranch() |
| 2052 else: | 2032 else: |
| 2053 print cl.GetUpstreamBranch() | 2033 print cl.GetUpstreamBranch() |
| 2054 return 0 | 2034 return 0 |
| 2055 | 2035 |
| 2056 | 2036 |
| 2057 def CMDset_commit(parser, args): | 2037 def CMDset_commit(parser, args): |
| 2058 """Sets the commit bit to trigger the Commit Queue.""" | 2038 """set the commit bit""" |
| 2059 _, args = parser.parse_args(args) | 2039 _, args = parser.parse_args(args) |
| 2060 if args: | 2040 if args: |
| 2061 parser.error('Unrecognized args: %s' % ' '.join(args)) | 2041 parser.error('Unrecognized args: %s' % ' '.join(args)) |
| 2062 cl = Changelist() | 2042 cl = Changelist() |
| 2063 cl.SetFlag('commit', '1') | 2043 cl.SetFlag('commit', '1') |
| 2064 return 0 | 2044 return 0 |
| 2065 | 2045 |
| 2066 | 2046 |
| 2067 def CMDset_close(parser, args): | 2047 def CMDset_close(parser, args): |
| 2068 """Closes the issue.""" | 2048 """close the issue""" |
| 2069 _, args = parser.parse_args(args) | 2049 _, args = parser.parse_args(args) |
| 2070 if args: | 2050 if args: |
| 2071 parser.error('Unrecognized args: %s' % ' '.join(args)) | 2051 parser.error('Unrecognized args: %s' % ' '.join(args)) |
| 2072 cl = Changelist() | 2052 cl = Changelist() |
| 2073 # Ensure there actually is an issue to close. | 2053 # Ensure there actually is an issue to close. |
| 2074 cl.GetDescription() | 2054 cl.GetDescription() |
| 2075 cl.CloseIssue() | 2055 cl.CloseIssue() |
| 2076 return 0 | 2056 return 0 |
| 2077 | 2057 |
| 2078 | 2058 |
| 2079 def CMDformat(parser, args): | 2059 def CMDformat(parser, args): |
| 2080 """Runs clang-format on the diff.""" | 2060 """run clang-format on the diff""" |
| 2081 CLANG_EXTS = ['.cc', '.cpp', '.h'] | 2061 CLANG_EXTS = ['.cc', '.cpp', '.h'] |
| 2082 parser.add_option('--full', action='store_true', default=False) | 2062 parser.add_option('--full', action='store_true', default=False) |
| 2083 opts, args = parser.parse_args(args) | 2063 opts, args = parser.parse_args(args) |
| 2084 if args: | 2064 if args: |
| 2085 parser.error('Unrecognized args: %s' % ' '.join(args)) | 2065 parser.error('Unrecognized args: %s' % ' '.join(args)) |
| 2086 | 2066 |
| 2087 # Generate diff for the current branch's changes. | 2067 # Generate diff for the current branch's changes. |
| 2088 diff_cmd = ['diff', '--no-ext-diff', '--no-prefix'] | 2068 diff_cmd = ['diff', '--no-ext-diff', '--no-prefix'] |
| 2089 if opts.full: | 2069 if opts.full: |
| 2090 # Only list the names of modified files. | 2070 # Only list the names of modified files. |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2167 return | 2147 return |
| 2168 | 2148 |
| 2169 if hamming_commands[0][0] < 0.8: | 2149 if hamming_commands[0][0] < 0.8: |
| 2170 # Not similar enough. Don't be a fool and run a random command. | 2150 # Not similar enough. Don't be a fool and run a random command. |
| 2171 return | 2151 return |
| 2172 | 2152 |
| 2173 return commands[hamming_commands[0][1]] | 2153 return commands[hamming_commands[0][1]] |
| 2174 | 2154 |
| 2175 | 2155 |
| 2176 def CMDhelp(parser, args): | 2156 def CMDhelp(parser, args): |
| 2177 """Prints list of commands or help for a specific command.""" | 2157 """print list of commands or help for a specific command""" |
| 2178 _, args = parser.parse_args(args) | 2158 _, args = parser.parse_args(args) |
| 2179 if len(args) == 1: | 2159 if len(args) == 1: |
| 2180 return main(args + ['--help']) | 2160 return main(args + ['--help']) |
| 2181 parser.print_help() | 2161 parser.print_help() |
| 2182 return 0 | 2162 return 0 |
| 2183 | 2163 |
| 2184 | 2164 |
| 2185 def GenUsage(parser, command): | 2165 def GenUsage(parser, command): |
| 2186 """Modify an OptParse object with the function's documentation.""" | 2166 """Modify an OptParse object with the function's documentation.""" |
| 2187 obj = Command(command) | 2167 obj = Command(command) |
| 2188 # Get back the real command name in case Command() guess the actual command | 2168 # Get back the real command name in case Command() guess the actual command |
| 2189 # name. | 2169 # name. |
| 2190 command = obj.__name__[3:] | 2170 command = obj.__name__[3:] |
| 2191 more = getattr(obj, 'usage_more', '') | 2171 more = getattr(obj, 'usage_more', '') |
| 2192 if command == 'help': | 2172 if command == 'help': |
| 2193 command = '<command>' | 2173 command = '<command>' |
| 2194 else: | 2174 else: |
| 2195 parser.description = obj.__doc__ | 2175 # OptParser.description prefer nicely non-formatted strings. |
| 2176 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__) |
| 2196 parser.set_usage('usage: %%prog %s [options] %s' % (command, more)) | 2177 parser.set_usage('usage: %%prog %s [options] %s' % (command, more)) |
| 2197 | 2178 |
| 2198 | 2179 |
| 2199 class OptionParser(optparse.OptionParser): | |
| 2200 """Creates the option parse and add --verbose support.""" | |
| 2201 def __init__(self, *args, **kwargs): | |
| 2202 optparse.OptionParser.__init__(self, *args, **kwargs) | |
| 2203 self.add_option( | |
| 2204 '-v', '--verbose', action='count', default=0, | |
| 2205 help='Use 2 times for more debugging info') | |
| 2206 | |
| 2207 def parse_args(self, args=None, values=None): | |
| 2208 options, args = optparse.OptionParser.parse_args(self, args, values) | |
| 2209 levels = [logging.WARNING, logging.INFO, logging.DEBUG] | |
| 2210 logging.basicConfig(level=levels[min(options.verbose, len(levels) - 1)]) | |
| 2211 return options, args | |
| 2212 | |
| 2213 def format_description(self, _): | |
| 2214 """Disables automatic reformatting.""" | |
| 2215 lines = self.description.rstrip().splitlines() | |
| 2216 lines_fixed = [lines[0]] + [l[2:] if len(l) >= 2 else l for l in lines[1:]] | |
| 2217 description = ''.join(l + '\n' for l in lines_fixed) | |
| 2218 return description[0].upper() + description[1:] | |
| 2219 | |
| 2220 | |
| 2221 def main(argv): | 2180 def main(argv): |
| 2222 """Doesn't parse the arguments here, just find the right subcommand to | 2181 """Doesn't parse the arguments here, just find the right subcommand to |
| 2223 execute.""" | 2182 execute.""" |
| 2224 if sys.hexversion < 0x02060000: | 2183 if sys.hexversion < 0x02060000: |
| 2225 print >> sys.stderr, ( | 2184 print >> sys.stderr, ( |
| 2226 '\nYour python version %s is unsupported, please upgrade.\n' % | 2185 '\nYour python version %s is unsupported, please upgrade.\n' % |
| 2227 sys.version.split(' ', 1)[0]) | 2186 sys.version.split(' ', 1)[0]) |
| 2228 return 2 | 2187 return 2 |
| 2229 | 2188 |
| 2230 # Reload settings. | 2189 # Reload settings. |
| 2231 global settings | 2190 global settings |
| 2232 settings = Settings() | 2191 settings = Settings() |
| 2233 | 2192 |
| 2234 # Do it late so all commands are listed. | 2193 # Do it late so all commands are listed. |
| 2235 commands = Commands() | 2194 commands = Commands() |
| 2236 length = max(len(c) for c in commands) | 2195 length = max(len(c) for c in commands) |
| 2237 | |
| 2238 def gen_summary(x): | |
| 2239 """Creates a oneline summary from the docstring.""" | |
| 2240 line = x.split('\n', 1)[0].rstrip('.') | |
| 2241 return line[0].lower() + line[1:] | |
| 2242 | |
| 2243 docs = sorted( | 2196 docs = sorted( |
| 2244 (name, gen_summary(handler.__doc__).strip()) | 2197 (name, handler.__doc__.split('\n')[0].strip()) |
| 2245 for name, handler in commands.iteritems()) | 2198 for name, handler in commands.iteritems()) |
| 2246 CMDhelp.usage_more = ('\n\nCommands are:\n' + '\n'.join( | 2199 CMDhelp.usage_more = ('\n\nCommands are:\n' + '\n'.join( |
| 2247 ' %-*s %s' % (length, name, doc) for name, doc in docs)) | 2200 ' %-*s %s' % (length, name, doc) for name, doc in docs)) |
| 2248 | 2201 |
| 2249 parser = OptionParser() | 2202 # Create the option parse and add --verbose support. |
| 2203 parser = optparse.OptionParser() |
| 2204 parser.add_option( |
| 2205 '-v', '--verbose', action='count', default=0, |
| 2206 help='Use 2 times for more debugging info') |
| 2207 old_parser_args = parser.parse_args |
| 2208 def Parse(args): |
| 2209 options, args = old_parser_args(args) |
| 2210 if options.verbose >= 2: |
| 2211 logging.basicConfig(level=logging.DEBUG) |
| 2212 elif options.verbose: |
| 2213 logging.basicConfig(level=logging.INFO) |
| 2214 else: |
| 2215 logging.basicConfig(level=logging.WARNING) |
| 2216 return options, args |
| 2217 parser.parse_args = Parse |
| 2218 |
| 2250 if argv: | 2219 if argv: |
| 2251 command = Command(argv[0]) | 2220 command = Command(argv[0]) |
| 2252 if command: | 2221 if command: |
| 2253 # "fix" the usage and the description now that we know the subcommand. | 2222 # "fix" the usage and the description now that we know the subcommand. |
| 2254 GenUsage(parser, argv[0]) | 2223 GenUsage(parser, argv[0]) |
| 2255 try: | 2224 try: |
| 2256 return command(parser, argv[1:]) | 2225 return command(parser, argv[1:]) |
| 2257 except urllib2.HTTPError, e: | 2226 except urllib2.HTTPError, e: |
| 2258 if e.code != 500: | 2227 if e.code != 500: |
| 2259 raise | 2228 raise |
| 2260 DieWithError( | 2229 DieWithError( |
| 2261 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' | 2230 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' |
| 2262 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 2231 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
| 2263 | 2232 |
| 2264 # Not a known command. Default to help. | 2233 # Not a known command. Default to help. |
| 2265 GenUsage(parser, 'help') | 2234 GenUsage(parser, 'help') |
| 2266 return CMDhelp(parser, argv) | 2235 return CMDhelp(parser, argv) |
| 2267 | 2236 |
| 2268 | 2237 |
| 2269 if __name__ == '__main__': | 2238 if __name__ == '__main__': |
| 2270 # These affect sys.stdout so do it outside of main() to simplify mocks in | 2239 # These affect sys.stdout so do it outside of main() to simplify mocks in |
| 2271 # unit testing. | 2240 # unit testing. |
| 2272 fix_encoding.fix_encoding() | 2241 fix_encoding.fix_encoding() |
| 2273 colorama.init() | 2242 colorama.init() |
| 2274 sys.exit(main(sys.argv[1:])) | 2243 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |