Saturday, March 17, 2018

Introduzione a Git

1. Introduzione

Git e' un sistema di version control (http://git-scm.org). Lo scopo di Git e' quello di tener traccia di tutti i cambiamenti effettuati all'interno di un progetto software in modo da assicurare sempre una versione di backup delle versioni precedenti. In questo modo, se qualcosa va storto, e' possibile invertire le modifiche e ripristinare il precedente stato funzionante del software.
A differenza degli altri sistemi di versionamento, quasi tutte le operazioni in Git non hanno bisogno di una connessione internet, perché sono operazioni che avvengono su disco locale. Git salva lo storico completo del progetto direttamente su disco locale e recuperare una vecchia versione di un file dalla history é una operazione istantanea. Anche le commit avvengono sul database locale

1.1 Git vs GitHub

Git e GitHub sono due cose diverse: GitHub è un servizio online basato su Git, ma non è indispensabile per usare Git e creare un repository. GitHub permette di hostare online un repository creato con Git e condividerlo con altri sviluppatori. L'hosting pubblico su GitHub è gratuito, l'hosting privato è a pagamento. Per questo motivo gli sviluppatori usano altri servizi online per condividere repository Git, come GitLab.com e bitbucket.org.

1.2 Le aree di lavoro di Git

Git e' composto da quattro aree di lavoro:


Il luogo dove lo sviluppatore crea e modifica il codice del progetto e' rappresentato dalla working directory di Git, che contiene i file che andranno a costituire il repository Git.

Per creare la cartella di progetto ed inizializzare un nuovo repository Git, lancio il comando:

git init myproject

Lo sviluppatore effettua delle modifiche al file nella working directory; quando la versione corrente del file produce il risultato aspettato, lo sviluppatore sposta il file in Staging. Per spostare un file dalla working directory allo Staging si lancia il comando git add:

git add <filepath>

Il comando git add non modifica in modo permanente il repository, ma specifica solo che il file parteciperá alla prossima commit.
Si puó utilizzare il comando git add <nome_file> per piú obbiettivi: 1) incominciare a fare tracking di un file, 2) aggiungere un file alla Staging Area e 3) marcare i conflitti di merging come risolti.

  1. Incominciare a fare tracking di un file:
    Quando crei un nuovo file nel tuo progetto, Git inizialmente non lo "traccia", cioe' Git non tiene traccia delle modifiche apportate a quel file. Il comando git add viene utilizzato per dire a Git di iniziare a monitorare le modifiche a un file specifico.
  2. Aggiungere un file alla Staging Area:
    La "Staging Area" (o "Index") è un'area intermedia in Git che funziona come una lista di modifiche che sei pronto a includere nel tuo prossimo commit. Quando esegui git add <nome_file>, il file (o le modifiche al file) viene aggiunto alla Staging Area. Questo ti permette di selezionare specifici file o modifiche da includere nel tuo commit.
  3. Marcare i conflitti di merging come risolti:
    Durante un merge, possono verificarsi dei "conflitti" se Git non riesce a risolvere automaticamente le differenze tra le modifiche fatte in diversi branch. Quando si verifica un conflitto, Git contrassegna il file con marcatori speciali (ad esempio, <<<<<<<, =======, >>>>>>>). Dopo aver risolto manualmente il conflitto (modificando il file per combinare le modifiche), utilizzi git add <nome_file> per dire a Git che il conflitto è stato risolto e che il file è pronto per essere committato.

1.3 Committare i file nel repository locale

Lo sviluppatore ha modificato alcuni file; i file di cui e' soddisfatto delle modifiche apportate si trovano nella Staging area, i file che richiedono ancora delle modifiche si trovano nella working directory. Supponiamo che lo sviluppatore ha completato la sessione di lavoro (per esempio ha risolto un bug oppure ha implementato una nuova feature) allora vuole registrare uno snapshot del progetto. Per fare cio' lo sviluppatore sposta i file dallo staging al repository. Per committare tutti i file che si trovano nella Staging Area, si lancia il comando commit:

git commit

