def finish(fast_forward: bool): """ Merge current feature branch into develop. """ pretend = context.get('pretend', False) if not pretend and (git.staged() or git.unstaged()): log.err("You have uncommitted changes in your repo!\n" "You need to stash them before you merge the hotfix branch") sys.exit(1) branch = git.current_branch(refresh=True) base = common.get_base_branch() prompt = "<32>Merge <33>{}<32> into <33>{}<0>?".format(branch.name, base) if not click.confirm(shell.fmt(prompt)): log.info("Cancelled") return common.assert_branch_type('task') hooks.register.call('pre-task-finish', branch, base) # Merge task into it's base feature branch common.git_checkout(base) common.git_pull(base) common.git_merge(base, branch.name, no_ff=not fast_forward) # Cleanup common.git_branch_delete(branch.name) common.git_prune() common.git_checkout(base) hooks.register.call('post-task-finish', branch, base)
def finish(fast_forward: bool): """ Merge current feature branch into develop. """ pretend = context.get('pretend', False) if not pretend and (git.staged() or git.unstaged()): log.err("You have uncommitted changes in your repo!\n" "You need to stash them before you merge the feature branch") sys.exit(1) develop = conf.get('git.devel_branch', 'develop') branch = git.current_branch(refresh=True) common.assert_branch_type('feature') hooks.register.call('pre-feature-finish', branch) # Merge feature into develop common.git_checkout(develop) common.git_pull(develop) common.git_merge(develop, branch.name, no_ff=not fast_forward) # Cleanup common.git_branch_delete(branch.name) common.git_prune() common.git_checkout(develop) hooks.register.call('post-feature-finish', branch)
def deploy(app_id, version, promote, quiet): # type: (str, str, bool, bool) -> None """ Deploy the app to AppEngine. Args: app_id (str): AppEngine App ID. Overrides config value app_id if given. version (str): AppEngine project version. Overrides config values if given. promote (bool): If set to **True** promote the current remote app version to the one that's being deployed. quiet (bool): If set to **True** this will pass the ``--quiet`` flag to gcloud command. """ gae_app = GaeApp.for_branch(git.current_branch().name) if gae_app is None and None in (app_id, version): msg = ("Can't find an AppEngine app setup for branch <35>{}<32> and" "--project and --version were not given.") log.err(msg, git.current_branch().name) sys.exit(1) if version is not None: gae_app.version = version if app_id is not None: gae_app.app_id = app_id gae_app.deploy(promote, quiet)
def add_hooks(pre_commit: str, pre_push: str): """ Add git hooks for commit and push to run linting and tests. """ # Detect virtualenv the hooks should use # Detect virtualenv virtual_env = conf.get_env('VIRTUAL_ENV') if virtual_env is None: log.err("You are not inside a virtualenv") confirm_msg = ( "Are you sure you want to use global python installation " "to run your git hooks? [y/N] " ) click.prompt(confirm_msg, default='') if not click.confirm(confirm_msg): log.info("Cancelling") return load_venv = '' else: load_venv = 'source "{}/bin/activate"'.format(virtual_env) commit_hook = conf.proj_path('.git/hooks/pre-commit') push_hook = conf.proj_path('.git/hooks/pre-push') # Write pre-commit hook log.info("Adding pre-commit hook <33>{}", commit_hook) fs.write_file(commit_hook, util.remove_indent(''' #!/bin/bash PATH="/opt/local/libexec/gnubin:$PATH" {load_venv} {command} '''.format(load_venv=load_venv, command=pre_commit))) # Write pre-push hook log.info("Adding pre-push hook: <33>{}", push_hook) fs.write_file(push_hook, util.remove_indent(''' #!/bin/bash PATH="/opt/local/libexec/gnubin:$PATH" {load_venv} peltak test --allow-empty {command} '''.format(load_venv=load_venv, command=pre_push))) log.info("Making hooks executable") if not context.get('pretend', False): os.chmod(conf.proj_path('.git/hooks/pre-commit'), 0o755) os.chmod(conf.proj_path('.git/hooks/pre-push'), 0o755)
def assert_on_branch(branch_name: str): """ Print error and exit if *branch_name* is not the current branch. Args: branch_name (str): The supposed name of the current branch. """ branch = git.current_branch(refresh=True) if branch.name != branch_name: if context.get('pretend', False): log.info("Would assert that you're on a <33>{}<32> branch", branch_name) else: log.err("You're not on a <33>{}<31> branch!", branch_name) sys.exit(1)
def start(name: str): """ Start working on a new feature by branching off develop. This will create a new branch off develop called feature/<name>. Args: name (str): The name of the new feature. """ branch = git.current_branch(refresh=True) task_branch = 'task/' + common.to_branch_name(name) if branch.type not in ('feature', 'hotfix'): log.err("Task branches can only branch off <33>feature<32> or " "<33>hotfix<32> branches") sys.exit(1) hooks.register.call('pre-task-start', name) common.git_checkout(task_branch, create=True) hooks.register.call('post-task-start', name)
def assert_branch_type(branch_type: str): """ Print error and exit if the current branch is not of a given type. Args: branch_type (str): The branch type. This assumes the branch is in the '<type>/<title>` format. """ branch = git.current_branch(refresh=True) if branch.type != branch_type: if context.get('pretend', False): log.info("Would assert that you're on a <33>{}/*<32> branch", branch_type) else: log.err("Not on a <33>{}<31> branch!", branch_type) fmt = ("The branch must follow <33>{required_type}/<name><31>" "format and your branch is called <33>{name}<31>.") log.err(fmt, required_type=branch_type, name=branch.name) sys.exit(1)
def finish(fast_forward: bool): """ Merge current release into develop and master and tag it. """ pretend = context.get('pretend', False) if not pretend and (git.staged() or git.unstaged()): log.err("You have uncommitted changes in your repo!\n" "You need to stash them before you merge the release branch") sys.exit(1) develop = conf.get('git.devel_branch', 'develop') master = conf.get('git.master_branch', 'master') branch = git.current_branch(refresh=True) common.assert_branch_type('release') hooks.register.call('pre-release-finish', branch) # Merge release into master common.git_checkout(develop) common.git_pull(develop) common.git_merge(develop, branch.name, no_ff=not fast_forward) # Merge release into develop common.git_checkout(master) common.git_pull(master) common.git_merge(master, branch.name, no_ff=not fast_forward) # Tag the release commit with version number tag(changelog()) # Cleanup common.git_branch_delete(branch.name) common.git_prune() common.git_checkout(master) hooks.register.call('post-release-finish', branch)
def choose_branch(exclude: Optional[Iterable[str]] = None) -> str: """ Show the user a menu to pick a branch from the existing ones. Args: exclude (list[str]): List of branch names to exclude from the menu. By default it will exclude master and develop branches. To show all branches pass an empty array here. Returns: str: The name of the branch chosen by the user. If the user inputs an invalid choice, he will be asked again (and again) until he picks a a valid branch. """ if exclude is None: master = conf.get('git.master_branch', 'master') develop = conf.get('git.devel_branch', 'develop') exclude = {master, develop} branches = list(set(git.branches()) - set(exclude)) # Print the menu for i, branch_name in enumerate(branches): shell.cprint('<90>[{}] <33>{}'.format(i + 1, branch_name)) # Get a valid choice from the user choice = 0 while choice < 1 or choice > len(branches): prompt = "Pick a base branch from the above [1-{}]".format( len(branches)) choice = click.prompt(prompt, value_proc=int) # type: ignore if not (1 <= choice <= len(branches)): fmt = "Invalid choice {}, you must pick a number between {} and {}" log.err(fmt.format(choice, 1, len(branches))) return branches[choice - 1]
def gen_pypirc(username: Optional[str] = None, password: Optional[str] = None): """ Generate ~/.pypirc with the given credentials. Useful for CI builds. Can also get credentials through env variables ``PYPI_USER`` and ``PYPI_PASS``. Args: username (str): pypi username. If not given it will try to take it from the `` PYPI_USER`` env variable. password (str): pypi password. If not given it will try to take it from the `` PYPI_PASS`` env variable. """ path = join(conf.get_env('HOME'), '.pypirc') username = username or conf.get_env('PYPI_USER', None) password = password or conf.get_env('PYPI_PASS', None) if username is None or password is None: log.err("You must provide $PYPI_USER and $PYPI_PASS") sys.exit(1) log.info("Generating <94>{}".format(path)) fs.write_file( path, util.remove_indent(''' [distutils] index-servers = pypi [pypi] repository: https://upload.pypi.org/legacy/ username: {username} password: {password} '''.format(username=username, password=password)))
def test_decorates_the_message(p_cprint): log.err('hello') p_cprint.assert_called_once_with('-- <31>hello<0>')
def test_formatting_works(p_cprint, msg, args, kw, expected): log.err(msg, *args, **kw) p_cprint.assert_called_once_with(expected)