Esempio n. 1
0
    def for_branch(cls, branch_name: str) -> Optional['GaeApp']:
        """ Return app configuration for the given branch.

        This will look for the configuration in the ``appengine.projects``
        config variable.

        Args:
            branch_name (str):
                The name of the branch we want the configuration for.

        Returns:
            Optional[GaeApp]: The `GaeApp` instance with the configuration for
                the project.
            None: If no project configuration can be found.
        """
        for proj in conf.get('appengine.projects', []):
            if fnmatch(branch_name, proj['branch']):
                proj = dict(proj)
                proj.pop('branch')
                proj['deployables'] = list(
                    frozenset(
                        itertools.chain(conf.get('appengine.deployables', []),
                                        proj.get('deployables', []))))
                return cls(**proj)

        return None
Esempio n. 2
0
def merged():
    """ Cleanup the release branch after it was remotely merged to master. """
    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-merged', branch)

    # Pull master with the merged release
    common.git_checkout(master)
    common.git_pull(master)

    # Merge to develop
    common.git_checkout(develop)
    common.git_pull(develop)
    common.git_merge(develop, branch.name)

    # Cleanup
    common.git_branch_delete(branch.name)
    common.git_prune()

    common.git_checkout(develop)
    hooks.register.call('post-release-merged', branch)
Esempio n. 3
0
File: cli.py Progetto: novopl/peltak
def _manage_cmd(cmd, settings=None):
    # type: () -> None
    """ Run django ./manage.py command manually.

    This function eliminates the need for having ``manage.py`` (reduces file
    clutter).
    """
    import sys
    from os import environ
    from peltak.core import conf
    from peltak.core import context
    from peltak.core import log

    sys.path.insert(0, conf.get('src_dir'))

    settings = settings or conf.get('django.settings', None)
    environ.setdefault("DJANGO_SETTINGS_MODULE", settings)

    args = sys.argv[0:-1] + cmd

    if context.get('pretend', False):
        log.info("Would run the following manage command:\n<90>{}", args)
    else:
        from django.core.management import execute_from_command_line
        execute_from_command_line(args)
Esempio n. 4
0
def get_version_files() -> List[VersionFile]:
    version_files = conf.get('version.files', [])

    if not version_files:
        # TODO: 'version.file' is deprecated, use 'version.files' instead.
        single = conf.get('version.file', None)
        version_files = [single] if single else []

    return [load_version_file(p) for p in version_files]
Esempio n. 5
0
def clean(exclude: List[str]):
    """ Remove all unnecessary files.

    Args:
        exclude (list[str]):
            A list of path patterns to exclude from deletion.
    """
    pretend = context.get('pretend', False)
    exclude = list(exclude) + conf.get('clean.exclude', [])
    clean_patterns = conf.get('clean.patterns', [
        '*__pycache__*',
        '*.py[cod]',
        '*.swp',
        "*.mypy_cache",
        "*.pytest_cache",
        "*.build",
    ])

    if context.get('verbose'):
        log.info('Clean patterns:')
        for pattern in clean_patterns:
            log.info(f'  <90>{pattern}')

        log.info('Exclude:')
        for pattern in exclude:
            log.info(f'  <90>{pattern}')

    num_files = 0
    with util.timed_block() as t:
        files = fs.filtered_walk(conf.proj_path(), clean_patterns, exclude)
        log.info('')
        log.info('Deleting:')
        for path in files:
            try:
                num_files += 1

                if not isdir(path):
                    log.info('  <91>[file] <90>{}', path)
                    if not pretend:
                        os.remove(path)
                else:
                    log.info('  <91>[dir]  <90>{}', path)
                    if not pretend:
                        rmtree(path)

            except OSError:
                log.info("<33>Failed to remove <90>{}", path)

    if pretend:
        msg = "Would delete <33>{}<32> files. Took <33>{}<32>s"
    else:
        msg = "Deleted <33>{}<32> files in <33>{}<32>s"

    log.info(msg.format(num_files, t.elapsed_s))
Esempio n. 6
0
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)
Esempio n. 7
0
def post_conf_load():
    """ After the config was loaded, register all scripts as click commands. """
    scripts = conf.get('scripts', {})

    for name, script_conf in scripts.items():
        script = Script.from_config(name, script_conf)
        script.register(root_cli if script.root_cli else run_cli)