Supponiamo che lo sviluppatore stia modificando uno staged file nella working directory, non vuole fare il commit del file perche' non ha ancora finito di lavorare sul file, ma vuole cambiare branches per lavorare su qualcos'altro. A questo punto, se lo sviluppatore non vuole salvare il lavoro incompleto nel repository, puo' salvare lo stato della Working Directory nello Stashing. In futuro lo sviluppatore puo' ripristinare lo stato della Working Directory dallo Stashing. Per spostare i files dalla Working Directory allo Stashing uso il comando:

git stash 

Per spostare i files dallo Stashing alla Working Directory uso il comando:

git stash pop

1.4 Il comando git checkout

Il comando git checkout in Git serve principalmente per 1) cambiare ramo 2) creare un nuovo ramo 3) per ripristinare file a uno versione precedente o 4) tornare a una versione (commit) specifica di un progetto.

1.4.1 Il comando checkout per cambiare branch

Puoi usare git checkout per passare da un branch ad un altro, nel tuo repository.

git checkout nome-branch

Questo comando cambia il branch attualmente attivo. Git aggiorna il contenuto della tua directory di lavoro con i file del branch selezionato.

Esempio:

git checkout main   # passa al branch main

Se hai modifiche non salvate e il passaggio al nuovo branch le sovrascriverebbe, Git ti impedirà di cambiare finché non le sistemi (con git stash, git commit o git reset).

1.4.2 Il comando checkout per creare e passare a un nuovo branch

git checkout -b nuovo-branch

Effetto: Crea un nuovo ramo e ci si sposta immediatamente.

Esempio:

git checkout -b feature/login  # crea il branch feature/login e ci si sposta sopra.

1.4.3. Il comando checkout per ripristinare file o cartella a una versione precedente

1.4.3.1 Ripristinare un file dallo stato del repository (HEAD)

git checkout HEAD -- nomefile

Questo annulla le modifiche non committate su quel file.

1.4.3.2 Recuperare un file o cartella da un commit precedente:

git checkout <commit-id> -- <percorso/file_o_cartella>
  • <commit-id> l’hash (o parte dell’hash) del commit da cui vuoi recuperare il file.
  • -- separa il commit dal percorso del file.
  • <percorso/file_o_cartella> il file o la cartella da recuperare.

Effetto: Prende un file o un insieme di file a come erano in un commit specifico e lo ripristina nella tua working directory.

Esempio: ripristina il file index

  1. Vedi la cronologia dei commit
  2. git log --oneline
    e3a1f6b Fix bug login
    c7d92a8 Update README
    4f3b1c9 Add index.html
    
  3. Recuperare index.html com’era nel commit 4f3b1c9
  4. git checkout 4f3b1c9 -- index.html
    

Questo comando sovrascrive index.html nella working directory, ma non cambia tutto il progetto e non cambia branch. Il comando checkout modifica il file nella working directory, e il file non è ancora salvato nel commit attuale. Per farlo:

git add index.html
git commit -m "Ripristinato index.html dal commit 4f3b1c9"

Esempio: ripristina la cartella components:

git checkout 4f3b1c9 -- src/components/

Esempio:

git checkout HEAD~1 -- index.html  # ripristina `index.html` alla versione prima dell'ultimo commit
git checkout HEAD -- index.html  # ripristina `index.html` alla versione dell'ultimo commit (HEAD).

1.4.4 Il comando checkout per tornare a una versione (commit) specifica di un progetto

Il comando git checkout si usa per tornare temporaneamente a uno specifico commit identificato dal suo hash o tag; l'hash e' il codice alfanumerico che Git assegna a ogni commit. E quindi permette di recuperare una precedente versione del progetto dal repository locale.

Il comando:

git checkout <hash>
# oppure:
git checkout <tag>

serve per osservare o testare una vecchia versione del progetto, per fare debug di un problema introdotto dopo un certo commit, per creare un nuovo branch da un commit specifico (es. hotfix o esperimenti).

Esempio:

Immagina che tu abbia questa cronologia:

* a1b2c3d (HEAD -> main)   <- Ultimo commit
* d4e5f6g
* 7h8i9j0

E vuoi vedere com’era il progetto al commit d4e5f6g. Usi:

git checkout d4e5f6g

Git fa tre cose:

  • Ripristina i file della working directory per riflettere i file così com’erano in quel commit.
  • Aggiorna anche la staging area (index) per per rispecchiare il commit.
  • Git entra nello stato chiamato detached HEAD.

