A la lliçó 02-06 vam aprendre a moure'ns per l'historial: git log amb els seus formats, els seus filtres per autor i data, i la cerca amb -S i -G. Allò era suficient per a un projecte d'una sola branca i uns quants commits.

Ara gestor-tasques té vuit mesos, quatre branques actives, fusions, etiquetes de versió i tres persones treballant-hi. I les preguntes que es fa l'equip han canviat de naturalesa:

  • Quina forma té l'historial? Què s'ha fusionat a main aquest mes?
  • Què hi ha a la branca de la Carla que no sigui a main, i a l'inrevés?
  • Qui ha aportat què, i quant?
  • Quines són les fites —etiquetes i fusions— sense el soroll dels commits intermedis?

I hi ha un segon problema, més prosaic però igual de real. La invocació de la lliçó anterior era git blame -w -C --date=short -L :normalitzaText:app.js app.js. Ningú escriu això dues vegades. La segona meitat de la lliçó va d'això: convertir les ordres llargues que fas servir cada dia en paraules curtes, amb els àlies de Git.

Contingut

  1. --graph: veure la forma de l'historial
  2. --first-parent: la línia principal, sense soroll
  3. --simplify-by-decoration: només les fites
  4. --merges i --no-merges
  5. Rangs de tres punts i --left-right
  6. --pretty=format: avançat: colors i alineació
  7. git shortlog: qui ha aportat què
  8. Component consultes amb el que hem après
  9. Àlies: què són i on viuen
  10. Àlies amb !: shell i funcions
  11. Un joc d'àlies per al dia a dia
  12. Riscos i bones pràctiques amb els àlies

  1. --graph: veure la forma de l'historial

git log per defecte dona una llista plana ordenada per data, que en un historial amb branques és enganyosa: no es veu què venia d'on. --graph dibuixa el DAG (lliçó 03-01) amb caràcters ASCII a l'esquerra.

git log --graph --oneline --decorate --all
*   c8f2a1e (HEAD -> main, origin/main) Fusiona funcionalitat/filtre-estat
|\
| * 4e8b2c9 (funcionalitat/filtre-estat) Afegeix el filtre per estat de la tasca
| * 2c6e9a4 Extreu els estils del llistat a estils.css
|/
* 8d4f2a7 Delega els esdeveniments del llistat al contenidor
| * 7a1f5c3 (correccio/esborrat-svg) Usa closest() en detectar el boto d'esborrar
|/
* 3f1a8d6 Elimina els caracters invisibles en normalitzar el text de les tasques
* b7e2c4a (tag: v1.0.0) Normalitza el text de les tasques abans de desar-les

Els tres modificadors són inseparables a la pràctica:

Opció Què aporta
--graph Les línies i asteriscos que dibuixen la topologia
--oneline Un commit per línia: sense això, el graf és il·legible
--decorate Els (HEAD -> main, tag: v1.0.0): on apunten les referències
--all Totes les referències, no només la branca actual

Com llegir el dibuix:

  • Cada * és un commit. La columna en què es troba indica el seu "carril".
  • |\ marca un commit de fusió: hi entren dues línies.
  • |/ marca el punt en què dos carrils s'uneixen cap enrere: l'ancestre comú.
  • La branca correccio/esborrat-svg apareix penjant de 3f1a8d6 sense haver-se fusionat: són commits que existeixen només en aquella branca.

Un detall sobre --decorate: des de Git 2.13 està actiu per defecte quan la sortida va a un terminal (--decorate=auto), així que sovint no cal escriure'l. Es pot fixar amb:

git config --global log.decorate short    # short | full | auto | no

I variants de --all que eviten el soroll de desenes de branques remotes antigues:

git log --graph --oneline --branches            # només branques locals
git log --graph --oneline --branches --tags     # locals + etiquetes
git log --graph --oneline --remotes=origin      # només les d'origin
git log --graph --oneline main funcionalitat/filtre-estat   # només aquestes dues

  1. --first-parent: la línia principal, sense soroll

Ja va aparèixer a la lliçó 06-02 amb bisect, i a la 05-06 en triar -m 1. La idea és la mateixa: en un commit de fusió, el primer pare és la branca en què eres (normalment main) i el segon és la que vas portar.

git log --oneline --first-parent main
c8f2a1e Fusiona funcionalitat/filtre-estat
8d4f2a7 Delega els esdeveniments del llistat al contenidor
3f1a8d6 Elimina els caracters invisibles en normalitzar el text de les tasques
b7e2c4a Normalitza el text de les tasques abans de desar-les

Compara-ho amb el --graph de l'apartat anterior: han desaparegut 4e8b2c9 i 2c6e9a4, que eren els commits dins de la branca fusionada. El que queda és la història de main a nivell d'integracions: cada línia és o un commit directe o "aquí va entrar una funcionalitat sencera".

És la vista correcta per a:

  • Redactar les notes d'una versió.
  • Respondre a "què ha entrat a main aquesta setmana?".
  • Comptar quantes funcionalitats es van integrar en un període.
# El que ha entrat a main des de l'última etiqueta
git log --oneline --first-parent v1.0.0..main

