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)
Esempio n. 5
0
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)
Esempio n. 7
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)
Esempio n. 8
0
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
Esempio n. 9
0
 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)
Esempio n. 10
0
    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))
Esempio n. 11
0
    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)