def git_merge(base: str, head: str, no_ff: bool = False): """ Merge *head* into *base*. Args: base (str): The base branch. *head* will be merged into this branch. head (str): The branch that will be merged into *base*. no_ff (bool): If set to **True** it will force git to create merge commit. If set to **False** (default) it will do a fast-forward merge if possible. """ pretend = context.get('pretend', False) branch = git.current_branch(refresh=True) if branch.name != base and not pretend: git_checkout(base) args = [] if no_ff: args.append('--no-ff') log.info("Merging <33>{}<32> into <33>{}<32>", head, base) shell.run('git merge {args} {branch}'.format( args=' '.join(args), branch=head, )) if branch.name != base and not pretend: git_checkout(branch.name)
def deploy(self, promote=False, quiet=False): # type: (bool, bool, bool) -> None """ Deploy the code to AppEngine. Args: promote (bool): Migrate the traffic to the deployed version. quiet (bool): Pass ``--quiet`` flag to gcloud command """ args = [ '--promote' if promote else '--no-promote', '--version {}'.format(self.app_version), '--project {}'.format(self.app_id), ] if quiet: args += ['--quiet'] cmd = 'gcloud app deploy {args} {deployables}'.format( deployables=fs.wrap_paths(self.deployables), args=' '.join(args)) if context.get('pretend', False): log.info("Would deploy version <35>{ver}<32> to <35>{app}".format( ver=self.app_version, app=self.app_id)) shell.cprint('<90>{}', cmd) else: log.info("Deploying version <35>{ver}<32> to <35>{app}".format( ver=self.app_version, app=self.app_id, )) shell.run(cmd)
def delete_remote(): """ Delete the current branch on origin. This is an equivalent of ``git push origin :<branch>``. Easy way to quickly delete the remote branch without having to type in the branch name. """ branch = git.current_branch().name shell.run('git push -u origin {}'.format(branch))
def test_setting_capture_to_True_will_pipe_stdout_and_stderr(p_popen): shell.run('hello', capture=True) p_popen.assert_called_once_with( 'hello', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )
def git_pull(branch_name: str): """ Pull from remote branch. Args: branch_name (str): The remote branch to pull. """ log.info("Pulling latest changes on <33>{}", branch_name) shell.run('git pull origin {}'.format(branch_name))
def test_will_not_crash_if_communicated_returns_strings(p_popen): shell.run('hello', capture=True) p_popen.assert_called_once_with( 'hello', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )
def push(): """ Push the current branch to origin. This is an equivalent of ``git push -u origin <branch>``. Mainly useful for the first push as afterwards ``git push`` is just quicker. Free's you from having to manually type the current branch name in the first push. """ branch = git.current_branch().name shell.run('git push -u origin {}'.format(branch))
def git_branch_delete(branch_name: str): """ Delete the given branch. Args: branch_name (str): Name of the branch to delete. """ if branch_name not in git.protected_branches(): log.info("Deleting branch <33>{}", branch_name) shell.run('git branch -d {}'.format(branch_name))
def test_inherits_existing_env_when_env_is_given(p_popen): with patch('os.environ', {'fake1': 'env'}): shell.run('hello', env={'fake2': 'arg'}) p_popen.assert_called_once_with( 'hello', env={ 'fake1': 'env', 'fake2': 'arg' }, shell=True, )
def git_checkout(branch_name: str, create: bool = False): """ Checkout or create a given branch Args: branch_name (str): The name of the branch to checkout or create. create (bool): If set to **True** it will create the branch instead of checking it out. """ log.info("Checking out <33>{}".format(branch_name)) shell.run('git checkout {} {}'.format('-b' if create else '', branch_name))
def git_branch_rename(new_name: str): """ Rename the current branch Args: new_name (str): New name for the current branch. """ curr_name = git.current_branch(refresh=True).name if curr_name not in git.protected_branches(): log.info("Renaming branch from <33>{}<32> to <33>{}".format( curr_name, new_name)) shell.run('git branch -m {}'.format(new_name))
def upload(target: str): """ Upload the release to a pypi server. TODO: Make sure the git directory is clean before allowing a release. Args: target (str): pypi target as defined in ~/.pypirc """ log.info("Uploading to pypi server <33>{}".format(target)) with conf.within_proj_dir(): shell.run('python setup.py sdist register -r "{}"'.format(target)) shell.run('python setup.py sdist upload -r "{}"'.format(target))
def _get_todo_details(file_path: str, lines: LineRange) -> Tuple[str, str, int]: result = shell.run( f"git blame {file_path} -L {lines.start},{lines.end} -p", capture=True ) re_name = re.compile(r'^author (?P<name>.*)$') re_mail = re.compile(r'^author-mail <(?P<mail>[^\s]+)>$') re_time = re.compile(r'^author-time (?P<time>\d+)$') author_name = 'Not Committed Yet' author_email = 'not.committed.yet' author_time = int(datetime.now().timestamp()) for line in result.stdout.splitlines(): m = re_name.match(line) if m: author_name = m.group('name') else: m = re_mail.match(line) if m: author_email = m.group('mail') else: m = re_time.match(line) if m: author_time = int(m.group('time')) return author_name, author_email, author_time
def start(component: str, exact: str): """ Create a new release branch. Args: component (str): Version component to bump when creating the release. Can be *major*, *minor* or *patch*. exact (str): The exact version to set for the release. Overrides the component argument. This allows to re-release a version if something went wrong with the release upload. """ version_files = versioning.get_version_files() develop = conf.get('git.devel_branch', 'develop') common.assert_on_branch(develop) with conf.within_proj_dir(): out = shell.run('git status --porcelain', capture=True).stdout lines = out.split(os.linesep) has_changes = any(not line.startswith('??') for line in lines if line.strip()) if has_changes: log.info("Cannot release: there are uncommitted changes") exit(1) old_ver, new_ver = versioning.bump(component, exact) log.info("Bumping package version") log.info(" old version: <35>{}".format(old_ver)) log.info(" new version: <35>{}".format(new_ver)) with conf.within_proj_dir(): branch = 'release/' + new_ver hooks.register.call('pre-release-start', branch, old_ver, new_ver) common.git_checkout(branch, create=True) log.info("Creating commit for the release") shell.run('git add {files} && git commit -m "{msg}"'.format( files=' '.join(f'"{v.path}"' for v in version_files), msg="Releasing v{}".format(new_ver))) hooks.register.call('post-release-start', branch, old_ver, new_ver)
def setup_ci(): # type: () -> None """ Setup AppEngine SDK on CircleCI """ gcloud_path = shell.run('which gcloud', capture=True).stdout.strip() sdk_path = normpath(join(gcloud_path, '../../platform/google_appengine')) gcloud_cmd = gcloud_path + ' --quiet' if not exists(sdk_path): log.info("Installing AppEngine SDK") shell.run( 'sudo {} components install app-engine-python'.format(gcloud_cmd)) else: # Only initialise once. To reinitialise, just build without cache. log.info("AppEngine SDK already initialised") log.info("Using service account authentication") shell.run('{} auth activate-service-account --key-file {}'.format( gcloud_cmd, conf.proj_path('ops/client_secret.json')))
def devserver(port, admin_port, clear): # type: (int, int, bool) -> None """ Run devserver. Args: port (int): Port on which the app will be served. admin_port (int): Port on which the admin interface is served. clear (bool): If set to **True**, clear the datastore on startup. """ admin_port = admin_port or (port + 1) args = ['--port={}'.format(port), '--admin_port={}'.format(admin_port)] if clear: args += ['--clear_datastore=yes'] with conf.within_proj_dir(): shell.run('dev_appserver.py . {args}'.format(args=' '.join(args)))
def check(paths, include, exclude, only_staged, untracked): # type: (str, Sequence[str], Sequence[str], bool, bool) -> None """ Run mypy and pylint against the current directory.""" files = types.FilesCollection( paths=paths, include=['*.py'] + list(include), # We only want to lint python files. exclude=exclude, only_staged=only_staged, untracked=untracked, ) paths = fs.collect_files(files) wrapped_paths = fs.wrap_paths(paths) log.info("Paths: <33>{}", paths) log.info("Wrapped paths: <33>{}", wrapped_paths) log.info("Running <35>mypy") shell.run('mypy --ignore-missing-imports {}'.format(wrapped_paths)) log.info("Running <35>pylint") shell.run('pylint {}'.format(wrapped_paths))
def _get_commits_in_range(start_rev: Optional[str], end_rev: Optional[str]) -> List[git.CommitDetails]: if not start_rev: versions = [x for x in git.tags() if versioning.is_valid(x[1:])] start_rev = versions[-1] if versions else '' if not end_rev: end_rev = 'HEAD' cmd = 'git log --format=%H' if start_rev and end_rev: cmd += f" {start_rev}..{end_rev}" elif end_rev: cmd += f" {end_rev}" hashes = shell.run(cmd, capture=True).stdout.strip().splitlines() return [git.CommitDetails.get(h) for h in hashes]
def check_todos( untracked: bool, file_path: Optional[str], authors: List[str], verify_complete: bool, ) -> None: repo_path = Path( shell.run("git rev-parse --show-toplevel", capture=True).stdout.strip()) if file_path: if file_path != ':commit': input_files = frozenset([file_path]) else: input_files = frozenset([ repo_path / fpath for fpath in (git.staged() + git.unstaged()) ]) else: input_files = frozenset( repo_path / fpath for fpath in (parser.get_changed_files(base_branch='master') + git.staged() + git.unstaged())) if untracked: input_files |= frozenset([(repo_path / fpath) for fpath in git.untracked()]) todos = parser.extract_from_files(list(input_files)) filtered_todos = [ t for t in todos if not authors or any(a.lower() in t.author.lower() for a in authors) ] _render_todos(filtered_todos) if verify_complete and len(filtered_todos) > 0: sys.exit(127)
def test_will_not_exit_if_exit_on_error_is_set_to_False(): assert shell.run('hello', exit_on_error=False).return_code == 1
def _find_appengine_sdk(): # type: () -> str gcloud_path = shell.run('which gcloud', capture=True).stdout.strip() return normpath(join(gcloud_path, '../../platform/google_appengine'))
def git_prune(): """ Prune dead branches. """ log.info("Pruning") shell.run('git fetch --prune origin')
def test_line_buffered_by_default(p_popen): shell.run('hello') p_popen.assert_called_once_with('hello', shell=True)
def get_changed_files(base_branch: str = 'master') -> List[str]: result = shell.run( f"git diff --name-only HEAD..$(git merge-base HEAD {base_branch})", capture=True, ) return result.stdout.splitlines()
def test_sets_result_success_status_from_retcode(): result = shell.run('hello', capture=True) assert result.succeeded is True assert result.failed is False
def test_if_not_given_set_exit_on_error_to_opposite_of_capture(): with pytest.raises(SystemExit): shell.run('hello') assert shell.run('hello', capture=True).return_code == 1
def test_will_exit_if_exit_on_error_is_set_to_True(): with pytest.raises(SystemExit): shell.run('hello', exit_on_error=True)