Files
fn_registry/bash/functions/infra/tbd_branch_finish.sh
T
egutierrez 2a3d780347 feat(doctor): add fn doctor CLI + 14 functions for system management
Adds `fn doctor` read-only diagnostic command with subcommands artefacts,
services, sync, uses-functions, unused, and --json flag for agents.
Each subcommand wraps a registry function in functions/infra/.

New functions:
- artefact_doctor, services_status, pc_locations_drift,
  audit_uses_functions, find_unused_functions (Go diagnostics)
- backup_sqlite_db, rotate_backups, wait_for_http, wait_for_port,
  port_kill, tail_journal, pre_commit_hook_install (bash utilities)
- notify_telegram (Go HTTP)
- backup_all pipeline (tag launcher)

Plus prior session leftovers (scan_secrets_in_dirty, append_diary_entry,
git utilities, http_session_cookie_middleware, compile/full-git pipelines).

Fixes pc_locations_drift filepath.Join bug with absolute dir_path.
Documents fn doctor in CLAUDE.md, .claude/rules/fn_doctor.md (rule 23),
docs/architecture.md, CHANGELOG.md (2026-05-07), and diary entry.

First fn doctor uses-functions run found drift in 7/12 apps (deuda
para sincronizar app.md con imports reales).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 01:42:10 +02:00

312 lines
9.6 KiB
Bash

