Example #1
0
def show(ctx, recursive, with_tags, directory):
    """
    current release version.

    If a single directory is specified, it will print out the current release version in the
    form of `<release>[<-sha-commit>[-dirty]]`. If multiple directories are specified, it will print out the
    directory name followed by the release version.
    """
    release_infos = ReleaseInfo.find_all(directory, recursive,
                                         ctx.obj["dry_run"])
    for release_info in release_infos:
        if not release_info.has_release_configuration:
            log.error(
                f"directory {release_info.directory} has no release configuration"
            )
            exit(1)

        if recursive:
            if with_tags:
                print(
                    f'{release_info.directory}\t{release_info.current_version}\t{release_info.tag}'
                )
            else:
                print(
                    f'{release_info.directory}\t{release_info.current_version}'
                )
        else:
            print(release_info.current_version)
Example #2
0
    def __init__(self, path: str, dry_run: bool = False):
        super(ReleaseInfo, self).__init__()
        self.dry_run = dry_run
        self.directory = path
        self.path = os.path.join(self.directory, ".release")

        self.base_tag = None
        self._semver = None
        self._pre_tag_command = None

        if not os.path.isdir(self.directory):
            log.error(f"directory {self.directory} does not exist")
            exit(1)

        if self.has_release_configuration:
            self.read()
Example #3
0
    def next_version(self, level):
        assert self.semver
        release = list(map(lambda n: int(n), self.semver.split(".")))
        if level == ReleaseInfo.PATCH:
            release[ReleaseInfo.PATCH] += 1
        elif level == ReleaseInfo.MINOR:
            release[ReleaseInfo.MINOR] += 1
            release[ReleaseInfo.PATCH] = 0
        elif level == ReleaseInfo.MAJOR:
            release[ReleaseInfo.MAJOR] += 1
            release[ReleaseInfo.MINOR] = 0
            release[ReleaseInfo.PATCH] = 0
        else:
            log.error("I can only bump PATCH, MINOR or MAJOR levels")
            exit(1)

        self.semver = "%d.%d.%d" % (release[0], release[1], release[2])
Example #4
0
    def tag_next_release(self,
                         level,
                         message: str = None,
                         force: bool = False):
        if not force:
            if not self.changes_since_tag:
                log.info(
                    f"{self.directory} has no changes since {self.semver}.")
                return

        self.next_version(level)
        if self.tag in self.all_tags:
            log.error(f"tag {self.tag} already exists")
            exit(1)

        self.write()
        if not message:
            message = f"bumped {self.git_prefix} to release {self.semver}"
        self.commit_and_tag(message)
Example #5
0
    def exec_pre_tag_command(self):
        if self.dry_run and self.pre_tag_command:
            log.debug(f"$ {self.pre_tag_command}")
            return

        if self.pre_tag_command:
            cmd = self.process_pre_tag_command()
            process = subprocess.Popen(
                cmd,
                shell=True,
                cwd=self.directory,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
            )
            out = process.communicate()
            if process.returncode != 0:
                log.error(
                    f"{self.pre_tag_command} in {self.directory}, returned {process.returncode}, output {out[1]}"
                )
                exit(1)
            return out[0]
Example #6
0
    def read(self):
        result = {}
        with open(self.path, "r") as f:
            for line in f:
                line = line.rstrip()
                if len(line) > 0 and line[0] != "#":
                    value = line.split("=", 1)
                    result[value[0].strip()] = value[1].strip()
        if not ("release" in result and "tag" in result):
            log.error(
                f"{self.path} does not contain release and/or tag values")
            exit(1)

        if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", result["release"]):
            log.error(
                f"ERROR: incorrect format of release in {self.path}, expected <major.minor.patch>"
            )
            exit(1)

        match = re.fullmatch(
            r"(?P<base_tag>.*)(?P<release>[0-9]+\.[0-9]+\.[0-9]+$)",
            result["tag"])
        if not match:
            log.error(
                f"ERROR: incorrect format old the tag in {self.path}, expected tag <base><major.minor.patch>"
            )
            exit(1)

        self.semver = result["release"]
        self.pre_tag_command = result.get("pre_tag_command")
        self.base_tag = match.group("base_tag")
        if match.group("release") != self.semver:
            log.warning(
                f"tag {self.tag} in {self.path} does not match specified release {match.group('release')}"
            )
