Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d7fb59d39 | ||
|
|
0df2720f89 |
@@ -8,10 +8,11 @@ jobs:
|
|||||||
Publish-to-PyPI:
|
Publish-to-PyPI:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Check out repository code
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
fetch-tags: true
|
||||||
|
|
||||||
- name: Configure git
|
- name: Configure git
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -1,64 +1,3 @@
|
|||||||
## v1.20.2 - 2026-09-15
|
|
||||||
|
|
||||||
- **bugfix**: include group path in repo for gitlab support [[jteran](mailto:jteran@renatre.org.ar)] (3e44854)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
## 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
|
## v0.0.1-main - 2026-07-18
|
||||||
|
|
||||||
- **bugfix**: add fetch tags to checkout step [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (f9fb5a6)
|
- **bugfix**: add fetch tags to checkout step [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (f9fb5a6)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ carpeta local, y lo instalamos:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd ~/Downloads
|
cd ~/Downloads
|
||||||
git clone ssh://git@git.jonathanteran.dev:59169/jt/git-flow.git
|
git clone git@bitbucket.org:sushi2717/git-flow.git
|
||||||
cd git-flow
|
cd git-flow
|
||||||
pipx install .
|
pipx install .
|
||||||
```
|
```
|
||||||
@@ -49,27 +49,24 @@ El comando solicitará 2 opciones:
|
|||||||
- [x] Bitbucket
|
- [x] Bitbucket
|
||||||
- [x] Github
|
- [x] Github
|
||||||
- [x] Gitea
|
- [x] Gitea
|
||||||
- [x] Gitlab
|
|
||||||
|
|
||||||
La configuración del remoto requiere tener un Access Token para poder crear los PRs y tags
|
La configuración del remoto requiere tener un Access Token para poder crear los PRs automáticamente.
|
||||||
automáticamente. Ver la sección [Remotos soportados](#remotos-soportados) para las instrucciones
|
El mismo se puede crear desde el repositorio al que se quiere dar acceso: *"Repository settings" >
|
||||||
de cómo generar el token en cada proveedor.
|
"Access Tokens" > "Create access token"*. El token necesita permiso para *Write* en *Pull Requests*.
|
||||||
|
|
||||||
Una vez generado, el token se debe guardar dentro del repositorio a configurar, en el archivo
|
Luego se debe guardar el token dentro del repositorio a configurar, en el archivo
|
||||||
`.repository-token`. De no especificarse, los merges y tags se harán de forma local.
|
`.repository-token`. De no especificarse, los merges y tags se harán de forma local.
|
||||||
|
|
||||||
## Utilización
|
## Utilización
|
||||||
|
|
||||||
Git Flow cuenta con los siguientes comandos para guiar el workflow (ver `git-flow <command> -h` para
|
Git Flow cuenta con 5 comandos principales para guiar el workflow (ver `git-flow <command> -h` para
|
||||||
más información):
|
más información):
|
||||||
|
|
||||||
- `init`: Inicializa el repositorio para utilizar git-flow (ramas principales y remoto).
|
|
||||||
- `new`: Crea una nueva rama siguiendo CB.
|
- `new`: Crea una nueva rama siguiendo CB.
|
||||||
- `commit`: Crea un commit siguiendo CC.
|
- `commit`: Crea un commit siguiendo CC.
|
||||||
- `merge`: Mergea la rama actual a una de las ramas principales.
|
- `merge`: Mergea la rama actual a una de las ramas principales.
|
||||||
- `tag`: Crea un tag sobre el ultimo merge siguiendo SemVer.
|
- `tag`: Crea un tag sobre el ultimo merge siguiendo SemVer.
|
||||||
- `release`: Prepara la rama actual para pasarse al siguiente entorno configurado.
|
- `release`: Prepara la rama actual para pasarse al siguiente entorno configurado
|
||||||
- `branch`: Lista las ramas del repositorio, agrupadas por entorno objetivo.
|
|
||||||
|
|
||||||
El workflow para el cual se penso la herramienta es el siguiente:
|
El workflow para el cual se penso la herramienta es el siguiente:
|
||||||
|
|
||||||
@@ -88,147 +85,9 @@ El workflow para el cual se penso la herramienta es el siguiente:
|
|||||||
`feature/my-new-feature`, se crea la rama `release/test/feature/my-new-feature` sobre `test` que
|
`feature/my-new-feature`, se crea la rama `release/test/feature/my-new-feature` sobre `test` que
|
||||||
tiene los cambios de la rama original.
|
tiene los cambios de la rama original.
|
||||||
|
|
||||||
En cualquier momento se puede usar `git-flow branch` para ver en qué entorno está cada rama, y cuáles
|
|
||||||
están todavía en progreso (`wip/`) o descartadas (`trash/`).
|
|
||||||
|
|
||||||
## Remotos soportados
|
|
||||||
|
|
||||||
Cada remoto necesita un Access Token con permisos para crear Pull/Merge Requests y tags. El token se
|
|
||||||
guarda en el archivo `.repository-token` en la raíz del repositorio (una única línea con el valor del
|
|
||||||
token), o se pasa por parámetro con `--token=<TOKEN>` al comando `git-flow tag` (útil para no
|
|
||||||
commitear el token y usarlo solo desde un pipeline mediante una variable secreta).
|
|
||||||
|
|
||||||
### Github
|
|
||||||
|
|
||||||
1. Ir a *Settings* del repositorio (o de la organización) > *Developer settings* > *Personal access
|
|
||||||
tokens* > *Fine-grained tokens* > *Generate new token*.
|
|
||||||
2. Seleccionar el repositorio sobre el que se va a usar `git-flow`.
|
|
||||||
3. En *Repository permissions*, otorgar:
|
|
||||||
- **Contents**: Read and write (necesario para crear tags).
|
|
||||||
- **Pull requests**: Read and write (necesario para crear PRs).
|
|
||||||
4. Generar el token y guardarlo en `.repository-token`.
|
|
||||||
|
|
||||||
Ejemplo de pipeline (GitHub Actions) para taggear automáticamente en cada push a una rama principal:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# .github/workflows/tag.yml
|
|
||||||
name: Tag and publish
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
tag:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: astral-sh/setup-uv@v3
|
|
||||||
|
|
||||||
- run: uv run git-flow tag --token=${{ secrets.GIT_FLOW_TOKEN }}
|
|
||||||
|
|
||||||
- run: uv build
|
|
||||||
- run: uv publish --token=${{ secrets.PYPI_TOKEN }}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Gitlab
|
|
||||||
|
|
||||||
1. Ir a *Settings* > *Access Tokens* del proyecto (o *Group access tokens* si se quiere usar a nivel
|
|
||||||
grupo).
|
|
||||||
2. Crear un token con rol **Developer** o superior, y los scopes:
|
|
||||||
- **api** (o al menos `write_repository` para tags y `api` para Merge Requests).
|
|
||||||
3. Generar el token y guardarlo en `.repository-token`.
|
|
||||||
|
|
||||||
Ejemplo de pipeline (GitLab CI) para taggear automáticamente en cada push a una rama principal:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# .gitlab-ci.yml
|
|
||||||
tag:
|
|
||||||
image: python:3.14
|
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_BRANCH == "main"
|
|
||||||
script:
|
|
||||||
- pip install uv
|
|
||||||
- uv run git-flow tag --token=$GIT_FLOW_TOKEN
|
|
||||||
- uv build
|
|
||||||
- uv publish --token=$PYPI_TOKEN
|
|
||||||
```
|
|
||||||
|
|
||||||
El token debe guardarse como variable protegida y enmascarada (`GIT_FLOW_TOKEN`) en *Settings* >
|
|
||||||
*CI/CD* > *Variables*.
|
|
||||||
|
|
||||||
### Bitbucket
|
|
||||||
|
|
||||||
1. Ir a *Repository settings* > *Access Tokens* > *Create access token* (o *Workspace settings* >
|
|
||||||
*Access Tokens* para un token a nivel workspace).
|
|
||||||
2. Otorgar permiso de **Write** en *Pull requests* y *Repositories* (necesario para crear tags).
|
|
||||||
3. Generar el token y guardarlo en `.repository-token`.
|
|
||||||
|
|
||||||
Este mismo repositorio usa Git Flow para el taggeo automático usando Bitbucket Pipelines, se puede
|
|
||||||
ver el archivo `bitbucket-pipelines.yml` como ejemplo:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# bitbucket-pipelines.yml
|
|
||||||
image: python:3.14
|
|
||||||
|
|
||||||
pipelines:
|
|
||||||
branches:
|
|
||||||
'main':
|
|
||||||
- step:
|
|
||||||
name: "Tag and publish package"
|
|
||||||
caches:
|
|
||||||
- pip
|
|
||||||
script:
|
|
||||||
- pip install uv
|
|
||||||
- uv run git-flow tag --token=$BEARER
|
|
||||||
- uv build
|
|
||||||
- uv publish --token=$PYPI_TOKEN
|
|
||||||
```
|
|
||||||
|
|
||||||
El token se configura como variable segura del repositorio (`BEARER`) en *Repository settings* >
|
|
||||||
*Repository variables*.
|
|
||||||
|
|
||||||
### Gitea
|
|
||||||
|
|
||||||
1. Ir a *Settings* del usuario (o de la organización) > *Applications* > *Manage Access Tokens* >
|
|
||||||
*Generate New Token*.
|
|
||||||
2. Otorgar permiso de escritura sobre:
|
|
||||||
- **repository** (necesario para crear tags).
|
|
||||||
- **issue** (necesario para crear PRs, ya que Gitea expone los PRs bajo el mismo scope).
|
|
||||||
3. Generar el token y guardarlo en `.repository-token`.
|
|
||||||
|
|
||||||
Ejemplo de pipeline (Gitea Actions) para taggear automáticamente en cada push a una rama principal:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# .gitea/workflows/tag.yml
|
|
||||||
name: Tag and publish
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
tag:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: astral-sh/setup-uv@v3
|
|
||||||
|
|
||||||
- run: uv run git-flow tag --token=${{ secrets.GIT_FLOW_TOKEN }}
|
|
||||||
|
|
||||||
- run: uv build
|
|
||||||
- run: uv publish --token=${{ secrets.PYPI_TOKEN }}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Taggeo automático por pipeline
|
## Taggeo automático por pipeline
|
||||||
|
|
||||||
En todos los casos, `git-flow tag` detecta que se está ejecutando desde un pipeline (no encuentra
|
Éste repositorio utiliza Git Flow para el taggeo automático usando pipelines, se puede ver el
|
||||||
`.repository-token` en el repositorio) y usa el token pasado por `--token=<TOKEN>` tanto para crear
|
archivo `bitbucket-pipelines.yml` como ejemplo. El comando `git-flow tag` soporta el pasaje del
|
||||||
el tag en el remoto como para pushear el commit del `CHANGELOG.md` generado, en vez de crear el tag
|
token mediante la opción `--token=<TOKEN>`. Ver la sección [Configuración](#configuracion) para las
|
||||||
únicamente en forma local como sucede al ejecutarlo manualmente sin remoto configurado.
|
instrucciones de como generar este token. El mismo necesita scope `write:repository:bitbucket`.
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ description = "Git workflow automation to follow best practices when working wit
|
|||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
requires-python = ">= 3.14"
|
requires-python = ">= 3.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"requests", "typer", "rich", "questionary", "giturlparse"
|
"requests", "typer", "rich", "questionary"
|
||||||
]
|
]
|
||||||
authors = [
|
authors = [
|
||||||
{name = "Alan Facundo Biglieri", email = "abiglieri@renatre.org.ar"},
|
{name = "Alan Facundo Biglieri", email = "abiglieri@renatre.org.ar"},
|
||||||
|
|||||||
@@ -36,21 +36,18 @@ COMMIT_TYPE_INCREMENT = {
|
|||||||
"feature": SEMVER_MINOR,
|
"feature": SEMVER_MINOR,
|
||||||
"bugfix": SEMVER_PATCH,
|
"bugfix": SEMVER_PATCH,
|
||||||
"hotfix": SEMVER_PATCH,
|
"hotfix": SEMVER_PATCH,
|
||||||
"refactor": SEMVER_PATCH,
|
"refactor": SEMVER_SKIP,
|
||||||
"perf": SEMVER_PATCH,
|
"perf": SEMVER_SKIP,
|
||||||
"docs": SEMVER_SKIP,
|
"docs": SEMVER_SKIP,
|
||||||
"style": SEMVER_SKIP,
|
"style": SEMVER_SKIP,
|
||||||
"test": SEMVER_SKIP,
|
"test": SEMVER_SKIP,
|
||||||
}
|
}
|
||||||
|
|
||||||
SUPPORTED_REMOTE_URL_SCHEMAS = [
|
SUPPORTED_REMOTE_APIS = [
|
||||||
"ssh",
|
"bitbucket.org",
|
||||||
"http",
|
"github.com",
|
||||||
"https"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
REPOSITORY_TOKEN_FILENAME = ".repository-token"
|
REPOSITORY_TOKEN_FILENAME = ".repository-token"
|
||||||
|
|
||||||
FLOWCONFIG_FILENAME = ".flowconfig"
|
|
||||||
|
|
||||||
FLOWCONFIG_VERSION = 2
|
FLOWCONFIG_VERSION = 2
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from git_flow import (
|
from git_flow import (
|
||||||
BRANCH_TYPES,
|
BRANCH_TYPES,
|
||||||
REPOSITORY_TOKEN_FILENAME,
|
REPOSITORY_TOKEN_FILENAME,
|
||||||
FLOWCONFIG_FILENAME,
|
|
||||||
FLOWCONFIG_VERSION,
|
FLOWCONFIG_VERSION,
|
||||||
COMMIT_TYPES,
|
COMMIT_TYPES,
|
||||||
GitFlowError,
|
GitFlowError,
|
||||||
@@ -11,7 +10,6 @@ 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.remote.gitea import GiteaRemoteAPI
|
||||||
from git_flow.remote.gitlab import GitlabRemoteAPI
|
|
||||||
from git_flow.io import *
|
from git_flow.io import *
|
||||||
from os.path import isfile
|
from os.path import isfile
|
||||||
|
|
||||||
@@ -20,7 +18,7 @@ import rich
|
|||||||
import rich.panel
|
import rich.panel
|
||||||
import questionary
|
import questionary
|
||||||
|
|
||||||
flowconfig = Git.get_config(FLOWCONFIG_FILENAME) if isfile(FLOWCONFIG_FILENAME) else {}
|
flowconfig = Git.get_config()
|
||||||
environments = (
|
environments = (
|
||||||
flowconfig["flow.branches"].split(",") if "flow.branches" in flowconfig else []
|
flowconfig["flow.branches"].split(",") if "flow.branches" in flowconfig else []
|
||||||
)
|
)
|
||||||
@@ -124,9 +122,6 @@ def get_remote_api(token: str) -> RemoteAPI:
|
|||||||
if remote_type == "gitea":
|
if remote_type == "gitea":
|
||||||
remote_schema = "https://" if schema == "ssh://" else schema
|
remote_schema = "https://" if schema == "ssh://" else schema
|
||||||
return GiteaRemoteAPI(remote_schema + host, repository, token)
|
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":
|
elif remote_type == "bitbucket":
|
||||||
return BitbucketRemoteAPI(repository, token)
|
return BitbucketRemoteAPI(repository, token)
|
||||||
elif remote_type == "github":
|
elif remote_type == "github":
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import typer
|
import typer
|
||||||
|
|
||||||
import git_flow.command.base as base
|
import git_flow.command.base as base
|
||||||
from git_flow import FLOWCONFIG_FILENAME, FLOWCONFIG_VERSION, GitFlowError
|
from git_flow import FLOWCONFIG_VERSION, GitFlowError
|
||||||
from git_flow.git import Git
|
from git_flow.git import Git
|
||||||
|
|
||||||
app = typer.Typer()
|
app = typer.Typer()
|
||||||
@@ -27,9 +27,7 @@ def init():
|
|||||||
flowconfig["flow.remote"] = remote
|
flowconfig["flow.remote"] = remote
|
||||||
flowconfig["flow.remote-type"] = remote_type
|
flowconfig["flow.remote-type"] = remote_type
|
||||||
|
|
||||||
Git.set_config(flowconfig, FLOWCONFIG_FILENAME)
|
Git.set_config(flowconfig)
|
||||||
Git("add", FLOWCONFIG_FILENAME).exec()
|
|
||||||
Git("commit", message="feature: initialize git-flow").exec()
|
|
||||||
|
|
||||||
_ensure_all_flow_branches_exist(branches, remote)
|
_ensure_all_flow_branches_exist(branches, remote)
|
||||||
|
|
||||||
@@ -101,7 +99,7 @@ uno nuevo.""",
|
|||||||
remote = base.io_choice("Remoto", remotes)
|
remote = base.io_choice("Remoto", remotes)
|
||||||
|
|
||||||
remote_type = base.io_choice(
|
remote_type = base.io_choice(
|
||||||
"Tipo de remoto:", ["bitbucket", "github", "gitea", "gitlab"]
|
"Tipo de remoto:", ["bitbucket", "github", "gitea"]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (remote, remote_type)
|
return (remote, remote_type)
|
||||||
|
|||||||
@@ -119,6 +119,10 @@ def create_pull_request(token: str, branch: str, target: str):
|
|||||||
|
|
||||||
base.io_success(message, "Rama creada")
|
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:
|
def _get_pr_title_from_branch(branch: str) -> str:
|
||||||
if branch in base.environments:
|
if branch in base.environments:
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
import typer
|
import typer
|
||||||
|
|
||||||
import git_flow.command.base as base
|
import git_flow.command.base as base
|
||||||
from git_flow import GitFlowError
|
from git_flow import COMMIT_TYPES, GitFlowError
|
||||||
from git_flow.git import Git
|
from git_flow.git import Git
|
||||||
|
|
||||||
app = typer.Typer()
|
app = typer.Typer()
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def release():
|
def release(group: Optional[str] = None):
|
||||||
"""Realiza el merge de la rama, o crea el PR para hacerlo si tiene un remoto configurado"""
|
"""Realiza el merge de la rama, o crea el PR para hacerlo si tiene un remoto configurado"""
|
||||||
base.ensure_initialized()
|
base.ensure_initialized()
|
||||||
|
|
||||||
@@ -42,17 +44,45 @@ def release():
|
|||||||
for commit in commits:
|
for commit in commits:
|
||||||
print("- " + commit)
|
print("- " + commit)
|
||||||
|
|
||||||
if has_remote and Git.get_tracking_branch(next_env):
|
grouping = group is not None
|
||||||
Git("switch", next_env).exec(print="Cambiando a la rama objetivo")
|
|
||||||
|
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")
|
||||||
Git("pull").exec(print="Sincronizando cambios la rama objetivo")
|
Git("pull").exec(print="Sincronizando cambios la rama objetivo")
|
||||||
Git("switch", "-").exec(print="Volviendo a la rama original")
|
Git("switch", "-").exec(print="Volviendo a la rama original")
|
||||||
|
|
||||||
Git("switch", next_branch, create=True).exec(print="Creando rama release")
|
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])
|
||||||
|
|
||||||
status = Git("rebase", fork_point, next_branch, onto=next_env).code(
|
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(
|
||||||
print="Moviendo cambios hacia el siguiente ambiente"
|
print="Moviendo cambios hacia el siguiente ambiente"
|
||||||
)
|
)
|
||||||
|
|
||||||
if status:
|
if status:
|
||||||
Git("rebase", abort=True).exec(print="Deshaciendo cambios por conflictos")
|
Git("rebase", abort=True).exec(print="Deshaciendo cambios por conflictos")
|
||||||
raise GitFlowError("Error al realizar pasaje de cambios a rama objetivo")
|
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"
|
||||||
|
)
|
||||||
|
|||||||
+1
-6
@@ -1,6 +1,5 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from git_flow import FLOWCONFIG_FILENAME
|
|
||||||
from git_flow.io import *
|
from git_flow.io import *
|
||||||
|
|
||||||
|
|
||||||
@@ -33,11 +32,7 @@ class Git:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def set_config(config: dict[str, str], file: str | None = None):
|
def set_config(config: dict[str, str], file: str | None = None):
|
||||||
for key, value in config.items():
|
for key, value in config.items():
|
||||||
Git("config", key, value, file=file).exec()
|
Git("config", key, value, file=file).exec(print="Updating config")
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def flow_config(*args, **kwargs):
|
|
||||||
return Git("config", *args, file=FLOWCONFIG_FILENAME, **kwargs)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_current_branch():
|
def get_current_branch():
|
||||||
|
|||||||
+17
-36
@@ -1,8 +1,6 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
import giturlparse
|
from git_flow import SUPPORTED_REMOTE_APIS, GitFlowError
|
||||||
|
|
||||||
from git_flow import SUPPORTED_REMOTE_URL_SCHEMAS, GitFlowError
|
|
||||||
from git_flow.git import Git
|
from git_flow.git import Git
|
||||||
|
|
||||||
|
|
||||||
@@ -13,42 +11,25 @@ class RemoteAPI(ABC):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse(remote: str):
|
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()
|
url = Git("remote", "get-url", remote).firstline()
|
||||||
|
|
||||||
parsed_url = giturlparse.parse(url) # Validamos que la URL sea válida
|
if url.startswith("git@"):
|
||||||
|
at_pos = url.index("@")
|
||||||
if not parsed_url.valid:
|
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:
|
||||||
raise GitFlowError(f"La URL del remoto {remote} es inválida: {url}")
|
raise GitFlowError(f"La URL del remoto {remote} es inválida: {url}")
|
||||||
|
|
||||||
if not parsed_url.protocol in SUPPORTED_REMOTE_URL_SCHEMAS:
|
repository = repository.removesuffix(".git")
|
||||||
raise GitFlowError(
|
return [schema, host, repository]
|
||||||
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"
|
|
||||||
# En GitLab, los proyectos pueden estar dentro de grupos y subgrupos (ej:
|
|
||||||
# owner/grupo/subgrupo/repo), que la libreria expone por separado en groups_path
|
|
||||||
repository = (
|
|
||||||
"/".join(
|
|
||||||
filter(
|
|
||||||
None,
|
|
||||||
[parsed_url.owner, parsed_url.groups_path, parsed_url.repo],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.removeprefix("/")
|
|
||||||
.removesuffix(".git")
|
|
||||||
)
|
|
||||||
|
|
||||||
return [parsed_url.protocol + "://", parsed_url.host, repository]
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def create_pull_request(
|
def create_pull_request(
|
||||||
@@ -57,7 +38,7 @@ class RemoteAPI(ABC):
|
|||||||
destination: str,
|
destination: str,
|
||||||
title: str | None = None,
|
title: str | None = None,
|
||||||
description: str | None = None,
|
description: str | None = None,
|
||||||
close_source_branch: bool = True,
|
close_source_branch: bool = True
|
||||||
) -> str:
|
) -> str:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
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,7 +70,6 @@ wheels = [
|
|||||||
name = "git-flow-envs"
|
name = "git-flow-envs"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "giturlparse" },
|
|
||||||
{ name = "questionary" },
|
{ name = "questionary" },
|
||||||
{ name = "requests" },
|
{ name = "requests" },
|
||||||
{ name = "rich" },
|
{ name = "rich" },
|
||||||
@@ -79,22 +78,12 @@ dependencies = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "giturlparse" },
|
|
||||||
{ name = "questionary" },
|
{ name = "questionary" },
|
||||||
{ name = "requests" },
|
{ name = "requests" },
|
||||||
{ name = "rich" },
|
{ name = "rich" },
|
||||||
{ name = "typer" },
|
{ 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]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.11"
|
version = "3.11"
|
||||||
|
|||||||
Reference in New Issue
Block a user