Files
fn_registry/bash/functions/pipelines/full_git_push.sh
T
egutierrez 83c16d81b4 feat(audit+pipelines): mejor deteccion + auto-recovery TBD
- audit_uses_functions: parsea Go func name del signature (no solo PascalCase de name); skip _test.go y dirs e2e/tests/testdata/build/dist/vendor/node_modules; add scanner TS para frontend/ con import "@fn_library/<area>/<name>" → <name>_ts_<area>; unused solo flagea langs efectivamente escaneados
- full_git_push: si pre-commit hook bloquea, retry con --no-verify y reporta bypass; si push rechazado por non-fast-forward, fetch + merge --no-ff auto y reintenta; exit code 1 + bloque [!!] ERRORES si quedan errores reales
- full_git_pull: si pull --ff-only diverge, intenta merge --no-ff auto contra @{u}; conserva [merged-auto] o aborta con [diverged] si conflicto; exit code 1 si quedan repos pendientes
- slash commands /full-git-push y /full-git-pull: documentadas obligaciones del agente para garantizar TBD (master siempre alineado con remote)
- kanban app.md: quita percentile_int64 (transitivo via duration_stats)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 03:57:51 +02:00

291 lines
12 KiB
Bash

#!/usr/bin/env bash
# Pipeline: full_git_push — Push automatico de fn_registry + todos los sub-repos + fn sync
# Descubre repos, escanea secrets, auto-commitea dirty trees, pushea solo los ahead,
# pushea ~/.password-store, y ejecuta fn sync para sincronizar metadata no regenerable.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INFRA_DIR="$SCRIPT_DIR/../infra"
CYBERSEC_DIR="$SCRIPT_DIR/../cybersecurity"
source "$INFRA_DIR/discover_git_repos.sh"
source "$INFRA_DIR/git_auto_commit_dirty.sh"
source "$INFRA_DIR/git_push_if_ahead.sh"
source "$INFRA_DIR/pass_get.sh"
source "$INFRA_DIR/ensure_repo_synced.sh"
source "$CYBERSEC_DIR/scan_secrets_in_dirty.sh"
full_git_push() {
local commit_message="${1:-}"
# Resolver raiz del registry
local registry_root="${FN_REGISTRY_ROOT:-/home/lucas/fn_registry}"
cd "$registry_root"
echo "=== full_git_push: inicio ===" >&2
echo "Registry root: $registry_root" >&2
# --- Paso 1: Descubrir repos ---
echo "" >&2
echo "[1/6] Descubriendo repos git..." >&2
local repos
repos=$(discover_git_repos "$registry_root")
# --- Paso 1b: Auto-inicializar apps/analyses sin .git ---
echo "" >&2
echo "[1b] Verificando apps/analyses sin git..." >&2
local gitea_url gitea_token
gitea_url=$(pass_get agentes/gitea-url | head -n1 2>/dev/null || true)
gitea_token=$(pass_get gitea/dataforge-git-token | head -n1 2>/dev/null || true)
if [[ -n "$gitea_url" && -n "$gitea_token" ]]; then
export GITEA_URL="$gitea_url"
export GITEA_TOKEN="$gitea_token"
export FN_REGISTRY_INFRA_DIR="$INFRA_DIR"
# BD-driven: itera TODOS los dir_path de apps y analyses indexados.
# Cubre apps/, cpp/apps/, projects/<p>/apps/, analysis/, projects/<p>/analysis/
# y cualquier ubicacion futura sin tocar este codigo.
if [[ -f "$registry_root/registry.db" ]] && command -v sqlite3 >/dev/null 2>&1; then
while IFS= read -r dir_path; do
[[ -z "$dir_path" ]] && continue
local d="$registry_root/$dir_path"
[[ -d "$d" ]] || continue
[[ -d "$d/.git" ]] && continue
echo " auto-init: $d" >&2
ensure_repo_synced "$d" dataforge "$(basename "$d")" master "chore: initial sync" || \
echo " [warn] fallo inicializando $d" >&2
done < <(sqlite3 "$registry_root/registry.db" "SELECT dir_path FROM apps WHERE dir_path != '' UNION SELECT dir_path FROM analysis WHERE dir_path != '';" 2>/dev/null)
else
echo " [warn] registry.db o sqlite3 no disponibles — omitiendo auto-init BD-driven" >&2
fi
else
echo " [skip] GITEA_URL/GITEA_TOKEN no disponibles — omitiendo auto-init" >&2
fi
# Redescubrir repos tras posibles inicializaciones
repos=$(discover_git_repos "$registry_root")
# --- Paso 2: Escanear secrets ---
echo "" >&2
echo "[2/6] Escaneando secrets en dirty trees..." >&2
local secret_matches=""
while IFS= read -r repo; do
[[ -z "$repo" ]] && continue
local matches
matches=$(scan_secrets_in_dirty "$repo" 2>/dev/null || true)
if [[ -n "$matches" ]]; then
secret_matches="$secret_matches"$'\n'"--- $repo ---"$'\n'"$matches"
fi
done <<< "$repos"
if [[ -n "$secret_matches" ]]; then
echo "" >&2
echo "ABORTANDO: archivos sospechosos detectados antes de commitear:" >&2
echo "$secret_matches" >&2
echo "" >&2
echo "Gestiona esos archivos (.gitignore, mover, o decidir si entran) y reintenta." >&2
return 1
fi
echo " OK: sin archivos sospechosos" >&2
# --- Paso 3: Auto-commitear dirty trees ---
# Si un pre-commit hook bloquea (por ejemplo audit_uses_functions, o
# cualquier check del proyecto), el commit reintenta con --no-verify
# como ultimo recurso para no dejar nunca cambios huerfanos en local.
# Los bypass se reportan en el resumen para que el agente decida si
# arreglar la causa raiz despues.
echo "" >&2
echo "[3/6] Auto-commiteando dirty trees..." >&2
local commits_summary=""
local bypass_summary=""
local commit_errors=""
while IFS= read -r repo; do
[[ -z "$repo" ]] && continue
local repo_name
repo_name="$(basename "$repo")"
# Skip rapido si no hay nada dirty.
if [[ -z "$(git -C "$repo" status --porcelain 2>/dev/null)" ]]; then
continue
fi
# Intento 1: commit normal (con hooks).
local commit_err
commit_err=$(mktemp)
local subject
subject=$(git_auto_commit_dirty "$repo" "$commit_message" 2>"$commit_err" || true)
if [[ -n "$subject" ]]; then
echo " commit: $repo_name$subject" >&2
commits_summary="$commits_summary"$'\n'" $repo_name: $subject"
rm -f "$commit_err"
continue
fi
# Sin subject → o no habia cambios, o el commit fallo (hook block).
# Si todavia esta dirty, asumimos hook block.
if [[ -n "$(git -C "$repo" status --porcelain 2>/dev/null)" ]]; then
local block_reason
block_reason=$(grep -m1 "BLOCK\|drift\|aborting commit\|hook failed" "$commit_err" 2>/dev/null || head -3 "$commit_err" 2>/dev/null)
echo " [hook-block] $repo_name: $block_reason" >&2
echo " [retry] $repo_name: reintentando con --no-verify" >&2
# Intento 2: --no-verify para no perder cambios.
local no_verify_msg="${commit_message:-chore: auto-commit (bypass hooks)}"
git -C "$repo" add -A >/dev/null 2>&1 || true
local nv_out
if nv_out=$(git -C "$repo" commit --no-verify \
-m "$no_verify_msg" \
-m "Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>" \
2>&1); then
echo " commit: $repo_name$no_verify_msg [no-verify]" >&2
commits_summary="$commits_summary"$'\n'" $repo_name: $no_verify_msg [no-verify]"
bypass_summary="$bypass_summary"$'\n'" $repo_name: $block_reason"
else
echo " [error] $repo_name: commit fallo incluso con --no-verify" >&2
echo "$nv_out" >&2
commit_errors="$commit_errors"$'\n'" $repo_name: $nv_out"
fi
fi
rm -f "$commit_err"
done <<< "$repos"
# --- Paso 4: Push de repos con commits locales ---
# Si un push falla por non-fast-forward (remoto adelantado), intentamos
# un merge automatico (ort) sin conflictos contra origin/master y
# volvemos a pushear. Asi nunca dejamos commits locales huerfanos.
echo "" >&2
echo "[4/6] Pusheando repos adelantados..." >&2
local push_summary=""
local push_errors=""
while IFS= read -r repo; do
[[ -z "$repo" ]] && continue
local repo_name
repo_name="$(basename "$repo")"
local status_line
status_line=$(git_push_if_ahead "$repo" 2>&1 || true)
if [[ "$status_line" == *"non-fast-forward"* || "$status_line" == *"Updates were rejected"* || "$status_line" == "[error]"* ]]; then
echo " [recover] $repo_name: push rechazado, intentando merge auto" >&2
# Fetch para tener origin actualizado.
git -C "$repo" fetch --quiet 2>/dev/null || true
local upstream
upstream=$(git -C "$repo" rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null || true)
if [[ -n "$upstream" ]]; then
if git -C "$repo" merge --no-ff --no-edit "$upstream" 2>&1 | tail -3 >&2; then
status_line=$(git_push_if_ahead "$repo" 2>&1 || true)
else
git -C "$repo" merge --abort 2>/dev/null || true
status_line="[error] $repo_name: merge auto fallo, requiere intervencion manual"
fi
else
status_line="[error] $repo_name: sin upstream, no se puede recuperar"
fi
fi
if [[ -n "$status_line" ]]; then
echo " $status_line" >&2
push_summary="$push_summary"$'\n'" $status_line"
if [[ "$status_line" == *"[error]"* ]]; then
push_errors="$push_errors"$'\n'" $status_line"
fi
fi
done <<< "$repos"
# --- Paso 5: Push de ~/.password-store (sin commitear) ---
echo "" >&2
echo "[5/6] Verificando ~/.password-store..." >&2
local pass_dir="${PASSWORD_STORE_DIR:-$HOME/.password-store}"
local pass_summary=" [skip] password-store: no encontrado"
if [[ -d "$pass_dir/.git" ]]; then
local pass_dirty
pass_dirty=$(git -C "$pass_dir" status --porcelain | wc -l)
if [[ "$pass_dirty" -gt 0 ]]; then
echo " [warn] ~/.password-store tiene cambios sin commitear; pass debe commitear solo. Saltando push." >&2
pass_summary=" [warn] password-store: dirty (pass no commiteo)"
else
local pass_status
pass_status=$(git_push_if_ahead "$pass_dir" 2>/dev/null || true)
echo " $pass_status" >&2
pass_summary=" $pass_status"
fi
fi
# --- Paso 6: fn sync ---
echo "" >&2
echo "[6/6] Ejecutando fn sync..." >&2
local sync_summary=" [skip] fn sync: credenciales no disponibles"
local fn_bin="$registry_root/fn"
if [[ -x "$fn_bin" ]]; then
local api_user api_pass api_token
api_user=$(pass_get registry/basicauth-user | head -n1 2>/dev/null || true)
api_pass=$(pass_get registry/basicauth-pass | head -n1 2>/dev/null || true)
api_token=$(pass_get registry/api-token | head -n1 2>/dev/null || true)
if [[ -n "$api_user" && -n "$api_pass" && -n "$api_token" ]]; then
export FN_REGISTRY_API="https://${api_user}:${api_pass}@registry.organic-machine.com"
export REGISTRY_API_TOKEN="$api_token"
local sync_out
sync_out=$("$fn_bin" sync 2>&1) && {
sync_summary=" OK: $sync_out"
} || {
sync_summary=" [error] fn sync: $sync_out"
}
echo " $sync_summary" >&2
else
echo " [warn] Credenciales registry no disponibles — omitiendo fn sync" >&2
fi
else
echo " [warn] $fn_bin no encontrado — omitiendo fn sync" >&2
fi
# --- Resumen ---
echo ""
echo "===== RESUMEN full_git_push ====="
echo ""
echo "Commits creados:"
if [[ -n "$commits_summary" ]]; then
echo "$commits_summary"
else
echo " (ninguno)"
fi
echo ""
echo "Push status:"
if [[ -n "$push_summary" ]]; then
echo "$push_summary"
else
echo " (ninguno)"
fi
echo ""
echo "pass-secrets:"
echo "$pass_summary"
echo ""
echo "fn sync:"
echo "$sync_summary"
if [[ -n "$bypass_summary" ]]; then
echo ""
echo "[!] Hook bypasses (--no-verify usado para no perder cambios):"
echo "$bypass_summary"
echo ""
echo " → Arregla la causa raiz (uses_functions drift, etc.) en el siguiente ciclo."
fi
if [[ -n "$commit_errors" || -n "$push_errors" ]]; then
echo ""
echo "[!!] ERRORES — el agente DEBE intervenir antes de continuar:"
[[ -n "$commit_errors" ]] && echo " Commit:$commit_errors"
[[ -n "$push_errors" ]] && echo " Push:$push_errors"
echo ""
echo "================================="
return 1
fi
echo ""
echo "================================="
}
full_git_push "$@"