NOTA: Il comando git checkout allinea tutto, working directory e staging area, per portarti in uno stato coerente e fedele al commit scelto. Se invece volessi solo cambiare file nella working directory senza toccare la staging area, dovresti usare altri comandi (come git restore, introdotto nelle versioni più recenti).

Il comando checkout recupera il commit indicato dal repo locale e lo copia nella working directory e nella staging area. Per scoprire qual'e' l'hash del commit si puo' usare un client grafico, ad esempio gitk, oppure usare il comando git log --oneline

Effettivamente, dopo l'esecuzione del comando git checkout, il file system è tornato allo stato del precedente commit. Se si prova a lanciare gitk, si vedra' che apparentemente l'ultimo commit è scomparso, ma in realta' e' solo nascosto. Per visualizzare tutti i commit, si lancia il comando gitk --all oppure il comando git log --graph --all --oneline.

Cos'è lo stato detached HEAD? In Git, il HEAD è un puntatore che indica il commit attuale su cui stai lavorando. Normalmente, HEAD punta a un branch (es. main, develop, ecc.). Quando sei in uno stato normale, i nuovi commit vengono aggiunti in cima a quel branch. Detached HEAD significa che HEAD non punta più a un branch, ma direttamente a un commit.

Cosa comporta lo stato detached HEAD? Se modifichi file e fai commit, quei commit non saranno collegati a nessun branch. Se fai dei commit in questo stato e poi torni a un branch, i nuovi commit potrebbero andare persi se non li salvi in un nuovo branch.

Cosa fare nello stato detached HEAD? Dopo aver usato il comando git checkout hash, puoi creare un nuovo branch da quel precedente commit oppure tornare al branch principale.

  • Creare un nuovo branch da quel commit:

    crea un nuovo branch, se fai dei commit mentre sei in detached HEAD, così i commit verranno salvati in un branch e non andranno persi. Crea un nuovo branch per recuperare una vecchia versione del progetto dal repository locale e metterla in un nuovo branch, senza modificare il branch principale:

    git checkout <hash> -b new-branch  # recupera commit nel nuovo ramo "new-branch"
  • Ritornare al brach principale:

    Se non ti serve mantenere nulla, per tornare a lavorare su un branch:

    git checkout nome-branch # torna a "main" o "master" o qualunque branch tu stessi usando

    Dopo aver eseguito il comando git checkout <nome-branch>, Git aggiorna la working directory per riflettere lo stato dell’ultimo commit di quel ramo, e non sei piu' in detached HEAD: sei sul ramo <nome-branch> e puoi continuare a fare commit normalmente.

NOTA: Dal 2020, il comando git checkout è stato parzialmente sostituito da comandi più specifici: vedi paragrafo 3


Il workflow di Git

Gli stati di un file: committed, modified, staged
untraked
Un file nuovo, appena creato, per Git si trova nello stato untraked.
staged
Quando si sposta il file nella Staging area con git add, il file si trova nello stato staged
committed
Quando si committa il file nel database, il file si trova nello stato committed.
modified
Se il file si trova nello stato staged o committed e viene editato, Git segnala che esiste una copia del file nel working tree nello stato modified

2. Comandi Base di Git con esempio step-by-step

Vediamo come creare di un nuovo local repository Git, partendo da zero.

 cd  progetto-con-git/
 git init

Vediamo cosa ha fatto il comando git init, lanciando il comando ls: Git ha creato la cartella di progetto di nome progetto-con-git. Se mi sposto all'interno della cartella di progetto e lancio il comando ls -la, vedo la cartella di sistema di nome .git, che contiene i file che controllano le versioni storiche dei file di progetto, lo snapshots di documenti, immagini, sorgenti, etc.
Creo di un nuovo file chiamato my-file.txt, usando l'editor di testo nano.

 cd progetto-con-git
 nano my-file.txt 
Per vedere lo stato del working tree:
git status

Si puo' leggere che sono sul ramo master, git segnala il file appena creato come untraked.

Il file e' pronto, per cui lo invio alla staging area (anche detta index):

