summaryrefslogtreecommitdiffstats
path: root/subcmds/upload.py
diff options
context:
space:
mode:
Diffstat (limited to 'subcmds/upload.py')
-rw-r--r--subcmds/upload.py1152
1 files changed, 664 insertions, 488 deletions
diff --git a/subcmds/upload.py b/subcmds/upload.py
index 9c279230..63216afb 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -32,69 +32,77 @@ _DEFAULT_UNUSUAL_COMMIT_THRESHOLD = 5
32 32
33 33
34def _VerifyPendingCommits(branches: List[ReviewableBranch]) -> bool: 34def _VerifyPendingCommits(branches: List[ReviewableBranch]) -> bool:
35 """Perform basic safety checks on the given set of branches. 35 """Perform basic safety checks on the given set of branches.
36 36
37 Ensures that each branch does not have a "large" number of commits 37 Ensures that each branch does not have a "large" number of commits
38 and, if so, prompts the user to confirm they want to proceed with 38 and, if so, prompts the user to confirm they want to proceed with
39 the upload. 39 the upload.
40 40
41 Returns true if all branches pass the safety check or the user 41 Returns true if all branches pass the safety check or the user
42 confirmed. Returns false if the upload should be aborted. 42 confirmed. Returns false if the upload should be aborted.
43 """ 43 """
44 44
45 # Determine if any branch has a suspicious number of commits. 45 # Determine if any branch has a suspicious number of commits.
46 many_commits = False 46 many_commits = False
47 for branch in branches: 47 for branch in branches:
48 # Get the user's unusual threshold for the branch. 48 # Get the user's unusual threshold for the branch.
49 # 49 #
50 # Each branch may be configured to have a different threshold. 50 # Each branch may be configured to have a different threshold.
51 remote = branch.project.GetBranch(branch.name).remote 51 remote = branch.project.GetBranch(branch.name).remote
52 key = f'review.{remote.review}.uploadwarningthreshold' 52 key = f"review.{remote.review}.uploadwarningthreshold"
53 threshold = branch.project.config.GetInt(key) 53 threshold = branch.project.config.GetInt(key)
54 if threshold is None: 54 if threshold is None:
55 threshold = _DEFAULT_UNUSUAL_COMMIT_THRESHOLD 55 threshold = _DEFAULT_UNUSUAL_COMMIT_THRESHOLD
56 56
57 # If the branch has more commits than the threshold, show a warning. 57 # If the branch has more commits than the threshold, show a warning.
58 if len(branch.commits) > threshold: 58 if len(branch.commits) > threshold:
59 many_commits = True 59 many_commits = True
60 break 60 break
61 61
62 # If any branch has many commits, prompt the user. 62 # If any branch has many commits, prompt the user.
63 if many_commits: 63 if many_commits:
64 if len(branches) > 1: 64 if len(branches) > 1:
65 print('ATTENTION: One or more branches has an unusually high number ' 65 print(
66 'of commits.') 66 "ATTENTION: One or more branches has an unusually high number "
67 else: 67 "of commits."
68 print('ATTENTION: You are uploading an unusually high number of commits.') 68 )
69 print('YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across ' 69 else:
70 'branches?)') 70 print(
71 answer = input( 71 "ATTENTION: You are uploading an unusually high number of "
72 "If you are sure you intend to do this, type 'yes': ").strip() 72 "commits."
73 return answer == 'yes' 73 )
74 74 print(
75 return True 75 "YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across "
76 "branches?)"
77 )
78 answer = input(
79 "If you are sure you intend to do this, type 'yes': "
80 ).strip()
81 return answer == "yes"
82
83 return True
76 84
77 85
78def _die(fmt, *args): 86def _die(fmt, *args):
79 msg = fmt % args 87 msg = fmt % args
80 print('error: %s' % msg, file=sys.stderr) 88 print("error: %s" % msg, file=sys.stderr)
81 sys.exit(1) 89 sys.exit(1)
82 90
83 91
84def _SplitEmails(values): 92def _SplitEmails(values):
85 result = [] 93 result = []
86 for value in values: 94 for value in values:
87 result.extend([s.strip() for s in value.split(',')]) 95 result.extend([s.strip() for s in value.split(",")])
88 return result 96 return result
89 97
90 98
91class Upload(InteractiveCommand): 99class Upload(InteractiveCommand):
92 COMMON = True 100 COMMON = True
93 helpSummary = "Upload changes for code review" 101 helpSummary = "Upload changes for code review"
94 helpUsage = """ 102 helpUsage = """
95%prog [--re --cc] [<project>]... 103%prog [--re --cc] [<project>]...
96""" 104"""
97 helpDescription = """ 105 helpDescription = """
98The '%prog' command is used to send changes to the Gerrit Code 106The '%prog' command is used to send changes to the Gerrit Code
99Review system. It searches for topic branches in local projects 107Review system. It searches for topic branches in local projects
100that have not yet been published for review. If multiple topic 108that have not yet been published for review. If multiple topic
@@ -195,443 +203,611 @@ threshold to a different value.
195Gerrit Code Review: https://www.gerritcodereview.com/ 203Gerrit Code Review: https://www.gerritcodereview.com/
196 204
197""" 205"""
198 PARALLEL_JOBS = DEFAULT_LOCAL_JOBS 206 PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
199 207
200 def _Options(self, p): 208 def _Options(self, p):
201 p.add_option('-t', 209 p.add_option(
202 dest='auto_topic', action='store_true', 210 "-t",
203 help='send local branch name to Gerrit Code Review') 211 dest="auto_topic",
204 p.add_option('--hashtag', '--ht', 212 action="store_true",
205 dest='hashtags', action='append', default=[], 213 help="send local branch name to Gerrit Code Review",
206 help='add hashtags (comma delimited) to the review') 214 )
207 p.add_option('--hashtag-branch', '--htb', 215 p.add_option(
208 action='store_true', 216 "--hashtag",
209 help='add local branch name as a hashtag') 217 "--ht",
210 p.add_option('-l', '--label', 218 dest="hashtags",
211 dest='labels', action='append', default=[], 219 action="append",
212 help='add a label when uploading') 220 default=[],
213 p.add_option('--re', '--reviewers', 221 help="add hashtags (comma delimited) to the review",
214 type='string', action='append', dest='reviewers', 222 )
215 help='request reviews from these people') 223 p.add_option(
216 p.add_option('--cc', 224 "--hashtag-branch",
217 type='string', action='append', dest='cc', 225 "--htb",
218 help='also send email to these email addresses') 226 action="store_true",
219 p.add_option('--br', '--branch', 227 help="add local branch name as a hashtag",
220 type='string', action='store', dest='branch', 228 )
221 help='(local) branch to upload') 229 p.add_option(
222 p.add_option('-c', '--current-branch', 230 "-l",
223 dest='current_branch', action='store_true', 231 "--label",
224 help='upload current git branch') 232 dest="labels",
225 p.add_option('--no-current-branch', 233 action="append",
226 dest='current_branch', action='store_false', 234 default=[],
227 help='upload all git branches') 235 help="add a label when uploading",
228 # Turn this into a warning & remove this someday. 236 )
229 p.add_option('--cbr', 237 p.add_option(
230 dest='current_branch', action='store_true', 238 "--re",
231 help=optparse.SUPPRESS_HELP) 239 "--reviewers",
232 p.add_option('--ne', '--no-emails', 240 type="string",
233 action='store_false', dest='notify', default=True, 241 action="append",
234 help='do not send e-mails on upload') 242 dest="reviewers",
235 p.add_option('-p', '--private', 243 help="request reviews from these people",
236 action='store_true', dest='private', default=False, 244 )
237 help='upload as a private change (deprecated; use --wip)') 245 p.add_option(
238 p.add_option('-w', '--wip', 246 "--cc",
239 action='store_true', dest='wip', default=False, 247 type="string",
240 help='upload as a work-in-progress change') 248 action="append",
241 p.add_option('-r', '--ready', 249 dest="cc",
242 action='store_true', default=False, 250 help="also send email to these email addresses",
243 help='mark change as ready (clears work-in-progress setting)') 251 )
244 p.add_option('-o', '--push-option', 252 p.add_option(
245 type='string', action='append', dest='push_options', 253 "--br",
246 default=[], 254 "--branch",
247 help='additional push options to transmit') 255 type="string",
248 p.add_option('-D', '--destination', '--dest', 256 action="store",
249 type='string', action='store', dest='dest_branch', 257 dest="branch",
250 metavar='BRANCH', 258 help="(local) branch to upload",
251 help='submit for review on this target branch') 259 )
252 p.add_option('-n', '--dry-run', 260 p.add_option(
253 dest='dryrun', default=False, action='store_true', 261 "-c",
254 help='do everything except actually upload the CL') 262 "--current-branch",
255 p.add_option('-y', '--yes', 263 dest="current_branch",
256 default=False, action='store_true', 264 action="store_true",
257 help='answer yes to all safe prompts') 265 help="upload current git branch",
258 p.add_option('--ignore-untracked-files', 266 )
259 action='store_true', default=False, 267 p.add_option(
260 help='ignore untracked files in the working copy') 268 "--no-current-branch",
261 p.add_option('--no-ignore-untracked-files', 269 dest="current_branch",
262 dest='ignore_untracked_files', action='store_false', 270 action="store_false",
263 help='always ask about untracked files in the working copy') 271 help="upload all git branches",
264 p.add_option('--no-cert-checks', 272 )
265 dest='validate_certs', action='store_false', default=True, 273 # Turn this into a warning & remove this someday.
266 help='disable verifying ssl certs (unsafe)') 274 p.add_option(
267 RepoHook.AddOptionGroup(p, 'pre-upload') 275 "--cbr",
268 276 dest="current_branch",
269 def _SingleBranch(self, opt, branch, people): 277 action="store_true",
270 project = branch.project 278 help=optparse.SUPPRESS_HELP,
271 name = branch.name 279 )
272 remote = project.GetBranch(name).remote 280 p.add_option(
273 281 "--ne",
274 key = 'review.%s.autoupload' % remote.review 282 "--no-emails",
275 answer = project.config.GetBoolean(key) 283 action="store_false",
276 284 dest="notify",
277 if answer is False: 285 default=True,
278 _die("upload blocked by %s = false" % key) 286 help="do not send e-mails on upload",
279 287 )
280 if answer is None: 288 p.add_option(
281 date = branch.date 289 "-p",
282 commit_list = branch.commits 290 "--private",
283 291 action="store_true",
284 destination = opt.dest_branch or project.dest_branch or project.revisionExpr 292 dest="private",
285 print('Upload project %s/ to remote branch %s%s:' % 293 default=False,
286 (project.RelPath(local=opt.this_manifest_only), destination, 294 help="upload as a private change (deprecated; use --wip)",
287 ' (private)' if opt.private else '')) 295 )
288 print(' branch %s (%2d commit%s, %s):' % ( 296 p.add_option(
289 name, 297 "-w",
290 len(commit_list), 298 "--wip",
291 len(commit_list) != 1 and 's' or '', 299 action="store_true",
292 date)) 300 dest="wip",
293 for commit in commit_list: 301 default=False,
294 print(' %s' % commit) 302 help="upload as a work-in-progress change",
295 303 )
296 print('to %s (y/N)? ' % remote.review, end='', flush=True) 304 p.add_option(
297 if opt.yes: 305 "-r",
298 print('<--yes>') 306 "--ready",
299 answer = True 307 action="store_true",
300 else: 308 default=False,
301 answer = sys.stdin.readline().strip().lower() 309 help="mark change as ready (clears work-in-progress setting)",
302 answer = answer in ('y', 'yes', '1', 'true', 't') 310 )
303 if not answer: 311 p.add_option(
304 _die("upload aborted by user") 312 "-o",
305 313 "--push-option",
306 # Perform some basic safety checks prior to uploading. 314 type="string",
307 if not opt.yes and not _VerifyPendingCommits([branch]): 315 action="append",
308 _die("upload aborted by user") 316 dest="push_options",
309 317 default=[],
310 self._UploadAndReport(opt, [branch], people) 318 help="additional push options to transmit",
311 319 )
312 def _MultipleBranches(self, opt, pending, people): 320 p.add_option(
313 projects = {} 321 "-D",
314 branches = {} 322 "--destination",
315 323 "--dest",
316 script = [] 324 type="string",
317 script.append('# Uncomment the branches to upload:') 325 action="store",
318 for project, avail in pending: 326 dest="dest_branch",
319 project_path = project.RelPath(local=opt.this_manifest_only) 327 metavar="BRANCH",
320 script.append('#') 328 help="submit for review on this target branch",
321 script.append(f'# project {project_path}/:') 329 )
322 330 p.add_option(
323 b = {} 331 "-n",
324 for branch in avail: 332 "--dry-run",
325 if branch is None: 333 dest="dryrun",
326 continue 334 default=False,
335 action="store_true",
336 help="do everything except actually upload the CL",
337 )
338 p.add_option(
339 "-y",
340 "--yes",
341 default=False,
342 action="store_true",
343 help="answer yes to all safe prompts",
344 )
345 p.add_option(
346 "--ignore-untracked-files",
347 action="store_true",
348 default=False,
349 help="ignore untracked files in the working copy",
350 )
351 p.add_option(
352 "--no-ignore-untracked-files",
353 dest="ignore_untracked_files",
354 action="store_false",
355 help="always ask about untracked files in the working copy",
356 )
357 p.add_option(
358 "--no-cert-checks",
359 dest="validate_certs",
360 action="store_false",
361 default=True,
362 help="disable verifying ssl certs (unsafe)",
363 )
364 RepoHook.AddOptionGroup(p, "pre-upload")
365
366 def _SingleBranch(self, opt, branch, people):
367 project = branch.project
327 name = branch.name 368 name = branch.name
328 date = branch.date 369 remote = project.GetBranch(name).remote
329 commit_list = branch.commits 370
330 371 key = "review.%s.autoupload" % remote.review
331 if b: 372 answer = project.config.GetBoolean(key)
332 script.append('#') 373
333 destination = opt.dest_branch or project.dest_branch or project.revisionExpr 374 if answer is False:
334 script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % ( 375 _die("upload blocked by %s = false" % key)
335 name, 376
336 len(commit_list), 377 if answer is None:
337 len(commit_list) != 1 and 's' or '', 378 date = branch.date
338 date, 379 commit_list = branch.commits
339 destination)) 380
340 for commit in commit_list: 381 destination = (
341 script.append('# %s' % commit) 382 opt.dest_branch or project.dest_branch or project.revisionExpr
342 b[name] = branch 383 )
343 384 print(
344 projects[project_path] = project 385 "Upload project %s/ to remote branch %s%s:"
345 branches[project_path] = b 386 % (
346 script.append('') 387 project.RelPath(local=opt.this_manifest_only),
347 388 destination,
348 script = Editor.EditString("\n".join(script)).split("\n") 389 " (private)" if opt.private else "",
349 390 )
350 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$') 391 )
351 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*') 392 print(
352 393 " branch %s (%2d commit%s, %s):"
353 project = None 394 % (
354 todo = [] 395 name,
355 396 len(commit_list),
356 for line in script: 397 len(commit_list) != 1 and "s" or "",
357 m = project_re.match(line) 398 date,
358 if m: 399 )
359 name = m.group(1) 400 )
360 project = projects.get(name) 401 for commit in commit_list:
361 if not project: 402 print(" %s" % commit)
362 _die('project %s not available for upload', name) 403
363 continue 404 print("to %s (y/N)? " % remote.review, end="", flush=True)
364
365 m = branch_re.match(line)
366 if m:
367 name = m.group(1)
368 if not project:
369 _die('project for branch %s not in script', name)
370 project_path = project.RelPath(local=opt.this_manifest_only)
371 branch = branches[project_path].get(name)
372 if not branch:
373 _die('branch %s not in %s', name, project_path)
374 todo.append(branch)
375 if not todo:
376 _die("nothing uncommented for upload")
377
378 # Perform some basic safety checks prior to uploading.
379 if not opt.yes and not _VerifyPendingCommits(todo):
380 _die("upload aborted by user")
381
382 self._UploadAndReport(opt, todo, people)
383
384 def _AppendAutoList(self, branch, people):
385 """
386 Appends the list of reviewers in the git project's config.
387 Appends the list of users in the CC list in the git project's config if a
388 non-empty reviewer list was found.
389 """
390 name = branch.name
391 project = branch.project
392
393 key = 'review.%s.autoreviewer' % project.GetBranch(name).remote.review
394 raw_list = project.config.GetString(key)
395 if raw_list is not None:
396 people[0].extend([entry.strip() for entry in raw_list.split(',')])
397
398 key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
399 raw_list = project.config.GetString(key)
400 if raw_list is not None and len(people[0]) > 0:
401 people[1].extend([entry.strip() for entry in raw_list.split(',')])
402
403 def _FindGerritChange(self, branch):
404 last_pub = branch.project.WasPublished(branch.name)
405 if last_pub is None:
406 return ""
407
408 refs = branch.GetPublishedRefs()
409 try:
410 # refs/changes/XYZ/N --> XYZ
411 return refs.get(last_pub).split('/')[-2]
412 except (AttributeError, IndexError):
413 return ""
414
415 def _UploadAndReport(self, opt, todo, original_people):
416 have_errors = False
417 for branch in todo:
418 try:
419 people = copy.deepcopy(original_people)
420 self._AppendAutoList(branch, people)
421
422 # Check if there are local changes that may have been forgotten
423 changes = branch.project.UncommitedFiles()
424 if opt.ignore_untracked_files:
425 untracked = set(branch.project.UntrackedFiles())
426 changes = [x for x in changes if x not in untracked]
427
428 if changes:
429 key = 'review.%s.autoupload' % branch.project.remote.review
430 answer = branch.project.config.GetBoolean(key)
431
432 # if they want to auto upload, let's not ask because it could be automated
433 if answer is None:
434 print()
435 print('Uncommitted changes in %s (did you forget to amend?):'
436 % branch.project.name)
437 print('\n'.join(changes))
438 print('Continue uploading? (y/N) ', end='', flush=True)
439 if opt.yes: 405 if opt.yes:
440 print('<--yes>') 406 print("<--yes>")
441 a = 'yes' 407 answer = True
408 else:
409 answer = sys.stdin.readline().strip().lower()
410 answer = answer in ("y", "yes", "1", "true", "t")
411 if not answer:
412 _die("upload aborted by user")
413
414 # Perform some basic safety checks prior to uploading.
415 if not opt.yes and not _VerifyPendingCommits([branch]):
416 _die("upload aborted by user")
417
418 self._UploadAndReport(opt, [branch], people)
419
420 def _MultipleBranches(self, opt, pending, people):
421 projects = {}
422 branches = {}
423
424 script = []
425 script.append("# Uncomment the branches to upload:")
426 for project, avail in pending:
427 project_path = project.RelPath(local=opt.this_manifest_only)
428 script.append("#")
429 script.append(f"# project {project_path}/:")
430
431 b = {}
432 for branch in avail:
433 if branch is None:
434 continue
435 name = branch.name
436 date = branch.date
437 commit_list = branch.commits
438
439 if b:
440 script.append("#")
441 destination = (
442 opt.dest_branch
443 or project.dest_branch
444 or project.revisionExpr
445 )
446 script.append(
447 "# branch %s (%2d commit%s, %s) to remote branch %s:"
448 % (
449 name,
450 len(commit_list),
451 len(commit_list) != 1 and "s" or "",
452 date,
453 destination,
454 )
455 )
456 for commit in commit_list:
457 script.append("# %s" % commit)
458 b[name] = branch
459
460 projects[project_path] = project
461 branches[project_path] = b
462 script.append("")
463
464 script = Editor.EditString("\n".join(script)).split("\n")
465
466 project_re = re.compile(r"^#?\s*project\s*([^\s]+)/:$")
467 branch_re = re.compile(r"^\s*branch\s*([^\s(]+)\s*\(.*")
468
469 project = None
470 todo = []
471
472 for line in script:
473 m = project_re.match(line)
474 if m:
475 name = m.group(1)
476 project = projects.get(name)
477 if not project:
478 _die("project %s not available for upload", name)
479 continue
480
481 m = branch_re.match(line)
482 if m:
483 name = m.group(1)
484 if not project:
485 _die("project for branch %s not in script", name)
486 project_path = project.RelPath(local=opt.this_manifest_only)
487 branch = branches[project_path].get(name)
488 if not branch:
489 _die("branch %s not in %s", name, project_path)
490 todo.append(branch)
491 if not todo:
492 _die("nothing uncommented for upload")
493
494 # Perform some basic safety checks prior to uploading.
495 if not opt.yes and not _VerifyPendingCommits(todo):
496 _die("upload aborted by user")
497
498 self._UploadAndReport(opt, todo, people)
499
500 def _AppendAutoList(self, branch, people):
501 """
502 Appends the list of reviewers in the git project's config.
503 Appends the list of users in the CC list in the git project's config if
504 a non-empty reviewer list was found.
505 """
506 name = branch.name
507 project = branch.project
508
509 key = "review.%s.autoreviewer" % project.GetBranch(name).remote.review
510 raw_list = project.config.GetString(key)
511 if raw_list is not None:
512 people[0].extend([entry.strip() for entry in raw_list.split(",")])
513
514 key = "review.%s.autocopy" % project.GetBranch(name).remote.review
515 raw_list = project.config.GetString(key)
516 if raw_list is not None and len(people[0]) > 0:
517 people[1].extend([entry.strip() for entry in raw_list.split(",")])
518
519 def _FindGerritChange(self, branch):
520 last_pub = branch.project.WasPublished(branch.name)
521 if last_pub is None:
522 return ""
523
524 refs = branch.GetPublishedRefs()
525 try:
526 # refs/changes/XYZ/N --> XYZ
527 return refs.get(last_pub).split("/")[-2]
528 except (AttributeError, IndexError):
529 return ""
530
531 def _UploadAndReport(self, opt, todo, original_people):
532 have_errors = False
533 for branch in todo:
534 try:
535 people = copy.deepcopy(original_people)
536 self._AppendAutoList(branch, people)
537
538 # Check if there are local changes that may have been forgotten.
539 changes = branch.project.UncommitedFiles()
540 if opt.ignore_untracked_files:
541 untracked = set(branch.project.UntrackedFiles())
542 changes = [x for x in changes if x not in untracked]
543
544 if changes:
545 key = "review.%s.autoupload" % branch.project.remote.review
546 answer = branch.project.config.GetBoolean(key)
547
548 # If they want to auto upload, let's not ask because it
549 # could be automated.
550 if answer is None:
551 print()
552 print(
553 "Uncommitted changes in %s (did you forget to "
554 "amend?):" % branch.project.name
555 )
556 print("\n".join(changes))
557 print("Continue uploading? (y/N) ", end="", flush=True)
558 if opt.yes:
559 print("<--yes>")
560 a = "yes"
561 else:
562 a = sys.stdin.readline().strip().lower()
563 if a not in ("y", "yes", "t", "true", "on"):
564 print("skipping upload", file=sys.stderr)
565 branch.uploaded = False
566 branch.error = "User aborted"
567 continue
568
569 # Check if topic branches should be sent to the server during
570 # upload.
571 if opt.auto_topic is not True:
572 key = "review.%s.uploadtopic" % branch.project.remote.review
573 opt.auto_topic = branch.project.config.GetBoolean(key)
574
575 def _ExpandCommaList(value):
576 """Split |value| up into comma delimited entries."""
577 if not value:
578 return
579 for ret in value.split(","):
580 ret = ret.strip()
581 if ret:
582 yield ret
583
584 # Check if hashtags should be included.
585 key = "review.%s.uploadhashtags" % branch.project.remote.review
586 hashtags = set(
587 _ExpandCommaList(branch.project.config.GetString(key))
588 )
589 for tag in opt.hashtags:
590 hashtags.update(_ExpandCommaList(tag))
591 if opt.hashtag_branch:
592 hashtags.add(branch.name)
593
594 # Check if labels should be included.
595 key = "review.%s.uploadlabels" % branch.project.remote.review
596 labels = set(
597 _ExpandCommaList(branch.project.config.GetString(key))
598 )
599 for label in opt.labels:
600 labels.update(_ExpandCommaList(label))
601
602 # Handle e-mail notifications.
603 if opt.notify is False:
604 notify = "NONE"
605 else:
606 key = (
607 "review.%s.uploadnotify" % branch.project.remote.review
608 )
609 notify = branch.project.config.GetString(key)
610
611 destination = opt.dest_branch or branch.project.dest_branch
612
613 if branch.project.dest_branch and not opt.dest_branch:
614 merge_branch = self._GetMergeBranch(
615 branch.project, local_branch=branch.name
616 )
617
618 full_dest = destination
619 if not full_dest.startswith(R_HEADS):
620 full_dest = R_HEADS + full_dest
621
622 # If the merge branch of the local branch is different from
623 # the project's revision AND destination, this might not be
624 # intentional.
625 if (
626 merge_branch
627 and merge_branch != branch.project.revisionExpr
628 and merge_branch != full_dest
629 ):
630 print(
631 f"For local branch {branch.name}: merge branch "
632 f"{merge_branch} does not match destination branch "
633 f"{destination}"
634 )
635 print("skipping upload.")
636 print(
637 f"Please use `--destination {destination}` if this "
638 "is intentional"
639 )
640 branch.uploaded = False
641 continue
642
643 branch.UploadForReview(
644 people,
645 dryrun=opt.dryrun,
646 auto_topic=opt.auto_topic,
647 hashtags=hashtags,
648 labels=labels,
649 private=opt.private,
650 notify=notify,
651 wip=opt.wip,
652 ready=opt.ready,
653 dest_branch=destination,
654 validate_certs=opt.validate_certs,
655 push_options=opt.push_options,
656 )
657
658 branch.uploaded = True
659 except UploadError as e:
660 branch.error = e
661 branch.uploaded = False
662 have_errors = True
663
664 print(file=sys.stderr)
665 print("-" * 70, file=sys.stderr)
666
667 if have_errors:
668 for branch in todo:
669 if not branch.uploaded:
670 if len(str(branch.error)) <= 30:
671 fmt = " (%s)"
672 else:
673 fmt = "\n (%s)"
674 print(
675 ("[FAILED] %-15s %-15s" + fmt)
676 % (
677 branch.project.RelPath(local=opt.this_manifest_only)
678 + "/",
679 branch.name,
680 str(branch.error),
681 ),
682 file=sys.stderr,
683 )
684 print()
685
686 for branch in todo:
687 if branch.uploaded:
688 print(
689 "[OK ] %-15s %s"
690 % (
691 branch.project.RelPath(local=opt.this_manifest_only)
692 + "/",
693 branch.name,
694 ),
695 file=sys.stderr,
696 )
697
698 if have_errors:
699 sys.exit(1)
700
701 def _GetMergeBranch(self, project, local_branch=None):
702 if local_branch is None:
703 p = GitCommand(
704 project,
705 ["rev-parse", "--abbrev-ref", "HEAD"],
706 capture_stdout=True,
707 capture_stderr=True,
708 )
709 p.Wait()
710 local_branch = p.stdout.strip()
711 p = GitCommand(
712 project,
713 ["config", "--get", "branch.%s.merge" % local_branch],
714 capture_stdout=True,
715 capture_stderr=True,
716 )
717 p.Wait()
718 merge_branch = p.stdout.strip()
719 return merge_branch
720
721 @staticmethod
722 def _GatherOne(opt, project):
723 """Figure out the upload status for |project|."""
724 if opt.current_branch:
725 cbr = project.CurrentBranch
726 up_branch = project.GetUploadableBranch(cbr)
727 avail = [up_branch] if up_branch else None
728 else:
729 avail = project.GetUploadableBranches(opt.branch)
730 return (project, avail)
731
732 def Execute(self, opt, args):
733 projects = self.GetProjects(
734 args, all_manifests=not opt.this_manifest_only
735 )
736
737 def _ProcessResults(_pool, _out, results):
738 pending = []
739 for result in results:
740 project, avail = result
741 if avail is None:
742 print(
743 'repo: error: %s: Unable to upload branch "%s". '
744 "You might be able to fix the branch by running:\n"
745 " git branch --set-upstream-to m/%s"
746 % (
747 project.RelPath(local=opt.this_manifest_only),
748 project.CurrentBranch,
749 project.manifest.branch,
750 ),
751 file=sys.stderr,
752 )
753 elif avail:
754 pending.append(result)
755 return pending
756
757 pending = self.ExecuteInParallel(
758 opt.jobs,
759 functools.partial(self._GatherOne, opt),
760 projects,
761 callback=_ProcessResults,
762 )
763
764 if not pending:
765 if opt.branch is None:
766 print(
767 "repo: error: no branches ready for upload", file=sys.stderr
768 )
442 else: 769 else:
443 a = sys.stdin.readline().strip().lower() 770 print(
444 if a not in ('y', 'yes', 't', 'true', 'on'): 771 'repo: error: no branches named "%s" ready for upload'
445 print("skipping upload", file=sys.stderr) 772 % (opt.branch,),
446 branch.uploaded = False 773 file=sys.stderr,
447 branch.error = 'User aborted' 774 )
448 continue 775 return 1
449 776
450 # Check if topic branches should be sent to the server during upload 777 manifests = {
451 if opt.auto_topic is not True: 778 project.manifest.topdir: project.manifest
452 key = 'review.%s.uploadtopic' % branch.project.remote.review 779 for (project, available) in pending
453 opt.auto_topic = branch.project.config.GetBoolean(key) 780 }
454 781 ret = 0
455 def _ExpandCommaList(value): 782 for manifest in manifests.values():
456 """Split |value| up into comma delimited entries.""" 783 pending_proj_names = [
457 if not value: 784 project.name
458 return 785 for (project, available) in pending
459 for ret in value.split(','): 786 if project.manifest.topdir == manifest.topdir
460 ret = ret.strip() 787 ]
461 if ret: 788 pending_worktrees = [
462 yield ret 789 project.worktree
463 790 for (project, available) in pending
464 # Check if hashtags should be included. 791 if project.manifest.topdir == manifest.topdir
465 key = 'review.%s.uploadhashtags' % branch.project.remote.review 792 ]
466 hashtags = set(_ExpandCommaList(branch.project.config.GetString(key))) 793 hook = RepoHook.FromSubcmd(
467 for tag in opt.hashtags: 794 hook_type="pre-upload",
468 hashtags.update(_ExpandCommaList(tag)) 795 manifest=manifest,
469 if opt.hashtag_branch: 796 opt=opt,
470 hashtags.add(branch.name) 797 abort_if_user_denies=True,
471 798 )
472 # Check if labels should be included. 799 if not hook.Run(
473 key = 'review.%s.uploadlabels' % branch.project.remote.review 800 project_list=pending_proj_names, worktree_list=pending_worktrees
474 labels = set(_ExpandCommaList(branch.project.config.GetString(key))) 801 ):
475 for label in opt.labels: 802 ret = 1
476 labels.update(_ExpandCommaList(label)) 803 if ret:
477 804 return ret
478 # Handle e-mail notifications. 805
479 if opt.notify is False: 806 reviewers = _SplitEmails(opt.reviewers) if opt.reviewers else []
480 notify = 'NONE' 807 cc = _SplitEmails(opt.cc) if opt.cc else []
808 people = (reviewers, cc)
809
810 if len(pending) == 1 and len(pending[0][1]) == 1:
811 self._SingleBranch(opt, pending[0][1][0], people)
481 else: 812 else:
482 key = 'review.%s.uploadnotify' % branch.project.remote.review 813 self._MultipleBranches(opt, pending, people)
483 notify = branch.project.config.GetString(key)
484
485 destination = opt.dest_branch or branch.project.dest_branch
486
487 if branch.project.dest_branch and not opt.dest_branch:
488
489 merge_branch = self._GetMergeBranch(
490 branch.project, local_branch=branch.name)
491
492 full_dest = destination
493 if not full_dest.startswith(R_HEADS):
494 full_dest = R_HEADS + full_dest
495
496 # If the merge branch of the local branch is different from the
497 # project's revision AND destination, this might not be intentional.
498 if (merge_branch and merge_branch != branch.project.revisionExpr
499 and merge_branch != full_dest):
500 print(f'For local branch {branch.name}: merge branch '
501 f'{merge_branch} does not match destination branch '
502 f'{destination}')
503 print('skipping upload.')
504 print(f'Please use `--destination {destination}` if this is intentional')
505 branch.uploaded = False
506 continue
507
508 branch.UploadForReview(people,
509 dryrun=opt.dryrun,
510 auto_topic=opt.auto_topic,
511 hashtags=hashtags,
512 labels=labels,
513 private=opt.private,
514 notify=notify,
515 wip=opt.wip,
516 ready=opt.ready,
517 dest_branch=destination,
518 validate_certs=opt.validate_certs,
519 push_options=opt.push_options)
520
521 branch.uploaded = True
522 except UploadError as e:
523 branch.error = e
524 branch.uploaded = False
525 have_errors = True
526
527 print(file=sys.stderr)
528 print('----------------------------------------------------------------------', file=sys.stderr)
529
530 if have_errors:
531 for branch in todo:
532 if not branch.uploaded:
533 if len(str(branch.error)) <= 30:
534 fmt = ' (%s)'
535 else:
536 fmt = '\n (%s)'
537 print(('[FAILED] %-15s %-15s' + fmt) % (
538 branch.project.RelPath(local=opt.this_manifest_only) + '/',
539 branch.name,
540 str(branch.error)),
541 file=sys.stderr)
542 print()
543
544 for branch in todo:
545 if branch.uploaded:
546 print('[OK ] %-15s %s' % (
547 branch.project.RelPath(local=opt.this_manifest_only) + '/',
548 branch.name),
549 file=sys.stderr)
550
551 if have_errors:
552 sys.exit(1)
553
554 def _GetMergeBranch(self, project, local_branch=None):
555 if local_branch is None:
556 p = GitCommand(project,
557 ['rev-parse', '--abbrev-ref', 'HEAD'],
558 capture_stdout=True,
559 capture_stderr=True)
560 p.Wait()
561 local_branch = p.stdout.strip()
562 p = GitCommand(project,
563 ['config', '--get', 'branch.%s.merge' % local_branch],
564 capture_stdout=True,
565 capture_stderr=True)
566 p.Wait()
567 merge_branch = p.stdout.strip()
568 return merge_branch
569
570 @staticmethod
571 def _GatherOne(opt, project):
572 """Figure out the upload status for |project|."""
573 if opt.current_branch:
574 cbr = project.CurrentBranch
575 up_branch = project.GetUploadableBranch(cbr)
576 avail = [up_branch] if up_branch else None
577 else:
578 avail = project.GetUploadableBranches(opt.branch)
579 return (project, avail)
580
581 def Execute(self, opt, args):
582 projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
583
584 def _ProcessResults(_pool, _out, results):
585 pending = []
586 for result in results:
587 project, avail = result
588 if avail is None:
589 print('repo: error: %s: Unable to upload branch "%s". '
590 'You might be able to fix the branch by running:\n'
591 ' git branch --set-upstream-to m/%s' %
592 (project.RelPath(local=opt.this_manifest_only), project.CurrentBranch,
593 project.manifest.branch),
594 file=sys.stderr)
595 elif avail:
596 pending.append(result)
597 return pending
598
599 pending = self.ExecuteInParallel(
600 opt.jobs,
601 functools.partial(self._GatherOne, opt),
602 projects,
603 callback=_ProcessResults)
604
605 if not pending:
606 if opt.branch is None:
607 print('repo: error: no branches ready for upload', file=sys.stderr)
608 else:
609 print('repo: error: no branches named "%s" ready for upload' %
610 (opt.branch,), file=sys.stderr)
611 return 1
612
613 manifests = {project.manifest.topdir: project.manifest
614 for (project, available) in pending}
615 ret = 0
616 for manifest in manifests.values():
617 pending_proj_names = [project.name for (project, available) in pending
618 if project.manifest.topdir == manifest.topdir]
619 pending_worktrees = [project.worktree for (project, available) in pending
620 if project.manifest.topdir == manifest.topdir]
621 hook = RepoHook.FromSubcmd(
622 hook_type='pre-upload', manifest=manifest,
623 opt=opt, abort_if_user_denies=True)
624 if not hook.Run(project_list=pending_proj_names,
625 worktree_list=pending_worktrees):
626 ret = 1
627 if ret:
628 return ret
629
630 reviewers = _SplitEmails(opt.reviewers) if opt.reviewers else []
631 cc = _SplitEmails(opt.cc) if opt.cc else []
632 people = (reviewers, cc)
633
634 if len(pending) == 1 and len(pending[0][1]) == 1:
635 self._SingleBranch(opt, pending[0][1][0], people)
636 else:
637 self._MultipleBranches(opt, pending, people)