Compare commits
12
Commits
v1.23.3
...
631c507064
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
631c507064 | ||
|
|
b20c7e0cb5 | ||
|
|
6a4f6c9804 | ||
|
|
5ece118c05 | ||
|
|
d383fa4003 | ||
|
|
3a1220f257 | ||
|
|
83dc348fff | ||
|
|
5f65df64e4 | ||
|
|
e4a633e2e2 | ||
|
|
ae81260e38 | ||
|
|
04c05ca8ce | ||
|
|
0d1ce5305b |
@@ -13,37 +13,21 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config --global user.name "gitea-actions"
|
||||
git config --global user.email "actions@git.jonathanteran.dev"
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version-file: ".python-version"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v8.3.2
|
||||
|
||||
- name: Install git-flow and bump version
|
||||
id: version
|
||||
run: |
|
||||
pip install git-flow-envs=="${{ vars.GITFLOWENVS_VERSION }}"
|
||||
git-flow tag --token="${{ secrets.GITEA_TOKEN }}"
|
||||
RESULT="$?"
|
||||
|
||||
if [ "$RESULT" -eq 0 ]; then
|
||||
echo "New tag available."
|
||||
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "No new tag available, skipping next step."
|
||||
echo "should_build=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v8.3.2
|
||||
|
||||
- name: Build and publish
|
||||
if: steps.version.outputs.should_build == 'true'
|
||||
run: |
|
||||
uv build
|
||||
uv publish --token="${{ secrets.PYPI_TOKEN }}"
|
||||
uv publish --token="${{ secrets.PYPI_TOKEN }}"
|
||||
|
||||
@@ -67,9 +67,12 @@ más información):
|
||||
- `new`: Crea una nueva rama siguiendo CB.
|
||||
- `commit`: Crea un commit siguiendo CC.
|
||||
- `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 (ver [Tag](#tag)).
|
||||
- `release`: Prepara la rama actual para pasarse al siguiente entorno configurado.
|
||||
- `branch`: Lista las ramas del repositorio, agrupadas por entorno objetivo.
|
||||
- `branch ls`: Lista las ramas del repositorio, agrupadas por entorno objetivo.
|
||||
- `branch clean`: Elimina las ramas alcanzables desde HEAD (locales por defecto, o remotas con
|
||||
`--remote`), dejando un reporte CSV (`branch-clean-report.csv`) con nombre, commit, autor y
|
||||
fecha de cada rama eliminada.
|
||||
|
||||
El workflow para el cual se penso la herramienta es el siguiente:
|
||||
|
||||
@@ -88,9 +91,30 @@ 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
|
||||
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
|
||||
En cualquier momento se puede usar `git-flow branch ls` para ver en qué entorno está cada rama, y cuáles
|
||||
están todavía en progreso (`wip/`) o descartadas (`trash/`).
|
||||
|
||||
### Tag
|
||||
|
||||
`git-flow tag [--target=<RAMA>] [--token=<TOKEN>] [--update-changelog]`
|
||||
|
||||
- `--target`: rama principal sobre la que se taggea. Por defecto, la rama actual. Debe ser una de las
|
||||
ramas principales configuradas.
|
||||
- `--token`: Access Token del remoto. Si se pasa, se asume que corre en un pipeline: los commits y el
|
||||
último tag se obtienen por API del remoto y el tag se crea en el remoto (y también localmente). Si
|
||||
no, se usa `.repository-token` y todo se resuelve localmente.
|
||||
- `--update-changelog`: genera la entrada correspondiente en `CHANGELOG.md` y la commitea antes de
|
||||
taggear (en un pipeline, además hace push del commit).
|
||||
|
||||
Comportamiento:
|
||||
|
||||
- El tag se crea sobre el último merge (first-parent) de la rama. Si ese merge ya tiene un tag, no se
|
||||
hace nada.
|
||||
- La versión se calcula a partir de los tipos de commit (CC) del último merge. Si ningún commit
|
||||
implica un salto de versión, se avisa y el comando termina con código de salida 1.
|
||||
- Con más de un entorno configurado, cada rama versiona por separado con el nombre del entorno como
|
||||
sufijo (por ejemplo, `v1.2.0-prod`). Con un solo entorno no se agrega sufijo.
|
||||
|
||||
## Remotos soportados
|
||||
|
||||
Cada remoto necesita un Access Token con permisos para crear Pull/Merge Requests y tags. El token se
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
name,commit sha,author,date
|
||||
worktree-kind-watching-hearth,e8c0c0b777d31fa94cd3f01fbf9c68b165d77015,Jonathan Teran Carballo,2026-09-13T23:18:47-03:00
|
||||
|
@@ -0,0 +1,7 @@
|
||||
import typer
|
||||
|
||||
from git_flow.command.branch import clean, ls
|
||||
|
||||
app = typer.Typer(name="branch")
|
||||
app.add_typer(ls.app)
|
||||
app.add_typer(clean.app)
|
||||
@@ -0,0 +1,124 @@
|
||||
import csv
|
||||
import datetime
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated
|
||||
|
||||
import typer
|
||||
|
||||
from git_flow import GitFlowError
|
||||
from git_flow.command import base
|
||||
from git_flow.git import Git
|
||||
|
||||
REPORT_FORMAT = "%(refname:short)\t%(objectname)\t%(author)\t%(authordate:iso-strict)"
|
||||
REPORT_FILENAME = "branch-clean-report.csv"
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
|
||||
@dataclass
|
||||
class BranchInfo:
|
||||
name: str
|
||||
sha: str
|
||||
author: str
|
||||
date: str
|
||||
|
||||
|
||||
@app.command()
|
||||
def clean(
|
||||
remote: Annotated[
|
||||
bool,
|
||||
typer.Option(
|
||||
help="Elimina las ramas remotas alcanzables desde HEAD, en vez de las locales"
|
||||
),
|
||||
] = False,
|
||||
):
|
||||
"""Elimina las ramas alcanzables desde HEAD, dejando un reporte CSV de las ramas eliminadas"""
|
||||
base.ensure_initialized()
|
||||
|
||||
branches = get_remote_branches() if remote else get_local_branches()
|
||||
|
||||
if not branches:
|
||||
base.io_info("No hay ramas alcanzables desde HEAD para eliminar.")
|
||||
return
|
||||
|
||||
base.io_info(
|
||||
"\n".join(f"- {branch.name}" for branch in branches),
|
||||
title=f"Ramas a eliminar ({'remoto' if remote else 'local'})",
|
||||
)
|
||||
|
||||
if not base.io_confirm(f"¿Eliminar {len(branches)} rama(s)?", False):
|
||||
raise GitFlowError("Ejecución abortada")
|
||||
|
||||
report = write_report(branches)
|
||||
|
||||
for branch in branches:
|
||||
delete_branch(branch, remote)
|
||||
|
||||
base.io_success(f"Se eliminaron {len(branches)} rama(s). Reporte generado en {report}")
|
||||
|
||||
|
||||
def get_local_branches() -> list[BranchInfo]:
|
||||
current_branch = Git.get_current_branch()
|
||||
branches = get_merged_branches("refs/heads/")
|
||||
|
||||
return [b for b in branches if b.name != current_branch and b.name not in base.environments]
|
||||
|
||||
|
||||
def get_remote_branches() -> list[BranchInfo]:
|
||||
if "flow.remote" not in base.flowconfig:
|
||||
raise GitFlowError("El repositorio no tiene configurado un remoto.")
|
||||
|
||||
remote = base.flowconfig["flow.remote"]
|
||||
current_tracking = Git.get_tracking_branch(Git.get_current_branch())
|
||||
branches = get_merged_branches(f"refs/remotes/{remote}/")
|
||||
|
||||
result = []
|
||||
for branch in branches:
|
||||
if branch.name == current_tracking or branch.name == f"{remote}/HEAD":
|
||||
continue
|
||||
|
||||
name = branch.name.removeprefix(f"{remote}/")
|
||||
|
||||
if name in base.environments:
|
||||
continue
|
||||
|
||||
result.append(BranchInfo(name, branch.sha, branch.author, branch.date))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_merged_branches(pattern: str) -> list[BranchInfo]:
|
||||
lines = Git("for-each-ref", pattern, merged="HEAD", format=REPORT_FORMAT).lines()
|
||||
|
||||
return [parse_branch(line) for line in lines]
|
||||
|
||||
|
||||
def parse_branch(line: str) -> BranchInfo:
|
||||
name, sha, author, date = line.split("\t")
|
||||
return BranchInfo(name, sha, author, date)
|
||||
|
||||
|
||||
def write_report(branches: list[BranchInfo]) -> str:
|
||||
now = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
|
||||
report = "branch-clean-report-" + now + ".csv"
|
||||
|
||||
with open(report, "w", newline="") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(["name", "commit", "author", "date"])
|
||||
|
||||
for branch in branches:
|
||||
writer.writerow([branch.name, branch.sha, branch.author, branch.date])
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def delete_branch(branch: BranchInfo, remote: bool):
|
||||
if remote:
|
||||
remote_name = base.flowconfig["flow.remote"]
|
||||
Git("push", remote_name, branch.name, delete=True).exec(
|
||||
print=f"Eliminando rama remota '{branch.name}'"
|
||||
)
|
||||
else:
|
||||
Git("branch", branch.name, d=True).exec(
|
||||
print=f"Eliminando rama local '{branch.name}'"
|
||||
)
|
||||
@@ -3,12 +3,12 @@ from typing import Annotated
|
||||
import typer
|
||||
from rich.console import Console
|
||||
|
||||
import git_flow.command.base as base
|
||||
from git_flow import (
|
||||
TRASH_BRANCH_PREFIX,
|
||||
WIP_BRANCH_PREFIX,
|
||||
GitFlowError,
|
||||
)
|
||||
from git_flow.command import base
|
||||
from git_flow.git import Git
|
||||
|
||||
BRANCH_FORMAT = "%(refname:short)"
|
||||
@@ -18,7 +18,7 @@ console = Console()
|
||||
|
||||
|
||||
@app.command()
|
||||
def branch(
|
||||
def ls(
|
||||
environment: Annotated[
|
||||
str | None,
|
||||
typer.Argument(
|
||||
@@ -25,11 +25,6 @@ def tag(target: str | None = None, token: str | None = None, update_changelog: b
|
||||
if target not in base.environments:
|
||||
raise GitFlowError("Solo se pueden taggear commits en ramas principales.")
|
||||
|
||||
token, ci = get_token_and_ci(token)
|
||||
|
||||
if ci and not token:
|
||||
raise GitFlowError("No hay token disponible")
|
||||
|
||||
merge_commit = Git(
|
||||
"log", first_parent=True, merges=True, max_count=1, format="%H"
|
||||
).firstline()
|
||||
@@ -37,6 +32,19 @@ def tag(target: str | None = None, token: str | None = None, update_changelog: b
|
||||
if not merge_commit:
|
||||
raise GitFlowError("No hay merges a taggear.")
|
||||
|
||||
merge_commit_tag = Git("tag", "--points-at", merge_commit).firstline()
|
||||
if merge_commit_tag:
|
||||
base.io_info(f"El último merge ya tiene un tag: {merge_commit_tag}")
|
||||
return
|
||||
|
||||
# Con varios entornos cada uno versiona por separado (v1.0.0-prod), con uno solo no hace falta
|
||||
suffix = f"-{target}" if len(base.environments) > 1 else ""
|
||||
|
||||
token, ci = get_token_and_ci(token)
|
||||
|
||||
if ci and not token:
|
||||
raise GitFlowError("No hay token disponible")
|
||||
|
||||
# Con varios entornos cada uno versiona por separado (v1.0.0-prod), con uno solo no hace falta
|
||||
suffix = f"-{target}" if len(base.environments) > 1 else ""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user