@@ -243,6 +243,8 @@ def __init__(self, cfg, gh, j):
243243 else :
244244 self .test_ref = '%s-integration-%s-%s' % (self .user , self .num , self .ref )
245245
246+ self .batch_ref = 'batch'
247+
246248 self .title = ustr (j ["title" ])
247249 self .body = ustr (j ["body" ])
248250 self .merge_sha = None
@@ -342,12 +344,21 @@ def approval_list(self):
342344
343345 def priority (self ):
344346 p = 0
347+
348+ if self .batched (): p = - 1
349+
345350 for (d , u , c ) in self .head_comments :
346351 m = re .search (r"\bp=(-?\d+)\b" , c )
347352 if m is not None :
348353 p = max (p , int (m .group (1 )))
349354 return p
350355
356+ def batched (self ):
357+ for date , user , comment in self .head_comments :
358+ if re .search (r'\brollup\b' , comment ):
359+ return True
360+ return False
361+
351362 def prioritized_state (self ):
352363 return (self .current_state (),
353364 self .priority (),
@@ -540,6 +551,76 @@ def merge_pull_head_to_test_ref(self):
540551 self .add_comment (self .sha , s )
541552 self .set_error (s )
542553
554+ def merge_batched_pull_reqs_to_test_ref (self , pulls , cfg ):
555+ if cfg .get ('max_pulls_per_run' ) != 1 :
556+ msg = '{} - max_pulls_per_run should be 1 to merge batched pull requests' .format (self .short ())
557+ self .log .info (msg )
558+ self .add_comment (self .sha , msg )
559+ return
560+
561+ batch_msg = 'batch merging {} into {}' .format (self .short (), self .batch_ref )
562+ self .log .info (batch_msg )
563+ self .add_comment (self .sha , batch_msg )
564+
565+ info = self .dst ().git ().refs ().heads (self .target_ref ).get ()
566+ target_sha = info ['object' ]['sha' ].encode ('utf-8' )
567+ try :
568+ self .dst ().git ().refs ().heads (self .batch_ref ).get ()
569+ self .dst ().git ().refs ().heads (self .batch_ref ).patch (sha = target_sha , force = True )
570+ except github .ApiError :
571+ self .dst ().git ().refs ().post (sha = target_sha , ref = 'refs/heads/' + self .batch_ref )
572+
573+ successes = []
574+ failures = []
575+
576+ batch_sha = ''
577+
578+ for pull in pulls :
579+ if pull .current_state () == STATE_APPROVED :
580+ msg = 'Merge batched pull request #{} from {}/{}\n \n {}\n \n Reviewed-by: {}' .format (
581+ pull .num ,
582+ pull .src_owner , pull .ref ,
583+ pull .title ,
584+ ', ' .join (pull .approval_list ())
585+ )
586+ pull_repr = '- {}/{}: {}' .format (pull .src_owner , pull .ref , pull .title )
587+ try :
588+ info = self .dst ().merges ().post (base = self .batch_ref , head = pull .sha , commit_message = msg )
589+ batch_sha = info ['sha' ].encode ('utf-8' )
590+ successes .append (pull_repr )
591+ except github .ApiError :
592+ failures .append (pull_repr )
593+
594+ if batch_sha :
595+ try :
596+ self .dst ().git ().refs ().heads (self .test_ref ).get ()
597+ self .dst ().git ().refs ().heads (self .test_ref ).patch (sha = batch_sha )
598+ except github .ApiError as e :
599+ self .dst ().git ().refs ().post (sha = batch_sha , ref = 'refs/heads/' + self .test_ref )
600+
601+ url = 'https://{}/{}/{}/commit/{}' .format (self .gh_host , self .dst_owner , self .dst_repo , batch_sha )
602+ short_msg = 'running tests for rollup candidate {} ({} successes, {} failures)' .format (batch_sha , len (successes ), len (failures ))
603+ msg = 'Testing rollup candidate = {:.8}' .format (batch_sha )
604+ if successes : msg += '\n \n **Successful merges:**\n \n {}' .format ('\n ' .join (successes ))
605+ if failures : msg += '\n \n **Failed merges:**\n \n {}' .format ('\n ' .join (failures ))
606+
607+ self .log .info (short_msg )
608+ self .add_comment (self .sha , msg )
609+ self .set_pending (short_msg , url )
610+ else :
611+ batch_msg += ' failed'
612+
613+ self .log .info (batch_msg )
614+ self .add_comment (self .sha , batch_msg )
615+ self .set_error (batch_msg )
616+
617+ def merge_or_batch (self , pulls , cfg ):
618+ self .reset_test_ref_to_target ()
619+ if self .batched ():
620+ self .merge_batched_pull_reqs_to_test_ref (pulls , cfg )
621+ else :
622+ self .merge_pull_head_to_test_ref ()
623+
543624 def advance_target_ref_to_test (self ):
544625 assert self .merge_sha is not None
545626 s = ("fast-forwarding %s to %s = %.8s" %
@@ -556,6 +637,11 @@ def advance_target_ref_to_test(self):
556637 except github .ApiError :
557638 self .log .info ("deleting integration branch %s failed" % self .test_ref )
558639
640+ try :
641+ self .dst ().git ().refs ().heads (self .batch_ref ).delete ()
642+ except github .ApiError :
643+ self .log .info ('deleting batch branch {} failed' .format (self .batch_ref ))
644+
559645 self .maybe_delete_source_branch ()
560646
561647 except github .ApiError :
@@ -583,7 +669,7 @@ def fresh(self):
583669 target_sha in test_parents and
584670 self .sha in test_parents )
585671
586- def try_advance (self ):
672+ def try_advance (self , pulls , cfg ):
587673 s = self .current_state ()
588674
589675 self .log .info ("considering %s" , self .desc ())
@@ -601,8 +687,7 @@ def try_advance(self):
601687 self .src_repo ,
602688 self .sha ))))
603689
604- self .reset_test_ref_to_target ()
605- self .merge_pull_head_to_test_ref ()
690+ self .merge_or_batch (pulls , cfg )
606691
607692 elif s == STATE_PENDING :
608693 # Make sure the optional merge sha is loaded
@@ -612,13 +697,12 @@ def try_advance(self):
612697 test_sha = test_head ["object" ]["sha" ].encode ("utf8" )
613698 self .merge_sha = test_sha
614699
615- if not self .fresh ():
700+ if not self .fresh () and not self . batched () :
616701 c = ("Merge sha %.8s is stale."
617702 % (self .merge_sha ,))
618703 self .log .info (c )
619704 self .add_comment (self .sha , c )
620- self .reset_test_ref_to_target ()
621- self .merge_pull_head_to_test_ref ()
705+ self .merge_or_batch (pulls , cfg )
622706 return
623707 self .log .info ("%s - found pending state, checking tests" , self .short ())
624708 assert self .merge_sha is not None
@@ -676,16 +760,15 @@ def try_advance(self):
676760 self .log .info ("%s - tests successful, waiting for merge approval" ,
677761 self .short ())
678762 return
679- if self .fresh ():
763+ if self .fresh () or self . batched () :
680764 self .log .info ("%s - tests successful, attempting landing" , self .short ())
681765 self .advance_target_ref_to_test ()
682766 else :
683767 c = ("Merge sha %.8s is stale."
684768 % (self .merge_sha ,))
685769 self .log .info (c )
686770 self .add_comment (self .sha , c )
687- self .reset_test_ref_to_target ()
688- self .merge_pull_head_to_test_ref ()
771+ self .merge_or_batch (pulls , cfg )
689772
690773
691774
@@ -850,12 +933,14 @@ def main():
850933 pull .priority (),
851934 pull .desc ())
852935
936+ all_pulls = pulls
937+
853938 max_pulls_per_run = cfg .get ('max_pulls_per_run' )
854939 if max_pulls_per_run :
855940 logging .info ("Only considering %d pull-requests this run" , max_pulls_per_run )
856941 pulls = pulls [- max_pulls_per_run :]
857942
858- [p .try_advance () for p in reversed (pulls )]
943+ [p .try_advance (reversed ( all_pulls ), cfg ) for p in reversed (pulls )]
859944
860945if __name__ == "__main__" :
861946 try :
0 commit comments