# Quantes integracions en l'últim mes
git log --oneline --first-parent --since=1.month main | wc -l

  1. --simplify-by-decoration: només les fites

Aquesta opció és poc coneguda i molt útil. Mostra únicament els commits que tenen una referència apuntant-los (una branca, una etiqueta o HEAD), més els mínims necessaris per mantenir el graf connectat.

git log --graph --oneline --simplify-by-decoration --all
*   c8f2a1e (HEAD -> main, origin/main) Fusiona funcionalitat/filtre-estat
|\
| * 4e8b2c9 (funcionalitat/filtre-estat) Afegeix el filtre per estat de la tasca
* | 7a1f5c3 (correccio/esborrat-svg) Usa closest() en detectar el boto d'esborrar
|/
* b7e2c4a (tag: v1.0.0) Normalitza el text de les tasques abans de desar-les
* 1a5c9f3 (tag: v0.9.0) Prepara la versio de proves

Quatre-cents commits reduïts a cinc línies: l'esquelet del projecte. És el primer que convé executar en arribar a un repositori desconegut, perquè en deu segons et dona el mapa: quines versions hi ha, quines branques són vives i on van néixer.

Combinacions útils:

# Només l'evolució de les versions
git log --graph --oneline --simplify-by-decoration --tags

# Amb dates, per veure el ritme de publicació
git log --simplify-by-decoration --tags --date=short \
  --pretty=format:'%C(yellow)%d%C(reset) %ad %s'

  1. --merges i --no-merges

Dos filtres complementaris i directes:

git log --oneline --merges          # NOMÉS commits de fusió
git log --oneline --no-merges       # tots MENYS els de fusió
Filtre Què mostra Per a què serveix
--merges Només commits amb dos o més pares Veure l'historial d'integracions
--no-merges Només commits de feina real Estadístiques, notes de versió, revisions
--min-parents=N Commits amb almenys N pares --min-parents=3 troba octopus merges
--max-parents=1 Equivalent a --no-merges Igual, amb una altra sintaxi
--max-parents=0 Els commits arrel (sense pares) Detectar historials empeltats

--no-merges és especialment important per a les estadístiques: si comptes commits per autor sense aquesta opció, qui més fusiona sembla el més productiu.

# Les fusions d'aquest mes, amb qui les va fer
git log --merges --since=1.month --pretty=format:'%h %an %s'

# La feina real des de l'última versió, per a les notes de publicació
git log --no-merges --oneline v1.0.0..HEAD

  1. Rangs de tres punts i --left-right

De la lliçó 02-06 recordem A..B: "els commits assolibles des de B però no des d'A". És asimètric i respon a "què li falta a A de B?".

El triple punt A...B és la diferència simètrica: els commits que són en un o altre, però no en tots dos. Respon a "en què s'han separat aquestes dues branques?".

gitGraph
   commit id: "base"
   commit id: "M1"
   branch funcionalitat/filtre-estat
   commit id: "F1"
   commit id: "F2"
   checkout main
   commit id: "M2"
   commit id: "M3"
git log --oneline main..funcionalitat/filtre-estat     # F1, F2
git log --oneline funcionalitat/filtre-estat..main     # M2, M3
git log --oneline main...funcionalitat/filtre-estat    # F1, F2, M2, M3

Amb A...B tot sol no se sap quin commit és de quin costat. Per a això hi ha --left-right:

git log --oneline --left-right main...funcionalitat/filtre-estat
> 6f2b9d4 Afegeix el filtre per estat de la tasca
> a1e5c93 Extreu els estils del llistat a estils.css
< e91d4a8 Corregeix el comptador de tasques pendents
< 7d3a8f4 Actualitza el README amb les instruccions d'arrencada
Marca Significat
< El commit és al costat esquerre del rang (aquí, main)
> El commit és al costat dret (aquí, la branca)

És la vista perfecta abans de fusionar o rebasar: d'un cop d'ull veus quant s'ha mogut cada costat. I amb el remot:

# Com estic respecte de la meva branca de seguiment? (lliçó 04-06)
git log --oneline --left-right --graph HEAD...@{u}

# Només el recompte, que sovint és l'únic que vols
git rev-list --left-right --count main...funcionalitat/filtre-estat
2	2

Dos commits per davant a main, dos a la branca. Aquest rev-list --count és, per cert, el que hi ha darrere del [ahead 2, behind 2] de git status i git branch -vv.

Un afegit útil: --cherry-mark marca amb = els commits que són equivalents a un de l'altre costat (mateix canvi, hash diferent) —el que vèiem amb git cherry a la lliçó 05-03:

git log --oneline --left-right --cherry-mark main...funcionalitat/filtre-estat

  1. --pretty=format: avançat: colors i alineació

A la lliçó 02-06 vam veure els marcadors bàsics de --pretty=format:. Ara anem als dos grups que converteixen una sortida llegible en una sortida excel·lent: colors i alineació.

Colors

