@ -5,10 +5,12 @@
""" Splits a branch into smaller branches and uploads CLs. """
""" Splits a branch into smaller branches and uploads CLs. """
import collections
import collections
import dataclasses
import os
import os
import re
import re
import subprocess2
import subprocess2
import sys
import sys
from typing import List , Set , Tuple , Dict , Any
import gclient_utils
import gclient_utils
import git_footers
import git_footers
@ -27,10 +29,62 @@ CL_SPLIT_FORCE_LIMIT = 10
# will be listed.
# will be listed.
CL_SPLIT_TOP_REVIEWERS = 5
CL_SPLIT_TOP_REVIEWERS = 5
def EmitWarning ( msg : str ) :
print ( " Warning: " , msg )
FilesAndOwnersDirectory = collections . namedtuple ( " FilesAndOwnersDirectory " ,
FilesAndOwnersDirectory = collections . namedtuple ( " FilesAndOwnersDirectory " ,
" files owners_directories " )
" files owners_directories " )
@dataclasses.dataclass
class CLInfo :
"""
Data structure representing a single CL . The script will split the large CL
into a list of these .
Fields :
- reviewers : the reviewers the CL will be sent to .
- files : a list of < action > , < file > pairs in the CL .
Has the same format as ` git status ` .
- directories : a string representing the directories containing the files
in this CL . This is only used for replacing $ directory in
the user - provided CL description .
"""
# Have to use default_factory because lists are mutable
reviewers : Set [ str ] = dataclasses . field ( default_factory = set )
files : List [ Tuple [ str , str ] ] = dataclasses . field ( default_factory = list )
# This is only used for formatting in the CL description, so it just
# has to be convertible to string.
directories : Any = " "
def FormatForPrinting ( self ) - > str :
"""
Format the CLInfo for printing to a file in a human - readable format .
"""
# Don't quote the reviewer emails in the output
reviewers_str = " , " . join ( self . reviewers )
lines = [
f " Reviewers: [ { reviewers_str } ] " , f " Directories: { self . directories } "
] + [ f " { action } , { file } " for ( action , file ) in self . files ]
return " \n " . join ( lines )
def CLInfoFromFilesAndOwnersDirectoriesDict (
d : Dict [ Tuple [ str ] , FilesAndOwnersDirectory ] ) - > List [ CLInfo ] :
"""
Transform a dictionary mapping reviewer tuples to FilesAndOwnersDirectories
into a list of CLInfo
"""
cl_infos = [ ]
for ( reviewers , fod ) in d . items ( ) :
cl_infos . append (
CLInfo ( set ( reviewers ) , fod . files , fod . owners_directories ) )
return cl_infos
def EnsureInGitRepository ( ) :
def EnsureInGitRepository ( ) :
""" Throws an exception if the current directory is not a git repository. """
""" Throws an exception if the current directory is not a git repository. """
git . run ( ' rev-parse ' )
git . run ( ' rev-parse ' )
@ -230,7 +284,7 @@ def LoadDescription(description_file, dry_run):
return gclient_utils . FileRead ( description_file )
return gclient_utils . FileRead ( description_file )
def PrintSummary ( files_split_by_reviewer s, refactor_branch ) :
def PrintSummary ( cl_info s, refactor_branch ) :
""" Print a brief summary of the splitting so the user
""" Print a brief summary of the splitting so the user
can review it before uploading .
can review it before uploading .
@ -238,16 +292,17 @@ def PrintSummary(files_split_by_reviewers, refactor_branch):
files_split_by_reviewers : A dictionary mapping reviewer tuples
files_split_by_reviewers : A dictionary mapping reviewer tuples
to the files and directories assigned to them .
to the files and directories assigned to them .
"""
"""
for info in cl_infos :
print ( f ' Reviewers: { info . reviewers } , files: { len ( info . files ) } , ' ,
f ' directories: { info . directories } ' )
for reviewers , file_info in files_split_by_reviewers . items ( ) :
num_cls = len ( cl_infos )
print ( f ' Reviewers: { reviewers } , files: { len ( file_info . files ) } , '
f ' directories: { file_info . owners_directories } ' )
num_cls = len ( files_split_by_reviewers )
print ( f ' \n Will split branch { refactor_branch } into { num_cls } CLs. '
print ( f ' \n Will split branch { refactor_branch } into { num_cls } CLs. '
' Please quickly review them before proceeding. \n ' )
' Please quickly review them before proceeding. \n ' )
if ( num_cls > CL_SPLIT_FORCE_LIMIT ) :
if ( num_cls > CL_SPLIT_FORCE_LIMIT ) :
print ( ' Warning: Uploading this many CLs may potentially '
EmitWarning (
' Uploading this many CLs may potentially '
' reach the limit of concurrent runs, imposed on you by the '
' reach the limit of concurrent runs, imposed on you by the '
' build infrastructure. Your runs may be throttled as a '
' build infrastructure. Your runs may be throttled as a '
' result. \n \n Please email infra-dev@chromium.org if you '
' result. \n \n Please email infra-dev@chromium.org if you '
@ -311,30 +366,30 @@ def SplitCl(description_file, comment_file, changelist, cmd_upload, dry_run,
files_split_by_reviewers = SelectReviewersForFiles (
files_split_by_reviewers = SelectReviewersForFiles (
cl , author , files , max_depth )
cl , author , files , max_depth )
cl_infos = CLInfoFromFilesAndOwnersDirectoriesDict (
files_split_by_reviewers )
if not dry_run :
if not dry_run :
PrintSummary ( files_split_by_reviewer s, refactor_branch )
PrintSummary ( cl_info s, refactor_branch )
answer = gclient_utils . AskForData ( ' Proceed? (y/N): ' )
answer = gclient_utils . AskForData ( ' Proceed? (y/N): ' )
if answer . lower ( ) != ' y ' :
if answer . lower ( ) != ' y ' :
return 0
return 0
cls_per_reviewer = collections . defaultdict ( int )
cls_per_reviewer = collections . defaultdict ( int )
for cl_index , ( reviewers , cl_info ) in \
for cl_index , cl_info in enumerate ( cl_infos , 1 ) :
enumerate ( files_split_by_reviewers . items ( ) , 1 ) :
# Convert reviewers from tuple to set.
# Convert reviewers from tuple to set.
reviewer_set = set ( reviewers )
if dry_run :
if dry_run :
file_paths = [ f for _ , f in cl_info . files ]
file_paths = [ f for _ , f in cl_info . files ]
PrintClInfo ( cl_index , len ( files_split_by_reviewers) ,
PrintClInfo ( cl_index , len ( cl_infos) , cl_info . directories ,
cl_info. owners_directories , file_paths, description ,
file_paths, description , cl_info . reviewers ,
reviewer_set, cq_dry_run, enable_auto_submit , topic )
cq_dry_run, enable_auto_submit , topic )
else :
else :
UploadCl ( refactor_branch , refactor_branch_upstream ,
UploadCl ( refactor_branch , refactor_branch_upstream ,
cl_info . owners_ directories, cl_info . files , description ,
cl_info . directories, cl_info . files , description ,
comment , reviewer_ set , changelist , cmd_upload ,
comment , cl_info. reviewers, changelist , cmd_upload ,
cq_dry_run , enable_auto_submit , topic , repository_root )
cq_dry_run , enable_auto_submit , topic , repository_root )
for reviewer in reviewers:
for reviewer in cl_info. reviewers:
cls_per_reviewer [ reviewer ] + = 1
cls_per_reviewer [ reviewer ] + = 1
# List the top reviewers that will be sent the most CLs as a result of
# List the top reviewers that will be sent the most CLs as a result of