feature: add branch clean command #61
@@ -69,7 +69,10 @@ más información):
|
||||
- `merge`: Mergea la rama actual a una de las ramas principales.
|
||||
- `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,7 +91,7 @@ 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
|
||||
|
||||
@@ -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(
|
||||
Reference in New Issue
Block a user