|  | @@ -27,6 +27,7 @@ from release.utils import ScriptError
 | 
	
		
			
				|  |  |  from release.utils import update_init_py_version
 | 
	
		
			
				|  |  |  from release.utils import update_run_sh_version
 | 
	
		
			
				|  |  |  from release.utils import yesno
 | 
	
		
			
				|  |  | +from twine.commands.upload import main as twine_upload
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def create_initial_branch(repository, args):
 | 
	
	
		
			
				|  | @@ -78,10 +79,9 @@ def monitor_pr_status(pr_data):
 | 
	
		
			
				|  |  |                      continue
 | 
	
		
			
				|  |  |                  summary[detail.state] += 1
 | 
	
		
			
				|  |  |              print('{pending} pending, {success} successes, {failure} failures'.format(**summary))
 | 
	
		
			
				|  |  | -            if status.total_count == 0:
 | 
	
		
			
				|  |  | -                # Mostly for testing purposes against repos with no CI setup
 | 
	
		
			
				|  |  | -                return True
 | 
	
		
			
				|  |  | -            elif summary['pending'] == 0 and summary['failure'] == 0:
 | 
	
		
			
				|  |  | +            if summary['pending'] == 0 and summary['failure'] == 0 and summary['success'] > 0:
 | 
	
		
			
				|  |  | +                # This check assumes at least 1 non-DCO CI check to avoid race conditions.
 | 
	
		
			
				|  |  | +                # If testing on a repo without CI, use --skip-ci-check to avoid looping eternally
 | 
	
		
			
				|  |  |                  return True
 | 
	
		
			
				|  |  |              elif summary['failure'] > 0:
 | 
	
		
			
				|  |  |                  raise ScriptError('CI failures detected!')
 | 
	
	
		
			
				|  | @@ -156,7 +156,8 @@ def resume(args):
 | 
	
		
			
				|  |  |          if not pr_data:
 | 
	
		
			
				|  |  |              pr_data = repository.create_release_pull_request(args.release)
 | 
	
		
			
				|  |  |          check_pr_mergeable(pr_data)
 | 
	
		
			
				|  |  | -        monitor_pr_status(pr_data)
 | 
	
		
			
				|  |  | +        if not args.skip_ci:
 | 
	
		
			
				|  |  | +            monitor_pr_status(pr_data)
 | 
	
		
			
				|  |  |          downloader = BinaryDownloader(args.destination)
 | 
	
		
			
				|  |  |          files = downloader.download_all(args.release)
 | 
	
		
			
				|  |  |          if not gh_release:
 | 
	
	
		
			
				|  | @@ -195,7 +196,8 @@ def start(args):
 | 
	
		
			
				|  |  |          create_initial_branch(repository, args)
 | 
	
		
			
				|  |  |          pr_data = repository.create_release_pull_request(args.release)
 | 
	
		
			
				|  |  |          check_pr_mergeable(pr_data)
 | 
	
		
			
				|  |  | -        monitor_pr_status(pr_data)
 | 
	
		
			
				|  |  | +        if not args.skip_ci:
 | 
	
		
			
				|  |  | +            monitor_pr_status(pr_data)
 | 
	
		
			
				|  |  |          downloader = BinaryDownloader(args.destination)
 | 
	
		
			
				|  |  |          files = downloader.download_all(args.release)
 | 
	
		
			
				|  |  |          gh_release = create_release_draft(repository, args.release, pr_data, files)
 | 
	
	
		
			
				|  | @@ -239,8 +241,8 @@ def finalize(args):
 | 
	
		
			
				|  |  |          if not merge_status.merged:
 | 
	
		
			
				|  |  |              raise ScriptError('Unable to merge PR #{}: {}'.format(pr_data.number, merge_status.message))
 | 
	
		
			
				|  |  |          print('Uploading to PyPi')
 | 
	
		
			
				|  |  | -        run_setup(os.path.join(REPO_ROOT, 'setup.py'), script_args=['upload'])
 | 
	
		
			
				|  |  | -        img_manager.push_images(args.release)
 | 
	
		
			
				|  |  | +        twine_upload(['dist/*'])
 | 
	
		
			
				|  |  | +        img_manager.push_images()
 | 
	
		
			
				|  |  |          repository.publish_release(gh_release)
 | 
	
		
			
				|  |  |      except ScriptError as e:
 | 
	
		
			
				|  |  |          print(e)
 | 
	
	
		
			
				|  | @@ -310,6 +312,10 @@ def main():
 | 
	
		
			
				|  |  |          '--no-cherries', '-C', dest='cherries', action='store_false',
 | 
	
		
			
				|  |  |          help='If set, the program will not prompt the user for PR numbers to cherry-pick'
 | 
	
		
			
				|  |  |      )
 | 
	
		
			
				|  |  | +    parser.add_argument(
 | 
	
		
			
				|  |  | +        '--skip-ci-checks', dest='skip_ci', action='store_true',
 | 
	
		
			
				|  |  | +        help='If set, the program will not wait for CI jobs to complete'
 | 
	
		
			
				|  |  | +    )
 | 
	
		
			
				|  |  |      args = parser.parse_args()
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      if args.action == 'start':
 |