git add my-file.txt
Lancio ancora git status per vedere lo stato della working directory. Si puo' leggere che sono sul ramo master, il nome del file non e' piu' rosso ma verde e non ci sono commit ancora.
Inoltre, git suggerisce di usare il comando "git rm --cached <file>..." per rimuovere il file appena aggiunto dalla Staging area. Si provi ad usare il comando git rm per rimuovere il file dalla staging area.

Una volta copiato il file nella staging area, proviamo a modificare lo stesso file nella working directory. Per cui apriamo il file con l'editor di testo, lo modifichiamo e lo salviamo di nuovo. Lancio il comando git status e vedo che nello staging area c'é ancora il my-file.txt pronto per il commit, inoltre nel working tree c'é di nuovo my-file.txt nello stato modified pronto per essere aggiunto alla staging area per aggiornare la prossima commit.

Quindi, eseguo di nuovo git add . per avere tutte le modifiche pronte nella staging area.

Per committare le modifiche, uso il comando:

git commit

Il comando apre l'editor di testo preferito allo scopo di inserire il messaggio relativo a quel commit. Posso decommentare la riga "# Initial commit" ed inserire un mio messaggio sulla prima riga.
Ogni volta che si fa un commit in Git, quel commit ha un commento ed un codice: il commento e' il messaggio che si aggiunge al commit e commenta le modifiche fatte, il codice e' autogenerato da Git. Il codice del commit serve nel caso in cui si vuole tornare indietro e ripristinare lo stato del progetto al momento del commit con uno specifico commento.

Eseguire git commit con flag -m

Se eseguo di nuovo git status, appare il messaggio: "nothing to commit, working tree clean", che vuol dire che non ci sono modifiche da committare.
Poi, creo un altro file di nome "second-file.txt", lo aggiungo alla staging area e infine faccio la commit. Questa volta lancio il comando commit con il parametro -m, per evitare di inserire il messaggio con l'editor di testo .

git commit -m 'added second-file.txt'

Per vedere lo storico delle modifiche in Git posso usare il comando git log

Che mosta tutte le commit effettuate nel repository corrente con rispettivo codice e nome.

3. Nuovi Comandi Restore e Switch

Dal 2020, a partire da Git 2.23, Git ha introdotto git restore e git switch per rendere i comandi più espliciti e meno ambigui rispetto a git checkout, che è più "tuttofare":

  • git switch serve per cambiare branch o per creare e cambiare branch
  • git restore è stato introdotto per ripristinare file

Sintassi ed Equivalenza

Operazione Vecchio comando Nuovo comando
Passare a un altro branch git checkout nome-branch git switch nome-branch
Creare e passare a nuovo branch git checkout -b nuovo git switch -c nuovo
Ripristinare file da commit git checkout <commit-id> -- nome-file git restore --source=<commit-id> -- nome-file
3.1 git checkout

Comando vecchio e polivalente, può fare due cose:

  • Cambiare branch:

  • git checkout main
  • Ripristinare file (da commit o branch):

  • git checkout HEAD -- file.txt

Problema: Il comando può essere ambiguo. Non è chiaro se stai cambiando branch o ripristinando file.

3.2 git switch

Introdotto in Git 2.23, serve solo per cambiare branch.

Esempi:

git switch main            # passa al branch 'main'
git switch -c nuova-feature # crea e passa a un nuovo branch

Vantaggi: Più leggibile e sicuro. Non fa cose "strane" come ripristinare file accidentalmente.

3.3 git restore

Anche questo introdotto in Git 2.23, serve a ripristinare file, ovvero:

  • Tornare allo stato di un commit precedente.
  • Annullare modifiche non ancora messe in staging.
  • Annullare file già messi in staging.

Esempi:

git restore file.txt                     # rimuove modifiche non ancora stageate
git restore --staged file.txt          # rimuove file dallo staging area
git restore --source=HEAD file.txt    # ripristina file da HEAD

Quando usare cosa

Azione che vuoi fare Comando consigliato
Cambiare branch git switch <branch>
Creare e passare a un nuovo branch git switch -c <branch>
Ripristinare un file modificato git restore <file>
Togliere un file dallo staging area git restore --staged <file>
Tornare a un commit specifico (detached) git checkout <commit>

In sintesi: Usa git switch per cambiare branch e git restore per lavorare sui file. Evita git checkout, perché è meno esplicito, anche se ancora valido.

No comments:

Post a Comment