Example #7
0
    def validate(release_infos: List["ReleaseInfo"]) -> bool:
        result = True
        base_tags = {}
        for release_info in release_infos:

            base_tag = release_info.base_tag
            existing = base_tags.get(base_tag)
            if release_info.base_tag in base_tags:
                log.error(
                    f"{release_info.path} has the same base tag as {existing.path}: {base_tag}"
                )
                result = False
            else:
                base_tags[base_tag] = release_info

            if not release_info.is_inside_work_tree:
                log.error(
                    f"{release_info.directory} is not inside a git workspace")
                result = False
            else:
                if release_info.tag not in release_info.all_tags:
                    log.error(
                        f"tag {release_info.tag} in {release_info.path} does not exist in repository"
                    )
                    result = False

        return result
Example #8
0
def exec(cmd: List[str], cwd: str, dry_run:bool=False, fail_on_error: bool = True):
    log.debug("$ %s  #cwd = %s", _to_cli(cmd), cwd)

    if dry_run:
        return ("", ""), None

    process = subprocess.Popen(
        cmd,
        cwd=cwd,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        universal_newlines=True,
    )
    out = process.communicate()
    log.debug("returncode = %s", process.returncode)
    log.debug("stdout = %s", out[0])
    log.debug("stderr = %s", out[1])
    if fail_on_error and process.returncode != 0:
        log.error("%s failed in %s, %s", " ".join(cmd), cwd, (out[0]+out[1]))
        exit(1)

    return out, process
Example #9
0
def initialize(ctx, initial_release, tag_prefix, pre_tag_command, directory):
    """
    directory with release configuration.

    Creates a release configuration using the specified `initial-release`, `tag-prefix`, and
    `pre-tag-command`. the file is called .release and has the following syntax:

    \b
        release=<initial-release>
        tag=<tag-prefix><initial-release>
        pre-tag-command=<pre-tag-command>

    The `pre-tag-command` is executed and any outstanding changes are committed and tagged with
    the specified `tag`.

    The directories must be in a git workspace.
    """

    print(f'>{pre_tag_command}<')
    directories = sorted(directory,
                         key=lambda p: len(os.path.abspath(p).split("/")),
                         reverse=True)

    prefixes = list(
        map(lambda d: os.path.basename(os.path.abspath(d)), directories))
    if tag_prefix is not None and len(directories) > 1:
        log.error(
            'you cannot specify the same tag-prefix for different directories')
        exit(1)

    if len(set(prefixes)) != len(prefixes):
        log.error(
            'base directory names must be unique in order to avoid non unique tag-prefixes'
        )
        exit(1)

    for path in directories:
        component = ReleaseInfo(path)
        if not component.is_inside_work_tree:
            log.error('%s is not inside a git workspace, please run git init',
                      path)
            exit(1)

    result = True
    for path in directories:
        result = ReleaseInfo.initialize(
            path,
            semver=initial_release,
            base_tag=tag_prefix if tag_prefix is not None else
            f'{os.path.basename(os.path.abspath(path))}-',
            pre_tag_command=pre_tag_command,
            dry_run=ctx.obj["dry_run"],
        ) and result

    exit(not result)
Example #10
0
    def initialize(
        directory: str,
        semver: str = "0.0.0",
        base_tag: str = "",
        pre_tag_command: str = "",
        dry_run: bool = False,
    ) -> bool:

        if not os.path.isdir(directory):
            log.error(f"{directory} is not a directory.")
            exit(1)

        path = os.path.join(directory, ".release")
        if os.path.exists(path):
            log.error(f"{directory} is ready initialized.")
            return False

        info = ReleaseInfo(path=directory, dry_run=dry_run)
        info.semver = semver
        info.base_tag = base_tag
        info.pre_tag_command = pre_tag_command

        if info.is_inside_work_tree and info.tag in info.all_tags:
            log.error(
                f'tag {info.tag} already exist in git repository for {info.path}'
            )
            exit(1)

        info.write()
        if info.is_inside_work_tree:
            info.commit_and_tag(
                f"initialized {info.git_prefix} to release {info.semver}")
        else:
            log.warning(f"{info.path} is not inside a git workspace")

        return True