#!/usr/bin/env bash
# tbd_branch_finish — integra rama TBD a master/main y publica
#
# Uso:
# tbd_branch_finish [<merge_title>]
#
# Opciones especiales:
# --test ejecutar suite de tests internos y salir
set -euo pipefail
# ---------------------------------------------------------------------------
# helpers
# ---------------------------------------------------------------------------
_tbd_finish_detect_base() {
if git show-ref --verify --quiet refs/heads/master 2>/dev/null; then
echo "master"
elif git show-ref --verify --quiet refs/heads/main 2>/dev/null; then
echo "main"
else
echo ""
fi
}
_tbd_finish_require_git_repo() {
if ! git rev-parse --git-dir > /dev/null 2>&1; then
echo "ERROR: el directorio actual no es un repositorio git." >&2
return 1
fi
}
# ---------------------------------------------------------------------------
# funcion principal
# ---------------------------------------------------------------------------
tbd_branch_finish() {
local merge_title="${1:-}"
# Verificar repo git
_tbd_finish_require_git_repo
# Detectar rama actual
local branch
branch=$(git branch --show-current)
# Validar que es una rama TBD (issue/* o quick/*)
if [[ "$branch" != issue/* && "$branch" != quick/* ]]; then
if [[ "$branch" == "master" || "$branch" == "main" ]]; then
echo "ERROR: ya estas en ${branch}, nada que mergear." >&2
return 1
else
echo "ERROR: la rama actual '${branch}' no es una rama TBD (issue/* o quick/*)." >&2
return 1
fi
fi
# Verificar working tree limpio
local dirty
dirty=$(git status --porcelain)
if [[ -n "$dirty" ]]; then
echo "ERROR: hay cambios sin commitear. Haz commit antes de mergear." >&2
return 1
fi
# Detectar rama base
local base
base=$(_tbd_finish_detect_base)
if [[ -z "$base" ]]; then
echo "ERROR: no se encontro rama 'master' ni 'main' en el repo local." >&2
return 1
fi
# Componer titulo del merge commit
local title
if [[ -n "$merge_title" ]]; then
title="merge: ${branch}${merge_title}"
else
title="merge: ${branch}"
fi
# Cambiar a base y actualizar
echo "Cambiando a ${base}..."
git checkout "$base"
echo "Actualizando ${base}..."
git pull --rebase
# Merge --no-ff
echo "Mergeando ${branch} en ${base}..."
local merge_exit=0
git merge --no-ff "$branch" -m "$title" || merge_exit=$?
if [[ $merge_exit -ne 0 ]]; then
echo "merge conflict: resolver manualmente y continuar (git add + git commit)." >&2
return 2
fi
# Push
echo "Publicando ${base}..."
local push_exit=0
git push || push_exit=$?
if [[ $push_exit -ne 0 ]]; then
echo "ERROR: git push fallo (exit ${push_exit}). La rama '${branch}' NO fue eliminada." >&2
return 1
fi
# Eliminar rama local
git branch -d "$branch"
echo "Rama '${branch}' integrada a ${base} y publicada. Rama local eliminada."
return 0
}
# ---------------------------------------------------------------------------
# modo --test
# ---------------------------------------------------------------------------
_tbd_branch_finish_tests() {
local PASS=0
local FAIL=0
_assert_eq() {
local name="$1" expected="$2" got="$3"
if [[ "$expected" == "$got" ]]; then
echo "PASS: $name"
PASS=$((PASS + 1))
else
echo "FAIL: $name — expected '${expected}', got '${got}'"
FAIL=$((FAIL + 1))
fi
}
_assert_exit() {
local name="$1" expected_exit="$2"
shift 2
local got_exit=0
"$@" > /dev/null 2>&1 || got_exit=$?
if [[ "$expected_exit" == "$got_exit" ]]; then
echo "PASS: $name (exit ${got_exit})"
PASS=$((PASS + 1))
else
echo "FAIL: $name — expected exit ${expected_exit}, got ${got_exit}"
FAIL=$((FAIL + 1))
fi
}
# ---------------------------------------------------------------------------
# Setup: bare remote + repo de trabajo
# ---------------------------------------------------------------------------
local tmpdir
tmpdir=$(mktemp -d)
trap "rm -rf '$tmpdir'" EXIT
local bare="$tmpdir/remote.git"
local work="$tmpdir/work"
# Crear remote bare
git -c init.defaultBranch=master init --bare -q "$bare"
# Clonar para tener origin
git clone -q "$bare" "$work"
(
cd "$work"
git config user.email "test@test.com"
git config user.name "Test"
echo "init" > README.md
git add README.md
git commit -q -m "chore: init"
git push -q -u origin master
)
# ---------------------------------------------------------------------------
# Test: merge issue branch sin titulo
# ---------------------------------------------------------------------------
(
cd "$work"
git checkout -q -b issue/0001-add-feature
echo "feature" > feature.txt
git add feature.txt
git commit -q -m "feat: add feature"
)
local finish_exit=0
(
cd "$work"
tbd_branch_finish
) > /dev/null 2>&1 || finish_exit=$?
local current
current=$(cd "$work" && git branch --show-current)
_assert_eq "finish lands on base branch" "master" "$current"
local branch_gone=0
(cd "$work" && git show-ref --verify --quiet refs/heads/issue/0001-add-feature) && branch_gone=1 || branch_gone=0
_assert_eq "issue branch deleted locally" "0" "$branch_gone"
_assert_eq "finish exits 0" "0" "$finish_exit"
# Verificar merge commit en remote
local remote_log
remote_log=$(cd "$work" && git log --oneline -2 origin/master)
local has_merge=0
[[ "$remote_log" == *"merge: issue/0001-add-feature"* ]] && has_merge=1 || has_merge=0
_assert_eq "merge commit pushed to remote" "1" "$has_merge"
# ---------------------------------------------------------------------------
# Test: merge quick branch con titulo
# ---------------------------------------------------------------------------
(
cd "$work"
git checkout -q -b quick/fix-typo
echo "fix" > fix.txt
git add fix.txt
git commit -q -m "fix: typo"
)
(
cd "$work"
tbd_branch_finish "corregir typo en README"
) > /dev/null 2>&1
current=$(cd "$work" && git branch --show-current)
_assert_eq "quick branch finish lands on master" "master" "$current"
remote_log=$(cd "$work" && git log --oneline -2 origin/master)
local has_title=0
[[ "$remote_log" == *"merge: quick/fix-typo — corregir typo en README"* ]] && has_title=1 || has_title=0
_assert_eq "merge title included in commit" "1" "$has_title"
# ---------------------------------------------------------------------------
# Test: dirty tree → exit 1
# ---------------------------------------------------------------------------
(
cd "$work"
git checkout -q -b quick/dirty-test
)
echo "dirty" > "$work/dirty.txt"
_assert_exit "dirty tree exits 1" 1 bash -c "
cd '$work'
source '${BASH_SOURCE[0]}'
tbd_branch_finish
"
rm -f "$work/dirty.txt"
(cd "$work" && git checkout -q master)
(cd "$work" && git branch -D quick/dirty-test 2>/dev/null || true)
# ---------------------------------------------------------------------------
# Test: ya en master → exit 1
# ---------------------------------------------------------------------------
_assert_exit "on master exits 1" 1 bash -c "
cd '$work'
source '${BASH_SOURCE[0]}'
tbd_branch_finish
"
# ---------------------------------------------------------------------------
# Test: rama no TBD → exit 1
# ---------------------------------------------------------------------------
(cd "$work" && git checkout -q -b feature/non-tbd 2>/dev/null)
_assert_exit "non-TBD branch exits 1" 1 bash -c "
cd '$work'
source '${BASH_SOURCE[0]}'
tbd_branch_finish
"
(cd "$work" && git checkout -q master && git branch -D feature/non-tbd 2>/dev/null || true)
# ---------------------------------------------------------------------------
# Test: funciona con 'main' como rama base
# ---------------------------------------------------------------------------
local tmpdir2
tmpdir2=$(mktemp -d)
local bare2="$tmpdir2/remote.git"
local work2="$tmpdir2/work"
git -c init.defaultBranch=main init --bare -q "$bare2"
git clone -q "$bare2" "$work2"
(
cd "$work2"
git config user.email "test@test.com"
git config user.name "Test"
echo "init" > README.md
git add README.md
git commit -q -m "chore: init"
git push -q -u origin main
git checkout -q -b quick/use-main
echo "x" > x.txt
git add x.txt
git commit -q -m "feat: x"
tbd_branch_finish
) > /dev/null 2>&1
current=$(cd "$work2" && git branch --show-current)
_assert_eq "works with main as base" "main" "$current"
rm -rf "$tmpdir2"
echo "---"
echo "Results: ${PASS} passed, ${FAIL} failed"
[[ $FAIL -eq 0 ]] || exit 1
}
# ---------------------------------------------------------------------------
# entry point
# ---------------------------------------------------------------------------
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
if [[ "${1:-}" == "--test" ]]; then
_tbd_branch_finish_tests
else
tbd_branch_finish "$@"
fi
fi