Skip to content

Git — comandi e workflow

Riferimento per l’uso quotidiano, organizzato per intenzione: “cosa voglio ottenere”, non “quale comando esiste”. La regola che risolve metà dei dubbi: Git ha tre aree — working tree (i file sul disco), index o staging area (cosa entrerà nel prossimo commit), repository (i commit già registrati). Quasi ogni comando sposta qualcosa tra queste tre.

Configurazione iniziale

Terminal window
git config --global user.name "Nome Cognome"
git config --global user.email "nome@dominio.it"
git config --global init.defaultBranch main
git config --global pull.rebase true
git config --global core.editor "vim"

pull.rebase true evita i commit di merge automatici a ogni git pull: la storia resta lineare. Vedere la configurazione attiva e da quale file arriva:

Terminal window
git config --list --show-origin
git config --get remote.origin.url

Alias che risparmiano tempo:

Terminal window
git config --global alias.st "status --short --branch"
git config --global alias.lg "log --oneline --graph --decorate --all"
git config --global alias.last "log -1 --stat"
git config --global alias.unstage "restore --staged"

Stato e ispezione

Terminal window
git status --short --branch
git diff # working tree vs index: cosa NON è ancora staged
git diff --staged # index vs ultimo commit: cosa entrerà nel commit
git diff HEAD # working tree vs ultimo commit: tutto insieme
git diff main..feat/x # differenze tra due branch
git diff --stat # solo i nomi file e il conteggio righe

La distinzione tra le prime due righe è quella che confonde di più: git diff da solo non mostra ciò che hai già aggiunto con git add. Dopo uno git add che sembra “non aver fatto niente”, il diff da guardare è --staged.

Terminal window
git show HEAD # contenuto dell'ultimo commit
git show <sha>:<path> # una versione specifica di un file, senza checkout
git log --oneline -10
git log --graph --oneline --decorate --all
git log -p <path> # storia di un file con i diff
git log --follow <path> # segue anche le rinominazioni
git log --author="Tarozzi" --since="2 weeks ago"
git log main..feat/x # commit presenti nel branch e non in main

Chi ha toccato una riga e in quale commit:

Terminal window
git blame -L 40,60 <path>
git log -S "stringa_cercata" --oneline # commit che hanno aggiunto o rimosso quella stringa

git log -S è il “pickaxe”: cerca nel contenuto delle modifiche, non nei messaggi di commit. È il modo più rapido per trovare quando è entrata una riga di configurazione.

Commit

Terminal window
git add <path>
git add -p # sceglie interattivamente quali pezzi (hunk) mettere in stage
git add -A # tutto: modifiche, aggiunte, cancellazioni
git commit -m "messaggio"
git commit -am "messaggio" # add + commit, solo per file già tracciati

git add -p è il comando da imparare per primo: permette di separare in commit distinti modifiche fatte insieme, senza tornare indietro sul file.

Correggere l’ultimo commit — solo se non è ancora stato pushato:

Terminal window
git commit --amend -m "messaggio corretto"
git commit --amend --no-edit # aggiunge lo staged al commit precedente senza toccare il messaggio

L’amend riscrive lo SHA: dopo un push forzerebbe gli altri a un rebase. Su un branch condiviso, un commit in più è meno costoso di una storia riscritta.

Branch

Terminal window
git branch --show-current
git branch -vv # branch locali, ultimo commit, tracking remoto
git branch -a # include i remoti
git switch <branch> # cambia branch (sostituisce checkout)
git switch -c feat/nuovo # crea e passa
git switch -c feat/nuovo origin/main # parte da un branch remoto
git branch -m vecchio nuovo # rinomina
git branch -d feat/x # cancella (rifiuta se non mergiato)
git branch -D feat/x # cancella comunque

switch e restore hanno diviso i compiti che prima erano tutti in checkout: switch cambia branch, restore recupera file. Se hai git recente, usa quelli — l’intenzione è esplicita.

Branch già mergiati, da ripulire:

Terminal window
git branch --merged main | grep -v main
git fetch --prune # rimuove i riferimenti a branch cancellati sul remoto

Merge e rebase

Sono due modi di integrare il lavoro, con effetti diversi sulla storia.

Terminal window
git switch main
git merge feat/x # merge, con fast-forward se possibile
git merge --no-ff feat/x -m "..." # forza un commit di merge dedicato
git merge --squash feat/x # porta le modifiche come un unico commit da creare a mano

--no-ff mantiene visibile il confine del lavoro: puoi annullarlo in blocco con git revert -m 1 <sha-merge>. --squash collassa tutto in un commit solo, utile quando il branch ha venti commit di aggiustamento.

Terminal window
git switch feat/x
git rebase main # riscrive i commit del branch sopra main
git rebase -i HEAD~5 # rebase interattivo sugli ultimi 5 commit
git rebase --continue
git rebase --abort # torna indietro se si complica

Nel rebase interattivo le parole chiave sono pick (tieni), reword (cambia messaggio), squash (fondi nel precedente), fixup (fondi scartando il messaggio), drop (elimina).

Regola d’oro: non fare rebase di commit già pushati su un branch che altri usano. Il rebase crea nuovi SHA e chi ha già scaricato i vecchi si ritrova con una storia divergente.

Conflitti:

Terminal window
git status # elenca i file in conflitto
git diff --name-only --diff-filter=U
# risolvi a mano, poi
git add <file-risolto>
git rebase --continue # oppure git merge --continue
git merge --abort # per uscire senza risolvere

