def test_empty_octopus_merge(self): mock_repo = MagicMock(spec=Repo) api = LocalGitAPI(mock_repo) sha = api.octopus_merge('public/release-candidate', []) mock_repo.git.merge.assert_not_called() self.assertEqual(sha, mock_repo.head.commit.hexsha)
def test_octopus_merge(self): mock_repo = MagicMock(spec=Repo) api = LocalGitAPI(mock_repo) sha = api.octopus_merge('public/release-candidate', ['12345abcdef', 'deadbeef']) mock_repo.git.merge.assert_called_once_with('12345abcdef', 'deadbeef') self.assertEqual(sha, mock_repo.head.commit.hexsha)
def test_clone_failure(self, mock_repo, mock_rmtree): """ Tests failing to merge a branch. """ mock_repo.clone_from.side_effect = GitCommandError('cmd', 1) with self.assertRaises(GitCommandError): LocalGitAPI.clone('[email protected]:edx/tubular.git', 'bar') self.assertEqual(mock_rmtree.call_count, 0)
def test_cleanup(self, failing_mock, mock_repo, mock_rmtree): """ Tests failing to merge a branch. """ mock_repo.configure_mock(autospec=True, **{ '{}.side_effect'.format(failing_mock): GitCommandError('cmd', 1) }) with self.assertRaises(GitCommandError): LocalGitAPI.clone('[email protected]:edx/tubular.git', 'bar').merge_branch('foo', 'bar') mock_rmtree.assert_called_once_with('tubular')
def merge_branch(org, repo, source_branch, target_branch, fast_forward_only, output_file, reference_repo): u""" Merges the source branch into the target branch without creating a pull request for the merge. Clones the repo in order to perform the proper git commands locally. Args: org (str): repo (str): source_branch (str): target_branch (str): fast_forward_only (bool): If True, the branch merge will be performed as a fast-forward merge. If the merge cannot be performed as a fast-forward merge, the merge will fail. """ github_url = u'[email protected]:{}/{}.git'.format(org, repo) with LocalGitAPI.clone(github_url, target_branch, reference_repo).cleanup() as local_repo: merge_sha = local_repo.merge_branch(source_branch, target_branch, fast_forward_only) local_repo.push_branch(target_branch) with io.open(output_file, u'w') as stream: yaml.safe_dump( { u'org_name': org, u'repo_name': repo, u'source_branch_name': source_branch, u'target_branch_name': target_branch, u'fast_forward_only': fast_forward_only, u'sha': merge_sha }, stream, default_flow_style=False, explicit_start=True)
def push_public_to_private(private_org, private_repo, private_target_branch, public_org, public_repo, public_source_branch, output_file, reference_repo): u""" Push the results of a merge of private changes to public back over to the private repo to keep the repo branches in-sync. """ public_github_url = u'[email protected]:{}/{}.git'.format( public_org, public_repo) private_github_url = u'[email protected]:{}/{}.git'.format( private_org, private_repo) output_yaml = { u'private_github_url': private_github_url, u'private_target_branch_name': private_target_branch, u'public_github_url': public_github_url, u'public_target_branch_name': public_source_branch } # Clone the public repo, checking out the proper public branch. LOG.info('Cloning public repo %s with branch %s.', public_github_url, public_source_branch) with LocalGitAPI.clone(public_github_url, public_source_branch, reference_repo).cleanup() as local_repo: # Add the private repo as a remote for the public git working tree. local_repo.add_remote('private', private_github_url) try: # Push the public branch back to the private branch - without forcing. is_pushed = local_repo.push_branch(public_source_branch, 'private', private_target_branch, force=False, log_info=True) if is_pushed: output_yaml.update({u'branch_pushed': is_pushed}) LOG.info('public branch successfully pushed to repo.') else: LOG.warning( "Failed to push public branch %s to private branch %s", public_source_branch, private_target_branch) output_yaml.update({u'branch_pushed': False}) except Exception as exc: # pylint: disable=broad-except # On any failure besides auth, simply log and ignore. # The problem will work itself out in the next private->public cycle. LOG.warning( "Failed to push public branch %s to private branch %s without fast-forward: %s", public_source_branch, private_target_branch, exc) output_yaml.update({u'branch_pushed': False}) if output_file: with io.open(output_file, u'w') as stream: yaml.safe_dump(output_yaml, stream, default_flow_style=False, explicit_start=True) else: yaml.safe_dump( output_yaml, sys.stdout, )
def create_version_file(self): """ Creates a version.json file to be deployed with frontend """ # Add version.json file to build. version = { 'repo': self.app_name, 'commit': LocalGitAPI(Repo(self.app_name)).get_head_sha(), 'created': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), } try: with io.open(self.version_file, 'w') as output_file: json.dump(version, output_file) except IOError: self.FAIL(1, 'Could not write to version file for app {}.'.format(self.app_name))
def test_merge_branch_success(self, mock_repo, mock_rmtree): """ Tests merging a branch successfully. """ with LocalGitAPI.clone('[email protected]:edx/tubular.git', 'bar').cleanup() as repo: merge_sha = repo.merge_branch('foo', 'bar') mock_repo.clone_from.assert_called_once_with( '[email protected]:edx/tubular.git', to_path='tubular', branch='bar', ) git_wrapper = mock_repo.clone_from.return_value.git git_wrapper.merge.assert_called_once_with('foo', ff_only=True) git_wrapper.rev_parse.assert_called_once_with('HEAD') self.assertEqual(git_wrapper.rev_parse.return_value, merge_sha) mock_rmtree.assert_called_once_with( mock_repo.clone_from.return_value.working_dir)
def create_private_to_public_pr(private_org, private_repo, private_source_branch, public_org, public_repo, public_target_branch, token, output_file, reference_repo): u""" Creates a PR to merge the private source branch into the public target branch. Clones the repo in order to perform the proper git commands locally. """ public_github_url = u'[email protected]:{}/{}.git'.format( public_org, public_repo) private_github_url = u'[email protected]:{}/{}.git'.format( private_org, private_repo) output_yaml = { u'private_github_url': private_github_url, u'private_source_branch_name': private_source_branch, u'public_github_url': public_github_url, u'public_target_branch_name': public_target_branch } LOG.info('Cloning private repo %s with branch %s.', private_github_url, private_source_branch) with LocalGitAPI.clone(private_github_url, private_source_branch, reference_repo).cleanup() as local_repo: # Add the public repo as a remote for the private git working tree. local_repo.add_remote('public', public_github_url) # Create a new public branch with unique name. new_branch_name = 'private_to_public_{}'.format( local_repo.get_head_sha()[:7]) # Push the private branch into the public repo. LOG.info('Pushing private branch %s to public repo %s as branch %s.', private_source_branch, public_github_url, new_branch_name) local_repo.push_branch(private_source_branch, 'public', new_branch_name) github_api = GitHubAPI(public_org, public_repo, token) # Create a PR from new public branch to public master. try: pull_request = github_api.create_pull_request( title='Mergeback PR from private to public.', body= 'Merge private changes back to the public repo post-PR-merge.\n\n' 'Please review and tag appropriate parties.', head=new_branch_name, base=public_target_branch) except PullRequestCreationError as exc: LOG.info( "No pull request created for merging %s into %s in '%s' repo - nothing to merge: %s", new_branch_name, public_target_branch, public_github_url, exc) output_yaml.update({ 'pr_created': False, }) # Cleanup - delete the pushed branch. github_api.delete_branch(new_branch_name) else: LOG.info('Created PR #%s for repo %s: %s', pull_request.number, public_github_url, pull_request.html_url) output_yaml.update({ 'pr_created': True, 'pr_id': pull_request.id, 'pr_number': pull_request.number, 'pr_url': pull_request.url, 'pr_repo_url': github_api.github_repo.url, 'pr_head': pull_request.head.sha, 'pr_base': pull_request.base.sha, 'pr_html_url': pull_request.html_url, 'pr_diff_url': pull_request.diff_url, 'pr_mergable': pull_request.mergeable, 'pr_state': pull_request.state, 'pr_mergable_state': pull_request.mergeable_state, }) if output_file: with io.open(output_file, u'w') as stream: yaml.safe_dump(output_yaml, stream, default_flow_style=False, explicit_start=True) else: yaml.safe_dump( output_yaml, sys.stdout, )