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 json | 10 import json |
(...skipping 810 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
821 content = re.compile(r'^#.*$', re.MULTILINE).sub('', content).strip() | 821 content = re.compile(r'^#.*$', re.MULTILINE).sub('', content).strip() |
822 if not content.strip(): | 822 if not content.strip(): |
823 DieWithError('No CL description, aborting') | 823 DieWithError('No CL description, aborting') |
824 self.description = content | 824 self.description = content |
825 | 825 |
826 def ParseDescription(self): | 826 def ParseDescription(self): |
827 """Updates the list of reviewers and subject from the description.""" | 827 """Updates the list of reviewers and subject from the description.""" |
828 self.description = self.description.strip('\n') + '\n' | 828 self.description = self.description.strip('\n') + '\n' |
829 # Retrieves all reviewer lines | 829 # Retrieves all reviewer lines |
830 regexp = re.compile(r'^\s*(TBR|R)=(.+)$', re.MULTILINE) | 830 regexp = re.compile(r'^\s*(TBR|R)=(.+)$', re.MULTILINE) |
831 reviewers = ','.join( | 831 reviewers = ', '.join( |
832 i.group(2).strip() for i in regexp.finditer(self.description)) | 832 i.group(2).strip() for i in regexp.finditer(self.description)) |
833 if reviewers: | 833 if reviewers: |
834 self.reviewers = reviewers | 834 self.reviewers = reviewers |
835 | 835 |
836 def IsEmpty(self): | 836 def IsEmpty(self): |
837 return not self.description | 837 return not self.description |
838 | 838 |
839 | 839 |
840 def FindCodereviewSettingsFile(filename='codereview.settings'): | 840 def FindCodereviewSettingsFile(filename='codereview.settings'): |
841 """Finds the given file starting in the cwd and going up. | 841 """Finds the given file starting in the cwd and going up. |
(...skipping 257 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1099 # It is probably not worthwhile to support different workflows. | 1099 # It is probably not worthwhile to support different workflows. |
1100 remote = 'origin' | 1100 remote = 'origin' |
1101 branch = 'master' | 1101 branch = 'master' |
1102 if options.target_branch: | 1102 if options.target_branch: |
1103 branch = options.target_branch | 1103 branch = options.target_branch |
1104 | 1104 |
1105 log_desc = options.message or CreateDescriptionFromLog(args) | 1105 log_desc = options.message or CreateDescriptionFromLog(args) |
1106 if CHANGE_ID not in log_desc: | 1106 if CHANGE_ID not in log_desc: |
1107 AddChangeIdToCommitMessage(options, args) | 1107 AddChangeIdToCommitMessage(options, args) |
1108 if options.reviewers: | 1108 if options.reviewers: |
1109 log_desc += '\nR=' + options.reviewers | 1109 log_desc += '\nR=' + ', '.join(options.reviewers) |
1110 change_desc = ChangeDescription(log_desc, options.reviewers) | 1110 change_desc = ChangeDescription(log_desc, ', '.join(options.reviewers)) |
1111 change_desc.ParseDescription() | 1111 change_desc.ParseDescription() |
1112 if change_desc.IsEmpty(): | 1112 if change_desc.IsEmpty(): |
1113 print "Description is empty; aborting." | 1113 print "Description is empty; aborting." |
1114 return 1 | 1114 return 1 |
1115 | 1115 |
1116 receive_options = [] | 1116 receive_options = [] |
1117 cc = cl.GetCCList().split(',') | 1117 cc = cl.GetCCList().split(',') |
1118 if options.cc: | 1118 if options.cc: |
1119 cc += options.cc.split(',') | 1119 cc.extend(options.cc) |
1120 cc = filter(None, cc) | 1120 cc = filter(None, cc) |
1121 if cc: | 1121 if cc: |
1122 receive_options += ['--cc=' + email for email in cc] | 1122 receive_options += ['--cc=' + email for email in cc] |
1123 if change_desc.reviewers: | 1123 if change_desc.reviewers: |
1124 reviewers = filter(None, change_desc.reviewers.split(',')) | 1124 reviewers = filter( |
| 1125 None, (r.strip() for r in change_desc.reviewers.split(','))) |
1125 if reviewers: | 1126 if reviewers: |
1126 receive_options += ['--reviewer=' + email for email in reviewers] | 1127 receive_options += ['--reviewer=' + email for email in reviewers] |
1127 | 1128 |
1128 git_command = ['push'] | 1129 git_command = ['push'] |
1129 if receive_options: | 1130 if receive_options: |
1130 git_command.append('--receive-pack=git receive-pack %s' % | 1131 git_command.append('--receive-pack=git receive-pack %s' % |
1131 ' '.join(receive_options)) | 1132 ' '.join(receive_options)) |
1132 git_command += [remote, 'HEAD:refs/for/' + branch] | 1133 git_command += [remote, 'HEAD:refs/for/' + branch] |
1133 RunGit(git_command) | 1134 RunGit(git_command) |
1134 # TODO(ukai): parse Change-Id: and set issue number? | 1135 # TODO(ukai): parse Change-Id: and set issue number? |
(...skipping 17 matching lines...) Expand all Loading... |
1152 # for upload.py. Soon this will be changed to set the --message option. | 1153 # for upload.py. Soon this will be changed to set the --message option. |
1153 # Will wait until people are used to typing -t instead of -m. | 1154 # Will wait until people are used to typing -t instead of -m. |
1154 upload_args.extend(['--title', options.message]) | 1155 upload_args.extend(['--title', options.message]) |
1155 upload_args.extend(['--issue', str(cl.GetIssue())]) | 1156 upload_args.extend(['--issue', str(cl.GetIssue())]) |
1156 print ("This branch is associated with issue %s. " | 1157 print ("This branch is associated with issue %s. " |
1157 "Adding patch to that issue." % cl.GetIssue()) | 1158 "Adding patch to that issue." % cl.GetIssue()) |
1158 else: | 1159 else: |
1159 if options.title: | 1160 if options.title: |
1160 upload_args.extend(['--title', options.title]) | 1161 upload_args.extend(['--title', options.title]) |
1161 message = options.title or options.message or CreateDescriptionFromLog(args) | 1162 message = options.title or options.message or CreateDescriptionFromLog(args) |
1162 change_desc = ChangeDescription(message, options.reviewers) | 1163 change_desc = ChangeDescription(message, ','.join(options.reviewers)) |
1163 if not options.force: | 1164 if not options.force: |
1164 change_desc.Prompt() | 1165 change_desc.Prompt() |
1165 change_desc.ParseDescription() | 1166 change_desc.ParseDescription() |
1166 | 1167 |
1167 if change_desc.IsEmpty(): | 1168 if change_desc.IsEmpty(): |
1168 print "Description is empty; aborting." | 1169 print "Description is empty; aborting." |
1169 return 1 | 1170 return 1 |
1170 | 1171 |
1171 upload_args.extend(['--message', change_desc.description]) | 1172 upload_args.extend(['--message', change_desc.description]) |
1172 if change_desc.reviewers: | 1173 if change_desc.reviewers: |
1173 upload_args.extend(['--reviewers', change_desc.reviewers]) | 1174 upload_args.extend( |
| 1175 [ |
| 1176 '--reviewers', |
| 1177 ','.join(r.strip() for r in change_desc.reviewers.split(',')), |
| 1178 ]) |
1174 if options.send_mail: | 1179 if options.send_mail: |
1175 if not change_desc.reviewers: | 1180 if not change_desc.reviewers: |
1176 DieWithError("Must specify reviewers to send email.") | 1181 DieWithError("Must specify reviewers to send email.") |
1177 upload_args.append('--send_mail') | 1182 upload_args.append('--send_mail') |
1178 cc = ','.join(filter(None, (cl.GetCCList(), options.cc))) | 1183 cc = ','.join(filter(None, (cl.GetCCList(), ','.join(options.cc)))) |
1179 if cc: | 1184 if cc: |
1180 upload_args.extend(['--cc', cc]) | 1185 upload_args.extend(['--cc', cc]) |
1181 | 1186 |
1182 upload_args.extend(['--git_similarity', str(options.similarity)]) | 1187 upload_args.extend(['--git_similarity', str(options.similarity)]) |
1183 if not options.find_copies: | 1188 if not options.find_copies: |
1184 upload_args.extend(['--git_no_find_copies']) | 1189 upload_args.extend(['--git_no_find_copies']) |
1185 | 1190 |
1186 # Include the upstream repo's URL in the change -- this is useful for | 1191 # Include the upstream repo's URL in the change -- this is useful for |
1187 # projects that have their source spread across multiple repos. | 1192 # projects that have their source spread across multiple repos. |
1188 remote_url = cl.GetGitBaseUrlFromConfig() | 1193 remote_url = cl.GetGitBaseUrlFromConfig() |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1221 | 1226 |
1222 if not cl.GetIssue(): | 1227 if not cl.GetIssue(): |
1223 cl.SetIssue(issue) | 1228 cl.SetIssue(issue) |
1224 cl.SetPatchset(patchset) | 1229 cl.SetPatchset(patchset) |
1225 | 1230 |
1226 if options.use_commit_queue: | 1231 if options.use_commit_queue: |
1227 cl.SetFlag('commit', '1') | 1232 cl.SetFlag('commit', '1') |
1228 return 0 | 1233 return 0 |
1229 | 1234 |
1230 | 1235 |
| 1236 def cleanup_list(l): |
| 1237 """Fixes a list so that comma separated items are put as individual items. |
| 1238 |
| 1239 So that "--reviewers joe@c,john@c --reviewers joa@c" results in |
| 1240 options.reviewers == sorted(['joe@c', 'john@c', 'joa@c']). |
| 1241 """ |
| 1242 items = sum((i.split(',') for i in l), []) |
| 1243 stripped_items = (i.strip() for i in items) |
| 1244 return sorted(filter(None, stripped_items)) |
| 1245 |
| 1246 |
1231 @usage('[args to "git diff"]') | 1247 @usage('[args to "git diff"]') |
1232 def CMDupload(parser, args): | 1248 def CMDupload(parser, args): |
1233 """upload the current changelist to codereview""" | 1249 """upload the current changelist to codereview""" |
1234 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', | 1250 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', |
1235 help='bypass upload presubmit hook') | 1251 help='bypass upload presubmit hook') |
1236 parser.add_option('-f', action='store_true', dest='force', | 1252 parser.add_option('-f', action='store_true', dest='force', |
1237 help="force yes to questions (don't prompt)") | 1253 help="force yes to questions (don't prompt)") |
1238 parser.add_option('-m', dest='message', help='message for patchset') | 1254 parser.add_option('-m', dest='message', help='message for patchset') |
1239 parser.add_option('-t', dest='title', help='title for patchset') | 1255 parser.add_option('-t', dest='title', help='title for patchset') |
1240 parser.add_option('-r', '--reviewers', | 1256 parser.add_option('-r', '--reviewers', |
| 1257 action='append', default=[], |
1241 help='reviewer email addresses') | 1258 help='reviewer email addresses') |
1242 parser.add_option('--cc', | 1259 parser.add_option('--cc', |
| 1260 action='append', default=[], |
1243 help='cc email addresses') | 1261 help='cc email addresses') |
1244 parser.add_option('-s', '--send-mail', action='store_true', | 1262 parser.add_option('-s', '--send-mail', action='store_true', |
1245 help='send email to reviewer immediately') | 1263 help='send email to reviewer immediately') |
1246 parser.add_option("--emulate_svn_auto_props", action="store_true", | 1264 parser.add_option("--emulate_svn_auto_props", action="store_true", |
1247 dest="emulate_svn_auto_props", | 1265 dest="emulate_svn_auto_props", |
1248 help="Emulate Subversion's auto properties feature.") | 1266 help="Emulate Subversion's auto properties feature.") |
1249 parser.add_option('-c', '--use-commit-queue', action='store_true', | 1267 parser.add_option('-c', '--use-commit-queue', action='store_true', |
1250 help='tell the commit queue to commit this patchset') | 1268 help='tell the commit queue to commit this patchset') |
1251 parser.add_option('--target_branch', | 1269 parser.add_option('--target_branch', |
1252 help='When uploading to gerrit, remote branch to ' | 1270 help='When uploading to gerrit, remote branch to ' |
1253 'use for CL. Default: master') | 1271 'use for CL. Default: master') |
1254 add_git_similarity(parser) | 1272 add_git_similarity(parser) |
1255 (options, args) = parser.parse_args(args) | 1273 (options, args) = parser.parse_args(args) |
1256 | 1274 |
1257 if options.target_branch and not settings.GetIsGerrit(): | 1275 if options.target_branch and not settings.GetIsGerrit(): |
1258 parser.error('Use --target_branch for non gerrit repository.') | 1276 parser.error('Use --target_branch for non gerrit repository.') |
1259 | 1277 |
1260 # Print warning if the user used the -m/--message argument. This will soon | 1278 # Print warning if the user used the -m/--message argument. This will soon |
1261 # change to -t/--title. | 1279 # change to -t/--title. |
1262 if options.message: | 1280 if options.message: |
1263 print >> sys.stderr, ( | 1281 print >> sys.stderr, ( |
1264 '\nWARNING: Use -t or --title to set the title of the patchset.\n' | 1282 '\nWARNING: Use -t or --title to set the title of the patchset.\n' |
1265 'In the near future, -m or --message will send a message instead.\n' | 1283 'In the near future, -m or --message will send a message instead.\n' |
1266 'See http://goo.gl/JGg0Z for details.\n') | 1284 'See http://goo.gl/JGg0Z for details.\n') |
1267 | 1285 |
1268 if is_dirty_git_tree('upload'): | 1286 if is_dirty_git_tree('upload'): |
1269 return 1 | 1287 return 1 |
1270 | 1288 |
| 1289 options.reviewers = cleanup_list(options.reviewers) |
| 1290 options.cc = cleanup_list(options.cc) |
| 1291 |
1271 cl = Changelist() | 1292 cl = Changelist() |
1272 if args: | 1293 if args: |
1273 # TODO(ukai): is it ok for gerrit case? | 1294 # TODO(ukai): is it ok for gerrit case? |
1274 base_branch = args[0] | 1295 base_branch = args[0] |
1275 else: | 1296 else: |
1276 # Default to diffing against common ancestor of upstream branch | 1297 # Default to diffing against common ancestor of upstream branch |
1277 base_branch = RunGit(['merge-base', cl.GetUpstreamBranch(), 'HEAD']).strip() | 1298 base_branch = RunGit(['merge-base', cl.GetUpstreamBranch(), 'HEAD']).strip() |
1278 args = [base_branch, 'HEAD'] | 1299 args = [base_branch, 'HEAD'] |
1279 | 1300 |
1280 # Apply watchlists on upload. | 1301 # Apply watchlists on upload. |
1281 change = cl.GetChange(base_branch, None) | 1302 change = cl.GetChange(base_branch, None) |
1282 watchlist = watchlists.Watchlists(change.RepositoryRoot()) | 1303 watchlist = watchlists.Watchlists(change.RepositoryRoot()) |
1283 files = [f.LocalPath() for f in change.AffectedFiles()] | 1304 files = [f.LocalPath() for f in change.AffectedFiles()] |
1284 cl.SetWatchers(watchlist.GetWatchersForPaths(files)) | 1305 cl.SetWatchers(watchlist.GetWatchersForPaths(files)) |
1285 | 1306 |
1286 if not options.bypass_hooks: | 1307 if not options.bypass_hooks: |
1287 hook_results = cl.RunHook(committing=False, | 1308 hook_results = cl.RunHook(committing=False, |
1288 may_prompt=not options.force, | 1309 may_prompt=not options.force, |
1289 verbose=options.verbose, | 1310 verbose=options.verbose, |
1290 change=change) | 1311 change=change) |
1291 if not hook_results.should_continue(): | 1312 if not hook_results.should_continue(): |
1292 return 1 | 1313 return 1 |
1293 if not options.reviewers and hook_results.reviewers: | 1314 if not options.reviewers and hook_results.reviewers: |
1294 options.reviewers = hook_results.reviewers | 1315 options.reviewers = hook_results.reviewers.split(',') |
1295 | 1316 |
1296 if cl.GetIssue(): | 1317 if cl.GetIssue(): |
1297 latest_patchset = cl.GetMostRecentPatchset(cl.GetIssue()) | 1318 latest_patchset = cl.GetMostRecentPatchset(cl.GetIssue()) |
1298 local_patchset = cl.GetPatchset() | 1319 local_patchset = cl.GetPatchset() |
1299 if latest_patchset and local_patchset and local_patchset != latest_patchset: | 1320 if latest_patchset and local_patchset and local_patchset != latest_patchset: |
1300 print ('The last upload made from this repository was patchset #%d but ' | 1321 print ('The last upload made from this repository was patchset #%d but ' |
1301 'the most recent patchset on the server is #%d.' | 1322 'the most recent patchset on the server is #%d.' |
1302 % (local_patchset, latest_patchset)) | 1323 % (local_patchset, latest_patchset)) |
1303 ask_for_data('About to upload; enter to confirm.') | 1324 ask_for_data('About to upload; enter to confirm.') |
1304 | 1325 |
(...skipping 600 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1905 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 1926 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
1906 | 1927 |
1907 # Not a known command. Default to help. | 1928 # Not a known command. Default to help. |
1908 GenUsage(parser, 'help') | 1929 GenUsage(parser, 'help') |
1909 return CMDhelp(parser, argv) | 1930 return CMDhelp(parser, argv) |
1910 | 1931 |
1911 | 1932 |
1912 if __name__ == '__main__': | 1933 if __name__ == '__main__': |
1913 fix_encoding.fix_encoding() | 1934 fix_encoding.fix_encoding() |
1914 sys.exit(main(sys.argv[1:])) | 1935 sys.exit(main(sys.argv[1:])) |
OLD | NEW |