feat(infra): auto-commit con 88 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
---
|
||||
name: ensure_project_gitignore
|
||||
kind: function
|
||||
lang: bash
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "ensure_project_gitignore(project_dir: string) -> void"
|
||||
description: "Garantiza de forma idempotente que el .gitignore de un directorio de project contiene las lineas canonicas que excluyen del repo del project el contenido de sus sub-repos hijos (apps y analyses son repos Gitea independientes) y sus vaults (datos fuera de git). Evita el doble-tracking al hacer push del project."
|
||||
tags: [git, gitignore, projects, infra]
|
||||
params:
|
||||
- name: project_dir
|
||||
desc: "Ruta al directorio del project (p. ej. projects/aurgi). Debe existir; si no, error a stderr y return 1. El .gitignore se escribe/actualiza en <project_dir>/.gitignore."
|
||||
output: "Sin salida en stdout. A stderr informa de la accion realizada: 'created' si creo el .gitignore, 'updated: anadidas N lineas' si anadio lineas faltantes, u 'ok: ya completo' si nada cambiaba. Codigo de salida 0 en exito, 1 si project_dir falta o no existe."
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "bash/functions/infra/ensure_project_gitignore.sh"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
source bash/functions/infra/ensure_project_gitignore.sh
|
||||
|
||||
# Asegura que projects/aurgi/.gitignore excluye el contenido de sus hijos.
|
||||
ensure_project_gitignore projects/aurgi
|
||||
# stderr: ensure_project_gitignore: created projects/aurgi/.gitignore
|
||||
# (o: updated: anadidas 2 lineas / ok: ya completo)
|
||||
```
|
||||
|
||||
Las lineas canonicas que la funcion garantiza son:
|
||||
|
||||
```
|
||||
apps/*/
|
||||
analysis/*/
|
||||
vaults/*
|
||||
!vaults/.gitkeep
|
||||
!vaults/vault.yaml
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Llamala justo despues de crear un project nuevo (`mkdir -p projects/<nombre>/{apps,analysis,vaults}`) y antes de inicializar su repo Gitea con `ensure_repo_synced`, para que el repo del project nunca trackee el contenido de sus sub-repos hijos. Tambien al adoptar un project existente que aun no tiene estas exclusiones, o como paso de saneamiento cuando `git status` del project muestra contenido de `apps/`/`analysis/` que deberia estar ignorado.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- La funcion modifica el filesystem (escribe en `<project_dir>/.gitignore`): es impura. No commitea ni hace push — solo deja el `.gitignore` correcto.
|
||||
- La comparacion para no duplicar es linea-exacta (`grep -Fxq`). Una linea equivalente pero con espacios extra, comentario adjunto o glob distinto (p. ej. `apps/*` sin la barra final) NO se considera presente y la canonica se anade igualmente; podrian quedar ambas formas. Mantener el `.gitignore` con las lineas canonicas tal cual evita ruido.
|
||||
- Si el `.gitignore` existente no termina en salto de linea, la funcion anade uno antes de apendar para no pegar la primera linea nueva al final de la ultima existente.
|
||||
- Solo gestiona las exclusiones de sub-repos hijos y vaults del nivel-project; no toca otras reglas que el `.gitignore` ya contenga ni las reordena.
|
||||
- Si una linea canonica ya existia con su forma exacta, no se vuelve a anadir (idempotente): re-ejecutar es seguro.
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env bash
|
||||
# ensure_project_gitignore — Garantiza de forma idempotente que el .gitignore de
|
||||
# un directorio de project (projects/<nombre>/) contiene las lineas canonicas que
|
||||
# excluyen del repo del project el contenido de sus sub-repos hijos (apps y
|
||||
# analyses son repos Gitea independientes) y sus vaults (datos fuera de git).
|
||||
#
|
||||
# Esto evita que al hacer push del project se trackee por error el contenido de
|
||||
# los hijos (doble-tracking). Ver .claude/rules/apps_subrepo.md y
|
||||
# .claude/rules/projects.md.
|
||||
#
|
||||
# Uso:
|
||||
# ensure_project_gitignore <project_dir>
|
||||
#
|
||||
# Salida:
|
||||
# stdout vacio. A stderr informa de la accion realizada (created / updated / ok).
|
||||
|
||||
ensure_project_gitignore() {
|
||||
local project_dir="$1"
|
||||
|
||||
if [[ -z "$project_dir" ]]; then
|
||||
echo "ensure_project_gitignore: se requiere project_dir" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ ! -d "$project_dir" ]]; then
|
||||
echo "ensure_project_gitignore: directorio '$project_dir' no existe" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local gitignore="$project_dir/.gitignore"
|
||||
|
||||
# Lineas canonicas que deben estar presentes (orden de referencia).
|
||||
local -a canonical=(
|
||||
"apps/*/"
|
||||
"analysis/*/"
|
||||
"vaults/*"
|
||||
"!vaults/.gitkeep"
|
||||
"!vaults/vault.yaml"
|
||||
)
|
||||
|
||||
# Caso 1: el .gitignore no existe — crearlo con el contenido canonico.
|
||||
if [[ ! -f "$gitignore" ]]; then
|
||||
printf '%s\n' "${canonical[@]}" > "$gitignore"
|
||||
echo "ensure_project_gitignore: created $gitignore" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Caso 2: existe — anadir solo las lineas que falten (comparacion linea-exacta),
|
||||
# preservando el contenido y el orden existentes.
|
||||
# Si el archivo no termina en newline, anadir uno antes de apendar para no
|
||||
# pegar la primera linea nueva al final de la ultima existente.
|
||||
if [[ -s "$gitignore" && -n "$(tail -c 1 "$gitignore")" ]]; then
|
||||
printf '\n' >> "$gitignore"
|
||||
fi
|
||||
|
||||
local line added=0
|
||||
for line in "${canonical[@]}"; do
|
||||
# grep -F -x: match literal de linea completa, sin interpretar metacaracteres.
|
||||
if ! grep -Fxq -- "$line" "$gitignore"; then
|
||||
printf '%s\n' "$line" >> "$gitignore"
|
||||
added=$((added + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $added -gt 0 ]]; then
|
||||
echo "ensure_project_gitignore: updated: anadidas $added lineas a $gitignore" >&2
|
||||
else
|
||||
echo "ensure_project_gitignore: ok: ya completo $gitignore" >&2
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Si se invoca como script (no source), ejecutar la funcion.
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
ensure_project_gitignore "$@"
|
||||
fi
|
||||
@@ -3,14 +3,15 @@ name: full_git_pull
|
||||
kind: pipeline
|
||||
lang: bash
|
||||
domain: pipelines
|
||||
version: "1.0.0"
|
||||
version: "1.1.0"
|
||||
purity: impure
|
||||
signature: "full_git_pull() -> stdout: tabla resumen"
|
||||
description: "Pull automatico de fn_registry + todos los sub-repos locales + submodules + fn sync. Descubre repos locales, stashea dirty trees antes de pullear, hace pull --ff-only, actualiza submodulos del repo principal, pulla ~/.password-store, regenera registry.db con fn index y ejecuta fn sync."
|
||||
description: "Pull automatico de fn_registry + todos los sub-repos locales + submodules + fn sync. Descubre repos locales, stashea dirty trees antes de pullear, hace pull --ff-only, actualiza submodulos del repo principal, pulla ~/.password-store, regenera registry.db con fn index, ejecuta fn sync y reclona los sub-repos hijos faltantes de cada project (apps/analysis) via clone_project_subrepos."
|
||||
tags: [git, pull, sync, registry, pipeline, pendiente-usar]
|
||||
uses_functions:
|
||||
- discover_git_repos_bash_infra
|
||||
- git_pull_with_stash_bash_infra
|
||||
- clone_project_subrepos_bash_pipelines
|
||||
- pass_get_bash_infra
|
||||
uses_types: []
|
||||
returns: []
|
||||
@@ -51,4 +52,10 @@ bash bash/functions/pipelines/full_git_pull.sh
|
||||
|
||||
## Notas
|
||||
|
||||
Solo hace pull fast-forward — nunca rebase ni merge automatico. Los repos con divergencia o conflicto de stash se listan al final del resumen para intervencion manual, pero el pipeline no aborta por ellos. No clona repos faltantes: cada PC tiene el subset que le interesa (clonar manualmente si se necesita uno nuevo). Modo completamente no-interactivo.
|
||||
Solo hace pull fast-forward — nunca rebase ni merge automatico. Los repos con divergencia o conflicto de stash se listan al final del resumen para intervencion manual, pero el pipeline no aborta por ellos. Modo completamente no-interactivo.
|
||||
|
||||
Desde v1.1.0 SI reclona los sub-repos hijos faltantes de cada project: tras `fn sync` (que trae a `registry.db` las filas de apps/analysis de todos los PCs), itera los projects y llama `clone_project_subrepos` para traer al disco los hijos que falten, re-indexando si clono alguno. `registry.db` actua como manifest de sub-repos, asi que clonar el project paraguas + `/full-git-pull` reconstruye su arbol entero sin adivinar nombres. Los repos sueltos (sin project) siguen sin auto-clonarse: cada PC tiene el subset que le interesa.
|
||||
|
||||
## Capability growth log
|
||||
|
||||
- v1.1.0 (2026-06-10) — anade el paso 6: reclonado de sub-repos hijos de cada project via `clone_project_subrepos` tras `fn sync`, con re-index si clona alguno. Permite reconstruir el arbol completo de un project en un PC nuevo (issue 0171).
|
||||
|
||||
@@ -149,6 +149,42 @@ full_git_pull() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Paso 6: Reclonar sub-repos hijos de cada project (issue 0171) ---
|
||||
# Tras fn sync, registry.db contiene las filas apps/analysis de TODOS los PCs.
|
||||
# clone_project_subrepos clona en este disco los hijos que falten (skip si ya
|
||||
# existen). Asi, clonar el project paraguas y correr /full-git-pull reconstruye
|
||||
# su arbol entero sin adivinar nombres de sub-repos: registry.db ES el manifest.
|
||||
echo "" >&2
|
||||
echo "[6/6] Reclonando sub-repos de projects..." >&2
|
||||
local reclone_summary=" [skip] sin projects o registry.db"
|
||||
if [[ -f "$registry_root/registry.db" ]] && command -v sqlite3 >/dev/null 2>&1; then
|
||||
export FN_REGISTRY_ROOT="$registry_root"
|
||||
export GITEA_URL="${GITEA_URL:-$(pass_get agentes/gitea-url | head -n1 2>/dev/null || true)}"
|
||||
local clone_script="$SCRIPT_DIR/clone_project_subrepos.sh"
|
||||
local any_cloned=0
|
||||
if [[ -f "$clone_script" ]]; then
|
||||
while IFS= read -r proj_id; do
|
||||
[[ -z "$proj_id" ]] && continue
|
||||
local clone_out
|
||||
clone_out=$(bash "$clone_script" "$proj_id" 2>&1 || true)
|
||||
if echo "$clone_out" | grep -q '\[cloned\]'; then
|
||||
any_cloned=1
|
||||
echo " $proj_id: nuevos sub-repos clonados" >&2
|
||||
fi
|
||||
done < <(sqlite3 "$registry_root/registry.db" "SELECT id FROM projects;" 2>/dev/null)
|
||||
if [[ "$any_cloned" -eq 1 ]]; then
|
||||
echo " re-index tras clonado..." >&2
|
||||
[[ -x "$fn_bin" ]] && CGO_ENABLED=1 "$fn_bin" index >/dev/null 2>&1 || true
|
||||
reclone_summary=" OK: nuevos sub-repos clonados + re-index"
|
||||
else
|
||||
reclone_summary=" OK: nada que clonar (todo presente)"
|
||||
fi
|
||||
else
|
||||
reclone_summary=" [skip] clone_project_subrepos.sh no encontrado"
|
||||
fi
|
||||
fi
|
||||
echo " $reclone_summary" >&2
|
||||
|
||||
# --- Resumen ---
|
||||
echo ""
|
||||
echo "===== RESUMEN full_git_pull ====="
|
||||
@@ -171,6 +207,9 @@ full_git_pull() {
|
||||
echo ""
|
||||
echo "fn sync:"
|
||||
echo "$sync_summary"
|
||||
echo ""
|
||||
echo "Reclonado sub-repos de projects:"
|
||||
echo "$reclone_summary"
|
||||
|
||||
if [[ ${#diverged[@]} -gt 0 || ${#conflicts[@]} -gt 0 ]]; then
|
||||
echo ""
|
||||
|
||||
@@ -3,10 +3,10 @@ name: full_git_push
|
||||
kind: pipeline
|
||||
lang: bash
|
||||
domain: pipelines
|
||||
version: "1.0.0"
|
||||
version: "1.1.0"
|
||||
purity: impure
|
||||
signature: "full_git_push(commit_message?: string) -> stdout: tabla resumen"
|
||||
description: "Push automatico de fn_registry + todos los sub-repos + fn sync. Descubre repos, escanea secrets (aborta si detecta), auto-inicializa apps/analyses sin .git via ensure_repo_synced, auto-commitea dirty trees, pushea solo repos adelantados, pushea ~/.password-store sin commitear, y ejecuta fn sync."
|
||||
description: "Push automatico de fn_registry + todos los sub-repos + fn sync. Descubre repos, escanea secrets (aborta si detecta), auto-inicializa apps/analyses Y projects paraguas sin .git via ensure_repo_synced (asegurando el .gitignore canonico del project antes), auto-commitea dirty trees, pushea solo repos adelantados, pushea ~/.password-store sin commitear, y ejecuta fn sync."
|
||||
tags: [git, push, sync, registry, pipeline, pendiente-usar]
|
||||
uses_functions:
|
||||
- discover_git_repos_bash_infra
|
||||
@@ -14,6 +14,7 @@ uses_functions:
|
||||
- git_auto_commit_dirty_bash_infra
|
||||
- git_push_if_ahead_bash_infra
|
||||
- ensure_repo_synced_bash_infra
|
||||
- ensure_project_gitignore_bash_infra
|
||||
- pass_get_bash_infra
|
||||
uses_types: []
|
||||
returns: []
|
||||
@@ -62,3 +63,7 @@ bash bash/functions/pipelines/full_git_push.sh "feat: nueva funcion"
|
||||
## Notas
|
||||
|
||||
El unico motivo para abortar antes de commitear es la deteccion de secrets. Cualquier otro error (push rechazado por non-fast-forward, fn sync no disponible) se reporta en el resumen y el pipeline continua con el resto de repos. Modo completamente no-interactivo.
|
||||
|
||||
## Capability growth log
|
||||
|
||||
- v1.1.0 (2026-06-10) — auto-inicializa tambien los projects paraguas (`projects/<p>/`) sin repo Gitea, no solo apps/analyses. Antes de pushear cada project asegura su `.gitignore` canonico via `ensure_project_gitignore` para no trackear el contenido de los sub-repos hijos. Cierra el agujero por el que projects como aurgi/obsidian/osint vivian solo en disco y se perdian al borrar el PC (issue 0171).
|
||||
|
||||
@@ -13,6 +13,7 @@ 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 "$INFRA_DIR/ensure_project_gitignore.sh"
|
||||
source "$CYBERSEC_DIR/scan_secrets_in_dirty.sh"
|
||||
|
||||
full_git_push() {
|
||||
@@ -65,6 +66,32 @@ full_git_push() {
|
||||
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)
|
||||
|
||||
# Paso 1c: Auto-inicializar los PROJECTS paraguas sin .git (issue 0171).
|
||||
# El directorio projects/<p>/ versiona SOLO las docs de nivel-project
|
||||
# (project.md, vault.yaml, CONVENTIONS.md, tools/...). Sus hijos apps/* y
|
||||
# analysis/* son sub-repos Gitea independientes, excluidos por el .gitignore
|
||||
# canonico que ensure_project_gitignore garantiza ANTES del push para no
|
||||
# trackear su contenido (doble-tracking). Sin esto, un project sin repo
|
||||
# (aurgi, obsidian, osint) vivia solo en disco y se perdia al borrar el PC.
|
||||
if [[ -f "$registry_root/registry.db" ]] && command -v sqlite3 >/dev/null 2>&1; then
|
||||
while IFS= read -r proj_dir; do
|
||||
[[ -z "$proj_dir" ]] && continue
|
||||
local pd="$registry_root/$proj_dir"
|
||||
[[ -d "$pd" ]] || continue
|
||||
# Garantizar el .gitignore canonico ANTES de cualquier git add -A.
|
||||
ensure_project_gitignore "$pd" || \
|
||||
echo " [warn] no se pudo asegurar .gitignore de $pd" >&2
|
||||
if [[ -d "$pd/.git" ]]; then
|
||||
git -C "$pd" remote get-url origin >/dev/null 2>&1 && continue
|
||||
echo " fix-remote: $pd (.git sin origin)" >&2
|
||||
else
|
||||
echo " auto-init project: $pd" >&2
|
||||
fi
|
||||
ensure_repo_synced "$pd" dataforge "$(basename "$pd")" master "chore: initial sync project" || \
|
||||
echo " [warn] fallo inicializando project $pd" >&2
|
||||
done < <(sqlite3 "$registry_root/registry.db" "SELECT CASE WHEN dir_path != '' THEN dir_path ELSE 'projects/'||id END FROM projects;" 2>/dev/null)
|
||||
fi
|
||||
else
|
||||
echo " [warn] registry.db o sqlite3 no disponibles — omitiendo auto-init BD-driven" >&2
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user