Prendere una sola versione senza editare:

Terminal window
git checkout --ours <file> # durante un merge: la versione del branch corrente
git checkout --theirs <file> # la versione in arrivo

Attenzione: durante un rebase ours e theirs sono invertiti rispetto all’intuizione, perché Git sta riapplicando i tuoi commit sopra l’altro branch.

Annullare

La domanda giusta è: dove sta la cosa da annullare?

SituazioneComando
Modifica nel working tree, non stagedgit restore <file>
Modifica già in stagegit restore --staged <file>
Ultimo commit, tenere le modifiche in stagegit reset --soft HEAD~1
Ultimo commit, tenere le modifiche nel working treegit reset HEAD~1
Ultimo commit, buttare tuttogit reset --hard HEAD~1
Commit già pushatogit revert <sha>
Merge già pushatogit revert -m 1 <sha-merge>
Tutto il working tree a uno stato pulitogit reset --hard HEAD
Terminal window
git restore <file>
git restore --staged <file>
git reset --soft HEAD~1
git reset --hard HEAD~1
git revert <sha>

La differenza che conta: reset riscrive la storia (usalo solo in locale), revert crea un nuovo commit che annulla il precedente lasciando traccia (l’unico sicuro dopo un push).

Recuperare un file da un commit passato senza toccare il resto:

Terminal window
git restore --source=<sha> -- <path>
git checkout <sha> -- <path> # forma equivalente più vecchia

Stash

Per mettere da parte il lavoro in corso e cambiare contesto.

Terminal window
git stash push -m "lavoro su egressip"
git stash push -u # include anche i file non tracciati
git stash list
git stash show -p stash@{0} # cosa contiene
git stash pop # riapplica e rimuove dalla lista
git stash apply stash@{1} # riapplica senza rimuovere
git stash drop stash@{0}
git stash clear # svuota tutto — irreversibile

pop è comodo ma se genera conflitti lo stash resta comunque nella lista: controlla sempre con git stash list dopo un pop andato male.

Remoti

Terminal window
git remote -v
git remote add origin git@gitlab.com:user/repo.git
git remote set-url origin git@gitlab.com:user/repo.git
git fetch origin # scarica senza toccare il working tree
git fetch --all --prune
git pull # fetch + merge (o rebase, se configurato)
git pull --rebase
git push origin main
git push -u origin feat/x # push e imposta il tracking
git push --force-with-lease # push forzato, ma si ferma se il remoto è cambiato

Usa sempre --force-with-lease invece di --force: se qualcuno ha pushato nel frattempo, il comando si rifiuta invece di sovrascrivere il lavoro altrui.

Cancellare un branch remoto:

Terminal window
git push origin --delete feat/x

Tag e release

Terminal window
git tag # elenca
git tag -a v1.2.0 -m "release 1.2.0"
git tag -a v1.2.0 <sha> -m "..." # tagga un commit passato
git push origin v1.2.0
git push origin --tags
git tag -d v1.2.0 # cancella in locale
git push origin --delete v1.2.0 # e sul remoto
git describe --tags # versione leggibile del commit corrente

I tag annotati (-a) contengono autore, data e messaggio, e sono quelli da usare per le release. I tag leggeri sono solo un puntatore.

Recupero da errori

Terminal window
git reflog

È la rete di sicurezza: registra ogni spostamento di HEAD, inclusi reset, rebase e checkout, per 90 giorni di default. Anche un commit “perso” con un reset --hard è quasi sempre ancora lì.

Terminal window
git reflog # trova lo SHA di prima del disastro
git reset --hard <sha-buono>
git switch -c recupero <sha-buono> # più prudente: recupera su un branch nuovo

Trovare il commit che ha introdotto un bug:

Terminal window
git bisect start
git bisect bad # il commit attuale è rotto
git bisect good <sha-funzionante>
# Git fa checkout a metà: testa, poi
git bisect good # oppure git bisect bad
git bisect reset # a fine ricerca

Ricerca binaria sulla storia: con 1000 commit bastano dieci test.

Ripulire il working tree

Terminal window
git clean -n # dry-run: elenca cosa cancellerebbe
git clean -fd # rimuove file e directory non tracciati
git clean -fdx # include anche quelli ignorati da .gitignore

Lancia sempre -n prima: git clean non è recuperabile, quei file non sono mai stati in Git.

Sottomoduli

Terminal window
git submodule add <url> <path>
git submodule update --init --recursive
git clone --recurse-submodules <url>
git submodule foreach git pull origin main

Cose che salvano la giornata

Chi ha modificato una riga e perché, partendo dal codice:

Terminal window
git log -S "EgressIP" --oneline -- site/src/content/docs/

Vedere un file come era in un branch diverso, senza cambiare branch:

Terminal window
git show feat/x:site/astro.config.mjs | less

Portare un singolo commit da un altro branch:

Terminal window
git cherry-pick <sha>
git cherry-pick <sha1>..<sha2> # un intervallo
git cherry-pick --abort

Confrontare due branch prima di un merge:

Terminal window
git diff main...feat/x # tre punti: solo le modifiche del branch
git log main..feat/x --oneline # due punti: i commit da integrare

Escludere un file già tracciato senza cancellarlo dal disco:

Terminal window
git rm --cached <path>
echo "<path>" >> .gitignore

.gitignore agisce solo sui file non ancora tracciati: se il file è già in Git, va prima rimosso dall’index.