feat: add bash shell utility functions
12 funciones Bash del dominio shell: utilidades de scripting (bash_log, bash_colors, bash_check_deps, bash_confirm, bash_handle_error, bash_safe_run), manipulacion de texto (convert_text_case), estructura de proyectos (create_project_structure), y operaciones git (git_clean_branches, git_log_visual, git_push_all_remotes, git_repo_status). Cada una con su .sh y .md de frontmatter.
This commit is contained in:
@@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
name: bash_check_deps
|
||||||
|
kind: function
|
||||||
|
lang: bash
|
||||||
|
domain: shell
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "check_command(cmd: string, [error_code: string], [description: string]) -> void; check_commands(cmd...: string) -> void; check_directory(dir: string, [msg: string]) -> void; check_file(file: string, [msg: string]) -> void"
|
||||||
|
description: "Verifica existencia de comandos, directorios y archivos con output formateado. Complementa assert_command_exists con mensajes de error detallados y logging."
|
||||||
|
tags: [bash, check, dependency, command, exists, validation]
|
||||||
|
uses_functions: [bash_log_bash_shell]
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: cmd
|
||||||
|
desc: "nombre del comando a verificar en PATH"
|
||||||
|
- name: error_code
|
||||||
|
desc: "codigo identificador del error; default COMMAND_NOT_FOUND"
|
||||||
|
- name: description
|
||||||
|
desc: "mensaje de error personalizado; default 'El comando CMD no esta disponible'"
|
||||||
|
- name: dir
|
||||||
|
desc: "ruta del directorio a verificar"
|
||||||
|
- name: file
|
||||||
|
desc: "ruta del archivo a verificar"
|
||||||
|
output: "exit code 0 si todas las verificaciones pasan; exit code 1 en caso de fallo con mensaje de error formateado"
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "bash/functions/shell/bash_check_deps.sh"
|
||||||
|
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git"
|
||||||
|
source_license: "MIT"
|
||||||
|
source_file: "scripts/lib/common.sh"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source bash/functions/shell/bash_check_deps.sh
|
||||||
|
|
||||||
|
check_command "docker" "DOCKER_NOT_FOUND" "Docker no esta instalado"
|
||||||
|
check_commands "git" "curl" "jq"
|
||||||
|
check_directory "/var/data" "El directorio de datos no existe"
|
||||||
|
check_file "/etc/config.yaml" "Falta el archivo de configuracion"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
`check_command` acepta un error_code y descripcion opcionales para mensajes mas descriptivos que `assert_command_exists`. Usa `debug` internamente para loggear cada verificacion.
|
||||||
|
|
||||||
|
`check_commands` verifica multiples comandos en una sola llamada y reporta todos los faltantes antes de retornar 1.
|
||||||
|
|
||||||
|
Sourcea `bash_log.sh` automaticamente, que a su vez sourcea `bash_colors.sh`. No es necesario sourcea dependencias por separado.
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# bash_check_deps
|
||||||
|
# ---------------
|
||||||
|
# Verifica existencia de comandos, directorios y archivos.
|
||||||
|
# Output formateado con colores via bash_log.
|
||||||
|
#
|
||||||
|
# USO (sourced):
|
||||||
|
# source bash_check_deps.sh
|
||||||
|
# check_command "docker" "DOCKER_NOT_FOUND" "Docker no esta instalado"
|
||||||
|
# check_commands "git" "curl" "jq"
|
||||||
|
# check_directory "/path/to/dir"
|
||||||
|
# check_file "/path/to/file"
|
||||||
|
|
||||||
|
SCRIPT_DIR_BASH_CHECK="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR_BASH_CHECK/bash_log.sh"
|
||||||
|
bash_log_init
|
||||||
|
|
||||||
|
check_command() {
|
||||||
|
local cmd="$1"
|
||||||
|
local error_code="${2:-COMMAND_NOT_FOUND}"
|
||||||
|
local description="${3:-El comando '$cmd' no esta disponible}"
|
||||||
|
|
||||||
|
debug "Verificando comando: $cmd"
|
||||||
|
|
||||||
|
if ! command -v "$cmd" &> /dev/null; then
|
||||||
|
error "$description ($error_code)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
check_commands() {
|
||||||
|
local failed=0
|
||||||
|
for cmd in "$@"; do
|
||||||
|
if ! command -v "$cmd" &> /dev/null; then
|
||||||
|
error "Comando no encontrado: $cmd"
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $failed -eq 1 ]; then
|
||||||
|
error "Faltan multiples dependencias requeridas"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
check_directory() {
|
||||||
|
local dir="$1"
|
||||||
|
local error_msg="${2:-El directorio '$dir' no existe}"
|
||||||
|
|
||||||
|
debug "Verificando directorio: $dir"
|
||||||
|
|
||||||
|
if [ ! -d "$dir" ]; then
|
||||||
|
error "$error_msg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
check_file() {
|
||||||
|
local file="$1"
|
||||||
|
local error_msg="${2:-El archivo '$file' no existe}"
|
||||||
|
|
||||||
|
debug "Verificando archivo: $file"
|
||||||
|
|
||||||
|
if [ ! -f "$file" ]; then
|
||||||
|
error "$error_msg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
name: bash_colors
|
||||||
|
kind: function
|
||||||
|
lang: bash
|
||||||
|
domain: shell
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: pure
|
||||||
|
signature: "bash_colors() -> void"
|
||||||
|
description: "Exporta variables ANSI de colores, caracteres box drawing y simbolos unicode para uso en scripts de terminal."
|
||||||
|
tags: [bash, colors, ansi, terminal, symbols, box]
|
||||||
|
uses_functions: []
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: ""
|
||||||
|
imports: []
|
||||||
|
params: []
|
||||||
|
output: "exporta variables de entorno con codigos ANSI: colores (RED, GREEN, BLUE, etc.), box drawing (BOX_TL, BOX_H, etc.) y simbolos (CHECKMARK, CROSS, ARROW, etc.)"
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "bash/functions/shell/bash_colors.sh"
|
||||||
|
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git"
|
||||||
|
source_license: "MIT"
|
||||||
|
source_file: "scripts/lib/common.sh"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source bash/functions/shell/bash_colors.sh
|
||||||
|
bash_colors
|
||||||
|
|
||||||
|
echo -e "${GREEN}Todo bien${NC}"
|
||||||
|
echo -e "${RED}${CROSS} Error detectado${NC}"
|
||||||
|
echo -e "${BOX_TL}${BOX_H}${BOX_TR}"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
Funcion pura: solo define y exporta variables de entorno, sin I/O ni efectos secundarios. Debe llamarse una vez antes de usar cualquier variable de color o simbolo.
|
||||||
|
|
||||||
|
Variables de color disponibles: PURPLE, MAGENTA, GREEN, BLUE, YELLOW, RED, CYAN, ORANGE, GRAY, DIM_GRAY, BOLD, DIM, NC.
|
||||||
|
|
||||||
|
Variables box drawing: BOX_TL, BOX_TR, BOX_BL, BOX_BR, BOX_H, BOX_V, BOX_ML, BOX_MR, BOX_SEP.
|
||||||
|
|
||||||
|
Simbolos unicode: CHECKMARK, CROSS, ARROW, BULLET, WARNING, INFO.
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# bash_colors
|
||||||
|
# -----------
|
||||||
|
# Variables ANSI de color, caracteres box drawing y simbolos para scripts.
|
||||||
|
# Diseñado para ser sourced por otros scripts.
|
||||||
|
#
|
||||||
|
# USO (sourced):
|
||||||
|
# source bash_colors.sh
|
||||||
|
# bash_colors
|
||||||
|
|
||||||
|
bash_colors() {
|
||||||
|
export PURPLE='\033[35m'
|
||||||
|
export MAGENTA='\033[35m'
|
||||||
|
export GREEN='\033[0;32m'
|
||||||
|
export BLUE='\033[0;34m'
|
||||||
|
export YELLOW='\033[1;33m'
|
||||||
|
export RED='\033[0;31m'
|
||||||
|
export CYAN='\033[0;36m'
|
||||||
|
export ORANGE='\033[0;33m'
|
||||||
|
export GRAY='\033[0;90m'
|
||||||
|
export DIM_GRAY='\033[2;37m'
|
||||||
|
export BOLD='\033[1m'
|
||||||
|
export DIM='\033[2m'
|
||||||
|
export NC='\033[0m'
|
||||||
|
|
||||||
|
export BOX_TL="╔"
|
||||||
|
export BOX_TR="╗"
|
||||||
|
export BOX_BL="╚"
|
||||||
|
export BOX_BR="╝"
|
||||||
|
export BOX_H="═"
|
||||||
|
export BOX_V="║"
|
||||||
|
export BOX_ML="╠"
|
||||||
|
export BOX_MR="╣"
|
||||||
|
export BOX_SEP="─"
|
||||||
|
|
||||||
|
export CHECKMARK="✓"
|
||||||
|
export CROSS="✗"
|
||||||
|
export ARROW="→"
|
||||||
|
export BULLET="•"
|
||||||
|
export WARNING="⚠"
|
||||||
|
export INFO="ℹ"
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
name: bash_confirm
|
||||||
|
kind: function
|
||||||
|
lang: bash
|
||||||
|
domain: shell
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "bash_confirm(prompt: string, [default: string]) -> exit_code"
|
||||||
|
description: "Dialogo interactivo de confirmacion y/n con valor por defecto configurable. Soporta respuestas yes/y/si."
|
||||||
|
tags: [bash, confirm, prompt, interactive, dialog]
|
||||||
|
uses_functions: [bash_colors_bash_shell]
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: prompt
|
||||||
|
desc: "pregunta a mostrar al usuario; default '¿Continuar?'"
|
||||||
|
- name: default
|
||||||
|
desc: "valor por defecto cuando el usuario presiona Enter sin escribir nada; 'y' o 'n'; default 'n'"
|
||||||
|
output: "exit code 0 si el usuario confirma (y/yes/si), exit code 1 si niega o acepta el default negativo"
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "bash/functions/shell/bash_confirm.sh"
|
||||||
|
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git"
|
||||||
|
source_license: "MIT"
|
||||||
|
source_file: "scripts/lib/common.sh"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source bash/functions/shell/bash_confirm.sh
|
||||||
|
|
||||||
|
# Con default no (muestra [y/N])
|
||||||
|
if bash_confirm "¿Deseas continuar?"; then
|
||||||
|
echo "Continuando..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Con default yes (muestra [Y/n])
|
||||||
|
bash_confirm "¿Eliminar archivos temporales?" "y" && rm -rf /tmp/cache
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
El prompt muestra el default en mayuscula: `[Y/n]` cuando default=y, `[y/N]` cuando default=n. Si el usuario presiona Enter sin escribir, se usa el valor por defecto.
|
||||||
|
|
||||||
|
Acepta variantes: y, Y, yes, YES, si, SI como afirmativo. Cualquier otra respuesta se trata como negativo.
|
||||||
|
|
||||||
|
Usa colores de `bash_colors` para el prompt en amarillo. Requiere terminal interactiva (lee de stdin con `read -r`).
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# bash_confirm
|
||||||
|
# ------------
|
||||||
|
# Dialogo de confirmacion y/n con valor por defecto.
|
||||||
|
#
|
||||||
|
# USO (sourced):
|
||||||
|
# source bash_confirm.sh
|
||||||
|
# bash_confirm "Continuar?" && echo "Si" || echo "No"
|
||||||
|
# bash_confirm "Eliminar?" "y" # default yes
|
||||||
|
|
||||||
|
SCRIPT_DIR_BASH_CONFIRM="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR_BASH_CONFIRM/bash_colors.sh"
|
||||||
|
bash_colors
|
||||||
|
|
||||||
|
bash_confirm() {
|
||||||
|
local prompt="${1:-¿Continuar?}"
|
||||||
|
local default="${2:-n}"
|
||||||
|
|
||||||
|
if [ "$default" = "y" ]; then
|
||||||
|
prompt="$prompt [Y/n]"
|
||||||
|
else
|
||||||
|
prompt="$prompt [y/N]"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -ne "${YELLOW}$prompt ${NC}"
|
||||||
|
read -r response
|
||||||
|
|
||||||
|
response=${response:-$default}
|
||||||
|
case "$response" in
|
||||||
|
[yY][eE][sS]|[yY]|[sS][iI])
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
name: bash_handle_error
|
||||||
|
kind: function
|
||||||
|
lang: bash
|
||||||
|
domain: shell
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "handle_error(error_code: string, error_description: string, [solution: string]) -> exit_code"
|
||||||
|
description: "Muestra un box de error formateado con contexto del fallo: script, linea, funcion, directorio y usuario. Registra en log."
|
||||||
|
tags: [bash, error, handler, box, formatted, context]
|
||||||
|
uses_functions: [bash_colors_bash_shell, bash_log_bash_shell]
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: error_code
|
||||||
|
desc: "codigo identificador del error, ej: BUILD_FAILED, DB_CONNECTION_ERROR"
|
||||||
|
- name: error_description
|
||||||
|
desc: "descripcion legible del error para mostrar al usuario"
|
||||||
|
- name: solution
|
||||||
|
desc: "solucion sugerida; opcional; si se provee se muestra en seccion separada"
|
||||||
|
output: "box de error formateado en stdout con contexto (script, linea, funcion, directorio, usuario) + registro en log; retorna exit code 1"
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "bash/functions/shell/bash_handle_error.sh"
|
||||||
|
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git"
|
||||||
|
source_license: "MIT"
|
||||||
|
source_file: "scripts/lib/common.sh"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source bash/functions/shell/bash_handle_error.sh
|
||||||
|
|
||||||
|
build_project() {
|
||||||
|
go build ./... || handle_error "BUILD_FAILED" \
|
||||||
|
"La compilacion del proyecto fallo" \
|
||||||
|
"Ejecuta 'go mod tidy' y verifica que todas las dependencias esten instaladas"
|
||||||
|
}
|
||||||
|
|
||||||
|
connect_db() {
|
||||||
|
psql "$DB_URL" -c '\q' 2>/dev/null || handle_error "DB_CONNECTION_ERROR" \
|
||||||
|
"No se pudo conectar a la base de datos"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
El box usa caracteres unicode de `bash_colors` (BOX_TL, BOX_H, etc.) para el borde en rojo. La informacion de contexto se extrae de `BASH_SOURCE`, `BASH_LINENO` y `FUNCNAME` con offset 2 para apuntar al caller del caller.
|
||||||
|
|
||||||
|
La funcion siempre retorna 1, permitiendo usarla como `cmd || handle_error ...` en pipelines de error.
|
||||||
|
|
||||||
|
Usa `bash_colors` y `bash_log` como dependencias. Sourcea ambas automaticamente al cargarse.
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# bash_handle_error
|
||||||
|
# -----------------
|
||||||
|
# Muestra un box de error formateado con contexto del fallo.
|
||||||
|
# Incluye script, linea, funcion y directorio.
|
||||||
|
#
|
||||||
|
# USO (sourced):
|
||||||
|
# source bash_handle_error.sh
|
||||||
|
# handle_error "BUILD_FAILED" "La compilacion fallo" "Verifica las dependencias"
|
||||||
|
|
||||||
|
SCRIPT_DIR_BASH_HANDLE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR_BASH_HANDLE/bash_colors.sh"
|
||||||
|
source "$SCRIPT_DIR_BASH_HANDLE/bash_log.sh"
|
||||||
|
bash_colors
|
||||||
|
bash_log_init
|
||||||
|
|
||||||
|
handle_error() {
|
||||||
|
local error_code="$1"
|
||||||
|
local error_description="$2"
|
||||||
|
local solution="$3"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${RED}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${RED}║ ${CROSS} ERROR DETECTADO ║${NC}"
|
||||||
|
echo -e "${RED}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "${RED}${BOLD}Error:${NC} ${error_description}"
|
||||||
|
echo -e "${GRAY}Codigo: ${error_code}${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -n "$solution" ]; then
|
||||||
|
echo -e "${YELLOW}${INFO} Solucion sugerida:${NC}"
|
||||||
|
echo -e " ${solution}"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GRAY}${INFO} Informacion adicional:${NC}"
|
||||||
|
echo -e " ${GRAY}${BULLET} Script: ${BASH_SOURCE[2]:-desconocido}${NC}"
|
||||||
|
echo -e " ${GRAY}${BULLET} Linea: ${BASH_LINENO[1]:-desconocido}${NC}"
|
||||||
|
echo -e " ${GRAY}${BULLET} Funcion: ${FUNCNAME[2]:-main}${NC}"
|
||||||
|
echo -e " ${GRAY}${BULLET} Directorio: $(pwd)${NC}"
|
||||||
|
echo -e " ${GRAY}${BULLET} Usuario: $(whoami)${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
log "ERROR" "Code: $error_code | Description: $error_description | Script: ${BASH_SOURCE[2]} | Line: ${BASH_LINENO[1]}"
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
name: bash_log
|
||||||
|
kind: function
|
||||||
|
lang: bash
|
||||||
|
domain: shell
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "bash_log_init() -> void; success(msg: string) -> void; info(msg: string) -> void; warning(msg: string) -> void; error(msg: string) -> void; debug(msg: string) -> void; progress(msg: string) -> void"
|
||||||
|
description: "Funciones de logging con colores para scripts bash. Incluye niveles success/info/warning/error/debug/progress con escritura a archivo de log."
|
||||||
|
tags: [bash, log, logging, colors, terminal]
|
||||||
|
uses_functions: [bash_colors_bash_shell]
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: msg
|
||||||
|
desc: "mensaje a mostrar y registrar en el archivo de log"
|
||||||
|
output: "mensaje formateado con colores en stdout/stderr y registro con timestamp en archivo de log (ERROR_LOG_FILE)"
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "bash/functions/shell/bash_log.sh"
|
||||||
|
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git"
|
||||||
|
source_license: "MIT"
|
||||||
|
source_file: "scripts/lib/common.sh"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source bash/functions/shell/bash_log.sh
|
||||||
|
bash_log_init
|
||||||
|
|
||||||
|
success "Servicio iniciado correctamente"
|
||||||
|
info "Procesando 42 registros..."
|
||||||
|
warning "El puerto 8080 ya esta en uso"
|
||||||
|
error "No se pudo conectar a la base de datos"
|
||||||
|
debug "Valor de variable: $VAR"
|
||||||
|
progress "Descargando imagen Docker..."
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
Llama a `bash_log_init` una vez al inicio para configurar ERROR_LOG_FILE y DEBUG_MODE. Por defecto el log va a `/tmp/script-errors-YYYYMMDD.log`.
|
||||||
|
|
||||||
|
La variable de entorno `DEBUG_MODE=1` activa los mensajes de debug a stderr y muestra timestamps en todos los logs.
|
||||||
|
|
||||||
|
`error` escribe a stderr; el resto a stdout. Todos los niveles escriben al archivo de log independientemente de DEBUG_MODE.
|
||||||
|
|
||||||
|
Fuente: sourcea `bash_colors.sh` automaticamente al cargarse.
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# bash_log
|
||||||
|
# --------
|
||||||
|
# Funciones de logging con colores para scripts.
|
||||||
|
# Incluye: log, success, info, warning, error, debug, progress.
|
||||||
|
#
|
||||||
|
# USO (sourced):
|
||||||
|
# source bash_log.sh
|
||||||
|
# bash_log_init
|
||||||
|
# success "Operacion completada"
|
||||||
|
# info "Procesando..."
|
||||||
|
# error "Algo fallo"
|
||||||
|
|
||||||
|
SCRIPT_DIR_BASH_LOG="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR_BASH_LOG/bash_colors.sh"
|
||||||
|
bash_colors
|
||||||
|
|
||||||
|
bash_log_init() {
|
||||||
|
export ERROR_LOG_FILE="${ERROR_LOG_FILE:-/tmp/script-errors-$(date +%Y%m%d).log}"
|
||||||
|
export DEBUG_MODE="${DEBUG_MODE:-0}"
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
local level="$1"
|
||||||
|
shift
|
||||||
|
local message="$@"
|
||||||
|
local timestamp
|
||||||
|
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
echo "[$timestamp] [$level] $message" >> "${ERROR_LOG_FILE:-/tmp/script-errors.log}"
|
||||||
|
|
||||||
|
if [ "${DEBUG_MODE:-0}" = "1" ]; then
|
||||||
|
echo -e "${GRAY}[$timestamp] [$level] $message${NC}" >&2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
success() {
|
||||||
|
echo -e "${GREEN}${CHECKMARK} $*${NC}"
|
||||||
|
log "SUCCESS" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
info() {
|
||||||
|
echo -e "${BLUE}${INFO} $*${NC}"
|
||||||
|
log "INFO" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
warning() {
|
||||||
|
echo -e "${YELLOW}${WARNING} $*${NC}"
|
||||||
|
log "WARNING" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
error() {
|
||||||
|
echo -e "${RED}${CROSS} Error: $*${NC}" >&2
|
||||||
|
log "ERROR" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
debug() {
|
||||||
|
if [ "${DEBUG_MODE:-0}" = "1" ]; then
|
||||||
|
echo -e "${GRAY}[DEBUG] $*${NC}" >&2
|
||||||
|
fi
|
||||||
|
log "DEBUG" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
progress() {
|
||||||
|
echo -e "${CYAN}${ARROW} $*${NC}"
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
name: bash_safe_run
|
||||||
|
kind: function
|
||||||
|
lang: bash
|
||||||
|
domain: shell
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "safe_run(cmd: string, [error_code: string], [error_desc: string]) -> void; setup_error_trap() -> void; error_trap_handler(exit_code: int, line_number: int) -> void"
|
||||||
|
description: "Ejecuta comandos con manejo de errores integrado. Incluye trap handler que captura fallos con numero de linea y codigo de salida."
|
||||||
|
tags: [bash, safe, run, error, trap, handler]
|
||||||
|
uses_functions: [bash_log_bash_shell]
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: cmd
|
||||||
|
desc: "comando a ejecutar via eval"
|
||||||
|
- name: error_code
|
||||||
|
desc: "codigo identificador del error en caso de fallo; default COMMAND_FAILED"
|
||||||
|
- name: error_desc
|
||||||
|
desc: "descripcion del error a mostrar; default 'El comando fallo: CMD'"
|
||||||
|
output: "exit code 0 si el comando tuvo exito; exit code 1 con mensaje de error formateado si fallo"
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "bash/functions/shell/bash_safe_run.sh"
|
||||||
|
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git"
|
||||||
|
source_license: "MIT"
|
||||||
|
source_file: "scripts/lib/common.sh"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source bash/functions/shell/bash_safe_run.sh
|
||||||
|
bash_log_init
|
||||||
|
setup_error_trap
|
||||||
|
|
||||||
|
safe_run "go build ./..." "BUILD_FAILED" "La compilacion fallo"
|
||||||
|
safe_run "docker compose up -d" "DOCKER_FAILED" "No se pudo iniciar Docker Compose"
|
||||||
|
safe_run "npm install" "NPM_FAILED"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
`safe_run` usa `eval` internamente para ejecutar el comando, lo que permite pasar comandos con pipes y redirecciones como string. Usar con precaucion en entornos con input no confiable.
|
||||||
|
|
||||||
|
`setup_error_trap` instala un trap `ERR` que llama a `error_trap_handler` automaticamente en cualquier comando fallido del script, mostrando numero de linea y codigo de salida.
|
||||||
|
|
||||||
|
`error_trap_handler` no llama a `exit` — el caller decide si continuar o abortar. Muestra la ruta al log para debugging.
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# bash_safe_run
|
||||||
|
# -------------
|
||||||
|
# Ejecutar comandos con manejo de errores y trap.
|
||||||
|
# Incluye safe_run, setup_error_trap y error_trap_handler.
|
||||||
|
#
|
||||||
|
# USO (sourced):
|
||||||
|
# source bash_safe_run.sh
|
||||||
|
# setup_error_trap
|
||||||
|
# safe_run "go build ./..." "BUILD_FAILED" "La compilacion fallo"
|
||||||
|
|
||||||
|
SCRIPT_DIR_BASH_SAFE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR_BASH_SAFE/bash_log.sh"
|
||||||
|
bash_log_init
|
||||||
|
|
||||||
|
safe_run() {
|
||||||
|
local cmd="$1"
|
||||||
|
local error_code="${2:-COMMAND_FAILED}"
|
||||||
|
local error_desc="${3:-El comando fallo: $cmd}"
|
||||||
|
|
||||||
|
debug "Ejecutando: $cmd"
|
||||||
|
|
||||||
|
if ! eval "$cmd"; then
|
||||||
|
error "$error_desc ($error_code)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_error_trap() {
|
||||||
|
trap 'error_trap_handler $? $LINENO' ERR
|
||||||
|
}
|
||||||
|
|
||||||
|
error_trap_handler() {
|
||||||
|
local exit_code=$1
|
||||||
|
local line_number=$2
|
||||||
|
|
||||||
|
if [ "$exit_code" -ne 0 ]; then
|
||||||
|
echo ""
|
||||||
|
error "El script fallo en la linea $line_number con codigo de salida $exit_code"
|
||||||
|
echo -e "${GRAY}Consulta el log: ${ERROR_LOG_FILE:-/tmp/script-errors.log}${NC}"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
name: convert_text_case
|
||||||
|
kind: function
|
||||||
|
lang: bash
|
||||||
|
domain: shell
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "convert_text_case(mode: string, file: string, [output_file: string]) -> void"
|
||||||
|
description: "Convierte el contenido de un archivo de texto. Modos: upper (todo mayúsculas), lower (todo minúsculas), lf (normaliza saltos de línea a LF eliminando \\r), crlf (normaliza saltos a CRLF). Sin output_file imprime a stdout."
|
||||||
|
tags: [bash, text, convert, case, encoding]
|
||||||
|
uses_functions: []
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: mode
|
||||||
|
desc: "transformación a aplicar: upper|lower|lf|crlf (requerido)"
|
||||||
|
- name: file
|
||||||
|
desc: "ruta al archivo de entrada (requerido)"
|
||||||
|
- name: output_file
|
||||||
|
desc: "ruta al archivo de salida (opcional; default: stdout)"
|
||||||
|
output: "texto convertido a stdout o escrito en output_file; exit code 1 si el modo o archivo son inválidos"
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "bash/functions/shell/convert_text_case.sh"
|
||||||
|
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git"
|
||||||
|
source_license: "MIT"
|
||||||
|
source_file: "scripts/linux/conversores/conversor_texto.sh"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source bash/functions/shell/convert_text_case.sh
|
||||||
|
|
||||||
|
# Convertir a mayúsculas y mostrar en stdout
|
||||||
|
convert_text_case upper mi_archivo.txt
|
||||||
|
|
||||||
|
# Convertir a minúsculas y guardar en archivo
|
||||||
|
convert_text_case lower input.txt output_lower.txt
|
||||||
|
|
||||||
|
# Normalizar saltos de línea CRLF a LF
|
||||||
|
convert_text_case lf archivo_windows.txt archivo_unix.txt
|
||||||
|
|
||||||
|
# Añadir CRLF (para Windows)
|
||||||
|
convert_text_case crlf unix.txt windows.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
Usa `awk` para las conversiones de case (portable) y `sed` para los saltos de línea. La función no modifica el archivo original si se provee `output_file`. Sin `output_file` imprime directamente a stdout, útil para pipes.
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# convert_text_case
|
||||||
|
# -----------------
|
||||||
|
# Convierte el contenido de texto de un archivo aplicando transformaciones:
|
||||||
|
# upper (mayúsculas), lower (minúsculas), lf (normalizar saltos a LF),
|
||||||
|
# crlf (normalizar saltos a CRLF).
|
||||||
|
#
|
||||||
|
# USO:
|
||||||
|
# source convert_text_case.sh
|
||||||
|
# convert_text_case mode file [output_file]
|
||||||
|
#
|
||||||
|
# ARGUMENTOS:
|
||||||
|
# mode Transformación a aplicar: upper|lower|lf|crlf (requerido)
|
||||||
|
# file Archivo de entrada (requerido)
|
||||||
|
# output_file Archivo de salida (opcional; por defecto imprime a stdout)
|
||||||
|
|
||||||
|
convert_text_case() {
|
||||||
|
local mode="${1:-}"
|
||||||
|
local input_file="${2:-}"
|
||||||
|
local output_file="${3:-}"
|
||||||
|
|
||||||
|
# Validar argumentos requeridos
|
||||||
|
if [[ -z "$mode" ]]; then
|
||||||
|
echo "convert_text_case: modo requerido (upper|lower|lf|crlf)" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$input_file" ]]; then
|
||||||
|
echo "convert_text_case: archivo de entrada requerido" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$input_file" ]]; then
|
||||||
|
echo "convert_text_case: archivo no encontrado: ${input_file}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Función auxiliar que aplica la conversión y escribe a stdout o archivo
|
||||||
|
_apply_conversion() {
|
||||||
|
local src="$1"
|
||||||
|
local op="$2"
|
||||||
|
case "$op" in
|
||||||
|
upper)
|
||||||
|
awk '{ print toupper($0) }' "$src"
|
||||||
|
;;
|
||||||
|
lower)
|
||||||
|
awk '{ print tolower($0) }' "$src"
|
||||||
|
;;
|
||||||
|
lf)
|
||||||
|
sed 's/\r$//' "$src"
|
||||||
|
;;
|
||||||
|
crlf)
|
||||||
|
sed 's/\r$//' "$src" | sed 's/$/\r/'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "convert_text_case: modo no soportado: ${op}. Usa: upper|lower|lf|crlf" >&2
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -n "$output_file" ]]; then
|
||||||
|
_apply_conversion "$input_file" "$mode" > "$output_file"
|
||||||
|
echo "convert_text_case: ${input_file} → ${output_file} (modo: ${mode})"
|
||||||
|
else
|
||||||
|
_apply_conversion "$input_file" "$mode"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ejecutar si se invoca directamente
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
convert_text_case "$@"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
name: create_project_structure
|
||||||
|
kind: function
|
||||||
|
lang: bash
|
||||||
|
domain: shell
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "create_project_structure(project_name: string) -> void"
|
||||||
|
description: "Crea la estructura de directorios estándar de un proyecto funcional en el directorio indicado: database (attachments, data, models), dist (desktop/mobile/docker), docker, docs, frontend, logs, notebooks, robots, scripts, src (application/core/middleware con tests y tipos)."
|
||||||
|
tags: [bash, project, structure, scaffold, init]
|
||||||
|
uses_functions: []
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: project_name
|
||||||
|
desc: "nombre del proyecto o ruta destino donde crear la estructura (requerido)"
|
||||||
|
output: "crea los directorios a stdout con conteo final; exit code 1 si no se proporciona nombre"
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "bash/functions/shell/create_project_structure.sh"
|
||||||
|
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git"
|
||||||
|
source_license: "MIT"
|
||||||
|
source_file: "scripts/linux/inicializar_repos/functional_structure.sh"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source bash/functions/shell/create_project_structure.sh
|
||||||
|
|
||||||
|
# Crear estructura en un directorio nuevo
|
||||||
|
create_project_structure mi-proyecto
|
||||||
|
|
||||||
|
# Usar una ruta
|
||||||
|
create_project_structure /home/user/projects/nuevo-proyecto
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
Crea 68 directorios organizados en la estructura funcional de DevLauncher. La estructura separa claramente application (orquestación), core (lógica pura) y middleware (efectos/I/O) bajo `src/`. No crea archivos, solo directorios. Idempotente: si los directorios ya existen, no falla.
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# create_project_structure
|
||||||
|
# ------------------------
|
||||||
|
# Crea la estructura de directorios estándar de un proyecto funcional
|
||||||
|
# (database, dist, docker, docs, frontend, logs, notebooks, robots,
|
||||||
|
# scripts, src con application/core/middleware) en el directorio indicado.
|
||||||
|
#
|
||||||
|
# USO:
|
||||||
|
# source create_project_structure.sh
|
||||||
|
# create_project_structure project_name
|
||||||
|
#
|
||||||
|
# ARGUMENTOS:
|
||||||
|
# project_name Nombre del proyecto / ruta destino (requerido)
|
||||||
|
|
||||||
|
create_project_structure() {
|
||||||
|
local project_name="${1:-}"
|
||||||
|
|
||||||
|
if [[ -z "$project_name" ]]; then
|
||||||
|
echo "create_project_structure: se requiere el nombre del proyecto" >&2
|
||||||
|
echo " Uso: create_project_structure <project_name>" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local destino="$project_name"
|
||||||
|
|
||||||
|
local directories=(
|
||||||
|
"database"
|
||||||
|
"database/attachments"
|
||||||
|
"database/attachments/audio"
|
||||||
|
"database/attachments/docs"
|
||||||
|
"database/attachments/images"
|
||||||
|
"database/attachments/video"
|
||||||
|
"database/data"
|
||||||
|
"database/data/01_raw"
|
||||||
|
"database/data/02_clean"
|
||||||
|
"database/data/03_objects"
|
||||||
|
"database/data/04_answers"
|
||||||
|
"database/models"
|
||||||
|
"dist"
|
||||||
|
"dist/desktop"
|
||||||
|
"dist/desktop/linux"
|
||||||
|
"dist/desktop/mac"
|
||||||
|
"dist/desktop/win"
|
||||||
|
"dist/docker"
|
||||||
|
"dist/mobile"
|
||||||
|
"dist/mobile/android"
|
||||||
|
"dist/mobile/ios"
|
||||||
|
"docker"
|
||||||
|
"docs"
|
||||||
|
"docs/documentation"
|
||||||
|
"docs/pdf"
|
||||||
|
"frontend"
|
||||||
|
"logs"
|
||||||
|
"notebooks"
|
||||||
|
"robots"
|
||||||
|
"scripts"
|
||||||
|
"src"
|
||||||
|
"src/application"
|
||||||
|
"src/application/event"
|
||||||
|
"src/application/jobs"
|
||||||
|
"src/application/pipes"
|
||||||
|
"src/application/sandbox"
|
||||||
|
"src/application/simulation"
|
||||||
|
"src/application/tasks"
|
||||||
|
"src/application/tests"
|
||||||
|
"src/application/tests/benchmark"
|
||||||
|
"src/application/tests/fuzzers"
|
||||||
|
"src/application/tests/mocks"
|
||||||
|
"src/application/tests/mutation"
|
||||||
|
"src/core"
|
||||||
|
"src/core/base"
|
||||||
|
"src/core/functions"
|
||||||
|
"src/core/rules"
|
||||||
|
"src/core/tests"
|
||||||
|
"src/core/tests/benchmark"
|
||||||
|
"src/core/tests/fuzzers"
|
||||||
|
"src/core/tests/mocks"
|
||||||
|
"src/core/tests/mutation"
|
||||||
|
"src/core/types"
|
||||||
|
"src/core/utils"
|
||||||
|
"src/middleware"
|
||||||
|
"src/middleware/api"
|
||||||
|
"src/middleware/backend"
|
||||||
|
"src/middleware/browser"
|
||||||
|
"src/middleware/config"
|
||||||
|
"src/middleware/connection"
|
||||||
|
"src/middleware/controller"
|
||||||
|
"src/middleware/i18n"
|
||||||
|
"src/middleware/interfaces"
|
||||||
|
"src/middleware/queue"
|
||||||
|
"src/middleware/servers"
|
||||||
|
"src/middleware/tests"
|
||||||
|
"src/middleware/tests/benchmark"
|
||||||
|
"src/middleware/tests/fuzzers"
|
||||||
|
"src/middleware/tests/mocks"
|
||||||
|
"src/middleware/tests/mutation"
|
||||||
|
"src/middleware/types"
|
||||||
|
"src/modules"
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "Creando estructura del proyecto: ${destino}"
|
||||||
|
|
||||||
|
for dir in "${directories[@]}"; do
|
||||||
|
mkdir -p "${destino%/}/${dir}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Estructura creada en: ${destino%/}"
|
||||||
|
echo " ${#directories[@]} directorios creados."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ejecutar si se invoca directamente
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
create_project_structure "$@"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
name: git_clean_branches
|
||||||
|
kind: function
|
||||||
|
lang: bash
|
||||||
|
domain: shell
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "git_clean_branches([base_branch: string], [--remote], [--force]) -> void"
|
||||||
|
description: "Elimina ramas locales ya mergeadas en la rama base (autodetecta main/master). Con --remote también borra las ramas en todos los remotes. Con --force omite la confirmación interactiva."
|
||||||
|
tags: [bash, git, branches, clean, merge]
|
||||||
|
uses_functions: []
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: base_branch
|
||||||
|
desc: "rama base para detectar merges (opcional; autodetecta main o master)"
|
||||||
|
- name: --remote
|
||||||
|
desc: "flag para también eliminar ramas en todos los remotes"
|
||||||
|
- name: --force
|
||||||
|
desc: "flag para omitir confirmación interactiva"
|
||||||
|
output: "lista de ramas eliminadas a stdout; exit code 1 si no es repo Git o no se detecta la rama base"
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "bash/functions/shell/git_clean_branches.sh"
|
||||||
|
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git"
|
||||||
|
source_license: "MIT"
|
||||||
|
source_file: "scripts/linux/git_utils/limpiar_ramas.sh"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source bash/functions/shell/git_clean_branches.sh
|
||||||
|
|
||||||
|
# Limpieza básica con confirmación
|
||||||
|
git_clean_branches
|
||||||
|
|
||||||
|
# Sin confirmación, sobre rama base "develop"
|
||||||
|
git_clean_branches develop --force
|
||||||
|
|
||||||
|
# Eliminar también en remotes
|
||||||
|
git_clean_branches main --remote --force
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
Ramas protegidas que nunca se eliminan: `main`, `master`, `develop`, `dev`, `staging`, `release`. Siempre hace prune de referencias remotas obsoletas antes de buscar candidatos.
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# git_clean_branches
|
||||||
|
# ------------------
|
||||||
|
# Elimina ramas locales ya mergeadas en la rama base (main/master por defecto).
|
||||||
|
# Opcionalmente también elimina las ramas en remotes.
|
||||||
|
#
|
||||||
|
# USO:
|
||||||
|
# source git_clean_branches.sh
|
||||||
|
# git_clean_branches [base_branch] [--remote] [--force]
|
||||||
|
#
|
||||||
|
# ARGUMENTOS:
|
||||||
|
# base_branch Rama base (opcional; autodetecta main/master si se omite)
|
||||||
|
# --remote Elimina también las ramas en todos los remotes
|
||||||
|
# --force Omite confirmación interactiva
|
||||||
|
|
||||||
|
git_clean_branches() {
|
||||||
|
local base_branch=""
|
||||||
|
local delete_remote=false
|
||||||
|
local force=false
|
||||||
|
|
||||||
|
# Parsear argumentos
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--remote) delete_remote=true ;;
|
||||||
|
--force) force=true ;;
|
||||||
|
*) [[ -z "$base_branch" ]] && base_branch="$arg" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Validar repo
|
||||||
|
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
|
||||||
|
echo "git_clean_branches: el directorio actual no es un repositorio Git" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Detectar rama base
|
||||||
|
if [[ -z "$base_branch" ]]; then
|
||||||
|
if git show-ref --verify --quiet refs/heads/main; then
|
||||||
|
base_branch="main"
|
||||||
|
elif git show-ref --verify --quiet refs/heads/master; then
|
||||||
|
base_branch="master"
|
||||||
|
else
|
||||||
|
echo "git_clean_branches: no se detectó main/master; pasa la rama base como argumento" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "git_clean_branches: rama base = ${base_branch}"
|
||||||
|
|
||||||
|
# Hacer prune de referencias remotas obsoletas
|
||||||
|
echo "Haciendo prune de referencias remotas..."
|
||||||
|
git remote | while IFS= read -r remote; do
|
||||||
|
git remote prune "$remote" 2>/dev/null && echo " Pruned: ${remote}"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Buscar ramas locales mergeadas
|
||||||
|
local candidates
|
||||||
|
candidates="$(git branch --merged "$base_branch" \
|
||||||
|
| grep -v -E "^\*|^\s*(main|master|develop|dev|staging|release)$" \
|
||||||
|
| sed 's/^[[:space:]]*//' \
|
||||||
|
| grep -v "^$" || true)"
|
||||||
|
|
||||||
|
if [[ -z "$candidates" ]]; then
|
||||||
|
echo "No hay ramas locales mergeadas para eliminar."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Ramas candidatas a eliminar:"
|
||||||
|
echo "$candidates" | while IFS= read -r b; do
|
||||||
|
echo " - ${b}"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Confirmación (omitir si --force)
|
||||||
|
if [[ "$force" != true ]]; then
|
||||||
|
echo -n "¿Eliminar estas ramas locales? [y/N] "
|
||||||
|
read -r answer
|
||||||
|
[[ "$answer" != "y" && "$answer" != "Y" ]] && { echo "Operación cancelada."; return 0; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Eliminar ramas locales
|
||||||
|
local deleted=0
|
||||||
|
local failed=0
|
||||||
|
while IFS= read -r branch; do
|
||||||
|
if git branch -d "$branch"; then
|
||||||
|
echo " Eliminada local: ${branch}"
|
||||||
|
deleted=$((deleted + 1))
|
||||||
|
else
|
||||||
|
echo " No se pudo eliminar: ${branch}" >&2
|
||||||
|
failed=$((failed + 1))
|
||||||
|
fi
|
||||||
|
done <<< "$candidates"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Resumen: ${deleted} eliminada(s), ${failed} fallida(s)"
|
||||||
|
|
||||||
|
# Eliminar en remotes si se solicitó
|
||||||
|
if [[ "$delete_remote" == true ]]; then
|
||||||
|
local remotes
|
||||||
|
remotes="$(git remote 2>/dev/null || true)"
|
||||||
|
if [[ -n "$remotes" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "Eliminando ramas en remotes..."
|
||||||
|
while IFS= read -r branch; do
|
||||||
|
while IFS= read -r remote; do
|
||||||
|
if git ls-remote --heads "$remote" "$branch" 2>/dev/null | grep -q "$branch"; then
|
||||||
|
if git push "$remote" --delete "$branch"; then
|
||||||
|
echo " Eliminada remota: ${remote}/${branch}"
|
||||||
|
else
|
||||||
|
echo " No se pudo eliminar remota: ${remote}/${branch}" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done <<< "$remotes"
|
||||||
|
done <<< "$candidates"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Limpieza completada."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ejecutar si se invoca directamente
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
git_clean_branches "$@"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
name: git_log_visual
|
||||||
|
kind: function
|
||||||
|
lang: bash
|
||||||
|
domain: shell
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "git_log_visual([--count N: int], [--author X: string], [--since D: string], [--all]) -> void"
|
||||||
|
description: "Muestra el historial de commits Git con grafo visual, colores y formato legible (hash, fecha, autor, mensaje, ramas). Soporta filtros por cantidad, autor, fecha y modo todas-las-ramas. Pagina con less."
|
||||||
|
tags: [bash, git, log, history, graph]
|
||||||
|
uses_functions: []
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: --count
|
||||||
|
desc: "número de commits a mostrar (default: 20)"
|
||||||
|
- name: --author
|
||||||
|
desc: "filtrar por nombre o email del autor (parcial, igual que git --author)"
|
||||||
|
- name: --since
|
||||||
|
desc: "mostrar solo commits desde esta fecha (ej: '1 week ago', '2025-01-01')"
|
||||||
|
- name: --all
|
||||||
|
desc: "incluir commits de todas las ramas, no solo la actual"
|
||||||
|
output: "historial de commits paginado con less -R; exit code 1 si no es un repo Git"
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "bash/functions/shell/git_log_visual.sh"
|
||||||
|
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git"
|
||||||
|
source_license: "MIT"
|
||||||
|
source_file: "scripts/linux/git_utils/historial_commits.sh"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source bash/functions/shell/git_log_visual.sh
|
||||||
|
|
||||||
|
# Últimos 20 commits (default)
|
||||||
|
git_log_visual
|
||||||
|
|
||||||
|
# Últimos 50 commits de todas las ramas
|
||||||
|
git_log_visual --count 50 --all
|
||||||
|
|
||||||
|
# Commits de un autor esta semana
|
||||||
|
git_log_visual --author "lucas" --since "1 week ago"
|
||||||
|
|
||||||
|
# Commits de hoy en todas las ramas
|
||||||
|
git_log_visual --since "00:00:00" --all --count 100
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
Usa `less -R --quit-if-one-screen` para paginar: si el log cabe en pantalla, no abre el paginador. El formato incluye hash corto (amarillo), fecha (cyan), autor (verde), mensaje y refs de ramas (rojo).
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# git_log_visual
|
||||||
|
# --------------
|
||||||
|
# Muestra el historial de commits con grafo visual, colores y formato legible.
|
||||||
|
# Soporta filtros por cantidad, autor, fecha y todas las ramas.
|
||||||
|
#
|
||||||
|
# USO:
|
||||||
|
# source git_log_visual.sh
|
||||||
|
# git_log_visual [--count N] [--author X] [--since D] [--all]
|
||||||
|
#
|
||||||
|
# ARGUMENTOS:
|
||||||
|
# --count N Número de commits a mostrar (default: 20)
|
||||||
|
# --author X Filtrar por nombre o email del autor
|
||||||
|
# --since D Mostrar commits desde fecha (ej: "1 week ago", "2025-01-01")
|
||||||
|
# --all Incluir todas las ramas
|
||||||
|
|
||||||
|
git_log_visual() {
|
||||||
|
local count=20
|
||||||
|
local author=""
|
||||||
|
local since=""
|
||||||
|
local all_branches=false
|
||||||
|
|
||||||
|
# Parsear argumentos
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--count|-n)
|
||||||
|
count="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--author|-a)
|
||||||
|
author="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--since|-s)
|
||||||
|
since="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--all)
|
||||||
|
all_branches=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Validar repo
|
||||||
|
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
|
||||||
|
echo "git_log_visual: el directorio actual no es un repositorio Git" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local log_format="%C(yellow)%h%C(reset) %C(cyan)%ad%C(reset) %C(green)%an%C(reset) %s%C(red)%d%C(reset)"
|
||||||
|
|
||||||
|
# Construir argumentos del comando
|
||||||
|
local args=()
|
||||||
|
args+=("--graph")
|
||||||
|
args+=("--color=always")
|
||||||
|
args+=("--date=short")
|
||||||
|
args+=("--format=${log_format}")
|
||||||
|
args+=("-n" "${count}")
|
||||||
|
|
||||||
|
[[ -n "$author" ]] && args+=("--author=${author}")
|
||||||
|
[[ -n "$since" ]] && args+=("--since=${since}")
|
||||||
|
[[ "$all_branches" == true ]] && args+=("--all")
|
||||||
|
|
||||||
|
# Ejecutar
|
||||||
|
git log "${args[@]}" 2>/dev/null | less -R --quit-if-one-screen
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ejecutar si se invoca directamente
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
git_log_visual "$@"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
name: git_push_all_remotes
|
||||||
|
kind: function
|
||||||
|
lang: bash
|
||||||
|
domain: shell
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "git_push_all_remotes([--message msg: string]) -> void"
|
||||||
|
description: "Hace commit de todos los cambios pendientes (git add -A + git commit) y pushea la rama actual a todos los remotes configurados. Si no hay cambios solo pushea. Requiere --message si hay cambios sin commitear."
|
||||||
|
tags: [bash, git, push, remote, all]
|
||||||
|
uses_functions: []
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: --message
|
||||||
|
desc: "mensaje de commit a usar si hay cambios pendientes (requerido cuando hay cambios)"
|
||||||
|
output: "progreso del push a stdout por cada remote; exit code 1 si algún push falla"
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "bash/functions/shell/git_push_all_remotes.sh"
|
||||||
|
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git"
|
||||||
|
source_license: "MIT"
|
||||||
|
source_file: "scripts/linux/git_utils/push_todos_remotes.sh"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source bash/functions/shell/git_push_all_remotes.sh
|
||||||
|
|
||||||
|
# Solo push (sin cambios pendientes)
|
||||||
|
git_push_all_remotes
|
||||||
|
|
||||||
|
# Commit + push a todos los remotes
|
||||||
|
git_push_all_remotes --message "feat: nueva funcionalidad"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
Usa `--set-upstream` automáticamente si la rama no existe en el remote. Sale con exit code 1 si hay cambios pero no se pasa `--message`, o si algún push falla.
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# git_push_all_remotes
|
||||||
|
# --------------------
|
||||||
|
# Hace commit de todos los cambios pendientes (si los hay) y pushea
|
||||||
|
# a todos los remotes configurados en el repositorio.
|
||||||
|
#
|
||||||
|
# USO:
|
||||||
|
# source git_push_all_remotes.sh
|
||||||
|
# git_push_all_remotes [--message "mensaje"]
|
||||||
|
#
|
||||||
|
# ARGUMENTOS:
|
||||||
|
# --message "msg" Mensaje de commit (si hay cambios). Si se omite y hay
|
||||||
|
# cambios, sale con error.
|
||||||
|
|
||||||
|
git_push_all_remotes() {
|
||||||
|
local commit_msg=""
|
||||||
|
|
||||||
|
# Parsear argumentos
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--message|-m)
|
||||||
|
commit_msg="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Validar repo
|
||||||
|
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
|
||||||
|
echo "git_push_all_remotes: el directorio actual no es un repositorio Git" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local branch
|
||||||
|
branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)"
|
||||||
|
|
||||||
|
local remotes
|
||||||
|
remotes="$(git remote 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [[ -z "$remotes" ]]; then
|
||||||
|
echo "git_push_all_remotes: no hay remotes configurados en este repositorio" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Rama actual: ${branch}"
|
||||||
|
echo "Remotes:"
|
||||||
|
echo "$remotes" | while IFS= read -r r; do
|
||||||
|
local url
|
||||||
|
url="$(git remote get-url "$r" 2>/dev/null || echo "?")"
|
||||||
|
echo " ${r} → ${url}"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Gestión del commit si hay cambios
|
||||||
|
local has_changes=false
|
||||||
|
if [[ -n "$(git status --porcelain 2>/dev/null)" ]]; then
|
||||||
|
has_changes=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$has_changes" == true ]]; then
|
||||||
|
if [[ -z "$commit_msg" ]]; then
|
||||||
|
echo "git_push_all_remotes: hay cambios pero no se proporcionó --message" >&2
|
||||||
|
echo " Usa: git_push_all_remotes --message \"tu mensaje\"" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Cambios detectados:"
|
||||||
|
git status --short | while IFS= read -r line; do
|
||||||
|
echo " ${line}"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Añadiendo todos los cambios..."
|
||||||
|
git add -A
|
||||||
|
|
||||||
|
echo "Haciendo commit: \"${commit_msg}\""
|
||||||
|
git commit -m "$commit_msg"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo "Directorio de trabajo limpio. Se empujarán commits existentes."
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Push a cada remote
|
||||||
|
local ok_count=0
|
||||||
|
local fail_count=0
|
||||||
|
local failed_remotes=()
|
||||||
|
local remote_count
|
||||||
|
remote_count="$(echo "$remotes" | wc -l | tr -d ' ')"
|
||||||
|
|
||||||
|
while IFS= read -r remote; do
|
||||||
|
# Verificar si la rama existe en el remote
|
||||||
|
local push_flags=""
|
||||||
|
if ! git ls-remote --heads "$remote" "$branch" 2>/dev/null | grep -q "$branch"; then
|
||||||
|
push_flags="--set-upstream"
|
||||||
|
echo "La rama '${branch}' no existe en '${remote}', se creará"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Push → ${remote} (${branch})..."
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
if git push $push_flags "$remote" "$branch" 2>&1; then
|
||||||
|
echo " Push completado → ${remote}/${branch}"
|
||||||
|
ok_count=$((ok_count + 1))
|
||||||
|
else
|
||||||
|
echo " Falló el push a ${remote}/${branch}" >&2
|
||||||
|
fail_count=$((fail_count + 1))
|
||||||
|
failed_remotes+=("$remote")
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
done <<< "$remotes"
|
||||||
|
|
||||||
|
echo "=== Resumen ==="
|
||||||
|
echo "Push completado en ${ok_count}/${remote_count} remote(s)"
|
||||||
|
|
||||||
|
if [[ $fail_count -gt 0 ]]; then
|
||||||
|
echo "Fallaron ${fail_count} remote(s): ${failed_remotes[*]}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ejecutar si se invoca directamente
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
git_push_all_remotes "$@"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
name: git_repo_status
|
||||||
|
kind: function
|
||||||
|
lang: bash
|
||||||
|
domain: shell
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "git_repo_status() -> void"
|
||||||
|
description: "Muestra el estado completo de un repositorio Git: rama actual, upstream (ahead/behind), cambios pendientes, stash, remotes y últimos 8 commits. Sale con exit code 1 si el directorio actual no es un repo Git."
|
||||||
|
tags: [bash, git, status, repo, branch]
|
||||||
|
uses_functions: []
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: "(ninguno)"
|
||||||
|
desc: "opera sobre el directorio de trabajo actual (cwd)"
|
||||||
|
output: "imprime el estado completo del repo a stdout; exit code 1 si no es un repo Git"
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "bash/functions/shell/git_repo_status.sh"
|
||||||
|
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git"
|
||||||
|
source_license: "MIT"
|
||||||
|
source_file: "scripts/linux/git_utils/estado_repo.sh"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Desde un directorio con repo git
|
||||||
|
cd /home/user/my-project
|
||||||
|
source bash/functions/shell/git_repo_status.sh
|
||||||
|
git_repo_status
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
No requiere dependencias externas más allá de git. Los colores del log de commits usan `--color=always` de git directamente. No produce output en stdout en caso de error — los mensajes de error van a stderr.
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# git_repo_status
|
||||||
|
# ---------------
|
||||||
|
# Muestra el estado completo de un repositorio Git: rama actual, upstream,
|
||||||
|
# cambios pendientes, stash, remotes y últimos commits.
|
||||||
|
# Sale con exit code 1 si el directorio actual no es un repositorio Git.
|
||||||
|
#
|
||||||
|
# USO:
|
||||||
|
# source git_repo_status.sh
|
||||||
|
# git_repo_status
|
||||||
|
#
|
||||||
|
# O como script directo:
|
||||||
|
# bash git_repo_status.sh
|
||||||
|
|
||||||
|
git_repo_status() {
|
||||||
|
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
|
||||||
|
echo "git_repo_status: el directorio actual no es un repositorio Git" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local branch
|
||||||
|
branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)"
|
||||||
|
|
||||||
|
# Upstream
|
||||||
|
local upstream
|
||||||
|
upstream="$(git rev-parse --abbrev-ref "${branch}@{upstream}" 2>/dev/null || echo "")"
|
||||||
|
local upstream_info
|
||||||
|
if [[ -z "$upstream" ]]; then
|
||||||
|
upstream_info="sin upstream"
|
||||||
|
else
|
||||||
|
local ahead behind
|
||||||
|
ahead="$(git rev-list "${upstream}..HEAD" --count 2>/dev/null || echo 0)"
|
||||||
|
behind="$(git rev-list "HEAD..${upstream}" --count 2>/dev/null || echo 0)"
|
||||||
|
upstream_info="↑${ahead} ↓${behind} (${upstream})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== Rama & Upstream ==="
|
||||||
|
echo " Rama actual: ${branch}"
|
||||||
|
echo " Upstream: ${upstream_info}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Cambios
|
||||||
|
echo "=== Cambios ==="
|
||||||
|
local changes
|
||||||
|
changes="$(git status --short 2>/dev/null)"
|
||||||
|
if [[ -z "$changes" ]]; then
|
||||||
|
echo " Directorio de trabajo limpio"
|
||||||
|
else
|
||||||
|
echo " Cambios pendientes:"
|
||||||
|
echo "$changes" | while IFS= read -r line; do
|
||||||
|
echo " ${line}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Stash
|
||||||
|
echo "=== Stash ==="
|
||||||
|
local stash_count
|
||||||
|
stash_count="$(git stash list 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
if [[ "$stash_count" -eq 0 ]]; then
|
||||||
|
echo " Sin entradas en stash"
|
||||||
|
else
|
||||||
|
echo " ${stash_count} entrada(s) en stash:"
|
||||||
|
git stash list | head -5 | while IFS= read -r line; do
|
||||||
|
echo " ${line}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Remotes
|
||||||
|
echo "=== Remotes ==="
|
||||||
|
local remotes
|
||||||
|
remotes="$(git remote -v 2>/dev/null | grep '(fetch)' || true)"
|
||||||
|
if [[ -z "$remotes" ]]; then
|
||||||
|
echo " Sin remotes configurados"
|
||||||
|
else
|
||||||
|
echo "$remotes" | while IFS= read -r line; do
|
||||||
|
local name url
|
||||||
|
name="$(echo "$line" | awk '{print $1}')"
|
||||||
|
url="$(echo "$line" | awk '{print $2}')"
|
||||||
|
echo " ${name} → ${url}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Últimos commits
|
||||||
|
echo "=== Últimos commits ==="
|
||||||
|
git log --oneline --decorate --color=always -8 2>/dev/null || true
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ejecutar si se invoca directamente
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
git_repo_status "$@"
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user