Compare commits

..
19 Commits
Author SHA1 Message Date
bitbucket-pipelines 9c5ec26529 chore: bump version and update CHANGELOG.md [skip ci] 2025-11-22 15:09:27 +00:00
JonathanGitFlowandjt e6386ed252 Merged in feature/add-git-exec-options (pull request #31)
feature: add git exec options

Approved-by: Jonathan Teran
2025-11-22 15:08:46 +00:00
jt 34aba71d32 bugfix: deshabilito check en merge --abort al buscar conflictos 2025-11-22 12:07:46 -03:00
jt 4135a789e3 feature: agregando descripcion a ejecucion de comandos importantes en new, tag y merge 2025-11-22 12:02:39 -03:00
jt faea8c2d2a feature: agregando descripcion a ejecucion de comandos importantes en init y commit 2025-11-22 11:49:12 -03:00
jt 11e2a9daa9 bugfix: cambiando tipo de print para agregar soporte de titulos 2025-11-22 11:48:19 -03:00
jt d133c480c5 bugfix: descartar output cuando el comando git se ejecuta con _run 2025-11-22 11:28:51 -03:00
jt 7346cfd1a8 feature: muevo opciones para check y print a metodos para ejecutar git 2025-11-22 11:23:24 -03:00
JonathanGitFlowandjt 37a7c101af Merged in refactor/read-flowconfig-before-run (pull request #30)
refactor: read flowconfig before run

Approved-by: Jonathan Teran
2025-11-21 23:48:58 +00:00
jt edf4948719 refactor: realizo parseo de flowconfig antes de ejecutar run, no en __init__ 2025-11-21 20:48:20 -03:00
bitbucket-pipelines 4dbef58e90 chore: bump version and update CHANGELOG.md [skip ci] 2025-11-21 23:44:00 +00:00
JonathanGitFlowandjt da385154d4 Merged in bugfix/tag-on-update-changelog (pull request #29)
bugfix: tag on update changelog

Approved-by: Jonathan Teran
2025-11-21 23:42:01 +00:00
jt 222c819b36 bugfix: creo tag en el commit que actualiza el changelog 2025-11-21 20:41:37 -03:00
jt 0e75a8ec1f style: ignorar carpeta dist/ 2025-11-21 20:33:55 -03:00
bitbucket-pipelines 58401bf6e1 chore: bump version and update CHANGELOG.md [skip ci] 2025-11-21 17:43:26 +00:00
JonathanGitFlowandjt acf1c0fdc8 Merged in refactor/separate-command-files (pull request #28)
refactor: separate command files

Approved-by: Jonathan Teran
2025-11-21 17:42:59 +00:00
jt bf829a114b bugfix: separar etapa de lectura y escritura de changelog 2025-11-21 14:41:49 -03:00
JonathanGitFlowandjt b8e43f065c Merged in refactor/separate-command-files (pull request #27)
refactor: separate command files

Approved-by: Jonathan Teran
2025-11-20 00:06:34 +00:00
jt 8d973f0b2b bugfix: separar etapa de lectura y escritura de changelog 2025-11-19 21:06:09 -03:00
11 changed files with 126 additions and 89 deletions
+1
View File
@@ -3,3 +3,4 @@
**/__pycache__/ **/__pycache__/
build/ build/
**/*.egg-info/ **/*.egg-info/
dist/
+34
View File
@@ -1,3 +1,37 @@
## v1.2.0 - 2025-11-22
### Commits
- **bugfix**: deshabilito check en merge --abort al buscar conflictos [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (34aba71)
- **feature**: agregando descripcion a ejecucion de comandos importantes en new, tag y merge [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (4135a78)
- **feature**: agregando descripcion a ejecucion de comandos importantes en init y commit [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (faea8c2)
- **bugfix**: cambiando tipo de print para agregar soporte de titulos [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (11e2a9d)
- **bugfix**: descartar output cuando el comando git se ejecuta con _run [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (d133c48)
- **feature**: muevo opciones para check y print a metodos para ejecutar git [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (7346cfd)
---
## v1.1.5 - 2025-11-21
### Commits
- **bugfix**: creo tag en el commit que actualiza el changelog [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (222c819)
- **style**: ignorar carpeta dist/ [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (0e75a8e)
---
## v1.1.4 - 2025-11-21
### Commits
- **bugfix**: separar etapa de lectura y escritura de changelog [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (bf829a1)
- **bugfix**: agrego http:// como protocolo soportado para pipelines [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (8f79a6a)
---
## Saturday, 8 de November de 2025, 00:28 ## Saturday, 8 de November de 2025, 00:28
- Autor: [bitbucket-pipelines](mailto:commits-noreply@bitbucket.org) - Autor: [bitbucket-pipelines](mailto:commits-noreply@bitbucket.org)
+2 -2
View File
@@ -54,7 +54,7 @@ class Changelog:
def _prepend(self, content: str): def _prepend(self, content: str):
if isfile(self.FILENAME): if isfile(self.FILENAME):
with open(self.FILENAME, "rw") as f: with open(self.FILENAME, "r") as f:
content += f.read() content += f.read()
f.seek(0) with open(self.FILENAME, "w") as f:
f.write(content) f.write(content)
+2 -5
View File
@@ -31,6 +31,8 @@ TYPE_INFO = 3
class Command(ABC): class Command(ABC):
flowconfig: dict[str, str]
@abstractmethod @abstractmethod
def name(self) -> str: def name(self) -> str:
pass pass
@@ -43,11 +45,6 @@ class Command(ABC):
def run(self, args: Namespace = Namespace()): def run(self, args: Namespace = Namespace()):
pass pass
def __init__(self) -> None:
self.flowconfig = (
Git.get_config(FLOWCONFIG_FILE) if isfile(FLOWCONFIG_FILE) else {}
)
def success(self, msg: str): def success(self, msg: str):
self._print(TYPE_SUCCESS, msg) self._print(TYPE_SUCCESS, msg)
+9 -9
View File
@@ -18,8 +18,8 @@ class CommitCommand(Command):
if not self._has_files_staged(): if not self._has_files_staged():
self.warning("No hay cambios en el indice para commitear.") self.warning("No hay cambios en el indice para commitear.")
if self.confirm("¿Desea agregar la carpeta actual (`git add .`)?"): if self.confirm("¿Desea agregar la carpeta actual?"):
Git("add", ".").exec() Git("add", ".").exec(print="Agregando cambios")
else: else:
raise GitFlowError( raise GitFlowError(
"Debe agregar algún cambio al indice para continuar." "Debe agregar algún cambio al indice para continuar."
@@ -33,19 +33,19 @@ class CommitCommand(Command):
if branch.startswith("wip/"): if branch.startswith("wip/"):
original_branch = branch.removeprefix("wip/") original_branch = branch.removeprefix("wip/")
Git("commit", m=message).exec() Git("commit", m=message).exec(print="Creando commit en rama WIP")
if commit_type != "wip": if commit_type != "wip":
suffix = datetime.now().strftime("%Y%m%dT%H%M") suffix = datetime.now().strftime("%Y%m%dT%H%M")
Git("switch", original_branch).exec() Git("switch", original_branch).exec(print="Volviendo a rama original")
Git("merge", branch, squash=True).exec() Git("merge", branch, squash=True).exec(print="Squasheando commits WIP en uno solo")
Git("commit", m=message).exec() Git("commit", m=message).exec(print="Creando commit final de rama WIP")
Git("branch", branch, "trash/" + branch + "/" + suffix, move=True).exec() Git("branch", branch, "trash/" + branch + "/" + suffix, move=True).exec(print="Backup de rama WIP")
else: else:
if commit_type == "wip": if commit_type == "wip":
Git("switch", "wip/" + branch, create=True).exec() Git("switch", "wip/" + branch, create=True).exec(print="Creando nueva rama WIP")
Git("commit", m=message).exec() Git("commit", m=message).exec(print="Creando commit")
def _has_files_staged(self): def _has_files_staged(self):
status = Git.status() status = Git.status()
+12 -11
View File
@@ -45,13 +45,10 @@ class InitCommand(Command):
Git("init").exec() Git("init").exec()
def _ensure_not_already_initialized(self): def _ensure_not_already_initialized(self):
if os.path.isfile(FLOWCONFIG_FILE): if not self.flowconfig:
flowconfig = Git.get_config(FLOWCONFIG_FILE) return
elif self.flowconfig.get("flow.initialized") == "true":
if flowconfig["flow.initialized"] == "true": raise GitFlowError("El repositorio ya fue inicializado para usar git-flow")
raise GitFlowError(
"El repositorio ya fue inicializado para usar git-flow"
)
else: else:
raise GitFlowError("Valor de 'flow.initialized' es inválido") raise GitFlowError("Valor de 'flow.initialized' es inválido")
@@ -79,7 +76,7 @@ class InitCommand(Command):
remote = None remote = None
if self.confirm("¿Configurar repositorio remoto?"): if self.confirm("¿Configurar repositorio remoto?"):
remotes = Git("remote").lines() remotes = Git("remote").lines(print="Listando remotos disponibles")
if not remotes: if not remotes:
self.info("No tiene ningún repositorio remoto, se creará uno.") self.info("No tiene ningún repositorio remoto, se creará uno.")
@@ -89,7 +86,7 @@ class InitCommand(Command):
persistent=True, persistent=True,
) )
remote = "origin" remote = "origin"
Git("remote", "add", remote, url).exec() Git("remote", "add", remote, url).exec(print="Agregando remoto")
elif len(remotes) == 1: elif len(remotes) == 1:
remote = remotes[0] remote = remotes[0]
else: else:
@@ -109,7 +106,11 @@ class InitCommand(Command):
for branch in branches: for branch in branches:
if branch not in existing_branches: if branch not in existing_branches:
Git("branch", branch, target_branch).exec() Git("branch", branch, target_branch).exec(
print="Creando rama inexistente"
)
if remote: if remote:
Git("push", remote, branch, set_upstream=True).exec() Git("push", remote, branch, set_upstream=True).exec(
print="Creando rama en remoto"
)
+15 -9
View File
@@ -29,14 +29,17 @@ class MergeCommand(Command):
def run_remote(self, remote: str, token: str, base: str, branch: str, target: str): def run_remote(self, remote: str, token: str, base: str, branch: str, target: str):
self.check_pending_changes(True) self.check_pending_changes(True)
Git("switch", target).exec() Git("switch", target).exec(print="Cambiando a rama destino")
Git("pull").exec() Git("pull").exec(print="Obteniendo ultimos cambios del remoto")
Git("switch", "-").exec() Git("switch", "-").exec(print="Volviendo a rama a mergear")
self.check_merge_conflicts(target) self.check_merge_conflicts(target)
self.show_commits_to_merge(base) self.show_commits_to_merge(base)
Git("push", remote, branch, set_upstream=True).exec() if self.confirm(f"¿Crear PR de '{branch}' a '{target}'?"):
Git("push", remote, branch, set_upstream=True).exec(
print="Subiendo rama al remoto para crear PR"
)
self.create_pull_request(token, base, branch, target) self.create_pull_request(token, base, branch, target)
@@ -45,9 +48,9 @@ class MergeCommand(Command):
self.check_merge_conflicts(target) self.check_merge_conflicts(target)
self.show_commits_to_merge(base) self.show_commits_to_merge(base)
if self.confirm(f"Mergear rama '{target}' <= '{branch}'?"): if self.confirm(f"¿Mergear rama '{branch}' a '{target}'?"):
Git("switch", target).exec() Git("switch", target).exec(print="Cambiando a rama destino")
Git("merge", branch, ff=False).exec() Git("merge", branch, ff=False).exec(print="Mergeando")
def check_pending_changes(self, has_remote: bool): def check_pending_changes(self, has_remote: bool):
status = Git.status() status = Git.status()
@@ -78,10 +81,13 @@ class MergeCommand(Command):
print("- " + commit) print("- " + commit)
def check_merge_conflicts(self, target: str): def check_merge_conflicts(self, target: str):
merge_conflicts = Git("merge", target, ff=False, commit=False).code() != 0 merge_conflicts = Git("merge", target, ff=False, commit=False).code(
print="Realizando merge de prueba para verificar conflictos"
)
Git("merge", abort=True).exec(print="Abortando merge de prueba", check=False)
if merge_conflicts: if merge_conflicts:
Git("merge", abort=True).exec()
self.error(f"La rama actual tiene conflictos con {target}") self.error(f"La rama actual tiene conflictos con {target}")
self.info( self.info(
f"""Se recomienda mergear {target} a la rama actual, resolver los conflictos localmente, y ejecutar nuevamente este comando""" f"""Se recomienda mergear {target} a la rama actual, resolver los conflictos localmente, y ejecutar nuevamente este comando"""
+1 -1
View File
@@ -22,7 +22,7 @@ class NewCommand(Command):
new_branch = self._get_unique_branch_name(branch_type) new_branch = self._get_unique_branch_name(branch_type)
if self.confirm(f"¿Crear rama '{new_branch}' sobre la rama actual?"): if self.confirm(f"¿Crear rama '{new_branch}' sobre la rama actual?"):
Git("switch", new_branch, create=True).exec() Git("switch", new_branch, create=True).exec(print="Creando nueva rama")
def _get_unique_branch_name(self, branch_type: str) -> str: def _get_unique_branch_name(self, branch_type: str) -> str:
self.info( self.info(
+11 -19
View File
@@ -28,11 +28,7 @@ class TagCommand(Command):
if target not in envs: if target not in envs:
raise GitFlowError("Solo se pueden taggear commits en ramas principales.") raise GitFlowError("Solo se pueden taggear commits en ramas principales.")
merge_commit = self.get_last_merge_commit() branch = Git("show", self.get_last_merge_commit() + "^2", patch=False, format="%h").firstline()
self.check_not_tagged(merge_commit)
branch = Git("show", merge_commit + "^2", patch=False, format="%h").firstline()
base = Git.get_first_fork_point(branch, target) base = Git.get_first_fork_point(branch, target)
commits = Git("log", base + ".." + branch, format="%s").lines() commits = Git("log", base + ".." + branch, format="%s").lines()
next_tag = self.get_next_tag_from_commits(commits, target) next_tag = self.get_next_tag_from_commits(commits, target)
@@ -44,18 +40,18 @@ class TagCommand(Command):
return return
(token, ci) = self.get_token_and_ci(args) (token, ci) = self.get_token_and_ci(args)
if ci:
message = self.get_remote_api(token).create_tag(next_tag, merge_commit)
changelog = Changelog() changelog = Changelog()
self.success(message)
changelog.update(next_tag, base, branch) changelog.update(next_tag, base, branch)
Git("add", Changelog.FILENAME).exec() Git("add", Changelog.FILENAME).exec(print="Agregando nuevo CHANGELOG.md")
Git("commit", message=Changelog.COMMIT_MESSAGE).exec() Git("commit", message=Changelog.COMMIT_MESSAGE).exec(print="Creando commit")
Git("push").exec() tag_commit = Git("show", "HEAD", patch=False, format="%h").firstline()
if ci:
Git("push").exec(print="Subiendo commit al remoto para taggearlo")
self.success(self.get_remote_api(token).create_tag(next_tag, tag_commit))
else: else:
Git("tag", next_tag, merge_commit).exec() Git("tag", next_tag, tag_commit).exec(print="Creando tag localmente")
def get_token_and_ci(self, args: Namespace): def get_token_and_ci(self, args: Namespace):
try: try:
@@ -79,14 +75,10 @@ class TagCommand(Command):
return commit return commit
def check_not_tagged(self, commit: str): def check_not_tagged(self, commit: str):
tag = ( tag = Git("describe", commit, tags=True, exact_match=True).firstline(check=False)
Git("describe", commit, tags=True, exact_match=True)
.without_checking()
.firstline()
)
if tag: if tag:
raise GitFlowError("El ultimo merge ya tiene tag: " + tag) raise GitFlowError("El commit a taggear ya tiene tag: " + tag)
def get_next_tag_from_commits(self, commits: list[str], branch: str) -> str | None: def get_next_tag_from_commits(self, commits: list[str], branch: str) -> str | None:
increment = SEMVER_SKIP increment = SEMVER_SKIP
+30 -26
View File
@@ -22,7 +22,6 @@ class Git:
self.file = line[3:].strip("\n") self.file = line[3:].strip("\n")
command: list[str] command: list[str]
check_returncode: bool = True
@staticmethod @staticmethod
def get_config(file: str | None = None): def get_config(file: str | None = None):
@@ -60,7 +59,7 @@ class Git:
@staticmethod @staticmethod
def get_current_tag(): def get_current_tag():
return Git("describe", abbrev="0", tags=True).without_checking().firstline() return Git("describe", abbrev="0", tags=True).firstline(check=False)
@staticmethod @staticmethod
def get_first_fork_point(branch: str, env: str): def get_first_fork_point(branch: str, env: str):
@@ -115,44 +114,47 @@ class Git:
self.command += args self.command += args
def lines(self, strip: bool = True): def lines(self, strip: bool = True, **kwargs):
process = subprocess.run(self.command, capture_output=True, text=True) process = self._get(**kwargs)
self.__print_process(process.stdout, process.stderr)
if self.check_returncode:
process.check_returncode()
lines = process.stdout.splitlines() lines = process.stdout.splitlines()
return lines if not strip else list(map(lambda l: l.strip(), lines)) return lines if not strip else list(map(lambda l: l.strip(), lines))
def firstline(self): def firstline(self, **kwargs: bool):
lines = self.lines() lines = self.lines(**kwargs)
return lines[0] if lines else "" return lines[0] if lines else ""
def exec(self): def code(self, **kwargs):
return self._run(check=False, **kwargs).returncode
def exec(self, **kwargs):
self._run(**kwargs)
def _get(self, print: str|None = None, check: bool = True):
process = subprocess.run(self.command, capture_output=True, text=True) process = subprocess.run(self.command, capture_output=True, text=True)
self.__print_process(process.stdout, process.stderr) if print is not None:
self._print_process(process.stdout, process.stderr, print)
if self.check_returncode: if check:
process.check_returncode() process.check_returncode()
def code(self): return process
process = subprocess.run(self.command, capture_output=True, text=True)
self.__print_process(process.stdout, process.stderr)
return process.returncode def _run(self, print: str|None = None, check: bool = True):
process = subprocess.run(self.command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def without_checking(self): if print is not None:
self.check_returncode = False self._print_process("", "", title=print)
return self if check:
process.check_returncode()
def __print_process(self, stdout: str, stderr: str): return process
def _print_process(self, stdout: str, stderr: str, title: str):
command = [] command = []
for arg in self.command: for arg in self.command:
@@ -164,10 +166,12 @@ class Git:
command.append(arg) command.append(arg)
print(COLOR_YELLOW + "> " + " ".join(command) + COLOR_RESET) title = title or "Ejecutando"
print(COLOR_YELLOW + "| " + title + COLOR_RESET)
print(COLOR_YELLOW + "| $ " + " ".join(command) + COLOR_RESET)
for line in stdout.splitlines(): for line in stdout.splitlines():
print(COLOR_BLUE + "< " + line + COLOR_RESET) print(COLOR_YELLOW + "| " + COLOR_RESET + "[out] " + line)
for line in stderr.splitlines(): for line in stderr.splitlines():
print(COLOR_RED + "! " + line + COLOR_RESET) print(COLOR_YELLOW + "| " + COLOR_RED + "[err] " + line + COLOR_RESET)
+4 -2
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os import os
from os.path import isfile
import sys import sys
import datetime import datetime
import requests import requests
@@ -15,7 +16,7 @@ from git_flow.command.init import InitCommand
from git_flow.command.merge import MergeCommand from git_flow.command.merge import MergeCommand
from git_flow.command.new import NewCommand from git_flow.command.new import NewCommand
from git_flow.command.tag import TagCommand from git_flow.command.tag import TagCommand
from git_flow.git import BUMP_VERSION, Git from git_flow.git import BUMP_VERSION, FLOWCONFIG_FILE, Git
from git_flow.io import * from git_flow.io import *
@@ -54,6 +55,7 @@ class GitFlowCommand(Command):
for command in self.commands: for command in self.commands:
if command.name() == args.command: if command.name() == args.command:
command.flowconfig = Git.get_config(FLOWCONFIG_FILE) if isfile(FLOWCONFIG_FILE) else {}
return command.run(args) return command.run(args)
@@ -216,7 +218,7 @@ def tag_command():
message = output[pos+1:] message = output[pos+1:]
merged_branch = None merged_branch = None
output = Git("describe", commit, tags=True, exact_match=True).without_checking().firstline() output = Git("describe", commit, tags=True, exact_match=True).firstline(check=False)
if output: if output:
print_error("El ultimo merge ya tiene tag: " + output) print_error("El ultimo merge ya tiene tag: " + output)