def test_should_only_merge_repos_with_matching_base_and_head(self): client = GitHubClient('', '') mock_repos = [[ Repository('', 'RepoA', 'WRITE', Ref('', '', 'release', '')), Repository('', 'RepoB', 'WRITE', Ref('', '', 'release', '')), Repository('', 'RepoC', 'WRITE', Ref('', '', 'release', '')), Repository('', 'RepoD', 'WRITE', None) ], [ Repository('', 'RepoA', 'WRITE', Ref('', '', 'master', '')), Repository('', 'RepoB', 'WRITE', Ref('', '', 'master', '')), Repository('', 'RepoC', 'WRITE', Ref('', '', 'master', '')), Repository('', 'RepoD', 'WRITE', Ref('', '', 'master', '')), ]] mock_merge_response = MergeResponse( '', 'https://github.com/org/repo/commits/#hash', 'merged') expected_merged_repos = 3 client.get_repositories = Mock(side_effect=mock_repos) client.merge_branch = Mock(return_value=mock_merge_response) results = auto_merge('master', 'release', '', client) (succeeded, unprocessed, failed) = results[0] self.assertEqual(client.merge_branch.call_count, expected_merged_repos) self.assertEqual(len(succeeded), expected_merged_repos) self.assertEqual(len(unprocessed), 0) self.assertEqual(len(failed), 0)
def test_should_merge_eligible_repos_base_branch_with_release_branch(self): client = GitHubClient('', '') mock_repos = [[ Repository('', 'RepoA', 'WRITE', Ref('', '', 'release', '')), Repository('', 'RepoB', 'WRITE', Ref('', '', 'release', '')), Repository('', 'RepoC', 'READ', Ref('', '', 'release', '')), Repository('', 'RepoD', 'WRITE', None) ], [ Repository('', 'RepoA', 'WRITE', Ref('', '', 'master', '')), Repository('', 'RepoB', 'WRITE', Ref('', '', 'master', '')), Repository('', 'RepoC', 'READ', Ref('', '', 'master', '')), Repository('', 'RepoD', 'WRITE', Ref('', '', 'master', '')), ], [ Repository('', 'RepoA', 'WRITE', Ref('', '', 'current_release', '')), Repository('', 'RepoB', 'WRITE', Ref('', '', 'current_release', '')) ]] def mock_merge_branch(repo, base, head, message): if repo.permission != 'WRITE': raise GitHubError('UNPROCESSABLE', 'Failed to merge: \"Already merged\"') return MergeResponse('', 'https://github.com/org/repo/commits/#hash', 'merged') expected_succeeded_repos = 2 expected_unprocessed_repos = 1 expected_curr_release_succeeded_repos = 2 client.get_repositories = Mock(side_effect=mock_repos) client.merge_branch = Mock(side_effect=mock_merge_branch) results = auto_merge('master', 'release', 'current_release', client) # assert the first merge from head to base (succeeded, unprocessed, failed) = results[0] self.assertEqual(len(succeeded), expected_succeeded_repos) self.assertEqual(len(unprocessed), expected_unprocessed_repos) self.assertEqual(len(failed), 0) # assert the second merge from base to current release (succeeded, unprocessed, failed) = results[1] self.assertEqual(len(succeeded), expected_curr_release_succeeded_repos) self.assertEqual(len(unprocessed), 0) self.assertEqual(len(failed), 0)
def test_should_raise_exception_when_no_matching_base_branch_is_found( self): client = GitHubClient('', '') mock_repos = [[ Repository('', 'RepoA', 'WRITE', Ref('', '', 'release', '')), ], []] client.get_repositories = Mock(side_effect=mock_repos) with self.assertRaises(Exception): auto_merge('master', 'release', '', client)
def test_should_fail_repos_with_invalid_permissions(self): client = GitHubClient('', '') mock_repos = [[ Repository('', 'RepoB', 'READ', Ref('', '', 'release', '')) ], [Repository('', 'RepoB', 'READ', Ref('', '', 'master', ''))]] expected_failed_repos = 1 client.get_repositories = Mock(side_effect=mock_repos) results = auto_merge('master', 'release', '', client) (succeeded, unprocessed, failed) = results[0] self.assertEqual(len(succeeded), 0) self.assertEqual(len(unprocessed), 0) self.assertEqual(len(failed), expected_failed_repos)
class TestGitHubClient(unittest.TestCase): def setUp(self): config = configparser.ConfigParser() config.read('config.ini') access_token = config['DEFAULT']['access_token'] organization = config['DEFAULT']['organization'] self.client = GitHubClient(access_token, organization) def test_can_fetch_repositories_with_branch(self): repos = self.client.get_repositories(rel_branch) repos_with_branch = [repo for repo in repos if repo.ref] self.assertTrue(len(repos_with_branch) >= 1) def test_can_merge_branch_with_base(self): test_repo = 'ProductFulfillment' repos = self.client.get_repositories(rel_branch) pf_rel_repo = [ repo for repo in repos if repo.ref and repo.name == test_repo ][0] master_repos = self.client.get_repositories(base_branch) pf_master_repo = [ repo for repo in master_repos if repo.ref and repo.name == test_repo ][0] expected_message = 'Merge completed by AutoMerge Integration Test' result = self.client.merge_branch(pf_rel_repo, base_branch, rel_branch, expected_message) self.assertEqual(result.commit_message, expected_message) # cleanup merge commit so the test can be run again self.client.update_ref(pf_master_repo, pf_master_repo.ref.oid, True)
def test_should_not_fail_when_branches_are_already_merged(self): client = GitHubClient('', '') mock_repos = [[ Repository('', 'RepoA', 'WRITE', Ref('', '', 'release', '')), Repository('', 'RepoB', 'WRITE', Ref('', '', 'release', '')), ], [ Repository('', 'RepoA', 'WRITE', Ref('', '', 'master', '')), Repository('', 'RepoB', 'WRITE', Ref('', '', 'master', '')), ]] count = 0 def mock_merge_branch(repo, base, head, message): nonlocal count if count > 0: raise GitHubError('UNPROCESSABLE', 'Failed to merge: \"Already merged\"') count = count + 1 return MergeResponse('', 'https://github.com/org/repo/commits/#hash', 'merged') expected_merged_repos = 2 expected_succeeded_repos = 1 expected_unprocessed_repos = 1 client.get_repositories = Mock(side_effect=mock_repos) client.merge_branch = Mock(side_effect=mock_merge_branch) results = auto_merge('master', 'release', '', client) (succeeded, unprocessed, failed) = results[0] self.assertEqual(client.merge_branch.call_count, expected_merged_repos) self.assertEqual(len(succeeded), expected_succeeded_repos) self.assertEqual(len(unprocessed), expected_unprocessed_repos) self.assertEqual(len(failed), 0)
def merge_branches(base: str, head: str, repos: [Repository], client: GitHubClient): succeeded = [] unprocessed = [] failed = [] for repo in repos: logging.info('Merging {}'.format(repo.name)) try: merge_result = client.merge_branch( repo, base, head, 'Merge of {} completed by AutoMerge utility'.format(head)) logging.info('Merge completed') succeeded.append((merge_result, repo)) except GitHubError as err: if 'already merged' in err.message.lower(): logging.warn('{}: Already Merged'.format(repo.name)) unprocessed.append((err.message, repo)) else: logging.exception('{}: Failed merge. {}'.format( repo.name, err.message)) failed.append((err.message, repo)) print('-' * 30) print('SUCCEEDED') print('-' * 30) for (result, repo) in succeeded: print("{}: {}".format(repo.name, result.commit_url)) print('-' * 30) print('-' * 30) print('UNPROCESSED') print('-' * 30) for (message, repo) in unprocessed: print('{}: {}'.format(repo.name, message)) print('-' * 30) print('-' * 30) print('FAILED') print('-' * 30) for (message, repo) in failed: print('{}: {}'.format(repo.name, message)) print('-' * 30) succeeded = [repo for (_, repo) in succeeded] unprocessed = [repo for (_, repo) in unprocessed] failed = [repo for (_, repo) in failed] return (succeeded, unprocessed, failed)
def auto_merge(base: str, head: str, curr_rel: str, client: GitHubClient): auto_merge_results = [] # get repositories with the head branch (i.e. all repos with a 'REL-2910' branch) head_repos = [repo for repo in client.get_repositories(head) if repo.ref] if not head_repos: raise Exception( "No repositories were found matching the head: {}".format(head)) logging.info('Found {} repositories matching head {}'.format( len(head_repos), head)) # get corresponding repositories with the base branch names = [repo.name for repo in head_repos] repos_for_merge = [ repo for repo in client.get_repositories(base) if repo.ref and repo.name in names ] if not repos_for_merge: raise Exception( "No repositories were found matching the base: {}".format(base)) logging.info('Found {} repositories matching base {}'.format( len(repos_for_merge), base)) logging.info('Preparing to merge the following repositories') for repo in repos_for_merge: logging.info("{}: {} ==>> {}".format(repo.name, head, base)) (succeeded, unprocessed, failed) = merge_branches(base, head, repos_for_merge, client) auto_merge_results.append((succeeded, unprocessed, failed)) logging.info('BASE MERGE COMPLETE') # succeeded and unprocessed branches are eligible to be merged into the current release branch eligible = [] eligible.extend(succeeded) eligible.extend(unprocessed) if not curr_rel or not eligible: return auto_merge_results logging.info('Beginning merge of base into current release branches...') # only merge repos that are eligible and have the current release branch names = [repo.name for repo in eligible] repos_for_merge = [ repo for repo in client.get_repositories(curr_rel) if repo.ref and repo.name in names ] if not repos_for_merge: raise Exception( "No eligible repositories matching the current release branch") auto_merge_results.append( merge_branches(curr_rel, base, repos_for_merge, client)) logging.info('CURRENT RELEASE MERGE COMPLETE') return auto_merge_results
def setUp(self): config = configparser.ConfigParser() config.read('config.ini') access_token = config['DEFAULT']['access_token'] organization = config['DEFAULT']['organization'] self.client = GitHubClient(access_token, organization)
help= "Optionally tell the script where to find the config file. By default it searches in the base directory" ) args = parser.parse_args() head_branch = args.head_branch base_branch = args.base_branch current_rel_branch = args.current_rel_branch if args.current_rel_branch else '' config = configparser.ConfigParser() if args.config_path: config.read(args.config_path) else: config.read('config.ini') access_token = config['DEFAULT']['access_token'] organization = config['DEFAULT']['organization'] if args.token: access_token = args.token if args.org: organization = args.org logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO) auto_merge(base_branch, head_branch, current_rel_branch, GitHubClient(access_token, organization))
def test_should_raise_exception_when_no_head_branch_is_found(self): client = GitHubClient('', '') client.get_repositories = Mock(return_value=[]) with self.assertRaises(Exception): auto_merge('master', 'rel', '', client)