Compare commits

..
18 Commits
Author SHA1 Message Date
jt d456565b2f bugfix: arreglo acceso a secretos 2026-07-08 22:10:35 -03:00
jt ad8984cb28 bugfix: agrego fetch-depth a checkout 2026-07-08 22:08:02 -03:00
jt bf6a0e8443 bugfix: agrego log de git luego del checkout 2026-07-08 22:06:24 -03:00
jt cb742425c1 bugfix: cambio version de action setup-uv 2026-07-08 21:58:52 -03:00
jt e94ee2ccc7 feature: agrego workflow para subir paquete a pypi 2026-07-08 21:55:16 -03:00
jt 069c62b697 Merge pull request 'feature: agrego implementacion de github remote api' (#2) from feature/add-github-remote-api into main
Reviewed-on: #2
2026-07-09 00:28:52 +00:00
jt c18ca454c0 feature: agrego implementacion de github remote api 2026-07-08 21:27:42 -03:00
jt 0fcf53bcb5 Merge pull request 'feature: add gitea remote api' (#1) from feature/add-gitea-remote-api into main
Reviewed-on: #1
2026-07-09 00:22:34 +00:00
jt f9dddbc4d6 bugfix: agregar schema a host de gitea api 2026-07-08 21:20:43 -03:00
jt c7888c74f1 bugfix: corregir headers de gitea 2026-07-08 21:19:24 -03:00
jt 87c5ba81c4 feature: agrego soporte para apis gitea 2026-07-08 21:12:06 -03:00
jt a10e9fe4fe Add .gitea/workflows/demo.yaml 2026-07-08 02:56:08 +00:00
bitbucket-pipelines c93833769b chore: bump version and update CHANGELOG.md [skip ci] 2026-06-03 00:40:26 +00:00
JonathanGitFlowandjt c536deb54d Merged in bugfix/allow-commit-messages-non-conventional (pull request #77)
bugfix: allow commits not compliant with conventional commits in changelog generation

Approved-by: Jonathan Teran
2026-06-03 00:39:43 +00:00
jt 3ff9f76a6c bugfix: allow commits not compliant with conventional commits in changelog generation 2026-06-02 21:38:18 -03:00
bitbucket-pipelines 1c319beff4 chore: bump version and update CHANGELOG.md [skip ci] 2026-05-24 05:42:13 +00:00
JonathanGitFlowandjt f885c27e08 Merged in bugfix/pr-title-with-ticket (pull request #76)
bugfix: use rest of branch name instead of ticket for pr title generation

Approved-by: Jonathan Teran
2026-05-24 05:41:05 +00:00
jt 094bc1a698 bugfix: use rest of branch name instead of ticket for pr title generation 2026-05-24 02:40:33 -03:00
12 changed files with 177 additions and 41 deletions
+2
View File
@@ -2,3 +2,5 @@
remote = origin remote = origin
branches = main branches = main
initialized = true initialized = true
version = 2
remote-type = gitea
+35
View File
@@ -0,0 +1,35 @@
name: Publish to PyPI
on:
push:
branches:
- main
jobs:
Publish-to-PyPI:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Show git log
run: |
git log --oneline --graph -10
- name: "Set up Python"
uses: actions/setup-python@v5
with:
python-version-file: ".python-version"
- name: Install uv
uses: astral-sh/setup-uv@v8.3.2
- name: Bump version
run: |
uv run git-flow tag --token="${{ secrets.GITEA_TOKEN}}"
- name: Build and publish
run: |
uv build
uv publish --token="${{ secrets.PYPI_TOKEN }}"
+14
View File
@@ -1,3 +1,17 @@
## v1.17.5 - 2026-06-03
- **bugfix**: allow commits not compliant with conventional commits in changelog generation [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (3ff9f76)
---
## v1.17.4 - 2026-05-24
- **bugfix**: use rest of branch name instead of ticket for pr title generation [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (094bc1a)
---
## v1.17.3 - 2026-05-22 ## v1.17.3 - 2026-05-22
- **bugfix**: dont close branches on merges between environments [[jteran](mailto:jteran@renatre.org.ar)] (8ee0129) - **bugfix**: dont close branches on merges between environments [[jteran](mailto:jteran@renatre.org.ar)] (8ee0129)
+2 -1
View File
@@ -47,7 +47,8 @@ El comando solicitará 2 opciones:
automáticamente. Por el momento, se soportan los siguientes remotos: automáticamente. Por el momento, se soportan los siguientes remotos:
- [x] Bitbucket - [x] Bitbucket
- [ ] Github: planeado - [x] Github
- [x] Gitea
La configuración del remoto requiere tener un Access Token para poder crear los PRs automáticamente. La configuración del remoto requiere tener un Access Token para poder crear los PRs automáticamente.
El mismo se puede crear desde el repositorio al que se quiere dar acceso: *"Repository settings" > El mismo se puede crear desde el repositorio al que se quiere dar acceso: *"Repository settings" >
+2
View File
@@ -51,3 +51,5 @@ SUPPORTED_REMOTE_APIS = [
REPOSITORY_TOKEN_FILENAME = ".repository-token" REPOSITORY_TOKEN_FILENAME = ".repository-token"
FLOWCONFIG_FILENAME = ".flowconfig" FLOWCONFIG_FILENAME = ".flowconfig"
FLOWCONFIG_VERSION = 2
+5 -1
View File
@@ -28,11 +28,15 @@ class Changelog:
message = Git("show", commit, patch=False, format="%s").firstline() message = Git("show", commit, patch=False, format="%s").firstline()
email = Git("show", commit, patch=False, format="%ae").firstline() email = Git("show", commit, patch=False, format="%ae").firstline()
username = email[: email.index("@")] username = email[: email.index("@")]
description = message
if ":" in message:
index = message.index(":") index = message.index(":")
commit_type = message[:index] commit_type = message[:index]
commit_message = message[index + 1 :] commit_message = message[index + 1 :]
description = f"**{commit_type}**: {commit_message}"
content += f"- **{commit_type}**: {commit_message} [[{username}](mailto:{email})] ({commit})\n" content += f"- {description} [[{username}](mailto:{email})] ({commit})\n"
return content return content
+17 -3
View File
@@ -2,12 +2,14 @@ from git_flow import (
BRANCH_TYPES, BRANCH_TYPES,
REPOSITORY_TOKEN_FILENAME, REPOSITORY_TOKEN_FILENAME,
FLOWCONFIG_FILENAME, FLOWCONFIG_FILENAME,
FLOWCONFIG_VERSION,
GitFlowError, GitFlowError,
) )
from git_flow.git import Git from git_flow.git import Git
from git_flow.remote.base import RemoteAPI from git_flow.remote.base import RemoteAPI
from git_flow.remote.bitbucket import BitbucketRemoteAPI from git_flow.remote.bitbucket import BitbucketRemoteAPI
from git_flow.remote.github import GithubRemoteAPI from git_flow.remote.github import GithubRemoteAPI
from git_flow.remote.gitea import GiteaRemoteAPI
from git_flow.io import * from git_flow.io import *
from os.path import isfile from os.path import isfile
@@ -32,6 +34,14 @@ def ensure_initialized():
f"El valor de `flow.initialized` ({initialized}) es inválido." f"El valor de `flow.initialized` ({initialized}) es inválido."
) )
version = int(flowconfig.get("flow.version", "1"))
if version != FLOWCONFIG_VERSION:
raise GitFlowError(
f"La versión del flowconfig ({version}) no es compatible con esta versión de git-flow ({FLOWCONFIG_VERSION}). "
f"Debe reinicializar el repositorio."
)
def ensure_right_branch(): def ensure_right_branch():
branch = Git.get_current_branch() branch = Git.get_current_branch()
@@ -104,14 +114,18 @@ def get_remote_api(token: str) -> RemoteAPI:
if "flow.remote" not in flowconfig: if "flow.remote" not in flowconfig:
raise GitFlowError("El repositorio no tiene configurado un remoto.") raise GitFlowError("El repositorio no tiene configurado un remoto.")
remote_type = flowconfig.get("flow.remote-type", "").lower()
[host, repository] = RemoteAPI.parse(flowconfig["flow.remote"]) [host, repository] = RemoteAPI.parse(flowconfig["flow.remote"])
if host == "bitbucket.org": if remote_type == "gitea":
return GiteaRemoteAPI("https://" + host, repository, token)
elif remote_type == "bitbucket":
return BitbucketRemoteAPI(repository, token) return BitbucketRemoteAPI(repository, token)
elif host == "github.com": elif remote_type == "github":
return GithubRemoteAPI(repository, token) return GithubRemoteAPI(repository, token)
else: else:
raise GitFlowError("El host del repositorio remoto es inválido") raise GitFlowError("El tipo del remoto es inválido")
def is_valid_ticket(ticket: str) -> bool: def is_valid_ticket(ticket: str) -> bool:
"""Check if the specified string is a valid ticket number (ABC-123)""" """Check if the specified string is a valid ticket number (ABC-123)"""
+9 -4
View File
@@ -1,6 +1,6 @@
import typer import typer
from git_flow import FLOWCONFIG_FILENAME, GitFlowError from git_flow import FLOWCONFIG_FILENAME, FLOWCONFIG_VERSION, GitFlowError
from git_flow.git import Git from git_flow.git import Git
from git_flow.command.base import * from git_flow.command.base import *
@@ -15,15 +15,17 @@ def init():
_ensure_not_already_initialized() _ensure_not_already_initialized()
branches = _setup_flow_branches() branches = _setup_flow_branches()
remote = _setup_flow_remote() (remote, remote_type) = _setup_flow_remote()
flowconfig = { flowconfig = {
"flow.version": str(FLOWCONFIG_VERSION),
"flow.initialized": "true", "flow.initialized": "true",
"flow.branches": ",".join(branches), "flow.branches": ",".join(branches),
} }
if remote: if remote and remote_type:
flowconfig["flow.remote"] = remote flowconfig["flow.remote"] = remote
flowconfig["flow.remote-type"] = remote_type
Git.set_config(flowconfig, FLOWCONFIG_FILENAME) Git.set_config(flowconfig, FLOWCONFIG_FILENAME)
Git("add", FLOWCONFIG_FILENAME).exec() Git("add", FLOWCONFIG_FILENAME).exec()
@@ -74,6 +76,7 @@ def _setup_flow_remote():
) )
remote = None remote = None
remote_type = None
if confirm("¿Configurar repositorio remoto?"): if confirm("¿Configurar repositorio remoto?"):
remotes = Git("remote").lines(print="Listando remotos disponibles") remotes = Git("remote").lines(print="Listando remotos disponibles")
@@ -93,7 +96,9 @@ def _setup_flow_remote():
info("Tiene más de un remoto, seleccione el que va a utilizar.") info("Tiene más de un remoto, seleccione el que va a utilizar.")
remote = choice("Remoto", remotes) remote = choice("Remoto", remotes)
return remote remote_type = choice("Tipo de remoto:", ["bitbucket", "github", "gitea"])
return (remote, remote_type)
def _ensure_all_flow_branches_exist(branches: list[str], remote: str | None): def _ensure_all_flow_branches_exist(branches: list[str], remote: str | None):
+1 -1
View File
@@ -138,7 +138,7 @@ def _get_pr_title_from_branch(branch: str) -> str:
maybe_ticket = branch_desc[:second_dash] maybe_ticket = branch_desc[:second_dash]
if is_valid_ticket(maybe_ticket): if is_valid_ticket(maybe_ticket):
branch_ticket = maybe_ticket branch_ticket = maybe_ticket
branch_desc = branch_desc[:second_dash+1] branch_desc = branch_desc[second_dash+1:]
return " ".join(filter(None, [ return " ".join(filter(None, [
f"[Pasaje a {components[1]}]" if len(components) == 4 else None, f"[Pasaje a {components[1]}]" if len(components) == 4 else None,
+4 -24
View File
@@ -12,43 +12,23 @@ class RemoteAPI(ABC):
@staticmethod @staticmethod
def parse(remote: str): def parse(remote: str):
url = Git("remote", "get-url", remote).firstline() url = Git("remote", "get-url", remote).firstline()
invalid_url_error = GitFlowError(f"La URL del remoto {remote} es inválida: {url}")
url_type = None
if url.startswith("git@"): if url.startswith("git@"):
url_type = "ssh"
elif url.startswith("http://") or url.startswith("https://"):
url_type = "https"
else:
raise invalid_url_error
if url_type == "ssh":
# git@host:user/repo[.git]
at_pos = url.index("@") at_pos = url.index("@")
colon_pos = url.index(":") colon_pos = url.index(":")
host = url[at_pos+1:colon_pos] host = url[at_pos+1:colon_pos]
repository = url[colon_pos+1:] repository = url[colon_pos+1:]
else: elif url.startswith("http://") or url.startswith("https://"):
# https://host/user/repo[.git]
host_start = url.index("://") + 3 host_start = url.index("://") + 3
uri_start = url.index("/", host_start) uri_start = url.index("/", host_start)
host = url[host_start:uri_start] host = url[host_start:uri_start]
repository = url[uri_start+1:] repository = url[uri_start+1:]
else:
raise GitFlowError(f"La URL del remoto {remote} es inválida: {url}")
# Esto permite configurar hosts "imaginarios" para permitir el uso de multiples claves ssh repository = repository.removesuffix(".git")
if host.endswith(".bitbucket.org"):
host = "bitbucket.org"
elif host.endswith(".github.com"):
host = "github.com"
if host not in SUPPORTED_REMOTE_APIS:
raise GitFlowError("El host del repositorio remoto es inválido")
repository.removesuffix(".git")
return [host, repository] return [host, repository]
@abstractmethod @abstractmethod
def create_pull_request( def create_pull_request(
self, self,
+55
View File
@@ -0,0 +1,55 @@
import requests
from git_flow import GitFlowError
from git_flow.remote.base import RemoteAPI
class GiteaRemoteAPI(RemoteAPI):
def __init__(self, base_url: str, repository: str, token: str) -> None:
super().__init__(repository, token)
self.base_url = base_url
def create_pull_request(
self,
source: str,
destination: str,
title: str | None = None,
description: str | None = None,
close_source_branch: bool = True
) -> str:
response = requests.post(
self.get_endpoint("/pulls"),
headers=self.get_headers(),
json={
"title": title,
"body": description,
"head": source,
"base": destination,
},
)
if response.ok:
return "PR creado exitosamente: " + response.json()["html_url"]
else:
raise GitFlowError("Ocurrió un error al crear el PR: " + response.text)
def create_tag(self, tag: str, commit: str) -> str:
response = requests.post(
self.get_endpoint("/tags"),
headers=self.get_headers(),
json={"tag_name": tag, "target": commit},
)
if response.ok:
return "Tag creado exitosamente: " + tag
else:
raise GitFlowError("Ocurrió un error al crear el tag: " + response.text)
def get_endpoint(self, resource: str) -> str:
return f"{self.base_url}/api/v1/repos/{self.repository}{resource}"
def get_headers(self):
return {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": "token " + self.token,
}
+28 -4
View File
@@ -1,9 +1,10 @@
import requests
from git_flow import GitFlowError from git_flow import GitFlowError
from git_flow.remote.base import RemoteAPI from git_flow.remote.base import RemoteAPI
class GithubRemoteAPI(RemoteAPI): class GithubRemoteAPI(RemoteAPI):
API_ENDPOINT = "https://github.com" API_ENDPOINT = "https://api.github.com/repos"
def create_pull_request( def create_pull_request(
self, self,
@@ -13,10 +14,33 @@ class GithubRemoteAPI(RemoteAPI):
description: str | None = None, description: str | None = None,
close_source_branch: bool = True close_source_branch: bool = True
) -> str: ) -> str:
raise GitFlowError("not implemented yet!") response = requests.post(
self.get_endpoint("/pulls"),
headers=self.get_headers(),
json={
"title": title,
"body": description,
"head": source,
"base": destination,
},
)
if response.ok:
return "PR creado exitosamente: " + response.json()["html_url"]
else:
raise GitFlowError("Ocurrió un error al crear el PR: " + response.text)
def create_tag(self, tag: str, commit: str) -> str: def create_tag(self, tag: str, commit: str) -> str:
raise GitFlowError("not implemented yet!") response = requests.post(
self.get_endpoint("/git/refs"),
headers=self.get_headers(),
json={"ref": f"refs/tags/{tag}", "sha": commit},
)
if response.ok:
return "Tag creado exitosamente: " + tag
else:
raise GitFlowError("Ocurrió un error al crear el tag: " + response.text)
def get_endpoint(self, resource: str) -> str: def get_endpoint(self, resource: str) -> str:
return GithubRemoteAPI.API_ENDPOINT return f"{GithubRemoteAPI.API_ENDPOINT}/{self.repository}{resource}"