Vai al contenuto
·23 min di lettura

Git oltre le basi: workflow che fanno risparmiare ore ogni settimana

Interactive rebase, cherry-pick, bisect, worktree, recupero con reflog e le strategie di branching che funzionano davvero. Comandi Git che uso ogni giorno e che la maggior parte degli sviluppatori non sa che esistono.

Condividi:X / TwitterLinkedIn

La maggior parte degli sviluppatori impara cinque comandi Git e si ferma lì. add, commit, push, pull, merge. Forse checkout e branch se si sentono avventurosi. Questo ti porta avanti per il primo anno. Poi il tuo branch ha 47 commit con messaggi come "fix" e "wip" e "please work", fai accidentalmente reset di qualcosa che non dovevi, e passi 40 minuti su Stack Overflow cercando di annullare un merge andato male.

Uso Git da anni. Non casualmente — intensamente. Più branch, più repository, più collaboratori, tutto il giorno, ogni giorno. Quello che segue sono i comandi e i workflow che uso effettivamente. Non quelli che fanno bella figura in un tutorial. Quelli che mi fanno risparmiare tempo reale, ogni singola settimana.

Interactive Rebase: pulisci il tuo pasticcio prima che qualcuno lo veda#

Il tuo branch ha dodici commit. La metà sono "fix typo." Uno dice "undo previous commit." Un altro dice "actually fix it this time." Stai per aprire una PR. Nessuno ha bisogno di vedere quella storia.

L'interactive rebase è il modo in cui riscrivi la storia sul tuo branch prima di condividerla. Ti permette di fare squash dei commit insieme, riscrivere i messaggi, riordinarli o eliminarli completamente.

Il comando base#

bash
git rebase -i HEAD~5

Questo apre un editor che mostra i tuoi ultimi 5 commit, dal più vecchio al più recente:

bash
pick a1b2c3d Add user authentication endpoint
pick d4e5f6g Fix typo in auth middleware
pick h7i8j9k Add rate limiting
pick l0m1n2o Fix rate limit bug
pick p3q4r5s Update auth tests

Ogni riga inizia con un comando. Cambia pick in uno di questi:

  • squash (o s) — Unisci questo commit a quello sopra, combinando i messaggi
  • fixup (o f) — Come squash, ma scarta il messaggio di questo commit
  • reword (o r) — Mantieni il commit ma cambia il suo messaggio
  • drop (o d) — Elimina completamente questo commit
  • edit (o e) — Metti in pausa il rebase a questo commit per modificarlo

Una vera sessione di pulizia#

Ecco cosa faccio realmente. Quella storia disordinata diventa:

bash
pick a1b2c3d Add user authentication endpoint
fixup d4e5f6g Fix typo in auth middleware
pick h7i8j9k Add rate limiting
fixup l0m1n2o Fix rate limit bug
pick p3q4r5s Update auth tests

Salva e chiudi. Ora hai tre commit puliti invece di cinque. Il fix del typo viene incorporato nel commit dell'autenticazione. Il fix del bug del rate limit viene incorporato nel commit del rate limit. Il tuo revisore della PR vede una progressione pulita e logica.

Riordinamento dei commit#

Puoi letteralmente riordinare le righe. Se il commit dei test dovrebbe venire prima del commit del rate limiting, sposta semplicemente la riga:

bash
pick a1b2c3d Add user authentication endpoint
pick p3q4r5s Update auth tests
pick h7i8j9k Add rate limiting

Git riprodurrà i tuoi commit in questo nuovo ordine. Se ci sono conflitti, si metterà in pausa e ti lascerà risolverli.

La scorciatoia autosquash#

Se sai che un commit è un fix per uno precedente, marcalo al momento del commit:

bash
git commit --fixup=a1b2c3d

Questo crea un commit con il messaggio fixup! Add user authentication endpoint. Poi quando fai rebase:

bash
git rebase -i --autosquash HEAD~5

Git riordina automaticamente i commit fixup subito sotto i loro target e li marca come fixup. Salvi e chiudi. Nessuna modifica manuale.

Lo uso costantemente. È il modo più veloce per iterare su un branch mantenendo pulita la storia finale.

La regola d'oro#

Non fare mai rebase di commit che sono stati pushati su un branch condiviso. Se altre persone hanno basato del lavoro su quei commit, riscrivere la storia causerà problemi reali. Fai rebase dei tuoi feature branch prima di mergiare. Non fare mai rebase di main.

