class ConfigurationVersionError(Exception):
class PunchConfig(object):
    def __init__(self, config_filepath): class FileUpdater(object):
    def __init__(self, file_configuration, replacer):
    def get_summary(self, current_version, new_version):
    def update(self, current_version, new_version): class MissingSerializer(Exception):
class Replacer(object):
    def __init__(self, serializers, main_serialize_name='0'):
    def update(self, serializers):
    def run_serializer(self, serializer_name,
                       current_version_dict, new_version_dict):
    def run_all_serializers(self, current_version_dict, new_version_dict):
    def replace(self, text, current_version_dict, new_version_dict): class Version():
    def __init__(self):
    def keys(self):
    def values(self):
    def __eq__(self, other):
    def add_part(self, part):
    def create_part(self, name, value,
                    cls=vpart.IntegerVersionPart, *args, **kwds):
    def add_part_from_dict(self, dic):
    def get_part(self, name):
    def _reset_following_parts(self, name):
    def inc(self, name):
    def set(self, adict):
    def set_and_reset(self, name, value):
    def copy(self):
    def as_dict(self):
    def as_list(self):
    def to_file(self, version_filepath):
    def from_file(cls, version_filepath, version_description):
    def _get_version_part(cls, version_module,
                          version_part, version_part_name): class ActionRegister(object):
    def register(cls, aclass, class_name):
    def get(cls, action_name): def import_file(filepath):
