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
git config --global user.name "Nome Cognome"git config --global user.email "nome@dominio.it"git config --global init.defaultBranch maingit config --global pull.rebase truegit 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:
git config --list --show-origingit config --get remote.origin.urlAlias che risparmiano tempo:
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
git status --short --branchgit diff # working tree vs index: cosa NON è ancora stagedgit diff --staged # index vs ultimo commit: cosa entrerà nel commitgit diff HEAD # working tree vs ultimo commit: tutto insiemegit diff main..feat/x # differenze tra due branchgit diff --stat # solo i nomi file e il conteggio righeLa 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.
git show HEAD # contenuto dell'ultimo commitgit show <sha>:<path> # una versione specifica di un file, senza checkoutgit log --oneline -10git log --graph --oneline --decorate --allgit log -p <path> # storia di un file con i diffgit log --follow <path> # segue anche le rinominazionigit log --author="Tarozzi" --since="2 weeks ago"git log main..feat/x # commit presenti nel branch e non in mainChi ha toccato una riga e in quale commit:
git blame -L 40,60 <path>git log -S "stringa_cercata" --oneline # commit che hanno aggiunto o rimosso quella stringagit 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
git add <path>git add -p # sceglie interattivamente quali pezzi (hunk) mettere in stagegit add -A # tutto: modifiche, aggiunte, cancellazionigit commit -m "messaggio"git commit -am "messaggio" # add + commit, solo per file già tracciatigit 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:
git commit --amend -m "messaggio corretto"git commit --amend --no-edit # aggiunge lo staged al commit precedente senza toccare il messaggioL’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
git branch --show-currentgit branch -vv # branch locali, ultimo commit, tracking remotogit branch -a # include i remotigit switch <branch> # cambia branch (sostituisce checkout)git switch -c feat/nuovo # crea e passagit switch -c feat/nuovo origin/main # parte da un branch remotogit branch -m vecchio nuovo # rinominagit branch -d feat/x # cancella (rifiuta se non mergiato)git branch -D feat/x # cancella comunqueswitch 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:
git branch --merged main | grep -v maingit fetch --prune # rimuove i riferimenti a branch cancellati sul remotoMerge e rebase
Sono due modi di integrare il lavoro, con effetti diversi sulla storia.
git switch maingit merge feat/x # merge, con fast-forward se possibilegit merge --no-ff feat/x -m "..." # forza un commit di merge dedicatogit 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.
git switch feat/xgit rebase main # riscrive i commit del branch sopra maingit rebase -i HEAD~5 # rebase interattivo sugli ultimi 5 commitgit rebase --continuegit rebase --abort # torna indietro se si complicaNel 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:
git status # elenca i file in conflittogit diff --name-only --diff-filter=U# risolvi a mano, poigit add <file-risolto>git rebase --continue # oppure git merge --continuegit merge --abort # per uscire senza risolverePrendere una sola versione senza editare:
git checkout --ours <file> # durante un merge: la versione del branch correntegit checkout --theirs <file> # la versione in arrivoAttenzione: 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?
| Situazione | Comando |
|---|---|
| Modifica nel working tree, non staged | git restore <file> |
| Modifica già in stage | git restore --staged <file> |
| Ultimo commit, tenere le modifiche in stage | git reset --soft HEAD~1 |
| Ultimo commit, tenere le modifiche nel working tree | git reset HEAD~1 |
| Ultimo commit, buttare tutto | git reset --hard HEAD~1 |
| Commit già pushato | git revert <sha> |
| Merge già pushato | git revert -m 1 <sha-merge> |
| Tutto il working tree a uno stato pulito | git reset --hard HEAD |
git restore <file>git restore --staged <file>git reset --soft HEAD~1git reset --hard HEAD~1git 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:
git restore --source=<sha> -- <path>git checkout <sha> -- <path> # forma equivalente più vecchiaStash
Per mettere da parte il lavoro in corso e cambiare contesto.
git stash push -m "lavoro su egressip"git stash push -u # include anche i file non tracciatigit stash listgit stash show -p stash@{0} # cosa contienegit stash pop # riapplica e rimuove dalla listagit stash apply stash@{1} # riapplica senza rimuoveregit stash drop stash@{0}git stash clear # svuota tutto — irreversibilepop è comodo ma se genera conflitti lo stash resta comunque nella lista: controlla sempre con git stash list dopo un pop andato male.
Remoti
git remote -vgit remote add origin git@gitlab.com:user/repo.gitgit remote set-url origin git@gitlab.com:user/repo.gitgit fetch origin # scarica senza toccare il working treegit fetch --all --prunegit pull # fetch + merge (o rebase, se configurato)git pull --rebasegit push origin maingit push -u origin feat/x # push e imposta il trackinggit push --force-with-lease # push forzato, ma si ferma se il remoto è cambiatoUsa 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:
git push origin --delete feat/xTag e release
git tag # elencagit tag -a v1.2.0 -m "release 1.2.0"git tag -a v1.2.0 <sha> -m "..." # tagga un commit passatogit push origin v1.2.0git push origin --tagsgit tag -d v1.2.0 # cancella in localegit push origin --delete v1.2.0 # e sul remotogit describe --tags # versione leggibile del commit correnteI 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
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ì.
git reflog # trova lo SHA di prima del disastrogit reset --hard <sha-buono>git switch -c recupero <sha-buono> # più prudente: recupera su un branch nuovoTrovare il commit che ha introdotto un bug:
git bisect startgit bisect bad # il commit attuale è rottogit bisect good <sha-funzionante># Git fa checkout a metà: testa, poigit bisect good # oppure git bisect badgit bisect reset # a fine ricercaRicerca binaria sulla storia: con 1000 commit bastano dieci test.
Ripulire il working tree
git clean -n # dry-run: elenca cosa cancellerebbegit clean -fd # rimuove file e directory non tracciatigit clean -fdx # include anche quelli ignorati da .gitignoreLancia sempre -n prima: git clean non è recuperabile, quei file non sono mai stati in Git.
Sottomoduli
git submodule add <url> <path>git submodule update --init --recursivegit clone --recurse-submodules <url>git submodule foreach git pull origin mainCose che salvano la giornata
Chi ha modificato una riga e perché, partendo dal codice:
git log -S "EgressIP" --oneline -- site/src/content/docs/Vedere un file come era in un branch diverso, senza cambiare branch:
git show feat/x:site/astro.config.mjs | lessPortare un singolo commit da un altro branch:
git cherry-pick <sha>git cherry-pick <sha1>..<sha2> # un intervallogit cherry-pick --abortConfrontare due branch prima di un merge:
git diff main...feat/x # tre punti: solo le modifiche del branchgit log main..feat/x --oneline # due punti: i commit da integrareEscludere un file già tracciato senza cancellarlo dal disco:
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.