Queste sono le note della lezione del corso su Git. Nelle prossime lezioni il progetto della pizzeria smette di essere una manciata di file e diventa un’applicazione vera, generata da una CLI, con decine di file e dipendenze. Da lì in poi lavorare senza un sistema di controllo versione smette di essere una scelta discutibile e diventa autolesionismo, quindi la lezione su Git va fatta adesso.
Tutti, prima di Git, siamo passati dalla stessa strada: la cartella progetto-finale, poi progetto-finale-DEFINITIVO, poi progetto-finale-DEFINITIVO-v2-questa-buona. Git risolve quel problema lì, e parecchi altri che non sapevate ancora di avere: una storia completa del progetto, la possibilità di tornare indietro, di lavorare in parallelo su più cose e di collaborare senza passarsi gli zip.
Sistemiamo subito anche l’equivoco più diffuso, perché lo sento a ogni corso: Git e GitHub sono due cose diverse. Git è il software di controllo versione, gira sul vostro computer e funziona benissimo anche senza internet. GitHub è un sito che ospita repository Git e ci costruisce sopra strumenti di collaborazione. Si può usare Git senza GitHub; il contrario no.
Configurazione iniziale
Git firma ogni salvataggio con un nome e una email, quindi la prima cosa da fare, una volta sola, è dirgli chi siete:
git config --global user.name "Paolino Paperino"
git config --global user.email "paolino@example.com"
Il primo repository
Un repository è un progetto sotto controllo versione. Per crearlo si entra nella cartella del progetto e si inizializza:
cd awesome-pizza
git init
Da fuori non è cambiato niente, e in effetti è cambiata una cosa sola: è comparsa una cartella nascosta .git, che è il database dove Git terrà tutta la storia del progetto. Il resto della cartella, i vostri file, è quella che Git chiama working directory. Non toccate mai niente dentro .git, e soprattutto non cancellatela: cancellarla significa cancellare la storia.
I tre stati e lo staging
Qui sta il modello mentale di Git, e vale la pena capirlo subito perché spiega il novanta per cento dei comandi. Una modifica attraversa tre zone:
- la working directory, dove modificate i file
- la staging area, dove preparate ciò che entrerà nel prossimo salvataggio
- il repository, dove i salvataggi diventano permanenti
I salvataggi si chiamano commit, e ognuno è una fotografia del progetto in un certo momento. Il giro completo:
git status
git add index.html
git commit -m "Aggiungi la pagina del menu"
git status vi dice in ogni momento cosa è modificato, cosa è in staging e cosa non è ancora tracciato. Prendete l’abitudine di lanciarlo in continuazione: è la bussola, non costa niente, e non ho mai visto nessuno pentirsi di un git status di troppo.
Una domanda legittima a questo punto: perché due passaggi? Non basterebbe un comando che salva tutto? Lo staging esiste perché un commit dovrebbe contenere una modifica logica, una sola. Se avete sistemato il form degli ordini e nel frattempo corretto un refuso nel footer, lo staging vi permette di farne due commit separati, scegliendo file per file cosa entra. La scorciatoia git add . mette in staging tutto, e va benissimo, finché sapete cosa state aggiungendo.
La storia
Ogni commit finisce nella storia del progetto, che si consulta con:
git log --oneline
L’output è la cronaca del progetto, un commit per riga:
f3a91c2 Aggiungi il form per gli ordini
8b27d10 Aggiungi la pagina del menu
a1c4e89 Primo commit
DON’T: messaggi di commit inutili
Prima o poi aprirete la storia di un progetto e troverete fix, modifiche, asd, ora funziona. Il messaggio di commit è documentazione: dice al collega del futuro, che statisticamente siete voi tra sei mesi, perché quella modifica esiste. Non serve un poema, serve una riga onesta che descriva la modifica: “Aggiungi la validazione al form degli ordini” batte “fix form” tutti i giorni.
.gitignore
Non tutto quello che sta nella cartella del progetto merita di entrare nella storia. L’esempio principe lo incontrerete alla prossima lezione: la cartella node_modules, che contiene le dipendenze scaricate, pesa centinaia di megabyte e si rigenera con un npm install. Versionarla è inutile e doloroso.
Il file .gitignore, nella radice del repository, elenca ciò che Git deve fingere di non vedere:
node_modules/
dist/
.DS_Store
Le CLI che useremo nelle prossime lezioni generano progetti con un .gitignore già pronto, ed è uno dei motivi per cui questa lezione viene prima di quelle.
Branch
Finora la storia è una linea retta. I branch permettono di farla ramificare: una linea di sviluppo parallela dove lavorare a una funzionalità senza toccare quella principale, che per convenzione si chiama main.
La pizzeria vuole sperimentare il menu estivo senza rompere il sito in produzione:
git switch -c menu-estivo
switch -c crea il branch e ci si sposta sopra. Nei tutorial più vecchi troverete git checkout -b, che fa la stessa cosa: switch è il comando moderno, nato apposta perché checkout faceva troppe cose diverse. Da qui in poi i commit finiscono sul branch menu-estivo, e main resta esattamente dov’era.
Quando il menu estivo è pronto, si torna su main e si fondono le storie:
git switch main
git merge menu-estivo
I conflitti, senza panico
Se le due linee di sviluppo hanno modificato le stesse righe degli stessi file, Git non decide al posto vostro: si ferma e ve lo chiede. Il file incriminato si presenta così:
<<<<<<< HEAD
<h2>Le nostre pizze</h2>
=======
<h2>Il menu dell'estate</h2>
>>>>>>> menu-estivo
Tra <<<<<<< e ======= c’è la versione del branch su cui siete, tra ======= e >>>>>>> quella del branch che state fondendo. Risolvere un conflitto significa decidere come deve essere il risultato: si modifica il file lasciando il contenuto giusto, si tolgono i marcatori, poi git add e git commit. Tutto qui. Il primo conflitto fa sempre una certa impressione, dal secondo in poi è ordinaria amministrazione.
Remoti e GitHub
Tutto quello visto finora vive sul vostro computer. Un remote è una copia del repository ospitata altrove, tipicamente su GitHub, che fa da punto di sincronizzazione e, non guasta, da backup.
Per portarsi sul computer un progetto esistente:
git clone https://github.com/awesomepizza/sito.git
Per mandare i propri commit al remote e per scaricare quelli degli altri:
git push
git pull
Il flusso quotidiano di chi lavora in squadra è un giro regolare: git pull per allinearsi, si lavora, git add, git commit, git push. Sul mondo che GitHub costruisce attorno a questo flusso, le pull request, le review, le issue, ci sarebbe da fare una lezione a parte; per ora vi basta il giro.
Salvagente
Chiudo con i due comandi da conoscere per i piccoli incidenti di tutti i giorni.
Avete modificato un file e ve ne siete pentiti, volete tornare all’ultimo commit:
git restore index.html
Attenzione: le modifiche scartate non si recuperano, quindi usatelo quando siete sicuri.
Avete appena fatto un commit e il messaggio è sbagliato:
git commit --amend -m "Il messaggio giusto"
Git ha una famiglia intera di comandi per riscrivere la storia, e prima o poi sentirete nominare rebase con un misto di rispetto e paura. Tutto a suo tempo: con questi due, git status e un po’ di disciplina sui commit andate avanti per mesi.
Per approfondire
- Pro Git - il libro ufficiale, gratuito e tradotto anche in italiano
- Learn Git Branching - per imparare i branch giocando
- GitHub Docs - per la parte di collaborazione