Esempio n. 8
0
def update():
    """ Update the feature with updates committed to develop.

    This will merge current develop into the current branch.
    """
    branch = git.current_branch(refresh=True)
    develop = conf.get('git.devel_branch', 'develop')

    common.assert_branch_type('feature')
    common.git_checkout(develop)
    common.git_pull(develop)
    common.git_checkout(branch.name)
    common.git_merge(branch.name, develop)
Esempio n. 9
0
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)
Esempio n. 10
0
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]
Esempio n. 11
0
def _get_all_changelog_items(start_rev: Optional[str],
                             end_rev: Optional[str]) -> ChangelogItems:
    commits = _get_commits_in_range(start_rev, end_rev)
    tags = [
        ChangelogTag(**x) for x in conf.get("changelog.tags", DEFAULT_TAGS)
    ]
    results: ChangelogItems = OrderedDict((tag.header, []) for tag in tags)

    for commit in commits:
        full_message = f"{commit.title}\n\n{commit.desc}"
        commit_items = extract_changelog_items(full_message, tags)
        for header, items in commit_items.items():
            results[header] += items

    return results
Esempio n. 12
0
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)
Esempio n. 13
0
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.
    """
    feature_name = 'feature/' + common.to_branch_name(name)
    develop = conf.get('git.devel_branch', 'develop')

    common.assert_on_branch(develop)

    hooks.register.call('pre-feature-start', name)
    common.git_checkout(feature_name, create=True)
    hooks.register.call('post-feature-start', name)
Esempio n. 14
0
def merged():
    """ Cleanup a remotely merged branch. """
    develop = conf.get('git.devel_branch', 'develop')
    branch = git.current_branch(refresh=True)

    common.assert_branch_type('feature')

    hooks.register.call('pre-feature-merged', branch)

    # Pull develop with the merged feature
    common.git_checkout(develop)
    common.git_pull(develop)

    # Cleanup
    common.git_branch_delete(branch.name)
    common.git_prune()

    common.git_checkout(develop)

    hooks.register.call('post-feature-merged', branch)
Esempio n. 15
0
def extract_changelog_items(text: str,
                            tags: List[ChangelogTag]) -> Dict[str, List[str]]:
    """ Extract all tagged items from text.

    Args:
        text (str):
            Text to extract the tagged items from. Each tagged item is a
            paragraph that starts with a tag. It can also be a text list item.

    Returns:
        tuple[list[str], list[str], list[str]]:
            A tuple of ``(features, changes, fixes)`` extracted from the given
            text.

    The tagged items are usually features/changes/fixes but it can be configured
    through `pelconf.yaml`.
    """
    tag_format = conf.get("changelog.tag_format", DEFAULT_TAG_FORMAT)
    continuation_tag = conf.get("changelog.continuation_tag",
                                DEFAULT_CONTINUATION_TAG)
    patterns = {
        tag.header: tag_re(tag_format.format(tag=tag.tag))
        for tag in tags
    }
    more_pttrn = tag_re(tag_format.format(tag=continuation_tag))
    items: ChangelogItems = {tag.header: [] for tag in tags}
    curr_tag = None
    curr_text = ''
    last_tag = None

    for line in text.splitlines():
        if not line.strip():
            if curr_tag is not None:
                items[curr_tag].append(curr_text)
                curr_text = ''
            last_tag = curr_tag
            curr_tag = None

        more_match = more_pttrn.match(line)

        if more_match and last_tag:
            # If it's a continuation tag, then just add it's text to the last
            # used tag. This only works if there was a previous tag.
            curr_tag = last_tag
            curr_text = items[last_tag][-1]
            items[last_tag] = items[last_tag][:-1]
            line = more_match.group('text')
        else:
            for tag in tags:
                m = patterns[tag.header].match(line)
                if m:
                    if curr_tag is not None:
                        # If we're already in a tag definition and we encountered
                        # a beginning of a new tag, just finish by adding new
                        # item to the current tag item list.
                        items[curr_tag].append(curr_text)
                        curr_text = ''
                    curr_tag = tag.header
                    line = m.group('text')
                    break

        if curr_tag is not None:
            if more_match:
                curr_text = '{}\n{}'.format(curr_text, line.strip()).strip()
            else:
                curr_text = '{} {}'.format(curr_text.strip(),
                                           line.strip()).strip()

    if curr_tag is not None:
        items[curr_tag].append(curr_text)

    return items