import subprocess from git_flow.io import * FLOWCONFIG_FILE = ".flowconfig" CHANGELOG_FILE = "CHANGELOG.md" BUMP_VERSION = "chore: bump version and update CHANGELOG.md [skip ci]" class Git: class Status: worktree: str index: str file: str def __init__(self, line: str) -> None: self.worktree = line[0] self.index = line[1] self.file = line[3:].strip("\n") command: list[str] check_returncode: bool = True @staticmethod def get_config(file: str | None = None): config: dict[str, str] = {} lines = Git("config", file=file, list=True).lines() for line in lines: pos = line.index("=") key = line[:pos] value = line[pos + 1 :] config[key] = value return config @staticmethod def set_config(config: dict[str, str], file: str | None = None): for key, value in config: Git("config", key, value, file=file).exec() @staticmethod def flow_config(*args, **kwargs): return Git("config", *args, file=FLOWCONFIG_FILE, **kwargs) @staticmethod def get_current_branch(): return Git("rev-parse", "HEAD", abbrev_ref=True).firstline() @staticmethod def get_branches(): return Git._get_references("heads") @staticmethod def status(): return list(map(Git.Status, Git("status", porcelain=True).lines(False))) @staticmethod def get_current_tag(): return Git("describe", abbrev="0", tags=True).without_checking().firstline() @staticmethod def get_first_fork_point(branch: str, env: str): boundary_commits = list( filter( lambda c: c.startswith("-"), # boundary commits Git( "rev-list", env + "..." + branch, topo_order=True, boundary=True ).lines(), ) ) if not boundary_commits: raise RuntimeError("No se encontro un commit base para realizar el rebase.") return boundary_commits[-1].removeprefix("-") @staticmethod def _get_references(kind: str): prefix = "refs/" + kind + "/" return list(map( lambda r: r.removeprefix(prefix), Git("for-each-ref", prefix + "*", format="%(refname)").lines(), )) @staticmethod def is_repository() -> bool: return Git("rev-parse", is_inside_work_tree=True).code() == 0 def __init__(self, subcommand: str, *args: str, **kwargs: str | int | bool | None) -> None: self.command = ["git", subcommand] for option, value in kwargs.items(): option = option.replace("_", "-") if isinstance(value, bool): if len(option) == 1: self.command.append("-" + option) else: prefix = "--" if value else "--no-" self.command.append(prefix + option) elif value is None: continue else: value = value if isinstance(value, str) else str(value) if len(option) == 1: self.command.append("-" + option) self.command.append(value) else: self.command.append("--" + option + "=" + value) self.command += args def lines(self, strip: bool = True): process = subprocess.run(self.command, capture_output=True, text=True) self.__print_process(process.stdout, process.stderr) if self.check_returncode: process.check_returncode() lines = process.stdout.splitlines() return lines if not strip else list(map(lambda l: l.strip(), lines)) def firstline(self): lines = self.lines() return lines[0] if lines else "" def exec(self): process = subprocess.run(self.command, capture_output=True, text=True) self.__print_process(process.stdout, process.stderr) if self.check_returncode: process.check_returncode() def code(self): process = subprocess.run(self.command, capture_output=True, text=True) self.__print_process(process.stdout, process.stderr) return process.returncode def without_checking(self): self.check_returncode = False return self def __print_process(self, stdout: str, stderr: str): command = [] for arg in self.command: if arg.startswith("--") and "=" in arg: pos = arg.index("=") arg = arg[: pos + 1] + '"' + arg[pos + 1 :] + '"' elif " " in arg: arg = '"' + arg + '"' command.append(arg) print(COLOR_YELLOW + "> " + " ".join(command) + COLOR_RESET) for line in stdout.splitlines(): print(COLOR_BLUE + "< " + line + COLOR_RESET) for line in stderr.splitlines(): print(COLOR_RED + "! " + line + COLOR_RESET)