Se hai già pushato il tuo feature branch e devi fare rebase:

bash
git push --force-with-lease

Il flag --force-with-lease è più sicuro di --force. Rifiuta di pushare se qualcun altro ha pushato sullo stesso branch dal tuo ultimo fetch. Non previene tutti i problemi, ma cattura quello più comune.

Cherry-Pick: trasferimenti chirurgici di commit#

Cherry-pick prende un commit specifico da un branch e lo applica a un altro. Non un merge. Non un rebase. Solo un commit, applicato in modo pulito.

Quando lo uso realmente#

Lo scenario più comune: ho fixato un bug sul mio feature branch, ma il fix deve andare su main o su un release branch subito. Non voglio mergiare l'intero feature branch. Voglio solo quel fix.

bash
# Trova l'hash del commit del fix
git log --oneline feature/user-auth
# a1b2c3d Fix null pointer in session validation
 
# Passa a main e fai cherry-pick
git checkout main
git cherry-pick a1b2c3d

Fatto. Il fix è su main come un nuovo commit con le stesse modifiche.

Cherry-pick senza committare#

A volte vuoi applicare le modifiche ma non committarle ancora. Forse vuoi combinare diversi cherry-pick in un unico commit, o modificare leggermente le modifiche:

bash
git cherry-pick --no-commit a1b2c3d

Le modifiche sono in staging ma non committate. Puoi modificarle, aggiungere altre modifiche, poi committare quando sei pronto.

Cherry-pick a intervallo#

Hai bisogno di più commit consecutivi? Usa la sintassi a intervallo:

bash
git cherry-pick a1b2c3d..f6g7h8i

Questo fa cherry-pick di tutto dopo a1b2c3d fino a e incluso f6g7h8i. Nota che a1b2c3d stesso è escluso. Se vuoi includerlo:

bash
git cherry-pick a1b2c3d^..f6g7h8i

Gestione dei conflitti#

I conflitti del cherry-pick funzionano come i conflitti del merge. Quando ne succede uno:

bash
# Git ti dirà che c'è un conflitto
# Fixa i file in conflitto, poi:
git add .
git cherry-pick --continue

Oppure se cambi idea:

bash
git cherry-pick --abort

Una cosa da tenere presente: i commit cherry-picked creano nuovi hash di commit. Se più tardi fai merge del branch originale, Git è di solito abbastanza intelligente da gestire la duplicazione. Ma se fai cherry-pick in modo aggressivo tra branch che alla fine verranno mergiati, potresti vedere conflitti inaspettati. Usalo chirurgicamente, non come strategia di merge.

Git Bisect: ricerca binaria per bug#

Qualcosa si è rotto. Sai che funzionava due settimane fa. Ci sono stati 200 commit da allora. Quale l'ha rotto?

Potresti controllare ogni commit manualmente. Oppure potresti usare git bisect, che usa la ricerca binaria per trovare l'esatto commit che ha rotto tutto in log2(n) passi. Per 200 commit, sono circa 7-8 controlli invece di 200.

Il modo manuale#

bash
# Inizia il bisect
git bisect start
 
# Marca il commit corrente come cattivo (il bug esiste qui)
git bisect bad
 
# Marca un commit noto come buono (il bug non esisteva qui)
git bisect good v2.1.0

Git fa checkout di un commit a metà strada tra buono e cattivo. Testalo. Poi:

bash
# Se il bug esiste in questo commit:
git bisect bad
 
# Se il bug non esiste in questo commit:
git bisect good

Git dimezza l'intervallo ogni volta. Dopo 7-8 passi, ti dice:

bash
a1b2c3d4e5f6g7h is the first bad commit
commit a1b2c3d4e5f6g7h
Author: Some Developer <dev@example.com>
Date: Tue Feb 18 14:23:01 2026 +0300
 
    Refactor session handling to use async middleware

Ora sai esattamente quale commit ha introdotto il bug. Quando hai finito:

bash
git bisect reset

Questo ti riporta da dove sei partito.

Il modo automatizzato (questo è il vero potere)#

Se hai un test che può rilevare il bug, puoi automatizzare l'intero processo:

bash
git bisect start
git bisect bad HEAD
git bisect good v2.1.0
git bisect run npm test -- --grep "session validation"

Git farà automaticamente checkout dei commit, eseguirà il test e li segnerà come buoni o cattivi in base al codice di uscita. Zero significa buono, diverso da zero significa cattivo. Allontanati, torna, e ti dice il commit esatto.