git log --pretty=format:'%C(yellow)%h%C(reset) %C(green)%ad%C(reset) %s'
Marcador Efecte
%C(rojo), %C(green), %C(yellow), %C(blue), %C(magenta), %C(cyan) Color de text
%C(bold blue) Color amb atribut (bold, dim, ul, reverse)
%C(reset) Torna al color per defecte
%C(auto) Fa servir el color que Git faria servir per defecte per a aquell camp
%C(auto,yellow) Groc, però només si el color és actiu
%C(always,red) Vermell sempre, fins i tot en redirigir a un fitxer

%C(auto) és el que cal fer servir per defecte, i hi ha una raó concreta: aplicat a %d (les decoracions), pinta cada referència amb el seu color canònic —HEAD en cian, les branques locals en verd, les remotes en vermell, les etiquetes en groc— exactament com fa git log --decorate sense format personalitzat. Escriure això a mà és impossible.

A més, %C(auto) respecta la configuració color.ui: si rediriges la sortida a un fitxer o a grep, els codis d'escapament no s'emeten i el fitxer queda net.

Alineació

Quan el format té diverses columnes, sense alineació queda un desastre visual. Els marcadors d'emplenament ho resolen:

Marcador Efecte
%<(N) Els camps següents ocupen com a mínim N caràcters, alineats a l'esquerra
%<(N,trunc) Igual, però trunca amb .. si se'n passa
%<(N,ltrunc) Trunca per l'esquerra (..final)
%<(N,mtrunc) Trunca pel mig (ini..fi)
%>(N) Alineat a la dreta
%><(N) Centrat
%<|(N) Omple fins a la columna N (absoluta, no relativa)

El format complet que fa servir l'equip:

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      fa 2 dies    (HEAD -> main, origin/main) Fusiona funcionalitat/filtre-estat
4e8b2c9 Bruno Salas     fa 3 dies    (funcionalitat/filtre-estat) Afegeix el filtre per estat
2c6e9a4 Carla Vidal     fa 4 dies    Extreu els estils del llistat a estils.css
8d4f2a7 Bruno Salas     fa 6 set.    Delega els esdeveniments del llistat al contenidor
b7e2c4a Carla Vidal     fa 5 mesos   (tag: v1.0.0) Normalitza el text de les tasques

Desglossament peça a peça:

  • %C(auto)%h → hash abreujat, amb el color estàndard de Git per als hashos.
  • %C(blue)%<(16,trunc)%an%C(reset) → autor en blau, exactament 16 columnes, truncat si no hi cap.
  • %C(green)%<(12)%ar%C(reset) → data relativa ("fa 2 dies") en verd, 12 columnes.
  • %C(auto)%d%C(reset) → les decoracions, cadascuna amb el seu color canònic.
  • %s → l'assumpte, que ocupa la resta.

El resultat és tabular, llegible i compacte. A l'apartat 11 el convertirem en un àlies.

Un recordatori dels marcadors més usats, per no haver de tornar a la lliçó 02-06:

Marcador Contingut
%H / %h Hash complet / abreujat
%an / %ae Nom / correu de l'autor
%cn / %ce Nom / correu del committer
%ad / %cd Data d'autoria / de commit
%ar / %cr Les mateixes, en format relatiu
%s / %b Assumpte / cos del missatge
%d / %D Decoracions amb parèntesis / sense
%p / %P Hashos abreujats / complets dels pares
%G? Estat de la signatura GPG (G bona, N sense signar...)
%n / %% Salt de línia / signe de percentatge literal

I per no repetir el format cada vegada, es pot desar amb nom a la configuració:

git config --global pretty.taula '%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=taula

  1. git shortlog: qui ha aportat què

git shortlog agrupa els commits per autor. Sense arguments, llista els missatges agrupats; amb -s (summary) i -n (numbered), dona el recompte ordenat:

git shortlog -sn
   187	Ana Ferrer
   142	Bruno Salas
    98	Carla Vidal
     3	Equip Infraestructura

Opcions útils:

Opció Efecte
-s Només el recompte, sense els missatges
-n Ordena per nombre de commits, no alfabèticament
-e Mostra el correu al costat del nom
--no-merges Exclou les fusions: gairebé sempre és el que vols
-c Agrupa per committer en lloc de per autor
--since / --until Acota el període
# Aportacions reals dels últims tres mesos
git shortlog -sn --no-merges --since=3.months

# Qui ha tocat app.js
git shortlog -sn --no-merges -- app.js

# Notes de versió agrupades per persona
git shortlog --no-merges v1.0.0..HEAD
Ana Ferrer (12):
      Elimina els caracters invisibles en normalitzar el text de les tasques
      Afegeix el comptador de tasques pendents
      ...

Bruno Salas (9):
      Delega els esdeveniments del llistat al contenidor
      ...

Aquesta última sortida és, literalment, un esborrany de notes de versió.

Dos advertiments importants, perquè shortlog es malinterpreta amb facilitat:

  • El nombre de commits no mesura la productivitat. Un commit pot ser una coma o una funcionalitat sencera. Com a mètrica de rendiment és pitjor que inútil: és perversa, perquè premia trossejar artificialment.
  • Una mateixa persona pot aparèixer diverses vegades si ha fet servir noms o correus diferents (portàtil personal, servidor, plataforma web). S'unifica amb un fitxer .mailmap a l'arrel del repositori:
# .mailmap
Ana Ferrer <[email protected]> <[email protected]>
Bruno Salas <[email protected]> <[email protected]>
Carla Vidal <[email protected]>

Git l'aplica automàticament a shortlog, log i blame.

  1. Component consultes amb el que hem après

Tot l'anterior es combina, i aquí hi ha la potència real. Alguns exemples que resolen preguntes concretes del dia a dia de gestor-tasques:

# Què ha entrat a main des de v1.0.0, sense soroll de fusions,
# amb qui i quan?
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
# Quins commits van tocar app.js i esmenten localStorage al diff?
# (pickaxe -S de la lliçó 02-06 + format)
git log -S "localStorage" --oneline -- app.js
# El commit culpable que va trobar bisect, en el seu context de graf
git log --graph --oneline --all --ancestry-path 8d4f2a7..main

--ancestry-path restringeix el rang als commits que són en el camí entre els dos extrems, que és exactament el que vols per respondre "com va arribar aquell commit fins a main?".

# Quines branques contenen el commit culpable?
git branch -a --contains 8d4f2a7
# L'evolució d'una funció concreta (log -L de la lliçó 06-03)
# limitada als últims tres mesos
git log -L :normalitzaText:app.js --since=3.months --oneline
# Commits sense signar en el que enviaré
git log --pretty=format:'%h %G? %an %s' @{u}..HEAD
# Quantes línies ha canviat cada persona? (amb totes les reserves
# de l'apartat 7 sobre les mètriques)
git log --no-merges --numstat --pretty=format:'%an' | \
  awk 'NF==1 {autor=$0} NF==3 {mes[autor]+=$1; menys[autor]+=$2}
       END {for (a in mes) printf "%-20s +%-8d -%d\n", a, mes[a], menys[a]}'

I una comparació de les tres vistes de l'historial que hem vist, per tenir-les juntes:

Vull veure... Ordre
La forma completa del projecte git log --graph --oneline --all
La història de main a nivell d'integracions git log --oneline --first-parent main
Només les fites (etiquetes i branques) git log --graph --oneline --simplify-by-decoration --all
La feina real, sense fusions git log --oneline --no-merges
En què s'han separat dues branques git log --oneline --left-right main...altra
Qui ha aportat quant git shortlog -sn --no-merges

Arribats aquí, el problema és evident: cap d'aquestes ordres no s'escriu a mà dues vegades.

  1. Àlies: què són i on viuen

Un àlies de Git és un nom curt per a una ordre llarga. Es defineix amb 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 commit

A partir d'aquí, git st és git status. Git resol l'àlies abans d'executar i li passa els arguments extra:

git st --short          # equival a git status --short

On viuen. Al .gitconfig corresponent al nivell triat (lliçó 01-05), sota la secció [alias]:

# ~/.gitconfig
[user]
	name = Carla Vidal
	email = [email protected]
[alias]
	st = status
	co = checkout
	br = branch
	ci = commit

Es poden editar a mà al fitxer —que és més còmode per als àlies llargs— o amb git config. I consultar-los:

git config --get-regexp '^alias\.'     # llistar-los tots
git config --get alias.lg              # veure'n un de concret
git config --global --unset alias.co   # esborrar-ne un

Els tres nivells funcionen igual que per a la resta de la configuració: --system, --global i local. Els àlies personals van a --global; un àlies específic d'un projecte, al local. I recorda que la configuració no es clona: els àlies són teus, no del repositori.

Dues regles que eviten sorpreses:

  • Un àlies no pot sobreescriure una ordre existent. Si defineixes alias.status, Git ignorarà l'àlies i executarà l'ordre real. És una protecció deliberada.
  • Els àlies es poden encadenar, però amb compte: un àlies que n'invoca un altre funciona, i un àlies recursiu penja Git.

  1. Àlies amb !: shell i funcions

Un àlies normal només pot començar per una subordre de Git. Per a tota la resta —encadenar ordres, fer servir canonades, invocar programes externs, col·locar els arguments en un lloc diferent del final— s'anteposa !, que li diu a Git: "això és una ordre de shell, executa-la tal com és".

git config --global alias.neteja '!git branch --merged main | grep -v main | xargs -r git branch -d'
git neteja

Tres coses fonamentals sobre els àlies amb !:

  1. S'executen des de l'ARREL del repositori

Aquest és el risc principal i la font número u de sorpreses. Git fa cd a l'arrel de l'arbre de treball abans d'executar l'àlies. Així que si ets a ~/projectes/gestor-tasques/components/ i executes un àlies amb !ls, veuràs el contingut de ~/projectes/gestor-tasques/, no el del directori en què ets.

Git dona una variable per pal·liar-ho:

git config --global alias.aqui '!f() { cd "${GIT_PREFIX:-.}" && ls -la; }; f'

