#!/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. Deriva de SCRIPT_DIR (bash/functions/pipelines/) # para funcionar en cualquier PC sin path hardcodeado. local registry_root="${FN_REGISTRY_ROOT:-$(cd "$SCRIPT_DIR/../../.." && pwd)}" 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/
/apps/, analysis/, projects/
/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 1c: Incluir el repo de configuracion de Claude ---
# Los archivos de ~/.claude/ (settings.json, commands, skills, CLAUDE.md...)
# son symlinks a un repo git externo (dataforge/repo_Claude). Lo resolvemos
# de forma portable siguiendo el symlink de settings.json — sin hardcodear
# el path, que difiere entre PCs. Si resuelve a un repo git, lo anadimos a
# la lista para que pase por scan-secrets + auto-commit + push como los demas.
local claude_repo=""
if [[ -L "$HOME/.claude/settings.json" ]]; then
local _claude_settings_real
_claude_settings_real=$(readlink -f "$HOME/.claude/settings.json" 2>/dev/null || true)
if [[ -n "$_claude_settings_real" ]]; then
claude_repo=$(git -C "$(dirname "$_claude_settings_real")" rev-parse --show-toplevel 2>/dev/null || true)
fi
fi
if [[ -n "$claude_repo" && -d "$claude_repo/.git" ]]; then
echo "[1c] Incluyendo repo de config Claude: $claude_repo" >&2
repos="$repos"$'\n'"$claude_repo"
fi
# --- 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)