Puoi usare qualsiasi script:

bash
git bisect run ./test-regression.sh

Dove test-regression.sh è:

bash
#!/bin/bash
npm run build 2>/dev/null || exit 125  # 125 significa "salta questo commit"
npm test -- --grep "session" || exit 1  # 1 significa "cattivo"
exit 0                                   # 0 significa "buono"

Il codice di uscita 125 è speciale — dice a bisect di saltare quel commit (utile se un commit non compila). Questa è una di quelle funzionalità che sembra di nicchia finché non ne hai bisogno, e poi ti risparmia un intero pomeriggio.

Una vera sessione di bisect#

Ecco come appare in pratica:

bash
$ git bisect start
$ git bisect bad HEAD
$ git bisect good abc1234
Bisecting: 97 revisions left to test after this (roughly 7 steps)
[def5678...] Commit message here
 
$ npm test -- --grep "login flow"
# I test falliscono
 
$ git bisect bad
Bisecting: 48 revisions left to test after this (roughly 6 steps)
[ghi9012...] Another commit message
 
$ npm test -- --grep "login flow"
# I test passano
 
$ git bisect good
Bisecting: 24 revisions left to test after this (roughly 5 steps)
...
 
# Dopo ~7 iterazioni:
abc1234def5678ghi is the first bad commit

Sette passi per trovare un ago in un pagliaio di 200 commit.

Git Worktree: più branch, zero stash#

Questa è la funzionalità Git più sottoutilizzata. Sono convinto che la maggior parte degli sviluppatori non sappia nemmeno che esiste.

Il problema: sei immerso in un feature branch. I file sono cambiati ovunque. Poi qualcuno dice "puoi dare un'occhiata veloce a questo bug in produzione?" Hai tre opzioni:

  1. Stasha tutto, cambia branch, fixa, ritorna, pop dello stash. Sperando che niente vada storto.
  2. Committa il tuo lavoro a metà con un messaggio "wip". Brutto ma funzionale.
  3. Clona il repository di nuovo in una directory diversa.

Oppure opzione 4: worktree.

Cos'è un worktree#

Un worktree è una seconda (o terza, o quarta) directory di lavoro collegata allo stesso repository. Ogni worktree ha il proprio branch checkoutato, i propri file di lavoro, il proprio indice. Ma condividono gli stessi dati .git, quindi non stai duplicando l'intero repository.

Aggiungere un worktree#

bash
# Sei su feature/user-auth, immerso nel lavoro
# Devi fixare un bug su main:
 
git worktree add ../hotfix-session main

Questo crea una nuova directory ../hotfix-session con main checkoutato. La tua directory corrente resta esattamente com'era. Niente stashato, niente committato, niente interrotto.

bash
cd ../hotfix-session
# Fixa il bug
git add .
git commit -m "Fix null pointer in session validation"
git push origin main
cd ../my-project
# Continua a lavorare sulla tua feature come se nulla fosse successo

Creare un nuovo branch in un worktree#

bash
git worktree add ../hotfix-nav -b hotfix/nav-crash main

Questo crea il worktree E crea un nuovo branch hotfix/nav-crash basato su main.

Gestione dei worktree#

bash
# Elenca tutti i worktree
git worktree list
# /home/dev/my-project         abc1234 [feature/user-auth]
# /home/dev/hotfix-session     def5678 [main]
 
# Rimuovi un worktree quando hai finito
git worktree remove ../hotfix-session
 
# Se la directory è già stata eliminata:
git worktree prune

Perché è meglio dello stash#

Lo stash va bene per cambi di contesto rapidi. Ma i worktree sono migliori per qualsiasi cosa che duri più di cinque minuti:

  • Il tuo IDE resta aperto sul tuo feature branch. Nessuna reindicizzazione, nessuna perdita della posizione di scroll.
  • Puoi eseguire test in entrambe le directory simultaneamente.
  • Nessun rischio di conflitti dello stash o di dimenticare cosa hai stashato.
  • Puoi avere un dev server in esecuzione in un worktree e una build pulita in un altro.

Tipicamente tengo due o tre worktree attivi: il mio feature branch principale, un worktree su main per controlli veloci, e a volte un worktree di review dove faccio checkout della PR di qualcun altro.

L'unica limitazione#

Non puoi avere lo stesso branch checkoutato in due worktree. È per design — ti impedisce di fare modifiche in conflitto allo stesso branch in due posti. Se ci provi, Git rifiuterà.