def optstr2dict(optstr, convert_boolean=False):
    def process_string(tup, convert_boolean=False): class VCSConfiguration(object):

    def __init__(self, name, options, global_variables, special_variables,
                 commit_message=None, finish_release=True,
                 include_files=None, include_all_files=False):
        self.name = name

        if commit_message is None:
            commit_message = \
                "Version updated {{ current_version }} -> {{ new_version }}"

        commit_message_template = Template(commit_message)

        template_variables = {}
        template_variables.update(global_variables)
        template_variables.update(special_variables)

        self.commit_message = commit_message_template.render(
            **template_variables)
        self.finish_release = finish_release

        self.options = {}
        for key, value in options.items():
            if isinstance(value, abc.Sequence):
                value_template = Template(value)
                self.options[key] = value_template.render(**template_variables)
            else:
                self.options[key] = value

        self.options.update(special_variables)

        self.include_files = include_files or []
        self.include_all_files = include_all_files

    @classmethod
    def from_dict(cls, vcs_configuration_dict,
                  global_variables, special_variables):
        return VCSConfiguration(
            vcs_configuration_dict['name'],
            vcs_configuration_dict.get('options', {}),
            global_variables,
            special_variables,
            vcs_configuration_dict.get(
                'commit_message', None),
            vcs_configuration_dict.get(
                'finish_release', True
            ),
            vcs_configuration_dict.get(
                'include_files', None
            ),
            vcs_configuration_dict.get(
                'include_all_files', False
            )
        ) class RepositorySystemError(RepositoryError):
    pass class NoVCSRepo(object):

    def __init__(self, working_path, config_obj, files_to_commit=None):
        self.working_path = working_path

    def get_current_branch(self):
        return ''

    def get_tags(self):
        return ''

    def tag(self, *args):
        pass

    def pre_start_release(self):
        pass

    def start_release(self):
        pass

    def finish_release(self):
        pass

    def post_finish_release(self):
        pass

    def get_info(self):
        return [] class GitFlowRepo(gr.GitRepo):

    def __init__(self, working_path, config_obj, files_to_commit=None):
        if six.PY2:
            super(GitFlowRepo, self).__init__(
                working_path, config_obj, files_to_commit)
        else:
            super().__init__(working_path, config_obj, files_to_commit)

        self.release_branch = "release/{}".format(
            self.config_obj.options['new_version']
        )

    def _set_command(self):
        self.commands = ['git', 'flow']
        self.command = 'git'

    def _check_system(self):
        # git flow -h returns 1 so the call fails

        p = subprocess.Popen(
            self.commands,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )

        stdout, stderr = p.communicate()

        if "git flow <subcommand>" not in stdout.decode('utf8'):
            raise RepositorySystemError("Cannot run {}".format(self.commands))

        if not os.path.exists(os.path.join(self.working_path, '.git')):
            raise RepositorySystemError(
                "The current directory {} is not a Git repository".format(
                    self.working_path))

    def pre_start_release(self):
        output = self._run([self.command, "status"])
        if "Changes to be committed:" in output:
            raise RepositoryStatusError(
                ("Cannot start release while repository "
                 "contains uncommitted changes")
            )

        self._run([self.command, "checkout", "develop"])

        branch = self.get_current_branch()

        if branch != "develop":
            raise RepositoryStatusError(
                "Current branch shall be develop but is {}".format(branch))

    def start_release(self):
        self._run(
            self.commands + [
                "release",
                "start",
                self.config_obj.options['new_version']
            ])

    def finish_release(self):
        branch = self.get_current_branch()

        command = [self.command, "add"]

        if self.config_obj.include_all_files:
            command.append(".")
        else:
            command.extend(self.config_obj.include_files)
            command.extend(self.files_to_commit)
        self._run(command)

        output = self._run([self.command, "status"])
        if "nothing to commit, working directory clean" in output or \
                "nothing to commit, working tree clean" in output:
            self._run([self.command, "checkout", "develop"])
            self._run([self.command, "branch", "-d", branch])
            return

        message = ["-m", self.config_obj.commit_message]

        command_line = [self.command, "commit"]
        command_line.extend(message)

        self._run(command_line)

        self._run(
            self.commands + [
                "release",
                "finish",
                "-m",
                branch,
                self.config_obj.options['new_version']
            ])

    def post_finish_release(self):
        pass

    def get_info(self):
        return [
            ("Commit message", self.config_obj.commit_message),
            ("Release branch", self.release_branch),
        ] class GitRepo(vr.VCSRepo):

    def __init__(self, working_path, config_obj, files_to_commit=None):
        if six.PY2:
            super(GitRepo, self).__init__(
                working_path, config_obj, files_to_commit)
        else:
            super().__init__(working_path, config_obj, files_to_commit)

        self.make_release_branch = self.config_obj.options.get(
            'make_release_branch',
            True
        )

        self.release_branch = self.config_obj.options['new_version']

        self.target_branch = self.config_obj.options.get(
            'target_branch',
            'master'
        )

        self.annotate_tags = self.config_obj.options.get(
            'annotate_tags',
            False
        )

        self.annotation_message = ''
        if self.annotate_tags:
            self.annotation_message = self.config_obj.options.get(
                'annotation_message',
                "Version {}".format(self.config_obj.options['new_version'])
            )

    def _check_config(self):
        # Tag names cannot contain spaces
        tag = self.config_obj.options.get('tag', '')
        if ' ' in tag:
            raise e.RepositoryConfigurationError(
                ("""You specified "'tag': {}". """
                 "Tag names cannot contain spaces").format(tag)
            )

    def _check_system(self):
        if six.PY2:
            super(GitRepo, self)._check_system()
        else:
            super()._check_system()

        if not os.path.exists(os.path.join(self.working_path, '.git')):
            raise e.RepositorySystemError(
                ("The current directory {} "
                 "is not a Git repository").format(self.working_path)
            )

    def _set_command(self):
        self.commands = ['git']
        self.command = 'git'

    def get_current_branch(self):
        stdout = self._run([self.command, "rev-parse", "--abbrev-ref", "HEAD"])

        branch = stdout.replace("\n", "")

        return branch

    def get_tags(self):
        return self._run([self.command, "tag"])

    def get_branches(self):
        return self._run([self.command, "branch"])

    def pre_start_release(self):
        output = self._run([self.command, "status"])
        if "Changes to be committed:" in output:
            raise e.RepositoryStatusError(
                ("Cannot checkout the target branch while repository "
                 "contains uncommitted changes")
            )

        self._run([self.command, "checkout", self.target_branch])

        branch = self.get_current_branch()

        if branch != self.target_branch:
            raise e.RepositoryStatusError(
                ("Current branch should be {} (the target branch), "
                 "but is instead {}").format(
                    self.target_branch,
                    branch,
                )
            )

    def start_release(self):
        if self.make_release_branch:
            self._run([
                self.command,
                "checkout",
                "-b",
                self.release_branch
            ])

    def finish_release(self):
        branch = self.get_current_branch()

        command = [self.command, "add"]

        if self.config_obj.include_all_files:
            command.append(".")
        else:
            command.extend(self.config_obj.include_files)
            command.extend(self.files_to_commit)
        self._run(command)

        output = self._run([self.command, "status"])

        if ("nothing to commit, working directory clean" in output or
                "nothing to commit, working tree clean" in output) and \
                self.make_release_branch:
            self._run([self.command, "checkout", self.target_branch])
            self._run([self.command, "branch", "-d", branch])
            return

        command_line = [self.command, "commit"]
        command_line.extend(["-m", self.config_obj.commit_message])

        self._run(command_line)

        if self.make_release_branch:
            self._run([self.command, "checkout", self.target_branch])
            self._run([self.command, "merge", branch])
            self._run([self.command, "branch", "-d", branch])

        try:
            tag_value = self.config_obj.options['tag']
        except KeyError:
            tag_value = self.release_branch

        if self.config_obj.options.get('annotate_tags', False):
            annotation_message = self.annotation_message
            self._run([
                self.command,
                "tag",
                "-a",
                tag_value,
                "-m",
                annotation_message
            ])
        else:
            self._run([self.command, "tag", tag_value])

    def post_finish_release(self):
        pass

    def tag(self, tag_name):
        self._run([self.command, "tag", tag_name])

    def get_info(self):
        return [
            ("Commit message", self.config_obj.commit_message),
            (
                "Create release branch",
                'yes' if self.make_release_branch else 'no'
            ),
            ("Release branch", self.release_branch),
            (
                "Annotate tags",
                'yes' if self.annotate_tags else 'no'
            ),
            ("Annotation message", self.annotation_message),
        ] class HgRepo(vr.VCSRepo):
    DEFAULT_BRANCH = 'default'

    def __init__(self, working_path, config_obj, files_to_commit=None):
        if six.PY2:
            super(HgRepo, self).__init__(
                working_path, config_obj,files_to_commit)
        else:
            super().__init__(working_path, config_obj, files_to_commit)

        self.branch = self.config_obj.options.get('branch', 'default')
        self._recorded_branch = None

    def get_current_branch(self):
        stdout = self._run([self.command, "branch"])

        branch = stdout.replace("\n", "")

        return branch

    def get_branches(self):
        stdout = self._run([self.command, "branches"])
        return {self._parse_branch_line(l) for l in stdout.splitlines()}

    def get_tags(self):
        tags_str = self._run([self.command, "tags"])
        tags = map(
            lambda l: l.rsplit(" ", 1)[0].strip(),
            tags_str.splitlines()
        )
        return "\n".join(tags)

    def get_summary(self):
        output = self._run([self.command, "summary"])
        keys = {"branch", "commit", "update"}
        summary = {}
        for l in output.splitlines():
            try:
                k, body = l.split(": ", 1)
                if k in keys:
                    summary[k] = body
            except ValueError:
                pass

        return summary

    def pre_start_release(self):
        if not self._is_clean():
            raise e.RepositoryStatusError(
                ("Cannot update default while repository "
                 "contains uncommitted changes")
            )
        self._recorded_branch = self.get_current_branch()

        self._change_branch(self.branch)

        branch = self.get_current_branch()

        if branch != self.branch:
            raise e.RepositoryStatusError(
                "Current branch shall be {} but is {}".format(
                    self.branch, branch))

    def start_release(self):
        pass

    def finish_release(self):
        self.get_current_branch()
        try:
            if self._is_clean():
                return
            command_line = [self.command, "commit"]
            command_line.extend(["-m", self.config_obj.commit_message])
            self._run(command_line)
            tag = self._configured_tag()
            self.tag(tag)
        finally:
            self._recover_branch()

    def tag(self, tag):
        self._run([self.command, "tag", tag])

    def _recover_branch(self):
        if self._recorded_branch is not None:
            self._change_branch(self._recorded_branch)
            self._recorded_branch = None

    def _change_branch(self, branch):
        self._run([self.command, "update", branch])

    def _check_config(self):
        # Tag names cannot contain spaces
        tag = self.config_obj.options.get('tag', '')
        if ' ' in tag:
            raise e.RepositoryConfigurationError(
                ("""You specified "'tag': {}". """
                 "Tag names cannot contain spaces").format(tag)
            )
        if re.match(r'^\d+$', tag):
            raise e.RepositoryConfigurationError(
                ("""You specified "'tag': {}". """
                 "Tag names cannot be just digits").format(tag)
            )

    def _check_system(self):
        if six.PY2:
            super(HgRepo, self)._check_system()
        else:
            super()._check_system()

        if not os.path.exists(os.path.join(self.working_path, '.hg')):
            raise e.RepositorySystemError(
                "The current directory {} is not a Hg repository".format(
                    self.working_path))

    def _set_command(self):
        self.commands = ['hg']
        self.command = 'hg'

    def _is_clean(self):
        return self.get_summary()["commit"].endswith("(clean)")

    @classmethod
    def _parse_branch_line(cls, line):
        return re.match(r'(?P<tag>.+)\s+\d+:.+$', line).group("tag").strip()

    def _configured_tag(self):
        try:
            return self.config_obj.options['tag']
        except KeyError:
            return self.config_obj.options['new_version']

    def get_info(self):
        return [
            ("Commit message", self.config_obj.commit_message),
        ] class VCSStartReleaseUseCase(use_case.VCSUseCase):
    def __init__(self, repo):
        self.repo = repo

    def execute(self):
        self.repo.pre_start_release()
        return self.repo.start_release() class VCSFinishReleaseUseCase(use_case.VCSUseCase):
    def __init__(self, repo):
        self.repo = repo

    def execute(self):
        self.repo.finish_release()
        return self.repo.post_finish_release()
import argparse
import sys
import os
import re
import punch
from jinja2 import Template
from punch import config as cfr
from punch import file_updater as fu
from punch import replacer as rep
from punch import version as ver
from punch import action_register as ar
from punch import helpers as hlp
from punch.vcs_configuration import VCSConfiguration
from punch.vcs_repositories.exceptions import RepositorySystemError
from punch.vcs_repositories.novcs_repo import NoVCSRepo
from punch.vcs_repositories.git_flow_repo import GitFlowRepo
from punch.vcs_repositories.git_repo import GitRepo
from punch.vcs_repositories.hg_repo import HgRepo
from punch.vcs_use_cases.vcs_start_release import VCSStartReleaseUseCase
from punch.vcs_use_cases.vcs_finish_release import VCSFinishReleaseUseCase
#!/usr/bin/env python

from __future__ import print_function, absolute_import, division





def fatal_error(message, exception=None):
    print(message)
    if exception is not None:
        print("Exception {}: {}".format(
            exception.__class__.__name__,
            str(exception)
        ))
    sys.exit(1)


def select_vcs_repo_class(vcs_configuration):
    if vcs_configuration is None:

        repo_class = NoVCSRepo