Files
fn_registry/bash/functions/pipelines/full_git_push.sh
T
egutierrez dfcb3c46a7 fix(infra): full_git_push auto-init handles .git without origin
Auto-init step skipped any dir with .git present, even when it lacked
an origin remote. Such dirs fell through to push step and failed with
"'origin' does not appear to be a git repository". Now skip only when
.git AND origin exist; otherwise run ensure_repo_synced to create the
Gitea repo + add origin + push.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 17:34:21 +02:00

298 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
# Skip solo si ya tiene .git CON remote origin. Un .git sin origin
# (init local que nunca llego a crear repo Gitea) cae a push step y
# falla con "'origin' does not appear to be a git repository".
if [[ -d "$d/.git" ]]; then
git -C "$d" remote get-url origin >/dev/null 2>&1 && continue
echo " fix-remote: $d (.git sin origin)" >&2
else
echo " auto-init: $d" >&2
fi
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 "$@"