Reflog: il pulsante annulla per tutto#

Hai fatto un hard reset e perso dei commit. Hai cancellato un branch. Hai fatto rebase e qualcosa è andato orribilmente male. Pensi che il tuo lavoro sia andato.

Non lo è. Git non cancella quasi mai niente davvero. Il reflog è la tua rete di sicurezza.

Cos'è il reflog#

Ogni volta che HEAD si muove — ogni commit, checkout, rebase, reset, merge — Git lo registra nel reflog. È un log di dove è stato il tuo HEAD, in ordine.

bash
git reflog

Output:

bash
a1b2c3d (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
f4e5d6c HEAD@{1}: commit: Add payment processing
b7a8c9d HEAD@{2}: commit: Update user dashboard
e0f1g2h HEAD@{3}: commit: Fix auth token refresh
i3j4k5l HEAD@{4}: checkout: moving from feature/payments to main

Ogni voce ha un indice (HEAD@{0}, HEAD@{1}, ecc.) e una descrizione di cosa è successo.

Recupero dopo un hard reset#

Hai accidentalmente eseguito git reset --hard HEAD~3 e perso tre commit. Sono proprio lì nel reflog:

bash
# Guarda cosa hai perso
git reflog
 
# Il commit prima del reset è HEAD@{1}
git reset --hard f4e5d6c

Tutti e tre i commit sono tornati. Crisi sventata.

Recupero di un branch cancellato#

Hai cancellato un branch che aveva lavoro non mergiato:

bash
git branch -D feature/experimental
# Oh no, c'erano due settimane di lavoro

I commit esistono ancora. Trovali:

bash
git reflog | grep "feature/experimental"
# Oppure cerca nel reflog l'ultimo commit su quel branch
 
# Trovato. Ricrea il branch a quel commit:
git branch feature/experimental a1b2c3d

Il branch è tornato, con tutti i suoi commit.

Recupero dopo un rebase andato male#

Hai fatto rebase e tutto è andato storto. Conflitti ovunque, commit sbagliati, caos:

bash
# Il reflog mostra dove eri prima del rebase
git reflog
# a1b2c3d HEAD@{0}: rebase (finish): ...
# ...
# f4e5d6c HEAD@{5}: rebase (start): checkout main
# b7a8c9d HEAD@{6}: commit: Your last good commit
 
# Torna a prima del rebase
git reset --hard b7a8c9d

Sei tornato esattamente dove eri prima che il rebase iniziasse. Come se non fosse mai successo.

La rete di sicurezza dei 30 giorni#

Di default, Git mantiene le voci del reflog per 30 giorni (90 giorni per i commit raggiungibili). Dopo, possono essere eliminati dal garbage collector. Quindi hai un mese per realizzare di aver fatto un errore. In pratica, è più che sufficiente.

Puoi controllare la scadenza:

bash
git config gc.reflogExpire
# default: 90.days.ago (per i raggiungibili)
git config gc.reflogExpireUnreachable
# default: 30.days.ago (per gli irraggiungibili)

Se sei paranoico, aumentalo:

bash
git config --global gc.reflogExpireUnreachable "180.days.ago"

Una regola personale#

Prima di qualsiasi operazione distruttiva — hard reset, force push, cancellazione di branch — eseguo prima git log --oneline -10. Prendo nota mentalmente dell'HEAD corrente. Ci vogliono due secondi e mi ha salvato più di una volta da un panico di cui non avevo bisogno.

Stash correttamente: non è solo git stash#

La maggior parte delle persone usa lo stash così:

bash
git stash
# fai qualcosa
git stash pop

Funziona, ma è l'equivalente di buttare tutto in una scatola etichettata "roba." Quando hai tre stash, non hai idea di quale sia quale.

Dai un nome ai tuoi stash#

bash
git stash push -m "WIP: user auth form validation"

Ora quando elenchi gli stash:

bash
git stash list
# stash@{0}: On feature/auth: WIP: user auth form validation
# stash@{1}: On main: Quick fix attempt for nav bug
# stash@{2}: On feature/payments: Experiment with Stripe webhooks

Puoi vedere esattamente cosa contiene ogni stash.

Includi i file non tracciati#

Di default, git stash stasha solo i file tracciati. I nuovi file che non hai ancora aggiunto vengono lasciati indietro:

bash
# Stasha tutto, compresi i nuovi file
git stash push --include-untracked -m "WIP: new auth components"
 
# O includi anche i file ignorati (raramente necessario)
git stash push --all -m "Full workspace snapshot"

Uso --include-untracked quasi ogni volta. Lasciare nuovi file quando cambi branch causa confusione.

Stash parziale#

Questo è quello che la maggior parte delle persone non conosce. Puoi stashare file specifici:

bash
# Stasha solo file specifici
git stash push -m "Just the auth changes" src/auth/ src/middleware.ts

Oppure usa la modalità patch per stashare hunk specifici all'interno dei file:

bash
git stash push --patch -m "Partial: only the validation logic"

Git passerà in rassegna ogni modifica interattivamente e chiederà se vuoi stasharla. y per sì, n per no, s per dividere l'hunk in pezzi più piccoli.

Apply vs pop#

bash
# Pop: applica e rimuovi dalla lista degli stash
git stash pop stash@{2}
 
# Apply: applica ma mantieni nella lista degli stash
git stash apply stash@{2}

Uso apply quando non sono sicuro che lo stash si applicherà in modo pulito. Se c'è un conflitto, lo stash viene preservato. Con pop, se c'è un conflitto, lo stash resta nella lista comunque (molti non lo sanno), ma preferisco l'intento esplicito di apply.

Visualizzazione del contenuto dello stash#

bash
# Vedi quali file sono cambiati in uno stash
git stash show stash@{0}
 
# Vedi il diff completo
git stash show -p stash@{0}

Creare un branch da uno stash#

Se il tuo stash è cresciuto in qualcosa di più sostanziale:

bash
git stash branch feature/auth-validation stash@{0}

Questo crea un nuovo branch dal commit dove originariamente hai stashato, applica lo stash e lo elimina. Pulito.

Strategie di branching: quale funziona davvero#

Ci sono tre strategie di branching mainstream. Ciascuna ha un contesto in cui eccelle e contesti in cui causa problemi.

Gitflow#

Il classico. main, develop, feature/*, release/*, hotfix/*. Creato da Vincent Driessen nel 2010.

bash
# Feature branch
git checkout -b feature/user-auth develop
# ... lavora ...
git checkout develop
git merge --no-ff feature/user-auth
 
# Release branch
git checkout -b release/2.1.0 develop
# ... fix finali ...
git checkout main
git merge --no-ff release/2.1.0
git tag -a v2.1.0 -m "Release 2.1.0"
git checkout develop
git merge --no-ff release/2.1.0
 
# Hotfix
git checkout -b hotfix/session-fix main
# ... fix ...
git checkout main
git merge --no-ff hotfix/session-fix
git checkout develop
git merge --no-ff hotfix/session-fix

Quando funziona: App mobile, software desktop, qualsiasi cosa con release versionati e nominati e versioni multiple supportate simultaneamente. Se rilasci la v2.1 e la v3.0 e devi patchare entrambe, Gitflow gestisce questo.

Quando non funziona: Applicazioni web con deployment continuo. Se fai deploy in produzione 5 volte al giorno, la cerimonia dei release branch e dei develop branch è puro overhead. La maggior parte dei team web che adotta Gitflow finisce con un branch develop che è perennemente rotto e release branch che nessuno capisce.

GitHub Flow#

Semplice. Hai main. Crei feature branch. Apri PR. Mergi su main. Fai deploy di main.

bash
git checkout -b feature/user-auth main
# ... lavora ...
git push origin feature/user-auth
# Apri PR, ricevi la review, mergia
# main è sempre deployabile

Quando funziona: Team piccoli-medi che rilasciano app web. Deployment continuo. Se main va sempre in produzione, questo è tutto ciò di cui hai bisogno. È ciò che usa questo sito.

Quando non funziona: Quando devi mantenere versioni multiple di release, o quando hai un lungo ciclo QA prima del deployment. GitHub Flow assume che main vada in produzione velocemente.

Trunk-Based Development#

Tutti committano su main (il "trunk") direttamente o tramite branch molto short-lived (meno di un giorno). Nessun feature branch di lunga durata.

bash
# Branch short-lived (mergiato lo stesso giorno)
git checkout -b fix/auth-token main
# ... modifica piccola e mirata ...
git push origin fix/auth-token
# PR revisionata e mergiata in ore, non giorni

Quando funziona: Team ad alte prestazioni con buona CI/CD, suite di test complete e feature flag. Google, Meta e la maggior parte delle grandi aziende tech usano il trunk-based development. Forza modifiche piccole e incrementali ed elimina l'inferno del merge.

Quando non funziona: Team senza buona copertura dei test o CI. Se mergiare sul trunk significa deployare codice non testato, romperai la produzione costantemente. Hai anche bisogno di feature flag per qualsiasi cosa che richieda più di un giorno da costruire:

bash
# Feature flag nel codice
if (featureFlags.isEnabled('new-checkout-flow')) {
  renderNewCheckout();
} else {
  renderLegacyCheckout();
}

La mia raccomandazione#

Per la maggior parte dei team di web development: inizia con GitHub Flow. È semplice, funziona, e non richiede tooling oltre a quello che GitHub/GitLab fornisce già.

Se il tuo team cresce oltre 15-20 ingegneri e fai deploy più volte al giorno, guarda al trunk-based development con feature flag. L'investimento nell'infrastruttura dei feature flag si ripaga da solo in conflitti di merge ridotti e iterazione più veloce.

Se rilasci software versionato (app mobile, strumenti CLI, librerie): Gitflow o una versione semplificata. Hai davvero bisogno di quei release branch.

Non scegliere una strategia perché un post di blog ha detto che è la migliore. Scegli quella che corrisponde a come rilasci effettivamente.

Git Hook: automatizza le cose che continui a dimenticare#

I Git hook sono script che si eseguono automaticamente in momenti specifici del workflow Git. Sono locali sulla tua macchina (non vengono pushati al remote), il che significa che hai bisogno di un modo per condividerli con il team.

Gli hook che contano davvero#

pre-commit — Si esegue prima di ogni commit. Usalo per linting e formattazione:

bash
#!/bin/bash
# .git/hooks/pre-commit
 
# Esegui ESLint solo sui file in staging
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|tsx)$')
 
if [ -n "$STAGED_FILES" ]; then
  echo "Esecuzione ESLint sui file in staging..."
  npx eslint $STAGED_FILES --quiet
  if [ $? -ne 0 ]; then
    echo "ESLint fallito. Correggi gli errori prima di committare."
    exit 1
  fi
fi
 
# Esegui Prettier sui file in staging
if [ -n "$STAGED_FILES" ]; then
  echo "Esecuzione Prettier..."
  npx prettier --check $STAGED_FILES
  if [ $? -ne 0 ]; then
    echo "Check Prettier fallito. Esegui prima 'npx prettier --write'."
    exit 1
  fi
fi

commit-msg — Valida il formato del messaggio di commit. Perfetto per imporre i conventional commit:

bash
#!/bin/bash
# .git/hooks/commit-msg
 
COMMIT_MSG=$(cat "$1")
PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{1,72}"
 
if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
  echo "Formato messaggio di commit non valido."
  echo "Atteso: type(scope): description"
  echo "Esempio: feat(auth): add session refresh endpoint"
  echo ""
  echo "Tipi validi: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert"
  exit 1
fi

pre-push — Si esegue prima del push. Usalo per i test:

bash
#!/bin/bash
# .git/hooks/pre-push
 
echo "Esecuzione test prima del push..."
npm test
if [ $? -ne 0 ]; then
  echo "Test falliti. Push annullato."
  exit 1
fi

Husky vs hook nativi#

Gli hook nativi vivono in .git/hooks/. Il problema: la directory .git non è tracciata da Git, quindi non puoi condividere gli hook attraverso il repository. Tutti devono configurarli manualmente.

Husky risolve questo. Memorizza le configurazioni degli hook nel repository e configura .git/hooks automaticamente su npm install:

bash
npx husky init

Questo crea una directory .husky/. Aggiungi gli hook come file:

bash
# .husky/pre-commit
npx lint-staged

Combinato con lint-staged, ottieni controlli pre-commit veloci e mirati:

json
{
  "lint-staged": {
    "*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.css": ["prettier --write"],
    "*.json": ["prettier --write"]
  }
}

Questo esegue ESLint e Prettier solo sui file che stai effettivamente committando. Non sull'intero codebase. Veloce.

Quando saltare gli hook#

A volte devi committare senza che gli hook si eseguano. Hotfix di emergenza, commit work-in-progress sul tuo branch:

bash
git commit --no-verify -m "WIP: debugging production issue"

Usa questo con parsimonia. Se ti ritrovi a saltare gli hook regolarmente, i tuoi hook sono probabilmente troppo lenti o troppo rigidi.

Alias che fanno risparmiare tempo#

Il mio .gitconfig si è evoluto nel corso degli anni. Questi sono gli alias che sono sopravvissuti — quelli che uso davvero ogni giorno, non quelli che ho aggiunto perché sembravano intelligenti.

Log leggibile#

Il git log predefinito è verboso. Questo ti dà una vista pulita, colorata e basata su grafo:

bash
git config --global alias.lg "log --oneline --graph --all --decorate"

Utilizzo:

bash
git lg
# * a1b2c3d (HEAD -> main) Fix session validation
# | * d4e5f6g (feature/payments) Add Stripe integration
# | * h7i8j9k Update payment models
# |/
# * l0m1n2o Merge PR #42
# * p3q4r5s Add user dashboard

Lo eseguo 20 volte al giorno. È il modo più veloce per capire lo stato del tuo repository.

Annulla l'ultimo commit#

Mantieni le modifiche, annulla solo il commit:

bash
git config --global alias.undo "reset HEAD~1 --mixed"

Utilizzo:

bash
git undo
# Il commit è sparito, ma tutte le modifiche sono ancora nella tua directory di lavoro

Lo uso quando committo troppo presto, dimentico un file, o voglio ristrutturare le modifiche.

Rimuovi tutto dallo staging#

bash
git config --global alias.unstage "reset HEAD --"

Utilizzo:

bash
git unstage src/auth/session.ts
# Il file è rimosso dallo staging ma le modifiche sono preservate

Elenca tutti gli alias#

Perché dimenticherai cosa hai configurato:

bash
git config --global alias.aliases "config --get-regexp ^alias\\."

Altri alias che uso#

bash
# Mostra cosa stai per committare
git config --global alias.staged "diff --staged"
 
# Stato breve
git config --global alias.st "status -sb"
 
# Amend senza cambiare il messaggio
git config --global alias.amend "commit --amend --no-edit"
 
# Mostra l'ultimo commit
git config --global alias.last "log -1 HEAD --stat"
 
# Pull con rebase invece di merge
git config --global alias.up "pull --rebase --autostash"
 
# Elimina i branch che sono stati mergiati a main
git config --global alias.cleanup "!git branch --merged main | grep -v '^[ *]*main$' | xargs git branch -d"

L'alias up è particolarmente buono. pull --rebase mantiene la tua storia lineare invece di creare commit di merge per ogni pull. --autostash stasha e ripristina automaticamente le tue modifiche se hai file sporchi. È ciò che il pull avrebbe dovuto essere di default.

L'alias cleanup elimina i branch locali che sono stati mergiati a main. Con il tempo, accumuli dozzine di branch stantii. Esegui questo settimanalmente.

I comandi che uso quotidianamente#

Questi non sono alias o funzionalità avanzate. Sono solo comandi che eseguo costantemente e che molti sviluppatori non sembrano conoscere.

Il comando log definitivo#

bash
git log --oneline --graph --all

Questo mostra ogni branch, ogni merge, l'intera topologia del tuo repository in una vista compatta. È la prima cosa che eseguo quando faccio pull delle modifiche. Risponde a "cosa sta succedendo in questo repository adesso?"

Diff delle modifiche in staging#

bash
git diff --staged

Questo mostra cosa sta per essere committato. Non cosa è cambiato nella tua directory di lavoro — cosa è effettivamente in staging. Lo eseguo sempre prima di committare. Sempre. Cattura inclusioni accidentali, statement di debug, console.log che non dovrebbero essere lì.

bash
# Vedi le modifiche non in staging
git diff
 
# Vedi le modifiche in staging
git diff --staged
 
# Vedi entrambe
git diff HEAD

Mostra un commit specifico#

bash
git show a1b2c3d

Mostra il diff completo di un singolo commit. Utile quando rivedi la storia, per capire cosa ha effettivamente cambiato un commit.

bash
# Mostra solo i file cambiati
git show --stat a1b2c3d
 
# Mostra un file specifico a un commit specifico
git show a1b2c3d:src/auth/session.ts

Quest'ultimo è incredibilmente utile. Puoi visualizzare qualsiasi file in qualsiasi punto della storia senza fare checkout di quel commit.

Blame con intervalli di righe#

bash
git blame -L 42,60 src/auth/session.ts

Mostra chi ha modificato per ultimo le righe 42-60. Più utile che blamare l'intero file, che è di solito opprimente.

bash
# Mostra anche il messaggio del commit, non solo l'hash
git blame -L 42,60 --show-name src/auth/session.ts
 
# Ignora le modifiche di spazi bianchi (molto utile)
git blame -w -L 42,60 src/auth/session.ts
 
# Mostra il commit prima di quello blamato (scava più a fondo)
git log --follow -p -- src/auth/session.ts

Il flag -w è importante. Senza, blame attribuirà le righe a chiunque abbia riformattato il file per ultimo, che raramente è la persona che stai cercando.

Trova una stringa in tutta la storia#

bash
# Trova quando una funzione è stata aggiunta o rimossa
git log -S "validateSession" --oneline
 
# Trova quando un pattern regex è apparso
git log -G "session.*timeout" --oneline

-S (il "pickaxe") trova commit dove il numero di occorrenze di una stringa è cambiato. -G trova commit dove il diff corrisponde a un regex. Entrambi sono potenti per l'archeologia — capire quando qualcosa è stato introdotto o rimosso.

Mostra cosa è cambiato tra due punti#

bash
# Cosa è cambiato tra due branch
git diff main..feature/auth
 
# Cosa è cambiato su questo branch da quando è divergo da main
git diff main...feature/auth
 
# Elenca solo i file cambiati
git diff main...feature/auth --name-only
 
# Vista statistiche (file + inserimenti/eliminazioni)
git diff main...feature/auth --stat

Due punti vs tre punti conta. Due punti mostra la differenza tra le punte di entrambi i branch. Tre punti mostra cosa è cambiato sul lato destro da quando è divergo dal lato sinistro. Tre punti è di solito ciò che vuoi quando rivedi un feature branch.

Pulisci i file non tracciati#

bash
# Vedi cosa verrebbe eliminato (dry run)
git clean -n
 
# Elimina i file non tracciati
git clean -f
 
# Elimina file e directory non tracciati
git clean -fd
 
# Elimina file non tracciati e ignorati (opzione nucleare)
git clean -fdx

Esegui sempre con -n prima. git clean -fdx eliminerà i tuoi node_modules, .env, artefatti di build — tutto ciò che non è tracciato da Git. Utile per un nuovo inizio veramente pulito, ma distruttivo.

Ripristina un singolo file da un altro branch#

bash
# Ottieni la versione di main di un file senza cambiare branch
git restore --source main -- src/config/database.ts

O da un commit specifico:

bash
git restore --source a1b2c3d -- src/config/database.ts

Questo è più pulito della vecchia sintassi git checkout main -- path/to/file, e non influenza HEAD.

Mettere tutto insieme: un workflow reale#

Ecco come appare una giornata tipica con questi strumenti:

bash
# Mattina: controlla cosa sta succedendo
git lg
git fetch --all
 
# Inizia una feature
git checkout -b feature/session-refresh main
 
# Lavora, committa incrementalmente
git add -p                        # Metti in staging hunk specifici, non interi file
git commit -m "Add token refresh endpoint"
git commit -m "Add refresh token rotation"
git commit -m "Fix: handle expired refresh tokens"
git commit -m "Add integration tests"
 
# Interrotto: devi fixare un bug in produzione
git worktree add ../hotfix main
cd ../hotfix
# ... fixa, committa, pusha, PR mergiata ...
cd ../my-project
git worktree remove ../hotfix
 
# Torna al lavoro sulla feature
git commit -m "Fix edge case in token validation"
 
# Pronto per la PR: pulisci la storia
git rebase -i main
# Squash dei commit fix, riscrittura per chiarezza
 
# Push e apri PR
git push -u origin feature/session-refresh
 
# Qualcosa si è rotto in staging? Scopri quale commit:
git bisect start
git bisect bad HEAD
git bisect good main
git bisect run npm test
 
# Ops, ho fatto hard-reset della cosa sbagliata
git reflog
git reset --hard HEAD@{2}

Ognuno di questi comandi richiede secondi. Insieme, risparmiano ore. Non ore ipotetiche — ore reali, ogni settimana, che altrimenti passerei a sbrogliare casini di Git, cercare manualmente bug, o perdere lavoro per i cambi di contesto.

Git è uno strumento che premia la profondità. Le basi ti portano avanti durante la giornata. Ma i comandi in questo post sono ciò che separa "uso Git" da "Git mi rende davvero più veloce." Impara incrementalmente. Scegli una nuova tecnica questa settimana e usala fino a che diventa memoria muscolare. Poi scegline un'altra.

Il tuo futuro te stesso, che fissa un bug in produzione alle 11 di sera, ti ringrazierà per conoscere git bisect.

Articoli correlati