Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4dc694db6 | ||
|
|
9120698900 | ||
|
|
c035a8867e | ||
|
|
0c31c747ff | ||
|
|
12ce68d954 | ||
|
|
29eacfdc4d | ||
|
|
748d4201cc | ||
|
|
94a219ed82 | ||
|
|
3f3650360a | ||
|
|
11a48a922c | ||
|
|
f4adb431db | ||
|
|
8f1a60cc06 | ||
|
|
fcc8114c8e | ||
|
|
c58c4cea96 | ||
|
|
7fbf499278 | ||
|
|
64fc890cb7 | ||
|
|
7199249f75 | ||
|
|
7efaf367e5 | ||
|
|
dc79ffa399 | ||
|
|
31513a3e3a | ||
|
|
c45f065e77 | ||
|
|
7f7dd7b538 | ||
|
|
adcb53156a | ||
|
|
5ca817c796 | ||
|
|
2be9b50ac4 | ||
|
|
347b8b9556 | ||
|
|
9b547bd6a9 | ||
|
|
f05582b50f | ||
|
|
a1a9a7acac | ||
|
|
190b4b8408 | ||
|
|
f9fb5a62d1 |
@@ -8,7 +8,7 @@ jobs:
|
||||
Publish-to-PyPI:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -1,3 +1,64 @@
|
||||
## v1.20.1 - 2026-09-14
|
||||
|
||||
- **refactor**: cambiar incremento de commits refactor y perf [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (c035a88)
|
||||
- **refactor**: eliminar preguntas innecesarias de release y merge [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (0c31c74)
|
||||
|
||||
---
|
||||
|
||||
|
||||
## v1.20.0 - 2026-09-13
|
||||
|
||||
- **feature**: add gitlab remote support [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (748d420)
|
||||
|
||||
---
|
||||
|
||||
|
||||
## v1.19.0 - 2026-07-31
|
||||
|
||||
- **style**: format with ruff [[jteran](mailto:jteran@renatre.org.ar)] (11a48a9)
|
||||
- **feature**: parse git remote url with giturlparse [[jteran](mailto:jteran@renatre.org.ar)] (f4adb43)
|
||||
|
||||
---
|
||||
|
||||
|
||||
## v1.18.0 - 2026-07-29
|
||||
|
||||
- **bugfix**: change checkout version to v7 [[jteran](mailto:jteran@renatre.org.ar)] (c58c4ce)
|
||||
- **bugfix**: move git fetch to separate line [[jteran](mailto:jteran@renatre.org.ar)] (64fc890)
|
||||
- **bugfix**: change actions/checkout to v4 and add step to fetch tags [[jteran](mailto:jteran@renatre.org.ar)] (dc79ffa)
|
||||
- **bugfix**: change actions/checkout version [[jteran](mailto:jteran@renatre.org.ar)] (c45f065)
|
||||
- **bugfix**: add fetch-tags: true [[jteran](mailto:jteran@renatre.org.ar)] (adcb531)
|
||||
- **bugfix**: remove checkout parameters [[jteran](mailto:jteran@renatre.org.ar)] (2be9b50)
|
||||
- **feature**: show commit count and list tags on workflow [[jteran](mailto:jteran@renatre.org.ar)] (f05582b)
|
||||
|
||||
---
|
||||
|
||||
|
||||
## v0.1.0-main - 2026-07-29
|
||||
|
||||
- **bugfix**: change actions/checkout to v4 and add step to fetch tags [[jteran](mailto:jteran@renatre.org.ar)] (dc79ffa)
|
||||
- **bugfix**: change actions/checkout version [[jteran](mailto:jteran@renatre.org.ar)] (c45f065)
|
||||
- **bugfix**: add fetch-tags: true [[jteran](mailto:jteran@renatre.org.ar)] (adcb531)
|
||||
- **bugfix**: remove checkout parameters [[jteran](mailto:jteran@renatre.org.ar)] (2be9b50)
|
||||
- **feature**: show commit count and list tags on workflow [[jteran](mailto:jteran@renatre.org.ar)] (f05582b)
|
||||
|
||||
---
|
||||
|
||||
|
||||
## v0.1.0-main - 2026-07-29
|
||||
|
||||
- **feature**: show commit count and list tags on workflow [[jteran](mailto:jteran@renatre.org.ar)] (f05582b)
|
||||
|
||||
---
|
||||
|
||||
|
||||
## v0.0.1-main - 2026-07-18
|
||||
|
||||
- **bugfix**: add fetch tags to checkout step [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (f9fb5a6)
|
||||
|
||||
---
|
||||
|
||||
|
||||
## v0.1.0-main - 2026-07-18
|
||||
|
||||
- **feature**: print current tag command output [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (0cdc012)
|
||||
|
||||
@@ -49,6 +49,7 @@ El comando solicitará 2 opciones:
|
||||
- [x] Bitbucket
|
||||
- [x] Github
|
||||
- [x] Gitea
|
||||
- [x] Gitlab
|
||||
|
||||
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" >
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ description = "Git workflow automation to follow best practices when working wit
|
||||
dynamic = ["version"]
|
||||
requires-python = ">= 3.14"
|
||||
dependencies = [
|
||||
"requests", "typer", "rich", "questionary"
|
||||
"requests", "typer", "rich", "questionary", "giturlparse"
|
||||
]
|
||||
authors = [
|
||||
{name = "Alan Facundo Biglieri", email = "abiglieri@renatre.org.ar"},
|
||||
|
||||
@@ -36,16 +36,17 @@ COMMIT_TYPE_INCREMENT = {
|
||||
"feature": SEMVER_MINOR,
|
||||
"bugfix": SEMVER_PATCH,
|
||||
"hotfix": SEMVER_PATCH,
|
||||
"refactor": SEMVER_SKIP,
|
||||
"perf": SEMVER_SKIP,
|
||||
"refactor": SEMVER_PATCH,
|
||||
"perf": SEMVER_PATCH,
|
||||
"docs": SEMVER_SKIP,
|
||||
"style": SEMVER_SKIP,
|
||||
"test": SEMVER_SKIP,
|
||||
}
|
||||
|
||||
SUPPORTED_REMOTE_APIS = [
|
||||
"bitbucket.org",
|
||||
"github.com",
|
||||
SUPPORTED_REMOTE_URL_SCHEMAS = [
|
||||
"ssh",
|
||||
"http",
|
||||
"https"
|
||||
]
|
||||
|
||||
REPOSITORY_TOKEN_FILENAME = ".repository-token"
|
||||
|
||||
@@ -11,6 +11,7 @@ from git_flow.remote.base import RemoteAPI
|
||||
from git_flow.remote.bitbucket import BitbucketRemoteAPI
|
||||
from git_flow.remote.github import GithubRemoteAPI
|
||||
from git_flow.remote.gitea import GiteaRemoteAPI
|
||||
from git_flow.remote.gitlab import GitlabRemoteAPI
|
||||
from git_flow.io import *
|
||||
from os.path import isfile
|
||||
|
||||
@@ -123,6 +124,9 @@ def get_remote_api(token: str) -> RemoteAPI:
|
||||
if remote_type == "gitea":
|
||||
remote_schema = "https://" if schema == "ssh://" else schema
|
||||
return GiteaRemoteAPI(remote_schema + host, repository, token)
|
||||
elif remote_type == "gitlab":
|
||||
remote_schema = "https://" if schema == "ssh://" else schema
|
||||
return GitlabRemoteAPI(remote_schema + host, repository, token)
|
||||
elif remote_type == "bitbucket":
|
||||
return BitbucketRemoteAPI(repository, token)
|
||||
elif remote_type == "github":
|
||||
|
||||
@@ -101,7 +101,7 @@ uno nuevo.""",
|
||||
remote = base.io_choice("Remoto", remotes)
|
||||
|
||||
remote_type = base.io_choice(
|
||||
"Tipo de remoto:", ["bitbucket", "github", "gitea"]
|
||||
"Tipo de remoto:", ["bitbucket", "github", "gitea", "gitlab"]
|
||||
)
|
||||
|
||||
return (remote, remote_type)
|
||||
|
||||
@@ -119,10 +119,6 @@ def create_pull_request(token: str, branch: str, target: str):
|
||||
|
||||
base.io_success(message, "Rama creada")
|
||||
|
||||
if base.io_confirm("¿Desea cambiar a la rama objetivo y bajar los cambios?"):
|
||||
Git("switch", target).exec(print="Cambiando a rama objetivo")
|
||||
Git("pull").exec(print="Obteniendo cambios")
|
||||
|
||||
|
||||
def _get_pr_title_from_branch(branch: str) -> str:
|
||||
if branch in base.environments:
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
from typing import Optional
|
||||
|
||||
import typer
|
||||
|
||||
import git_flow.command.base as base
|
||||
from git_flow import COMMIT_TYPES, GitFlowError
|
||||
from git_flow import GitFlowError
|
||||
from git_flow.git import Git
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
|
||||
@app.command()
|
||||
def release(group: Optional[str] = None):
|
||||
def release():
|
||||
"""Realiza el merge de la rama, o crea el PR para hacerlo si tiene un remoto configurado"""
|
||||
base.ensure_initialized()
|
||||
|
||||
@@ -44,45 +42,17 @@ def release(group: Optional[str] = None):
|
||||
for commit in commits:
|
||||
print("- " + commit)
|
||||
|
||||
grouping = group is not None
|
||||
|
||||
if not group:
|
||||
if base.io_confirm("¿Desea agrupar este release con otra rama?", False):
|
||||
grouping = True
|
||||
group = base.io_choice(
|
||||
"Grupo release: ", Git.get_branches("release/" + next_env + "/")
|
||||
)
|
||||
else:
|
||||
group = next_env
|
||||
|
||||
if has_remote and Git.get_tracking_branch(group):
|
||||
Git("switch", group).exec(print="Cambiando a la rama objetivo")
|
||||
if has_remote and Git.get_tracking_branch(next_env):
|
||||
Git("switch", next_env).exec(print="Cambiando a la rama objetivo")
|
||||
Git("pull").exec(print="Sincronizando cambios la rama objetivo")
|
||||
Git("switch", "-").exec(print="Volviendo a la rama original")
|
||||
|
||||
if len(commits) > 1 and base.io_confirm(
|
||||
f"¿Desea reemplazar los {len(commits)} commits de la rama por uno solo?", False
|
||||
):
|
||||
base.io_info("Debe ingresar el mensaje del commit a crear.")
|
||||
message = base.get_commit_message(COMMIT_TYPES[:-1])
|
||||
Git("switch", next_branch, create=True).exec(print="Creando rama release")
|
||||
|
||||
Git("switch", next_branch, fork_point, create=True).exec(
|
||||
print="Creando rama release en base"
|
||||
)
|
||||
Git("merge", branch, squash=True).exec(print="Squasheando commits en uno solo")
|
||||
Git("commit", m=message).exec(print="Creando commit único")
|
||||
else:
|
||||
Git("switch", next_branch, create=True).exec(print="Creando rama release")
|
||||
|
||||
status = Git("rebase", fork_point, next_branch, onto=group).code(
|
||||
status = Git("rebase", fork_point, next_branch, onto=next_env).code(
|
||||
print="Moviendo cambios hacia el siguiente ambiente"
|
||||
)
|
||||
|
||||
if status:
|
||||
Git("rebase", abort=True).exec(print="Deshaciendo cambios por conflictos")
|
||||
raise GitFlowError("Error al realizar pasaje de cambios a rama objetivo")
|
||||
elif grouping:
|
||||
Git("switch", group).exec(print="Cambiando a rama objetivo")
|
||||
Git("merge", next_branch, ff_only=True).exec(
|
||||
print="Mergeando cambios de la nueva rama"
|
||||
)
|
||||
|
||||
+29
-17
@@ -1,6 +1,8 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from git_flow import SUPPORTED_REMOTE_APIS, GitFlowError
|
||||
import giturlparse
|
||||
|
||||
from git_flow import SUPPORTED_REMOTE_URL_SCHEMAS, GitFlowError
|
||||
from git_flow.git import Git
|
||||
|
||||
|
||||
@@ -11,25 +13,35 @@ class RemoteAPI(ABC):
|
||||
|
||||
@staticmethod
|
||||
def parse(remote: str):
|
||||
"""Soportamos 3 tipos de URL: http, https y ssh. Si no se especifica el esquema, asumimos ssh:
|
||||
Ejemplos:
|
||||
- http://gitea/user/repo
|
||||
- https://gitea.com/user/repo.git
|
||||
- git@bitbucket.org:user/repo
|
||||
- git.mygiteainstance.com:user/repo.git
|
||||
- ssh://git@git.mygiteainstance.com:1234/user/repo.git
|
||||
"""
|
||||
url = Git("remote", "get-url", remote).firstline()
|
||||
|
||||
if url.startswith("git@"):
|
||||
at_pos = url.index("@")
|
||||
colon_pos = url.index(":")
|
||||
schema = "ssh://"
|
||||
host = url[at_pos+1:colon_pos]
|
||||
repository = url[colon_pos+1:]
|
||||
elif url.startswith("http://") or url.startswith("https://"):
|
||||
start_pos = url.index("://") + 3
|
||||
uri_start = url.index("/", start_pos)
|
||||
schema = url[:start_pos]
|
||||
host = url[start_pos:uri_start]
|
||||
repository = url[uri_start+1:]
|
||||
else:
|
||||
parsed_url = giturlparse.parse(url) # Validamos que la URL sea válida
|
||||
|
||||
if not parsed_url.valid:
|
||||
raise GitFlowError(f"La URL del remoto {remote} es inválida: {url}")
|
||||
|
||||
repository = repository.removesuffix(".git")
|
||||
return [schema, host, repository]
|
||||
if not parsed_url.protocol in SUPPORTED_REMOTE_URL_SCHEMAS:
|
||||
raise GitFlowError(
|
||||
f"La URL del remoto {remote} no tiene un esquema soportado: {url}"
|
||||
)
|
||||
|
||||
# Cuando el host es seteado por pipeline puede ser "http://gitea/OWNER/REPO", en ese caso
|
||||
# la libreria parsea OWNER="" y REPO="OWNER/REPO"
|
||||
repository = (
|
||||
"/".join(filter(None, [parsed_url.owner, parsed_url.repo]))
|
||||
.removeprefix("/")
|
||||
.removesuffix(".git")
|
||||
)
|
||||
|
||||
return [parsed_url.protocol + "://", parsed_url.host, repository]
|
||||
|
||||
@abstractmethod
|
||||
def create_pull_request(
|
||||
@@ -38,7 +50,7 @@ class RemoteAPI(ABC):
|
||||
destination: str,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
close_source_branch: bool = True
|
||||
close_source_branch: bool = True,
|
||||
) -> str:
|
||||
pass
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
from urllib.parse import quote
|
||||
|
||||
import requests
|
||||
from git_flow import GitFlowError
|
||||
from git_flow.remote.base import RemoteAPI
|
||||
|
||||
|
||||
class GitlabRemoteAPI(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("/merge_requests"),
|
||||
headers=self.get_headers(),
|
||||
json={
|
||||
"title": title,
|
||||
"description": description,
|
||||
"source_branch": source,
|
||||
"target_branch": destination,
|
||||
"remove_source_branch": close_source_branch,
|
||||
},
|
||||
)
|
||||
|
||||
if response.ok:
|
||||
return "PR creado exitosamente: " + response.json()["web_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("/repository/tags"),
|
||||
headers=self.get_headers(),
|
||||
json={"tag_name": tag, "ref": 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:
|
||||
project_id = quote(self.repository, safe="")
|
||||
return f"{self.base_url}/api/v4/projects/{project_id}{resource}"
|
||||
|
||||
def get_headers(self):
|
||||
return {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"PRIVATE-TOKEN": self.token,
|
||||
}
|
||||
@@ -70,6 +70,7 @@ wheels = [
|
||||
name = "git-flow-envs"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "giturlparse" },
|
||||
{ name = "questionary" },
|
||||
{ name = "requests" },
|
||||
{ name = "rich" },
|
||||
@@ -78,12 +79,22 @@ dependencies = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "giturlparse" },
|
||||
{ name = "questionary" },
|
||||
{ name = "requests" },
|
||||
{ name = "rich" },
|
||||
{ name = "typer" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "giturlparse"
|
||||
version = "0.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8c/8a/b70b84cc78f9059d627f09560c81a11e8f046570d220a24e169489cad6c9/giturlparse-0.15.0.tar.gz", hash = "sha256:9af3f1fd5c4a0cac94ddb283593635005646393ee0debbe330d1bdff8866bf2c", size = 16138, upload-time = "2026-06-16T07:28:56.021Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/96/147a2771ab655b9353781fb2f95c94eaf1d8576dccc991c1a61d0d355067/giturlparse-0.15.0-py2.py3-none-any.whl", hash = "sha256:76d2e6983b037356ab99b30683e533ac3db96409b68e2163a20fc3aff6446f10", size = 16683, upload-time = "2026-06-16T07:28:55.184Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
|
||||
Reference in New Issue
Block a user