En la lección 02-06 aprendimos a movernos por el historial: git log con sus formatos, sus filtros por autor y fecha, y la búsqueda con -S y -G. Aquello era suficiente para un proyecto de una sola rama y unos pocos commits.
Ahora gestor-tareas tiene ocho meses, cuatro ramas activas, fusiones, etiquetas de versión y tres personas trabajando. Y las preguntas que se hace el equipo han cambiado de naturaleza:
- ¿Qué forma tiene el historial? ¿Qué se ha fusionado en
maineste mes? - ¿Qué hay en la rama de Carla que no esté en
main, y al revés? - ¿Quién ha contribuido qué, y cuánto?
- ¿Cuáles son los hitos —etiquetas y fusiones— sin el ruido de los commits intermedios?
Y hay un segundo problema, más prosaico pero igual de real. La invocación de la lección anterior era git blame -w -C --date=short -L :normalizarTexto:app.js app.js. Nadie escribe eso dos veces. La segunda mitad de la lección va de eso: convertir los comandos largos que usas a diario en palabras cortas, con los alias de Git.
Contenido
--graph: ver la forma del historial--first-parent: la línea principal, sin ruido--simplify-by-decoration: solo los hitos--mergesy--no-merges- Rangos de tres puntos y
--left-right --pretty=format:avanzado: colores y alineacióngit shortlog: quién ha contribuido qué- Componiendo consultas con lo aprendido
- Alias: qué son y dónde viven
- Alias con
!: shell y funciones - Un juego de alias para el día a día
- Riesgos y buenas prácticas con los alias
--graph: ver la forma del historial
--graph: ver la forma del historialgit log por defecto da una lista plana ordenada por fecha, que en un historial con ramas es engañosa: no se ve qué venía de dónde. --graph dibuja el DAG (lección 03-01) con caracteres ASCII a la izquierda.
* c8f2a1e (HEAD -> main, origin/main) Fusionar funcionalidad/filtro-estado |\ | * 4e8b2c9 (funcionalidad/filtro-estado) Añadir el filtro por estado de la tarea | * 2c6e9a4 Extraer los estilos del listado a estilos.css |/ * 8d4f2a7 Delegar los eventos del listado en el contenedor | * 7a1f5c3 (correccion/borrado-svg) Usar closest() al detectar el boton de borrar |/ * 3f1a8d6 Eliminar caracteres invisibles al normalizar el texto de las tareas * b7e2c4a (tag: v1.0.0) Normalizar el texto de las tareas antes de guardarlas
Los tres modificadores son inseparables en la práctica:
| Opción | Qué aporta |
|---|---|
--graph |
Las líneas y asteriscos que dibujan la topología |
--oneline |
Un commit por línea: sin él, el grafo es ilegible |
--decorate |
Los (HEAD -> main, tag: v1.0.0): dónde apuntan las referencias |
--all |
Todas las referencias, no solo la rama actual |
Cómo leer el dibujo:
- Cada
*es un commit. La columna en la que está indica su "carril". |\marca un commit de fusión: dos líneas entran en él.|/marca el punto en el que dos carriles se unen hacia atrás: el ancestro común.- La rama
correccion/borrado-svgaparece colgando de3f1a8d6sin haberse fusionado: son commits que existen solo en esa rama.
Un detalle sobre --decorate: desde Git 2.13 está activo por defecto cuando la salida va a un terminal (--decorate=auto), así que a menudo no hace falta escribirlo. Se puede fijar con:
Y variantes de --all que evitan el ruido de decenas de ramas remotas antiguas:
git log --graph --oneline --branches # solo ramas locales
git log --graph --oneline --branches --tags # locales + etiquetas
git log --graph --oneline --remotes=origin # solo las de origin
git log --graph --oneline main funcionalidad/filtro-estado # solo estas dos
--first-parent: la línea principal, sin ruido
--first-parent: la línea principal, sin ruidoYa apareció en la lección 06-02 con bisect, y en la 05-06 al elegir -m 1. La idea es la misma: en un commit de fusión, el primer padre es la rama en la que estabas (normalmente main) y el segundo es la que trajiste.
c8f2a1e Fusionar funcionalidad/filtro-estado 8d4f2a7 Delegar los eventos del listado en el contenedor 3f1a8d6 Eliminar caracteres invisibles al normalizar el texto de las tareas b7e2c4a Normalizar el texto de las tareas antes de guardarlas
Compáralo con el --graph del apartado anterior: han desaparecido 4e8b2c9 y 2c6e9a4, que eran los commits dentro de la rama fusionada. Lo que queda es la historia de main a nivel de integraciones: cada línea es o un commit directo o "aquí entró una funcionalidad entera".
Es la vista correcta para:
- Redactar las notas de una versión.
- Responder a "¿qué ha entrado en
mainesta semana?". - Contar cuántas funcionalidades se integraron en un periodo.
# Lo que ha entrado en main desde la última etiqueta
git log --oneline --first-parent v1.0.0..main
# Cuántas integraciones en el último mes
git log --oneline --first-parent --since=1.month main | wc -l
--simplify-by-decoration: solo los hitos
--simplify-by-decoration: solo los hitosEsta opción es poco conocida y muy útil. Muestra únicamente los commits que tienen una referencia apuntándoles (una rama, una etiqueta o HEAD), más los mínimos necesarios para mantener el grafo conectado.
* c8f2a1e (HEAD -> main, origin/main) Fusionar funcionalidad/filtro-estado |\ | * 4e8b2c9 (funcionalidad/filtro-estado) Añadir el filtro por estado de la tarea * | 7a1f5c3 (correccion/borrado-svg) Usar closest() al detectar el boton de borrar |/ * b7e2c4a (tag: v1.0.0) Normalizar el texto de las tareas antes de guardarlas * 1a5c9f3 (tag: v0.9.0) Preparar la version de pruebas
Cuatrocientos commits reducidos a cinco líneas: el esqueleto del proyecto. Es lo primero que conviene ejecutar al llegar a un repositorio desconocido, porque en diez segundos te da el mapa: qué versiones hay, qué ramas están vivas y dónde nacieron.
Combinaciones útiles:
# Solo la evolución de las versiones
git log --graph --oneline --simplify-by-decoration --tags
# Con fechas, para ver el ritmo de publicación
git log --simplify-by-decoration --tags --date=short \
--pretty=format:'%C(yellow)%d%C(reset) %ad %s'
--merges y --no-merges
--merges y --no-mergesDos filtros complementarios y directos:
git log --oneline --merges # SOLO commits de fusión
git log --oneline --no-merges # todos MENOS los de fusión| Filtro | Qué muestra | Para qué sirve |
|---|---|---|
--merges |
Solo commits con dos o más padres | Ver el historial de integraciones |
--no-merges |
Solo commits de trabajo real | Estadísticas, notas de versión, revisiones |
--min-parents=N |
Commits con al menos N padres | --min-parents=3 encuentra octopus merges |
--max-parents=1 |
Equivalente a --no-merges |
Igual, con otra sintaxis |
--max-parents=0 |
Los commits raíz (sin padres) | Detectar historiales injertados |
--no-merges es especialmente importante para las estadísticas: si cuentas commits por autor sin él, quien más fusiona parece el más productivo.
# Las fusiones de este mes, con quién las hizo
git log --merges --since=1.month --pretty=format:'%h %an %s'
# El trabajo real desde la última versión, para las notas de publicación
git log --no-merges --oneline v1.0.0..HEAD
- Rangos de tres puntos y
--left-right
--left-rightDe la lección 02-06 recordamos A..B: "los commits alcanzables desde B pero no desde A". Es asimétrico y responde a "¿qué le falta a A de B?".
El triple punto A...B es la diferencia simétrica: los commits que están en uno u otro, pero no en ambos. Responde a "¿en qué se han separado estas dos ramas?".
gitGraph commit id: "base" commit id: "M1" branch funcionalidad/filtro-estado commit id: "F1" commit id: "F2" checkout main commit id: "M2" commit id: "M3"
git log --oneline main..funcionalidad/filtro-estado # F1, F2
git log --oneline funcionalidad/filtro-estado..main # M2, M3
git log --oneline main...funcionalidad/filtro-estado # F1, F2, M2, M3Con A...B a secas no se sabe qué commit es de cuál lado. Para eso está --left-right:
> 6f2b9d4 Añadir el filtro por estado de la tarea > a1e5c93 Extraer los estilos del listado a estilos.css < e91d4a8 Corregir el contador de tareas pendientes < 7d3a8f4 Actualizar el README con las instrucciones de arranque
| Marca | Significado |
|---|---|
< |
El commit está en el lado izquierdo del rango (aquí, main) |
> |
El commit está en el lado derecho (aquí, la rama) |
Es la vista perfecta antes de fusionar o rebasar: de un vistazo ves cuánto se ha movido cada lado. Y con el remoto:
# ¿Cómo estoy respecto a mi rama de seguimiento? (lección 04-06)
git log --oneline --left-right --graph HEAD...@{u}
# Solo el recuento, que a menudo es lo único que quieres
git rev-list --left-right --count main...funcionalidad/filtro-estadoDos commits por delante en main, dos en la rama. Ese rev-list --count es, por cierto, lo que hay detrás del [ahead 2, behind 2] de git status y git branch -vv.
Un añadido útil: --cherry-mark marca con = los commits que son equivalentes a uno del otro lado (mismo cambio, distinto hash) —lo que veíamos con git cherry en la lección 05-03:
--pretty=format: avanzado: colores y alineación
--pretty=format: avanzado: colores y alineaciónEn la lección 02-06 vimos los marcadores básicos de --pretty=format:. Ahora vamos a los dos grupos que convierten una salida legible en una salida excelente: colores y alineación.
Colores
| Marcador | Efecto |
|---|---|
%C(rojo), %C(green), %C(yellow), %C(blue), %C(magenta), %C(cyan) |
Color de texto |
%C(bold blue) |
Color con atributo (bold, dim, ul, reverse) |
%C(reset) |
Vuelve al color por defecto |
%C(auto) |
Usa el color que Git usaría por defecto para ese campo |
%C(auto,yellow) |
Amarillo, pero solo si el color está activo |
%C(always,red) |
Rojo siempre, incluso al redirigir a un fichero |
%C(auto) es el que hay que usar por defecto, y hay una razón concreta: aplicado a %d (las decoraciones), pinta cada referencia con su color canónico —HEAD en cian, las ramas locales en verde, las remotas en rojo, las etiquetas en amarillo— exactamente como hace git log --decorate sin formato personalizado. Escribir eso a mano es imposible.
Además, %C(auto) respeta la configuración color.ui: si rediriges la salida a un fichero o a grep, los códigos de escape no se emiten y el fichero queda limpio.
Alineación
Cuando el formato tiene varias columnas, sin alineación queda un desastre visual. Los marcadores de relleno lo resuelven:
| Marcador | Efecto |
|---|---|
%<(N) |
Los siguientes campos ocupan al menos N caracteres, alineados a la izquierda |
%<(N,trunc) |
Igual, pero trunca con .. si se pasa |
%<(N,ltrunc) |
Trunca por la izquierda (..final) |
%<(N,mtrunc) |
Trunca por el medio (ini..fin) |
%>(N) |
Alineado a la derecha |
%><(N) |
Centrado |
%<|(N) |
Rellena hasta la columna N (absoluta, no relativa) |
El formato completo que usa el equipo:
git log --pretty=format:'%C(auto)%h %C(blue)%<(16,trunc)%an%C(reset) %C(green)%<(12)%ar%C(reset) %C(auto)%d%C(reset) %s'c8f2a1e Ana Ferrer hace 2 días (HEAD -> main, origin/main) Fusionar funcionalidad/filtro-estado 4e8b2c9 Bruno Salas hace 3 días (funcionalidad/filtro-estado) Añadir el filtro por estado 2c6e9a4 Carla Vidal hace 4 días Extraer los estilos del listado a estilos.css 8d4f2a7 Bruno Salas hace 6 sem. Delegar los eventos del listado en el contenedor b7e2c4a Carla Vidal hace 5 meses (tag: v1.0.0) Normalizar el texto de las tareas
Desglose pieza a pieza:
%C(auto)%h→ hash abreviado, con el color estándar de Git para hashes.%C(blue)%<(16,trunc)%an%C(reset)→ autor en azul, exactamente 16 columnas, truncado si no cabe.%C(green)%<(12)%ar%C(reset)→ fecha relativa ("hace 2 días") en verde, 12 columnas.%C(auto)%d%C(reset)→ las decoraciones, cada una con su color canónico.%s→ el asunto, que ocupa el resto.
El resultado es tabular, legible y compacto. En el apartado 11 lo convertiremos en un alias.
Un recordatorio de los marcadores más usados, para no volver a la lección 02-06:
| Marcador | Contenido |
|---|---|
%H / %h |
Hash completo / abreviado |
%an / %ae |
Nombre / email del autor |
%cn / %ce |
Nombre / email del committer |
%ad / %cd |
Fecha de autoría / de commit |
%ar / %cr |
Las mismas, en formato relativo |
%s / %b |
Asunto / cuerpo del mensaje |
%d / %D |
Decoraciones con paréntesis / sin ellos |
%p / %P |
Hashes abreviados / completos de los padres |
%G? |
Estado de la firma GPG (G buena, N sin firma...) |
%n / %% |
Salto de línea / signo de porcentaje literal |
Y para no repetir el formato cada vez, se puede guardar con nombre en la configuración:
git config --global pretty.tabla '%C(auto)%h %C(blue)%<(16,trunc)%an%C(reset) %C(green)%<(12)%ar%C(reset) %C(auto)%d%C(reset) %s'
git log --pretty=tabla
git shortlog: quién ha contribuido qué
git shortlog: quién ha contribuido quégit shortlog agrupa los commits por autor. Sin argumentos, lista los mensajes agrupados; con -s (summary) y -n (numbered), da el recuento ordenado:
Opciones útiles:
| Opción | Efecto |
|---|---|
-s |
Solo el recuento, sin los mensajes |
-n |
Ordena por número de commits, no alfabéticamente |
-e |
Muestra el email junto al nombre |
--no-merges |
Excluye las fusiones: casi siempre lo quieres |
-c |
Agrupa por committer en lugar de por autor |
--since / --until |
Acota el periodo |
# Contribuciones reales de los últimos tres meses
git shortlog -sn --no-merges --since=3.months
# Quién ha tocado app.js
git shortlog -sn --no-merges -- app.js
# Notas de versión agrupadas por persona
git shortlog --no-merges v1.0.0..HEADAna Ferrer (12):
Eliminar caracteres invisibles al normalizar el texto de las tareas
Añadir el contador de tareas pendientes
...
Bruno Salas (9):
Delegar los eventos del listado en el contenedor
...Esa última salida es, literalmente, un borrador de notas de versión.
Dos advertencias importantes, porque shortlog se malinterpreta con facilidad:
- El número de commits no mide productividad. Un commit puede ser una coma o una funcionalidad entera. Como métrica de rendimiento es peor que inútil: es perversa, porque premia trocear artificialmente.
- Una misma persona puede aparecer varias veces si ha usado distintos nombres o correos (portátil personal, servidor, plataforma web). Se unifica con un fichero
.mailmapen la raíz del repositorio:
# .mailmap Ana Ferrer <[email protected]> <[email protected]> Bruno Salas <[email protected]> <[email protected]> Carla Vidal <[email protected]>
Git lo aplica automáticamente en shortlog, log y blame.
- Componiendo consultas con lo aprendido
Todo lo anterior se combina, y ahí está la potencia real. Algunos ejemplos que resuelven preguntas concretas del día a día de gestor-tareas:
# ¿Qué ha entrado en main desde v1.0.0, sin ruido de fusiones,
# con quién y cuándo?
git log --no-merges --first-parent v1.0.0..main \
--pretty=format:'%C(auto)%h %C(blue)%<(14,trunc)%an%C(reset) %ad %s' \
--date=short# ¿Qué commits tocaron app.js y mencionan localStorage en el diff?
# (pickaxe -S de la lección 02-06 + formato)
git log -S "localStorage" --oneline -- app.js# El commit culpable que encontró bisect, en su contexto de grafo
git log --graph --oneline --all --ancestry-path 8d4f2a7..main--ancestry-path restringe el rango a los commits que están en el camino entre los dos extremos, lo cual es exactamente lo que quieres para responder "¿cómo llegó ese commit hasta main?".
# La evolución de una función concreta (log -L de la lección 06-03)
# limitada a los últimos tres meses
git log -L :normalizarTexto:app.js --since=3.months --oneline# ¿Cuántas líneas ha cambiado cada persona? (con todas las reservas
# del apartado 7 sobre las métricas)
git log --no-merges --numstat --pretty=format:'%an' | \
awk 'NF==1 {autor=$0} NF==3 {mas[autor]+=$1; menos[autor]+=$2}
END {for (a in mas) printf "%-20s +%-8d -%d\n", a, mas[a], menos[a]}'Y una comparación de las tres vistas del historial que hemos visto, para tenerlas juntas:
| Quiero ver... | Comando |
|---|---|
| La forma completa del proyecto | git log --graph --oneline --all |
La historia de main a nivel de integraciones |
git log --oneline --first-parent main |
| Solo los hitos (etiquetas y ramas) | git log --graph --oneline --simplify-by-decoration --all |
| El trabajo real, sin fusiones | git log --oneline --no-merges |
| En qué se han separado dos ramas | git log --oneline --left-right main...otra |
| Quién ha contribuido cuánto | git shortlog -sn --no-merges |
Llegados aquí, el problema es evidente: ninguno de estos comandos se escribe a mano dos veces.
- Alias: qué son y dónde viven
Un alias de Git es un nombre corto para un comando largo. Se define con git config:
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commitA partir de ahí, git st es git status. Git resuelve el alias antes de ejecutar y le pasa los argumentos extra:
Dónde viven. En el .gitconfig correspondiente al nivel elegido (lección 01-05), bajo la sección [alias]:
# ~/.gitconfig
[user]
name = Carla Vidal
email = [email protected]
[alias]
st = status
co = checkout
br = branch
ci = commitSe pueden editar a mano en el fichero —que es más cómodo para los alias largos— o con git config. Y consultarlos:
git config --get-regexp '^alias\.' # listar todos
git config --get alias.lg # ver uno concreto
git config --global --unset alias.co # borrar unoLos tres niveles funcionan igual que para el resto de la configuración: --system, --global y local. Los alias personales van en --global; un alias específico de un proyecto, en el local. Y recuerda que la configuración no se clona: los alias son tuyos, no del repositorio.
Dos reglas que evitan sorpresas:
- Un alias no puede sobrescribir un comando existente. Si defines
alias.status, Git ignorará el alias y ejecutará el comando real. Es una protección deliberada. - Los alias pueden encadenarse, pero con cuidado: un alias que invoca a otro funciona, y un alias recursivo cuelga Git.
- Alias con
!: shell y funciones
!: shell y funcionesUn alias normal solo puede empezar por un subcomando de Git. Para todo lo demás —encadenar comandos, usar tuberías, invocar programas externos, colocar los argumentos en un sitio distinto del final— se antepone !, que le dice a Git: "esto es una orden de shell, ejecútala tal cual".
git config --global alias.limpiar '!git branch --merged main | grep -v main | xargs -r git branch -d'Tres cosas fundamentales sobre los alias con !:
- Se ejecutan desde la RAÍZ del repositorio
Este es el riesgo principal y la fuente número uno de sorpresas. Git hace cd a la raíz del árbol de trabajo antes de ejecutar el alias. Así que si estás en ~/proyectos/gestor-tareas/componentes/ y ejecutas un alias con !ls, verás el contenido de ~/proyectos/gestor-tareas/, no el del directorio en el que estás.
Git da una variable para paliarlo:
GIT_PREFIX contiene la ruta relativa desde la raíz hasta el directorio en el que estabas al invocar el alias (vacía si ya estabas en la raíz). Cualquier alias con ! que trabaje con rutas relativas debe tenerlo en cuenta:
- Los argumentos se añaden al final... salvo que uses una función
Git pega los argumentos que escribas al final del comando del alias. Eso es un problema cuando los necesitas en medio:
# MAL: 'git buscar hola' ejecuta 'git log --oneline | grep hola'
git config --global alias.buscar '!git log --oneline | grep'En este caso funciona por casualidad. Pero si el argumento va en medio, no:
# MAL: 'git desde main' ejecuta 'git log --oneline ..HEAD main'
git config --global alias.desde '!git log --oneline ..HEAD'La solución canónica es definir una función de shell y llamarla:
El patrón '!f() { ...; }; f' se lee así: define una función llamada f, y a continuación invócala. Los argumentos que Git pega al final se convierten en los argumentos de f. Es el idioma estándar y lo verás en todos los .gitconfig del mundo.
Usa "$@" para pasar todos los argumentos y "$1", "$2" para posiciones concretas. Y comíllalo siempre: sin comillas, un nombre de rama con caracteres raros rompe el alias.
- Se ejecutan con
sh, no con bash
sh, no con bashGit usa /bin/sh. En muchos sistemas eso es dash, no bash, y las extensiones de bash ([[ ]], arrays, mapfile) no funcionan. Escribe alias en shell POSIX o invoca explícitamente el intérprete:
Para cualquier cosa que pase de tres líneas, la respuesta correcta no es un alias sino un script en el PATH. Git tiene una convención preciosa para eso: cualquier ejecutable llamado git-<algo> que esté en el PATH se puede invocar como git <algo>:
# ~/bin/git-resumen (con chmod +x)
#!/usr/bin/env bash
set -euo pipefail
echo "=== Rama actual ==="
git branch --show-current
echo
echo "=== Últimos 10 commits ==="
git log --oneline -10
echo
echo "=== Estado ==="
git status --shortSin configurar nada. Es la vía correcta para la lógica compleja, y de paso el script se puede versionar y compartir.
- Un juego de alias para el día a día
Estos son los que de verdad se ganan el sitio. Empezando por el clásico absoluto:
git config --global alias.lg "log --graph --abbrev-commit --decorate --all --pretty=format:'%C(auto)%h%C(reset) -%C(auto)%d%C(reset) %s %C(green)(%ar)%C(reset) %C(blue)<%an>%C(reset)'"* c8f2a1e - (HEAD -> main, origin/main) Fusionar funcionalidad/filtro-estado (hace 2 días) <Ana Ferrer> |\ | * 4e8b2c9 - (funcionalidad/filtro-estado) Añadir el filtro por estado (hace 3 días) <Bruno Salas> | * 2c6e9a4 - Extraer los estilos del listado a estilos.css (hace 4 días) <Carla Vidal> |/ * 8d4f2a7 - Delegar los eventos del listado en el contenedor (hace 6 sem.) <Bruno Salas> * b7e2c4a - (tag: v1.0.0) Normalizar el texto de las tareas (hace 5 meses) <Carla Vidal>
El lg es probablemente el alias más copiado de la historia de Git, y con razón: sustituye a un comando de 180 caracteres que nadie recuerda.
La tabla completa del juego recomendado:
| Alias | Definición | Para qué |
|---|---|---|
st |
status --short --branch |
Estado compacto con la rama y el seguimiento |
lg |
El de arriba | El grafo completo, coloreado |
lgd |
log --graph --oneline --decorate |
Igual pero solo la rama actual |
ultimo |
log -1 HEAD --stat |
El último commit con sus ficheros |
linea |
log --oneline -20 |
Los últimos 20, sin más |
hitos |
log --graph --oneline --simplify-by-decoration --all |
El esqueleto del proyecto |
integrado |
log --oneline --first-parent |
La línea principal de main |
quien |
shortlog -sn --no-merges |
Contribuciones por persona |
culpa |
blame -w -C --date=short |
El blame de la lección 06-03, bien configurado |
historia |
!f() { git log -L :"$1":"$2"; }; f |
git historia normalizarTexto app.js |
pendiente |
!git log --oneline @{u}..HEAD |
Lo que tengo sin enviar |
entrante |
!git fetch -q && git log --oneline HEAD..@{u} |
Lo que hay en el remoto sin traer |
divergencia |
!f() { git log --oneline --left-right "$1"...HEAD; }; f |
En qué me he separado de una rama |
wip |
!git add -A && git commit -m 'WIP: trabajo en curso' --no-verify |
Guardado rápido antes de cambiar de contexto |
deshacer |
reset HEAD~1 --mixed |
Deshace el último commit conservando los cambios |
enmendar |
commit --amend --no-edit |
Añade lo preparado al último commit |
limpiar |
!git branch --merged main | grep -vE '^\*|main' | xargs -r git branch -d |
Borra las ramas ya fusionadas |
ramas |
branch -vv --sort=-committerdate |
Ramas por actividad reciente, con seguimiento |
guardado |
stash list --pretty=format:'%C(yellow)%gd%C(reset) %s' |
La pila de stash, legible |
etiquetas |
tag -l --sort=-v:refname --format='%(refname:short) %(creatordate:short) %(subject)' |
Etiquetas por versión descendente |
alias |
!git config --get-regexp '^alias\.' | sed 's/^alias\.//' |
Recordar qué alias tengo |
Ese último es más útil de lo que parece: a los seis meses nadie recuerda todos sus alias.
Para instalarlos de golpe, es más cómodo editar el fichero que lanzar veinte git config:
[alias]
st = status --short --branch
lg = log --graph --abbrev-commit --decorate --all --pretty=format:'%C(auto)%h%C(reset) -%C(auto)%d%C(reset) %s %C(green)(%ar)%C(reset) %C(blue)<%an>%C(reset)'
hitos = log --graph --oneline --simplify-by-decoration --all
integrado = log --oneline --first-parent
quien = shortlog -sn --no-merges
culpa = blame -w -C --date=short
historia = "!f() { git log -L :\"$1\":\"$2\"; }; f"
pendiente = "!git log --oneline @{u}..HEAD"
divergencia = "!f() { git log --oneline --left-right \"$1\"...HEAD; }; f"
deshacer = reset HEAD~1 --mixed
enmendar = commit --amend --no-edit
ramas = branch -vv --sort=-committerdate
alias = "!git config --get-regexp '^alias\\.' | sed 's/^alias\\.//'"Fíjate en el escapado: en el fichero, los valores con comillas o barras invertidas van entre comillas dobles y con las barras duplicadas. Es la parte más molesta de los alias con !, y la razón por la que conviene probarlos justo después de escribirlos.
Y esto conecta con los módulos anteriores: los alias no son solo para log. pendiente usa @{u} de la lección 04-06; enmendar es el --amend de la 02-04; limpiar es la gestión de ramas de la 03-06; culpa es el blame de la 06-03. Un alias es la forma de fijar en un gesto lo que has aprendido a hacer bien, para no tener que recordar las siete opciones cada vez.
- Riesgos y buenas prácticas con los alias
Riesgo 1: el directorio de ejecución. Ya visto: los alias con ! se ejecutan desde la raíz del repositorio. Usa GIT_PREFIX si el alias trabaja con rutas.
Riesgo 2: alias destructivos con nombres cortos. Un git p que haga push --force es un accidente esperando a ocurrir. Regla: cuanto más destructivo, más largo el nombre. Y en el caso de push --force, usa siempre --force-with-lease (lección 04-05).
Riesgo 3: dependencia de tu máquina. Tus alias no están en el portátil del compañero al que pides ayuda, ni en el servidor. Conviene saber el comando real que hay detrás de cada uno.
Riesgo 4: sh no es bash. Las extensiones de bash fallan silenciosamente o con errores raros.
Riesgo 5: alias que ocultan efectos. Un git sync que haga fetch + rebase + push está bien hasta el día que hay conflictos y no entiendes en qué punto se ha quedado el proceso.
Buenas prácticas:
- Que el nombre diga qué hace.
pendientees mejor quep. - Versiona tu
.gitconfig. Muchos guardan sus dotfiles en un repositorio propio: los alias son configuración valiosa y cuesta rehacerlos. - Añade alias poco a poco. Cuando notes que escribes lo mismo por tercera vez, ahí tienes un candidato. Copiar una lista de cuarenta alias de internet garantiza no usar ninguno.
- Prueba el alias nada más crearlo, especialmente los que llevan
!y comillas. - Para lógica compleja,
git-<nombre>en elPATH, no un alias de tres líneas.
Errores Comunes y Consejos
Error 1: --graph sin --oneline. El grafo con mensajes completos es ilegible. Van siempre juntos.
Error 2: olvidar --all y creer que la rama no existe. git log --graph solo muestra la rama actual. Sin --all, no ves las demás.
Error 3: confundir .. con .... A..B es asimétrico ("lo que le falta a A"); A...B es simétrico ("en qué se han separado"). Con --left-right la ambigüedad desaparece.
Error 4: contar commits con shortlog sin --no-merges. Quien más integra parece el que más produce.
Error 5: usar el recuento de commits como métrica de rendimiento. Es una mala métrica y crea incentivos perversos.
Error 6: alias con ! que asume el directorio actual. Se ejecuta desde la raíz. GIT_PREFIX.
Error 7: argumentos en medio de un alias sin usar función. Git los pega al final. El patrón '!f() { ...; }; f' es la solución.
Error 8: escapado mal hecho en el .gitconfig. Comillas dobles alrededor del valor y barras invertidas duplicadas. Prueba el alias inmediatamente.
Consejo 1: %C(auto) por defecto en tus formatos. Colorea las decoraciones correctamente y respeta la configuración y las redirecciones.
Consejo 2: guarda formatos con nombre. git config --global pretty.tabla '...' y después git log --pretty=tabla.
Consejo 3: --simplify-by-decoration al llegar a un repositorio nuevo. Diez segundos para el mapa completo.
Consejo 4: git rev-list --count A...B para el recuento puro. Cuando solo quieres saber cuánto, no qué.
Consejo 5: un .mailmap en cuanto alguien aparezca duplicado. Es un fichero de tres líneas que arregla shortlog, log y blame a la vez.
Ejercicios
Ejercicio 1: leer la forma de un historial
Crea un repositorio con esta topología: cuatro commits en main, una rama funcionalidad/a con dos commits fusionada con --no-ff, una rama funcionalidad/b con dos commits sin fusionar, y una etiqueta v1.0 en el segundo commit de main. Después:
- Muestra el grafo completo con
--graph --oneline --decorate --all. - Muestra solo la línea principal con
--first-parent. - Muestra solo los hitos con
--simplify-by-decoration --all. - Muestra solo las fusiones, y después solo los commits que no lo son.
- Explica qué commits desaparecen en cada vista y por qué.
Ejercicio 2: comparar dos ramas
Sobre el mismo repositorio:
- Añade dos commits más a
mainy dos más afuncionalidad/b. - Usa
main..funcionalidad/byfuncionalidad/b..main, y explica la diferencia. - Usa
main...funcionalidad/bcon--left-righte identifica cada lado. - Obtén el recuento con
git rev-list --left-right --count. - Crea un formato
--prettycon colores y alineación que muestre hash, autor (14 columnas truncadas), fecha corta y asunto.
Ejercicio 3: construir tus alias
- Crea los alias
lg,quien,pendienteyculpade la tabla del apartado 11. - Crea un alias
historiaque acepte dos argumentos (función y fichero) y ejecutegit log -L. Pruébalo. - Crea un alias que demuestre el problema de
GIT_PREFIX: uno que ejecutelssin más, y otro que lo haga bien. Ejecútalos desde un subdirectorio y compara. - Lista todos tus alias con un alias.
Soluciones
Solución 1:
mkdir /tmp/practica-log && cd /tmp/practica-log
git init -b main
for i in 1 2; do echo "main $i" >> main.txt; git add .; git commit -q -m "Commit main $i"; done
git tag v1.0
git switch -qc funcionalidad/a
echo "a1" > a.txt && git add . && git commit -q -m "Funcionalidad A parte 1"
echo "a2" >> a.txt && git commit -qam "Funcionalidad A parte 2"
git switch -q main
echo "main 3" >> main.txt && git commit -qam "Commit main 3"
git merge -q --no-ff funcionalidad/a -m "Fusionar funcionalidad/a"
echo "main 4" >> main.txt && git commit -qam "Commit main 4"
git switch -qc funcionalidad/b v1.0
echo "b1" > b.txt && git add . && git commit -q -m "Funcionalidad B parte 1"
echo "b2" >> b.txt && git commit -qam "Funcionalidad B parte 2"
git switch -q main* 9c4e7b2 (HEAD -> main) Commit main 4 * 5f1d8a3 Fusionar funcionalidad/a |\ | * 2a8c6f9 (funcionalidad/a) Funcionalidad A parte 2 | * 7d3e1b5 Funcionalidad A parte 1 * | 4b9f2c8 Commit main 3 |/ | * 8e5a3d7 (funcionalidad/b) Funcionalidad B parte 2 | * 1c7b4f9 Funcionalidad B parte 1 |/ * 3a6d9e2 (tag: v1.0) Commit main 2 * 6f2c8b1 Commit main 1
9c4e7b2 Commit main 4 5f1d8a3 Fusionar funcionalidad/a 4b9f2c8 Commit main 3 3a6d9e2 Commit main 2 6f2c8b1 Commit main 1
Han desaparecido 2a8c6f9 y 7d3e1b5: son los commits dentro de funcionalidad/a, a los que solo se llega por el segundo padre de la fusión.
* 9c4e7b2 (HEAD -> main) Commit main 4 | * 8e5a3d7 (funcionalidad/b) Funcionalidad B parte 2 |/ | * 2a8c6f9 (funcionalidad/a) Funcionalidad A parte 2 |/ * 3a6d9e2 (tag: v1.0) Commit main 2
Solo los commits con referencia y lo mínimo para conectarlos: el esqueleto.
9c4e7b2 Commit main 4 2a8c6f9 Funcionalidad A parte 2 7d3e1b5 Funcionalidad A parte 1 4b9f2c8 Commit main 3 3a6d9e2 Commit main 2 6f2c8b1 Commit main 1
Solución 2:
echo "main 5" >> main.txt && git commit -qam "Commit main 5"
echo "main 6" >> main.txt && git commit -qam "Commit main 6"
git switch -q funcionalidad/b
echo "b3" >> b.txt && git commit -qam "Funcionalidad B parte 3"
echo "b4" >> b.txt && git commit -qam "Funcionalidad B parte 4"
git switch -q maind4a8f2c Funcionalidad B parte 4 b1e6c9f Funcionalidad B parte 3 8e5a3d7 Funcionalidad B parte 2 1c7b4f9 Funcionalidad B parte 1
Lo que tiene la rama y no tiene main: lo que entraría al fusionar.
7f3c2e8 Commit main 6 2d9b5a1 Commit main 5 9c4e7b2 Commit main 4 5f1d8a3 Fusionar funcionalidad/a 2a8c6f9 Funcionalidad A parte 2 7d3e1b5 Funcionalidad A parte 1 4b9f2c8 Commit main 3
Lo que le falta a la rama de main: lo que traería al rebasar o fusionar main en ella.
< 7f3c2e8 Commit main 6 < 2d9b5a1 Commit main 5 < 9c4e7b2 Commit main 4 < 5f1d8a3 Fusionar funcionalidad/a < 2a8c6f9 Funcionalidad A parte 2 < 7d3e1b5 Funcionalidad A parte 1 < 4b9f2c8 Commit main 3 > d4a8f2c Funcionalidad B parte 4 > b1e6c9f Funcionalidad B parte 3 > 8e5a3d7 Funcionalidad B parte 2 > 1c7b4f9 Funcionalidad B parte 1
git log --pretty=format:'%C(auto)%h %C(blue)%<(14,trunc)%an%C(reset) %C(green)%ad%C(reset) %s' --date=short -8Solución 3:
git config --global alias.lg "log --graph --abbrev-commit --decorate --all --pretty=format:'%C(auto)%h%C(reset) -%C(auto)%d%C(reset) %s %C(green)(%ar)%C(reset) %C(blue)<%an>%C(reset)'"
git config --global alias.quien "shortlog -sn --no-merges"
git config --global alias.pendiente "!git log --oneline @{u}..HEAD"
git config --global alias.culpa "blame -w -C --date=short"
git config --global alias.historia '!f() { git log -L :"$1":"$2"; }; f'
git config --global alias.alias "!git config --get-regexp '^alias\\.' | sed 's/^alias\\.//'"El alias con argumentos:
cat > funciones.js <<'FIN'
function saludar(nombre) {
return "Hola " + nombre;
}
FIN
git add . && git commit -q -m "Añadir la funcion de saludo"
sed -i 's/"Hola "/"¡Hola, "/' funciones.js
git commit -qam "Añadir la exclamacion al saludo"
git historia saludar funciones.jsEl problema de GIT_PREFIX:
mkdir -p componentes && echo "x" > componentes/boton.js
git add . && git commit -q -m "Añadir el componente boton"
git config --global alias.mal '!ls'
git config --global alias.bien '!f() { cd "${GIT_PREFIX:-.}" && ls; }; f'
cd componentes
git malHa listado la raíz del repositorio, no el directorio en el que estamos.
Con GIT_PREFIX, el alias respeta el directorio desde el que se invocó.
alias !git config --get-regexp '^alias\.' | sed 's/^alias\.//'
bien !f() { cd "${GIT_PREFIX:-.}" && ls; }; f
culpa blame -w -C --date=short
historia !f() { git log -L :"$1":"$2"; }; f
lg log --graph --abbrev-commit --decorate --all --pretty=format:...
mal !ls
pendiente !git log --oneline @{u}..HEAD
quien shortlog -sn --no-mergesConclusión
Esta lección ha convertido git log de un listado en una herramienta de consulta, y ha eliminado la fricción de usarla. Lo esencial:
--graph --oneline --decorate --alles la vista canónica de la topología. Los cuatro van juntos.--first-parentda la historia demaina nivel de integraciones, sin el interior de las ramas fusionadas. Es el mismo "primer padre" del-m 1dereverty delbisect --first-parent.--simplify-by-decorationreduce el historial a sus hitos: lo primero que ejecutar en un repositorio desconocido.--merges/--no-mergesseparan integraciones de trabajo real;--no-mergeses casi obligatorio en cualquier estadística.A...Bcon--left-rightresponde a "¿en qué se han separado estas dos ramas?", con<y>marcando cada lado.git rev-list --left-right --countda solo el recuento.--pretty=format:con%C(auto)y los marcadores de alineación%<(N,trunc)produce salidas tabulares y coloreadas; guárdalos con nombre enpretty.<nombre>.git shortlog -sn --no-mergesresume las contribuciones, con.mailmappara unificar identidades — y con la advertencia de que contar commits no mide nada útil.- Los alias viven en
[alias]del.gitconfigdel nivel que elijas. Los simples son un nombre para un subcomando; los que empiezan por!son shell, con tres cautelas: se ejecutan desde la raíz (usaGIT_PREFIX), los argumentos se pegan al final (usa el patrón'!f() { ...; }; f') y corren bajosh, nobash. - Para lógica larga, un ejecutable
git-<nombre>en elPATHes mejor que un alias. - Y la idea de fondo: un alias fija en un gesto lo que has aprendido a hacer bien.
culpa,pendiente,hitosolimpiarson las lecciones anteriores condensadas en una palabra.
El equipo ya sabe interrogar su historial con comodidad. Pero gestor-tareas está a punto de dejar de ser un solo repositorio: los botones, los diálogos y los campos de formulario que Ana ha ido extrayendo se han convertido en una biblioteca interna, componentes-ui, que vive en git.ejemplo.es/equipo/componentes-ui.git y que otros dos proyectos de la empresa también quieren usar.
La pregunta es cómo se compone un proyecto a partir de varios repositorios sin copiar y pegar código, y sin perder la trazabilidad de qué versión de la biblioteca usa cada versión de la aplicación. La respuesta nativa de Git son los submódulos, y es la lección 06-05: Submódulos de Git.
Dominando Git: De Principiante a Avanzado
Módulo 1: Introducción a Git
- ¿Qué es Git?
- Instalando Git
- Terminología Básica de Git
- El Modelo de Datos de Git
- Configurando Git
- Configuración Inicial
Módulo 2: Operaciones Básicas de Git
- Creando un Repositorio
- Clonando un Repositorio
- Flujo de Trabajo Básico de Git
- Preparando y Confirmando Cambios
- Inspeccionando Cambios con git diff
- Visualizando el Historial de Confirmaciones
Módulo 3: Ramas y Fusión
- Entendiendo las Ramas
- Creando y Cambiando Ramas
- Fusionando Ramas
- Estrategias de Fusión
- Resolviendo Conflictos de Fusión
- Gestión de Ramas
Módulo 4: Trabajando con Repositorios Remotos
- Entendiendo los Repositorios Remotos
- Agregando un Repositorio Remoto
- Autenticación con Repositorios Remotos
- Obteniendo y Extrayendo Cambios
- Enviando Cambios
- Rastreando Ramas
Módulo 5: Operaciones Avanzadas de Git
- Rebase
- Rebase Interactivo
- Cherry-Picking de Confirmaciones
- Guardando Cambios Temporales
- Etiquetando Confirmaciones
- Revirtiendo Confirmaciones
Módulo 6: Herramientas y Técnicas de Git
- Usando Git Hooks
- Git Bisect
- Git Blame
- Git Log y Alias
- Submódulos de Git
- Múltiples Copias de Trabajo con git worktree
Módulo 7: Estrategias de Colaboración y Flujo de Trabajo
- Forks y Pull Requests
- Revisiones de Código con Git
- Flujo de Trabajo Git Flow
- GitHub Flow
- Trunk Based Development
- Integración Continua con Git
Módulo 8: Mejores Prácticas y Consejos de Git
- Escribiendo Buenos Mensajes de Confirmación
- Manteniendo un Historial Limpio
- Ignorando Archivos con .gitignore
- Atributos de Fichero con .gitattributes
- Mejores Prácticas de Seguridad
- Consejos de Rendimiento
Módulo 9: Solución de Problemas y Depuración
- Problemas Comunes de Git
- Deshaciendo Cambios
- Resolviendo Divergencias con el Remoto
- Recuperando Confirmaciones Perdidas
- Tratando con Repositorios Corruptos
- Técnicas Avanzadas de Depuración