GIT_PREFIX conté la ruta relativa des de l'arrel fins al directori en què eres en invocar l'àlies (buida si ja eres a l'arrel). Qualsevol àlies amb ! que treballi amb rutes relatives ha de tenir-ho en compte:

git config --global alias.afegeix '!f() { cd "${GIT_PREFIX:-.}" && git add "$@"; }; f'

  1. Els arguments s'afegeixen al final... llevat que facis servir una funció

Git enganxa els arguments que escriguis al final de l'ordre de l'àlies. Això és un problema quan els necessites al mig:

# MALAMENT: 'git cerca hola' executa 'git log --oneline | grep  hola'
git config --global alias.cerca '!git log --oneline | grep'

En aquest cas funciona per casualitat. Però si l'argument va al mig, no:

# MALAMENT: 'git des main' executa 'git log --oneline ..HEAD main'
git config --global alias.des '!git log --oneline ..HEAD'

La solució canònica és definir una funció de shell i cridar-la:

git config --global alias.des '!f() { git log --oneline "$1"..HEAD; }; f'
git des main       # → git log --oneline main..HEAD

El patró '!f() { ...; }; f' es llegeix així: defineix una funció anomenada f, i tot seguit invoca-la. Els arguments que Git enganxa al final es converteixen en els arguments d'f. És l'idioma estàndard i el veuràs a tots els .gitconfig del món.

Fes servir "$@" per passar tots els arguments i "$1", "$2" per a posicions concretes. I posa-hi cometes sempre: sense cometes, un nom de branca amb caràcters estranys trenca l'àlies.

  1. S'executen amb sh, no amb bash

Git fa servir /bin/sh. En molts sistemes això és dash, no bash, i les extensions de bash ([[ ]], arrays, mapfile) no funcionen. Escriu àlies en shell POSIX o invoca explícitament l'intèrpret:

git config --global alias.complexa '!bash -c '"'"'... script bash ...'"'"''

Per a qualsevol cosa que passi de tres línies, la resposta correcta no és un àlies sinó un script al PATH. Git té una convenció preciosa per a això: qualsevol executable anomenat git-<alguna-cosa> que sigui al PATH es pot invocar com git <alguna-cosa>:

# ~/bin/git-resum  (amb chmod +x)
#!/usr/bin/env bash
set -euo pipefail
echo "=== Branca actual ==="
git branch --show-current
echo
echo "=== Últims 10 commits ==="
git log --oneline -10
echo
echo "=== Estat ==="
git status --short
git resum

Sense configurar res. És la via correcta per a la lògica complexa, i de passada l'script es pot versionar i compartir.

  1. Un joc d'àlies per al dia a dia

Aquests són els que de debò es guanyen el lloc. Començant pel clàssic absolut:

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 lg
*   c8f2a1e - (HEAD -> main, origin/main) Fusiona funcionalitat/filtre-estat (fa 2 dies) <Ana Ferrer>
|\
| * 4e8b2c9 - (funcionalitat/filtre-estat) Afegeix el filtre per estat (fa 3 dies) <Bruno Salas>
| * 2c6e9a4 - Extreu els estils del llistat a estils.css (fa 4 dies) <Carla Vidal>
|/
* 8d4f2a7 - Delega els esdeveniments del llistat al contenidor (fa 6 set.) <Bruno Salas>
* b7e2c4a - (tag: v1.0.0) Normalitza el text de les tasques (fa 5 mesos) <Carla Vidal>

El lg és probablement l'àlies més copiat de la història de Git, i amb raó: substitueix una ordre de 180 caràcters que ningú recorda.

La taula completa del joc recomanat:

Àlies Definició Per a què
st status --short --branch Estat compacte amb la branca i el seguiment
lg El de dalt El graf complet, acolorit
lgd log --graph --oneline --decorate Igual però només la branca actual
ultim log -1 HEAD --stat L'últim commit amb els seus fitxers
linia log --oneline -20 Els últims 20, sense més
fites log --graph --oneline --simplify-by-decoration --all L'esquelet del projecte
integrat log --oneline --first-parent La línia principal de main
qui shortlog -sn --no-merges Aportacions per persona
culpa blame -w -C --date=short El blame de la lliçó 06-03, ben configurat
historia !f() { git log -L :"$1":"$2"; }; f git historia normalitzaText app.js
pendent !git log --oneline @{u}..HEAD El que tinc sense enviar
entrant !git fetch -q && git log --oneline HEAD..@{u} El que hi ha al remot sense portar
divergencia !f() { git log --oneline --left-right "$1"...HEAD; }; f En què m'he separat d'una branca
wip !git add -A && git commit -m 'WIP: feina en curs' --no-verify Desament ràpid abans de canviar de context
desfes reset HEAD~1 --mixed Desfà l'últim commit conservant els canvis
esmena commit --amend --no-edit Afegeix el que és preparat a l'últim commit
neteja !git branch --merged main | grep -vE '^\*|main' | xargs -r git branch -d Esborra les branques ja fusionades
branques branch -vv --sort=-committerdate Branques per activitat recent, amb seguiment
desa stash list --pretty=format:'%C(yellow)%gd%C(reset) %s' La pila de stash, llegible
etiquetes tag -l --sort=-v:refname --format='%(refname:short) %(creatordate:short) %(subject)' Etiquetes per versió descendent
alias !git config --get-regexp '^alias\.' | sed 's/^alias\.//' Recordar quins àlies tinc

Aquest últim és més útil del que sembla: al cap de sis mesos ningú recorda tots els seus àlies.

Per instal·lar-los de cop, és més còmode editar el fitxer que llançar vint git config:

git config --global --edit
[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)'
	fites = log --graph --oneline --simplify-by-decoration --all
	integrat = log --oneline --first-parent
	qui = shortlog -sn --no-merges
	culpa = blame -w -C --date=short
	historia = "!f() { git log -L :\"$1\":\"$2\"; }; f"
	pendent = "!git log --oneline @{u}..HEAD"
	divergencia = "!f() { git log --oneline --left-right \"$1\"...HEAD; }; f"
	desfes = reset HEAD~1 --mixed
	esmena = commit --amend --no-edit
	branques = branch -vv --sort=-committerdate
	alias = "!git config --get-regexp '^alias\\.' | sed 's/^alias\\.//'"

Fixa't en l'escapament: al fitxer, els valors amb cometes o barres invertides van entre cometes dobles i amb les barres duplicades. És la part més molesta dels àlies amb !, i la raó per la qual convé provar-los just després d'escriure'ls.

I això connecta amb els mòduls anteriors: els àlies no són només per a log. pendent fa servir @{u} de la lliçó 04-06; esmena és el --amend de la 02-04; neteja és la gestió de branques de la 03-06; culpa és el blame de la 06-03. Un àlies és la manera de fixar en un gest el que has après a fer bé, per no haver de recordar les set opcions cada vegada.

  1. Riscos i bones pràctiques amb els àlies

Risc 1: el directori d'execució. Ja vist: els àlies amb ! s'executen des de l'arrel del repositori. Fes servir GIT_PREFIX si l'àlies treballa amb rutes.

Risc 2: àlies destructius amb noms curts. Un git p que faci push --force és un accident esperant a passar. Regla: com més destructiu, més llarg el nom. I en el cas de push --force, fes servir sempre --force-with-lease (lliçó 04-05).

Risc 3: dependència de la teva màquina. Els teus àlies no són al portàtil del company a qui demanes ajuda, ni al servidor. Convé saber l'ordre real que hi ha al darrere de cadascun.

Risc 4: sh no és bash. Les extensions de bash fallen silenciosament o amb errors estranys.

Risc 5: àlies que amaguen efectes. Un git sync que faci fetch + rebase + push va bé fins al dia que hi ha conflictes i no entens en quin punt s'ha quedat el procés.

Bones pràctiques:

  • Que el nom digui què fa. pendent és millor que p.
  • Versiona el teu .gitconfig. Molts guarden els seus dotfiles en un repositori propi: els àlies són configuració valuosa i costa refer-los.
  • Afegeix àlies a poc a poc. Quan notis que escrius el mateix per tercera vegada, allà tens un candidat. Copiar una llista de quaranta àlies d'internet garanteix no fer-ne servir cap.
  • Prova l'àlies tot just crear-lo, especialment els que porten ! i cometes.
  • Per a lògica complexa, git-<nom> al PATH, no un àlies de tres línies.

Errors Habituals i Consells

Error 1: --graph sense --oneline. El graf amb missatges complets és il·legible. Van sempre junts.

Error 2: oblidar --all i creure que la branca no existeix. git log --graph només mostra la branca actual. Sense --all, no veus les altres.

Error 3: confondre .. amb .... A..B és asimètric ("el que li falta a A"); A...B és simètric ("en què s'han separat"). Amb --left-right l'ambigüitat desapareix.

Error 4: comptar commits amb shortlog sense --no-merges. Qui més integra sembla qui més produeix.

Error 5: fer servir el recompte de commits com a mètrica de rendiment. És una mala mètrica i crea incentius perversos.

Error 6: àlies amb ! que assumeix el directori actual. S'executa des de l'arrel. GIT_PREFIX.

Error 7: arguments al mig d'un àlies sense fer servir una funció. Git els enganxa al final. El patró '!f() { ...; }; f' és la solució.

Error 8: escapament mal fet al .gitconfig. Cometes dobles al voltant del valor i barres invertides duplicades. Prova l'àlies immediatament.

Consell 1: %C(auto) per defecte als teus formats. Acoloreix les decoracions correctament i respecta la configuració i les redireccions.

Consell 2: desa formats amb nom. git config --global pretty.taula '...' i després git log --pretty=taula.

Consell 3: --simplify-by-decoration en arribar a un repositori nou. Deu segons per al mapa complet.

Consell 4: git rev-list --count A...B per al recompte pur. Quan només vols saber quant, no què.

Consell 5: un .mailmap tan bon punt algú aparegui duplicat. És un fitxer de tres línies que arregla shortlog, log i blame alhora.

Exercicis

Exercici 1: llegir la forma d'un historial

Crea un repositori amb aquesta topologia: quatre commits a main, una branca funcionalitat/a amb dos commits fusionada amb --no-ff, una branca funcionalitat/b amb dos commits sense fusionar, i una etiqueta v1.0 al segon commit de main. Després:

  1. Mostra el graf complet amb --graph --oneline --decorate --all.
  2. Mostra només la línia principal amb --first-parent.
  3. Mostra només les fites amb --simplify-by-decoration --all.
  4. Mostra només les fusions, i després només els commits que no ho són.
  5. Explica quins commits desapareixen en cada vista i per què.

Exercici 2: comparar dues branques

Sobre el mateix repositori:

  1. Afegeix dos commits més a main i dos més a funcionalitat/b.
  2. Fes servir main..funcionalitat/b i funcionalitat/b..main, i explica la diferència.
  3. Fes servir main...funcionalitat/b amb --left-right i identifica cada costat.
  4. Obtén el recompte amb git rev-list --left-right --count.
  5. Crea un format --pretty amb colors i alineació que mostri hash, autor (14 columnes truncades), data curta i assumpte.

Exercici 3: construir els teus àlies

  1. Crea els àlies lg, qui, pendent i culpa de la taula de l'apartat 11.
  2. Crea un àlies historia que accepti dos arguments (funció i fitxer) i executi git log -L. Prova'l.
  3. Crea un àlies que demostri el problema de GIT_PREFIX: un que executi ls sense més, i un altre que ho faci bé. Executa'ls des d'un subdirectori i compara.
  4. Llista tots els teus àlies amb un àlies.

Solucions

Solució 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 funcionalitat/a
echo "a1" > a.txt && git add . && git commit -q -m "Funcionalitat A part 1"
echo "a2" >> a.txt && git commit -qam "Funcionalitat A part 2"

git switch -q main
echo "main 3" >> main.txt && git commit -qam "Commit main 3"
git merge -q --no-ff funcionalitat/a -m "Fusiona funcionalitat/a"
echo "main 4" >> main.txt && git commit -qam "Commit main 4"

git switch -qc funcionalitat/b v1.0
echo "b1" > b.txt && git add . && git commit -q -m "Funcionalitat B part 1"
echo "b2" >> b.txt && git commit -qam "Funcionalitat B part 2"
git switch -q main
git log --graph --oneline --decorate --all
* 9c4e7b2 (HEAD -> main) Commit main 4
*   5f1d8a3 Fusiona funcionalitat/a
|\
| * 2a8c6f9 (funcionalitat/a) Funcionalitat A part 2
| * 7d3e1b5 Funcionalitat A part 1
* | 4b9f2c8 Commit main 3
|/
| * 8e5a3d7 (funcionalitat/b) Funcionalitat B part 2
| * 1c7b4f9 Funcionalitat B part 1
|/
* 3a6d9e2 (tag: v1.0) Commit main 2
* 6f2c8b1 Commit main 1
git log --oneline --first-parent main
9c4e7b2 Commit main 4
5f1d8a3 Fusiona funcionalitat/a
4b9f2c8 Commit main 3
3a6d9e2 Commit main 2
6f2c8b1 Commit main 1

Han desaparegut 2a8c6f9 i 7d3e1b5: són els commits dins de funcionalitat/a, als quals només s'arriba pel segon pare de la fusió.

git log --graph --oneline --simplify-by-decoration --all
* 9c4e7b2 (HEAD -> main) Commit main 4
| * 8e5a3d7 (funcionalitat/b) Funcionalitat B part 2
|/
| * 2a8c6f9 (funcionalitat/a) Funcionalitat A part 2
|/
* 3a6d9e2 (tag: v1.0) Commit main 2

Només els commits amb referència i el mínim per connectar-los: l'esquelet.

git log --oneline --merges
git log --oneline --no-merges
5f1d8a3 Fusiona funcionalitat/a
9c4e7b2 Commit main 4
2a8c6f9 Funcionalitat A part 2
7d3e1b5 Funcionalitat A part 1
4b9f2c8 Commit main 3
3a6d9e2 Commit main 2
6f2c8b1 Commit main 1

Solució 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 funcionalitat/b
echo "b3" >> b.txt && git commit -qam "Funcionalitat B part 3"
echo "b4" >> b.txt && git commit -qam "Funcionalitat B part 4"
git switch -q main
git log --oneline main..funcionalitat/b
d4a8f2c Funcionalitat B part 4
b1e6c9f Funcionalitat B part 3
8e5a3d7 Funcionalitat B part 2
1c7b4f9 Funcionalitat B part 1

El que té la branca i no té main: el que entraria en fusionar.

git log --oneline funcionalitat/b..main
7f3c2e8 Commit main 6
2d9b5a1 Commit main 5
9c4e7b2 Commit main 4
5f1d8a3 Fusiona funcionalitat/a
2a8c6f9 Funcionalitat A part 2
7d3e1b5 Funcionalitat A part 1
4b9f2c8 Commit main 3

El que li falta a la branca de main: el que portaria en rebasar o fusionar main dins seu.

git log --oneline --left-right main...funcionalitat/b
< 7f3c2e8 Commit main 6
< 2d9b5a1 Commit main 5
< 9c4e7b2 Commit main 4
< 5f1d8a3 Fusiona funcionalitat/a
< 2a8c6f9 Funcionalitat A part 2
< 7d3e1b5 Funcionalitat A part 1
< 4b9f2c8 Commit main 3
> d4a8f2c Funcionalitat B part 4
> b1e6c9f Funcionalitat B part 3
> 8e5a3d7 Funcionalitat B part 2
> 1c7b4f9 Funcionalitat B part 1
git rev-list --left-right --count main...funcionalitat/b
7	4
git log --pretty=format:'%C(auto)%h %C(blue)%<(14,trunc)%an%C(reset) %C(green)%ad%C(reset) %s' --date=short -8

Solució 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.qui "shortlog -sn --no-merges"
git config --global alias.pendent "!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\\.//'"
cd /tmp/practica-log
git lg
git qui
   4	Carla Vidal
   ...

L'àlies amb arguments:

cat > funcions.js <<'FI'
function saluda(nom) {
  return "Hola " + nom;
}
FI
git add . && git commit -q -m "Afegeix la funcio de salutacio"
sed -i 's/"Hola "/"Hola! "/' funcions.js
git commit -qam "Afegeix l'exclamacio a la salutacio"

git historia saluda funcions.js
commit 4e9c2a7
    Afegeix l'exclamacio a la salutacio
...

El problema de GIT_PREFIX:

mkdir -p components && echo "x" > components/boto.js
git add . && git commit -q -m "Afegeix el component boto"

git config --global alias.mal '!ls'
git config --global alias.be '!f() { cd "${GIT_PREFIX:-.}" && ls; }; f'

cd components
git mal
components  funcions.js  main.txt  a.txt  b.txt

Ha llistat l'arrel del repositori, no el directori en què som.

git be
boto.js

Amb GIT_PREFIX, l'àlies respecta el directori des del qual es va invocar.

git alias
alias	!git config --get-regexp '^alias\.' | sed 's/^alias\.//'
be	!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
pendent	!git log --oneline @{u}..HEAD
qui	shortlog -sn --no-merges
git config --global --unset alias.mal      # neteja

Conclusió

Aquesta lliçó ha convertit git log d'un llistat en una eina de consulta, i ha eliminat la fricció de fer-la servir. L'essencial:

  • --graph --oneline --decorate --all és la vista canònica de la topologia. Els quatre van junts.
  • --first-parent dona la història de main a nivell d'integracions, sense l'interior de les branques fusionades. És el mateix "primer pare" del -m 1 de revert i del bisect --first-parent.
  • --simplify-by-decoration redueix l'historial a les seves fites: el primer que cal executar en un repositori desconegut.
  • --merges / --no-merges separen integracions de feina real; --no-merges és gairebé obligatori en qualsevol estadística.
  • A...B amb --left-right respon a "en què s'han separat aquestes dues branques?", amb < i > marcant cada costat. git rev-list --left-right --count dona només el recompte.
  • --pretty=format: amb %C(auto) i els marcadors d'alineació %<(N,trunc) produeix sortides tabulars i acolorides; desa-les amb nom a pretty.<nom>.
  • git shortlog -sn --no-merges resumeix les aportacions, amb .mailmap per unificar identitats — i amb l'advertiment que comptar commits no mesura res útil.
  • Els àlies viuen a [alias] del .gitconfig del nivell que triïs. Els simples són un nom per a una subordre; els que comencen per ! són shell, amb tres cauteles: s'executen des de l'arrel (fes servir GIT_PREFIX), els arguments s'enganxen al final (fes servir el patró '!f() { ...; }; f') i corren sota sh, no bash.
  • Per a lògica llarga, un executable git-<nom> al PATH és millor que un àlies.
  • I la idea de fons: un àlies fixa en un gest el que has après a fer bé. culpa, pendent, fites o neteja són les lliçons anteriors condensades en una paraula.

L'equip ja sap interrogar el seu historial amb comoditat. Però gestor-tasques és a punt de deixar de ser un sol repositori: els botons, els diàlegs i els camps de formulari que l'Ana ha anat extraient s'han convertit en una biblioteca interna, components-ui, que viu a git.exemple.cat/equip/components-ui.git i que altres dos projectes de l'empresa també volen fer servir.

La pregunta és com es compon un projecte a partir de diversos repositoris sense copiar i enganxar codi, i sense perdre la traçabilitat de quina versió de la biblioteca fa servir cada versió de l'aplicació. La resposta nativa de Git són els submòduls, i és la lliçó 06-05: Submòduls de Git.

Dominant Git: De Principiant a Avançat

Mòdul 1: Introducció a Git

Mòdul 2: Operacions Bàsiques de Git

Mòdul 3: Branques i Fusió

Mòdul 4: Treballant amb Repositoris Remots

Mòdul 5: Operacions Avançades de Git

Mòdul 6: Eines i Tècniques de Git

Mòdul 7: Estratègies de Col·laboració i Flux de Treball

Mòdul 8: Bones Pràctiques i Consells de Git

Mòdul 9: Resolució de Problemes i Depuració

Mòdul 10: Git al Món Real

© Copyright 2026. Tots els drets reservats