Sei sulla pagina 1di 30

git, un sistema di gestione del codice sorgente

Luca Manini

2015-2018

Contents
1 Introduzione 2
2 Sistemi di controllo di versione o di gestione del software 2
3 Concetti e funzionalità di base 3
3.1 Lavorare senza vcs . . . . . . . . . . . . . . . . . . . . . . . . 3
3.1.1 Singolo sviluppatore, singolo le, singola "linea di
sviluppo" . . . . . . . . . . . . . . . . . . . . . . . . . 3
3.1.2 Singolo sviluppatore, multipli le . . . . . . . . . . . . 4
3.1.3 Singolo sviluppatore, multiple "linee di sviluppo" . . . 5
3.2 Il repository . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3.2.1 Riassunto da "comprendere git concettualmente" . . . 6
3.3 Traccia della storia dei le . . . . . . . . . . . . . . . . . . . . 7
3.4 Metadati sui le . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.5 branch e merge . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.6 repo "personali": lavoro tranquillo ed isolato . . . . . . . . . . 7
3.7 Stati e usso dei le . . . . . . . . . . . . . . . . . . . . . . . 8

4 Esempi d'uso 8
4.1 Primo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
4.1.1 Creazione della directory di lavoro . . . . . . . . . . . 9
4.1.2 Inizializzazione del repo . . . . . . . . . . . . . . . . . 9
4.1.3 Creazione della prima versione del programma . . . . 9
4.1.4 Controllo della situazione . . . . . . . . . . . . . . . . 10
4.1.5 Prima registrazione e commit . . . . . . . . . . . . . . 10
4.1.6 Modica, di e nuovi add e commit . . . . . . . . . . 11
4.1.7 Ritorno ad una versione precedente (solo per stampa) 14
4.1.8 Ritorno ad una versione precedente (correzione errori) 15
4.1.9 Aggiunta di una feature (su master) e x di un baco
(con merge) . . . . . . . . . . . . . . . . . . . . . . . . 16
4.2 Secondo (semplicato) . . . . . . . . . . . . . . . . . . . . . . 18
4.3 Repo remoto con due cloni . . . . . . . . . . . . . . . . . . . . 20

1
4.3.1 Creazione nuova directory top level e repo iniziali . . . 20
4.3.2 Alice crea un le con tre commit e fa un push . . . . . 21
4.3.3 Bob "scarica" i commit di Alice . . . . . . . . . . . . . 22
4.3.4 Alice fa un branch, un merge con rebase e un push . . 23

5 Workow 24
6 github 25
7 TODO Riassunto dei comandi 25
7.1 init . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
7.2 add . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
7.3 commit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
7.4 status . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
7.5 log . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
7.6 checkout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
7.7 reset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
7.8 revert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
7.9 branch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
7.10 merge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
7.11 rebase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
7.12 fetch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
7.13 pull . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
7.14 push . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
7.15 clean . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
7.16 reog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

8 TODO Varie 30
8.1 .gitignore . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

1 Introduzione
Questo documento è una introduzione all'uso di git (http://www.git-scm),
un sistema di controllo di versione distribuito (distributed version control
system ), scritto da Linus Torvalds (l'autore di Linux, sempre lui) nel 2005 e
distribuito con una licenza "libera" (GPL 2.0).

2 Sistemi di controllo di versione o di gestione del


software
I sistemi di controllo di versione (version control systems ) servono a "gestire"
la storia di un insieme di le, ossia il cambiamento del loro contenuto nel
tempo. Spesso vengono chiamati chiamati sistemi di controllo di revisione

2
(revision control systems ) o di gestione del codice sorgente (source code man-
agement ). In questo documento li utilizzerò sempre il termine controllo di
versione e l'acronimo vcs. Sono utilizzati soprattutto per i le di testo ed in
particolare la gestione dello sviluppo del codice sorgente di un software (o
della sua documentazione o delle sue speciche o di qualsiasi altro documento
di testo), codice che in generale è formato da un insieme di le:

1. che cambia nel tempo, perché lo sto ancora scrivendo, o perché lo sto
correggendo, perché ho dei ripensamenti etc.;
2. di cui esistono "contemporaneamente" più "versioni": perché lo scrivo
per piattaforme diverse o per clienti diversi, o perché ho diverse com-
binazioni di funzionalità, perché ho una versione "di sviluppo", una di
testing e quella "uciale", perché ho quella gratuita di demo e quella
a pagamento etc.;
3. che viene creato e modicato da più persone che lavorano contempo-
raneamente sugli stessi le da posti diversi, su macchine diverse, in
momenti diversi, i cui "contributi" si devono integrare tra loro.

Lo scopo principale dei sistemi di controllo di revisione è "salvare" delle


istantanee di un progetto per permettere in qualsiasi momento di "tornare
indietro nel tempo" recuperando una versione anteriore. Ciò permette anche
di confrontare più versioni analizzandone le dierenze. Oltre al contenuto
dei le, i sistemi di controllo di revisione mantengono anche un insieme di
metadati (autore e data della modica, etichette varie, commenti aggiunti
nel momento del salvataggio, etc.) che arricchiscono la "storia" e permettono
di ritrovare una versione in base a vari criteri di "ricerca".

3 Concetti e funzionalità di base


3.1 Lavorare senza vcs
Supponiamo che io stia scrivendo un programma il cui codice sorgente, per
semplicità, è contenuto in un singolo le a.py (perché ovviamente sto pro-
grammando in Python!).

3.1.1 Singolo sviluppatore, singolo le, singola "linea di sviluppo"


Io inizio a scrivere il programma e ogni tanto salvo. Ovviamente ogni sal-
vataggio sovrascrive il le e non rimane traccia del contenuto delle versioni
precedenti, e questo è un primo problema. Succederà infatti sicuramente che
ad un certo momento il programma non funziona più, nel senso che prima
delle ultime modiche funzionava ma adesso non più.
Sarebbe bello poter "tornare indietro nel tempo", ossia annullare le ul-
time modiche. Peccato che quasi tutti i programmi (editor ) non permettono

3
di annullare le modiche dopo un salvataggio (anche se è proprio dopo un sal-
vataggio che sarebbe più utile poterlo fare!); e poi magari non sono nemmeno
più nella stessa "sessione" di lavoro (sono già "uscito" dall'editor ).
È quindi importante poter memorizzare più versioni di uno stesso le.
Una possibilità è salvare copie del le con nomi diversi, per esempio a-0.py,
a-1.py, a-2.py etc. (alcuni editor permettono di fare questi salvataggi in
modo "automatico"). Così però la mia directory di lavoro diventa disordi-
nata. Allora potrei mettere le versioni precedenti in una directory apposta,
chiamata per esempio old. In questo modo potrei sempre "tornare indietro",
almeno per le versioni che ho salvato in old.
Spesso però i problemi si evidenziano molto tempo dopo l'introduzione
degli "errori" (soprattutto se sto lavorando senza una suite di test auto-
matici, ma questa è un'altra storia). Ma allora come faccio a sapere a quale
versione voglio "tornare"? Non ho nessuna informazione sulle "dierenze"
tra le varie versioni. Questo problema si potrebbe gestire mantenendo un
change log : un le in cui, ogni volta che salvo una nuova versione in old,
scrivo una frase di commento che indica quantomeno il motivo delle modi-
che.
Un'altra attività utile è poter "calcolare" e mostrare le dierenze tra
due le, quindi anche tra versioni dello "stesso" le. In questo modo posso
scorrere le dierenze tra le coppie di versioni "successive" e cercare la mod-
ica "colpevole" del malfunzionamento. Questo attività di diff è fonda-
mentale e vi sono centinaia di programmi in grado di farlo in modo più o
meno intelligente e comodo (magari integrandosi direttamente con l'editor
che state usando). È evidente però che per fare un diff devo disporre delle
due "versioni".
Come già accennato, la gestione delle varie versioni può essere migliorata
se ad ogni le posso associare ulteriori informazioni (dei metadati ) ad esem-
pio un'etichetta (tag ) che indica la versione del programma (non quella del
singolo le!) o delle chiavi (keyword ) o dei commenti e se poi ci sono degli
strumenti per gestire questi metadati (ad esempio per fare delle ricerche).
Dovrebbe essere chiaro a questo punto che ciò che serve è un software dedi-
cato, ossia un sistema di controllo di revisione.

3.1.2 Singolo sviluppatore, multipli le


Nella realtà un prodotto software non deriva mai da un singolo le, per
cui devo gestire più le (spesso decine o centinaia). Ciò può sembrare
banale: basta gestire le varie versioni dei singoli le. Il problema sta
nell'interdipendenza dei vari le, e quindi delle loro versioni. Ciò che devo ge-
stire sono quindi delle "fotograe" dello stato di tutti i le che costituiscono
il mio "progetto".

4
3.1.3 Singolo sviluppatore, multiple "linee di sviluppo"
Un'altra complicazione deriva dal fatto che di uno stesso programma vi sono
normalmente più "linee di sviluppo" perché:

1. sto sviluppando varie versioni del programma per vari sistemi operativi
e mentre alcuni parti sono comuni, altre esistono in versioni speciche
per ciascun sistemi operativi;

2. ho una versione demo e una "completa";

3. sto modicando il programma in più punti per motivi diversi (nuove


funzionalità, miglioramento del codice, correzioni di errori, etc.) e
voglio tenere separate queste modiche per potervi lavorare indipen-
dentemente.

3.2 Il repository
Anche senza sapere quasi nulla su un vcs, si può immaginare che per man-
tenere tutta l'informazione necessaria a fornire le funzionalità richieste,
questo dovrà gestire un qualche tipo di "contenitore" per i dati gestiti (i
le del codice sorgente) e per i relativi metadati (chi ha fatto cosa quando
e perché). Questo contenitore viene chiamato repository (repo per brevità).
Se il tutto viene usato da un solo utente, si può pensare ad un unico repo,
posto magari "in locale" sulla macchina "di sviluppo". Se però vi sono varie
persone che devono collaborare allora si deve scegliere normalmente tra due
possibilità:

1. avere un unico repo centrale, condiviso da tutti, posto in un qualche


server ed accessibile a tutti via rete,
2. avere un repo per ciascuna persona più un meccanismo per sincroniz-
zare i vari repo (magari scegliendone uno come "principale") o almeno
un meccanismo per inviare le "dierenze" (patch ) da un utente all'altro
e poi integrarle.

Nel primo caso sono però vincolato ad avere accesso continuo al repository
ed inoltre "inviando" le mie modiche potrei sovrascrivere le modiche di
altri o comunque far sì che il progetto sia in uno stato non corretto. Nel
secondo caso può essere dicile conciliare le dierenze, specialmente se è
"passato molto tempo" dall'ultima "sincronizzazione".
Una delle caratteristiche di git è proprio quella di far sì che ogni utente
abbia, nel proprio repo personale, una copia completa di tutto il software
il che permette di:

1. lavorare anche o-line,

5
2. non essere vincolato al "ritmo di lavoro" degli altri sviluppatori,

3. non dipendere dalla "lentezza" della rete,

4. avere multiple copie del software (ridondanza),

5. etc.

Detto questo vediamo quali sono le funzionalità principali di un vcs.

3.2.1 Riassunto da "comprendere git concettualmente"


Il repository contiene, tra le altre cose:

1. un insieme di oggetti commit,


2. un insieme di riferimenti, chiamati instestazioni o head, agli oggetti
commit.

Un oggetto commit contiene, tra le altre cose:


1. un insieme di le che rispecchia lo stato del "progetto" ad un certo
punto della sua storia;

2. uno o più riferimenti ad oggetti commit "genitori" (parent ), che lo


precedono nella sua storia;

3. un nome sha1 che è il "condensato" (l'impronta digitale) del commit,


è una stringa di 40 caratteri che è universalmente univoca nel senso
che due commit hanno lo stesso nome se e solo se sono identici.

Per fare riferimento ad un commit posso sempre usare il suo nome, che
però non è molto facile da ricordare o da scrivere (anche se in generale è
suciente fornire i primi sette/otto caratteri) e non è signicativo (per gli
umani). git permette quindi di denire ed utilizzare delle "etichette" (o
intestazioni). Le più importanti sono:

1. master: è il nome di default del branch ("ramo di sviluppo") corrente;


2. HEAD: è l'etichetta associata alla "versione" corrente, tipicamente la più
recente del branch corrente.

6
3.3 Traccia della storia dei le
La funzionalità più ovvia di un vcs è mantenere la storia dei le (separata-
mente per ogni le oppure, come in git, considerando tutta un albero di
directory come un unico "oggetto") permettendo di "salvare" in ogni mo-
mento la versione corrente e poi proseguire nelle modiche, sapendo di poter
recuperare in ogni momento una qualsiasi delle "versioni" precedenti.
L'azione di "salvare" la versione corrente, prendendola dalla directory
di lavoro (working directory ) ed inserendola quindi nel repo è detta nor-
malmente commit (in altri sistemi anche check-in), quella di estrarre una
versione dal repo nella directory di lavoro è chiamata di solito check-out.
In molti sistemi, le varie versioni si distinguono per un qualche numero
progressivo (tipicamente automatico) o per delle tag (etichette) specicate
dall'utente. git ha un approccio diverso: ogni versione è identicata da una
stringa di 40 caratteri che è un hash del commit (per cui sono anche sicuro
dell'integrità dei dati quando li estraggo). git permette anche di rmare il
commit con una chiave crittografata, per poter anche sapere con sicurezza
chi ha fatto il commit.

3.4 Metadati sui le


Oltre a gestire il contenuto vero e proprio dei le, è anche importante poter
gestire dei metadati, ossia poter sapere, per ogni modica (versione) chi l'ha
eettuata, quando e perché.

3.5 branch e merge


I le sorgente non hanno però solo una storia "lineare". Spesso serve avere
varie versioni "parallele", per esempio quando si stanno facendo delle mod-
iche per ragioni diverse come l'implementazione di una nuova funzionalità
(feature ) e la correzione di un difetto (bug xing ). In questo caso si parte
magari da una stessa versione ma poi si creano delle "ramicazioni" (branch )
che ad un certo punto dovranno "ricongiungersi" (merge ), integrando le
dierenze, nella versione di partenza o magari in una più recente. Con-
cettualmente il branch è facile, potrebbe consistere in una semplice "copia del
tutto", ma si può facilmente intuire che il merge può essere molto complicato
perché è come "incollare" modiche diverse (che spesso si sovrappongono)
su uno stesso oggetto.

3.6 repo "personali": lavoro tranquillo ed isolato


Uno dei vantaggi, spesso sottovalutato perché "non tecnico", di gestire il
proprio lavoro con un vcs è la tranquillità che dà il fatto di sapere di poter
recuperare in ogni momento una versione precedente (e si spera funzionante
e corretta). Ciò permette di proseguire per "piccoli passi" che costituiscono

7
dei "sicuri progressi" che non c'è pericolo di perdere. Questa tranquillità si
trasforma poi anche in una maggiore velocità di lavoro!
Un eetto simile si ottiene anche usando dei sistemi automatici di "con-
trollo di corretto funzionamento" (testing ); che tra l'altro danno il meglio di
sé se usati in modo complementare ad un vcs.

3.7 Stati e usso dei le


Ogni le della working directory può trovarsi in vari stati:

1. untracked : il le non è "controllato" (tracked ) da git, che però con-


tinuerà a "nominarlo" in vari comandi (per ricordarci della sua pre-
senza "sospetta") a meno di non aver indicato esplicitamente di "igno-
ralo" (includendone il nome o un pattern nella lista contenuta nel le
.gitignore).
Il comando add copia il le, così com'è ora, nell'area di stage, che con-
tiene le versioni che saranno poi spostate nel repository al prossimo
commit (e che risultano quindi "tracked"). Il comando remove perme-
tte di rimuovere il le dall'area di stage (senza per questo alterare il
le in alcun modo).

2. modied : il le è gia "controllato" da git perché è già eseguito il


comando add almeno una volta, ma il suo contenuto corrente è diverso
da quello presente nella cache. La prossima azione sarà probabilmente
un add in attesa di un commit.

3. staged : il contenuto corrente del le è uguale a quello della cache e la


prossima azione sarà probabilmente un commit.

4 Esempi d'uso
4.1 Primo
Vediamo ora un esempio d'uso di git attraverso le varie fasi di sviluppo di
un semplice programma Python:

1. creazione delle directory di lavoro;

2. inizializzazione del repo ;


3. creazione di una prima versione del programma;

4. controllo della situazione

5. prima registrazione e commit ;


6. modica, di e nuovi add e commit ;

8
7. aggiunta di una tag ;
8. modica, ripensamento e ritorno alla versione precedente;

9. branch per sviluppo di una nuova feature;


10. modica sul trunk (XXX nome in git???)
11. merge del branch precedente;

4.1.1 Creazione della directory di lavoro


mkdir -p exa
cd exa
ls -la

4.1.2 Inizializzazione del repo


git init

Initialized empty Git repository in /home/manini/scuola/topics/git/exa/.git/

ls -a
.
..
.git

4.1.3 Creazione della prima versione del programma


Con l'editor che preferisco creo un le dati di prova e una prima versione del
programma:

cat exa/data.txt

zot 9
foo 123
bar 666
foo 40
foo 100

cat exa/account.py

# account.py
f = open("data.txt")
for r in f:
print r

9
4.1.4 Controllo della situazione
Vediamo ora qual'è la situazione 'secondo git', usando il comando status.
git status .

On branch master

Initial commit

Untracked files:
(use "git add <file>..." to include in what will be committed)

account.py
data.txt

nothing added to commit but untracked files present (use "git add" to track)

L'output di status indica che siamo sul branch (ramo) master, ossia
il ramo principale, nel commit iniziale (quello della creazione del repo ) e
che i due le che ho creato sono untracked ossia non gestiti da git. Inne
suggerisce di usare il comando add per aggiungerli all'area di stage ossia
all'insieme dei le pronti per il successivo commit. Siccome entrambi i le
sono in uno stato per me "corretto" (o di cui comunque voglio mantenere
memoria), seguo il consiglio.
Notare quindi che git ignora in linea di principio i le che non sono stati
esplicitamente aggiunti (con add) al repository. Ciò è sensato in quanto nella
directory di lavoro, ci sono sempre dei le "temporanei" o "non importanti"
di cui non si vuole tenere traccia. Questi le continuano però ad apparire
nell'output di status e di altri comandi. Se si desidera specicare esplicita-
mente quali le (e directory, anche ricorsivamente) si vogliono ignorare lo si
può fare creando un le 8.1. .gitignore

4.1.5 Prima registrazione e commit


git add data.txt account.py

On branch master

Initial commit

Changes to be committed:
(use "git rm --cached <file>..." to unstage)

new file: account.py


new file: data.txt

10
Ora eseguo il commit e ricontrollo con status.
git commit account.py data.txt -m "Esempio di accounting. Versione iniziale"

[master (root-commit) 5ea6761]


Esempio di accounting. Versione iniziale

2 files changed, 11 insertions(+)


create mode 100644 account.py
create mode 100644 data.txt

git status .

On branch master
nothing to commit, working directory clean

Vediamo adesso se git ha già qualche informazione sulla "storia" del


repository.

git log .

commit 5ea67613014666d9285107dda1796fcd86e371a0
Author: Luca Manini <manini.luca@tiscali.it>
Date: Mon Nov 10 14:36:45 2014 +0100

Esempio di accounting. Versione iniziale

Il codice di 40 caratteri che compare nella prima linea è l'identicativo


univoco del commit appena eettuato. È calcolato come hash di tutta
l'informazione relativa al commit, il che vuol dire che non solo è "univer-
salmente univoco", ma anche che funziona come "sicurezza" sull'integrità
del commit stesso (vedi oltre).
Ovviamente una stringa di 40 caratteri non è molto comoda da ricordare,
da scrivere e da dire a qualcuno, ma per fortuna git permette anche di usarne
solo la parte iniziale. Inoltre è anche possibile assegnare dei nomi (tag ) più
umani e signicativi (vedi oltre).

4.1.6 Modica, di e nuovi add e commit


Adesso faccio una modica al programma, che diventa:

# account.py
d = dict()
f = open("data.txt")
for r in f:
n,v = r.split()

11
if not n in d:
d[n] = 0
d[n] += int(v)
print (d)
Vediamo cosa ottendo da status e come posso vedere le dierenze tra
la versione corrente e l'ultima di cui ho fatto commit (la più recente nel
repository)>

git status .

On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified: account.py

no changes added to commit (use "git add" and/or "git commit -a")

git segnala giustamente che il le account.py è stato modicato


(rispetto all'ultimo commit) e che ho varie "possibilità":

1. usare add per aggiungerlo allo stage (in vista di un futuro commit );
2. fare un commit diretto usando l'opzione -a che esegue il commit non
dei le staged ma di tutti quelli modicati;

3. usare checkout per scartare le modiche nella directory di lavoro (come


vedremo più avanti).

Io non faccio nulla e invece uso il comando diff per vedere le dierenze
tra la versione corrente e quella del repository.

git diff account.py

diff --git a/account.py b/account.py


index 03ab70d..e52e5c9 100644
--- a/account.py
+++ b/account.py
@@ -3,3 +3,13 @@
f = open("data.txt")
for r in f:
print r
+
+# account.py

12
+d = dict()
+f = open("data.txt")
+for r in f:
+ n,v = r.split()
+ if not n in d:
+ d[n] = 0
+ d[n] += int(v)
+print (d)

Uhm! Se non si è abituati all'output di diff questa risposta non è


molto interessante, ma conferma il fatto che git è in grado di gestire le
dierenze! In realtà poi i diff si fanno sempre con strumenti più "comodi"
(che comunque si basano quasi sempre sull'output di git).
Ora faccio un nuovo add e vediamo cosa cambia.

git add account.py

On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

modified: account.py

Anche questa volta il le risulta modicato, ma l'opzione che mi viene


oerta è quella di usare reset per annullare la add (senza che ciò modichi
la versione della directory di lavoro).
In realtà la nuova versione mi pare vada bene (ci vedete qualche errore?)
e quindi faccio il commit.

git commit account.py -m "Aggiunto: calcolo dei totali in un dizionario."

[master c2aa528] Aggiunto: calcolo dei totali in un dizionario.


1 file changed 10 insertions(+)

La stringa di otto caratteri dopo la parola "master" è la parte iniziale del


nome del nuovo commit, come si può vericare con un log.

git log .

commit c2aa5289e4098eccf300e89f543bef29b77a8031
Author: Luca Manini <manini.luca@tiscali.it>
Date: Mon Nov 10 17:46:44 2014 +0100

Aggiunto: calcolo dei totali in un dizionario.

commit 5ea67613014666d9285107dda1796fcd86e371a0

13
Author: Luca Manini <manini.luca@tiscali.it>
Date: Mon Nov 10 14:36:45 2014 +0100

Esempio di accounting. Versione iniziale

4.1.7 Ritorno ad una versione precedente (solo per stampa)


Supponiamo che io voglia stampare la prima versione del mio programma.
Ciò richiede di "estrarre" nella directory corrente delle "copie" dei le così
com'erano in quel momento. Per fare ciò uso il comando checkout.

git checkout 5ea67

Note: checking out '5ea67'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

git checkout -b new_branch_name

HEAD is now at 5ea6761... Esempio di accounting. Versione iniziale

In eetti ora ho nella directory corrente la versione iniziale dei miei le,
ad esempio di account.py.

cat account.py

# account.py
f = open("data.txt")
for r in f:
print r

Adesso posso stamparla o fare ciò che mi pare e poi tornare ad usare la
"versione più recente" (master ) semplicemente con un altro checkout:

git checkout master

Previous HEAD position was 5ea6761... Esempio di accounting. Versione iniziale


Switched to branch 'master'

Notare che nel repository non rimane traccia di questa operazione, perché
non ha cambiato nulla nel repository stesso.

14
git log .

commit c2aa5289e4098eccf300e89f543bef29b77a8031
Author: Luca Manini <manini.luca@tiscali.it>
Date: Mon Nov 10 17:46:44 2014 +0100

Aggiunto: calcolo dei totali in un dizionario.

commit 5ea67613014666d9285107dda1796fcd86e371a0
Author: Luca Manini <manini.luca@tiscali.it>
Date: Mon Nov 10 14:36:45 2014 +0100

Esempio di accounting. Versione iniziale

4.1.8 Ritorno ad una versione precedente (correzione errori)


In realtà la prima versione aveva anche un difetto, leggeva le righe e le
stampava, ma la lettura mantiene gli "a capo", e print ne aggiunge uno
di suo! Forse è meglio correggere questo errore (anche se non inuenza le
versioni successive che non hanno più la print).

git checkout 5ea67 -b fix-spurious-newline

Switched to a new branch 'fix-spurious-newline'

git status .

On branch fix-spurious-newline
nothing to commit, working directory clean

git commit account.py -m "Fixed: spurious newline" [x-spurious-


newline 629024e] Fixed: spurious newline 1 le changed, 1 insertion(+),
2 deletions(-)
git merge x-spurious-newline Auto-merging account.py CONFLICT
(content): Merge conict in account.py Automatic merge failed; x conicts
and then commit the result.

# account.py
f = open("data.txt")
for r in f:
<<<<<<< HEAD
print r

# account.py
d = dict()

15
f = open("data.txt")
for r in f:
n,v = r.split()
if not n in d:
d[n] = 0
d[n] += int(v)
print (d)
=======
print r[:-1]
>>>>>>> fix-spurious-newline

rimetto a posto .....

git status .

On branch master
You have unmerged paths.
(fix conflicts and run "git commit")

Unmerged paths:
(use "git add <file>..." to mark resolution)

both modified: account.py

no changes added to commit (use "git add" and/or "git commit -a")

git add account.py


git commit -m "Conflict in merge solved (by hand)."

[master d73054a] Conflict in merge solved (by hand).

4.1.9 Aggiunta di una feature (su master) e x di un baco (con


merge)
Ora decido di salvare su le i totali. Quindi modico il le come segue e
faccio un commit.

git add account.py


git commit account.py -m "Added: write totals to file."

[master 92b7520] Added: write totals to file.


1 file changed, 5 insertions(+), 1 deletion(-)

Ora però, meglio tardi che mai, mi accorgo che il programma non funziona
"bene" in quanto non gestisce righe vuote (e nemmeno righe con un numero
di stringhe diverso da due e nemmeno il caso in cui la seconda non sia un

16
intero...). Quindi decido di fare un branch per il x. Vediamo se questa volta
sono più fortunato.
Posso fare il x sulla versione corrente, ma il baco è già presente nella sec-
onda versione (come posso scoprire facendo dei checkout o dei di o usando
git grep), quindi faccio un branch "basato" su quella.

git checkout c2aa5289e4098eccf300e89f543bef29b77a8031 -b fix-split-error

Switched to a new branch 'fix-split-error'

La versione corretta ora è questa:

# account.py
d = dict()
f = open("data.txt")
for r in f:
try:
n,v = r.split()
except ValueError:
continue
if not n in d:
d[n] = 0
d[n] += int(v)
print (d)
Ora add, commit, checkout master, merge...

git add account.py


git commit -m "Fixed: split error."

[fix-split-error 45fc908] Fixed: split error.


1 file changed, 4 insertions(+), 7 deletions(-)

git merge fix-split-error -m "Merge branch 'fix-split-error'"

Auto-merging account.py
Merge made by the 'recursive' strategy.
account.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)

git log .

commit 8161b2483c1182c2f3332d051808fb8ee7a0b18b
Merge: 92b7520 45fc908
Author: Luca Manini <manini.luca@tiscali.it>
Date: Mon Nov 10 19:48:01 2014 +0100

17
Merge branch 'fix-split-error'

commit 45fc908179df3ad79f6c93de17458a3ae9e0f963
Author: Luca Manini <manini.luca@tiscali.it>
Date: Mon Nov 10 19:46:20 2014 +0100

Fixed: split error.


................

OK. È andato tutto liscio, evidentemente le dierenze tra le versioni


riguardavano "sezioni" diverse del le ed il/merge/ automatico è andato a
buon ne. La cosa "interessante" è che ora la versione più recente (la cui
ultima modica era la scrittura su le dei totali) include anche il x di split!

cat account.py

# account.py
d = dict()
f = open("data.txt")
for r in f:
try:
n,v = r.split()
except ValueError:
continue
if not n in d:
d[n] = 0
d[n] += int(v)
f.close()
o = open("total.txt", "w")
for k,v in d.items():
o.write("%s -> %d\n" % (k, v))
o.close()

4.2 Secondo (semplicato)


Altro percorso, di cui dò solo la traccia:

mkdir foo # creo una directory per il progetto


cd foo # mi sposto nella nuova directory
git init # creo il repository
echo AAA > a.txt # creo un nuovo file a.txt
git status # git trova il file "untracked"
git add a.txt # aggiunto il file alla "cache"
git status # git trova il file "new"

18
git commit . -m "New file a.txt"
git status # git trova la directory "clean"
git log --oneline # una riga dell'unico commit

echo AAA > b.txt # creo un nuovo file b.txt


git add b.txt # aggiunto il file alla "cache"
git commit . -m "New file b.txt"
git log --oneline # due righe di commit

git branch f-1 # creo nuovo branch (etichetta)


git branch # due branch (master corrente con *)
git checkout f-1 # adesso f-1 è il branch corrente
git branch # due branch (f-1 corrente con *)

echo BBB > c.txt # nuovo file


git add c.txt # metto nella cache
git commit . -m "New file c.txt"
git log --oneline # tre righe di commit

git checkout master # torno al branch originale


ls # c.txt non c'è più
git log --oneline # solo i primi due commit

git checkout -b f-2 # nuovo branch f-2


echo CCC > c.txt # nuovo file ...
git add c.txt # add ...
git commit . -m "New file c.txt"

git checkout master


git merge f-2 # fast forward
git log --oneline

git checkout -b f-3


echo DDD > d.txt
git add d.txt
git commit . -m "New file d.txt"
git log --oneline

git checkout master


echo EEE > e.txt
git add e.txt
git commit . -m "New file e.txt"
git log --oneline

19
git merge f-3 -m "Merging f-3 into master"
git log --oneline

git checkout master


git checkout -b f-4
echo FFF > f.txt
git add f.txt
git commit . -m "New file f.txt"

git checkout master


git checkout -b f-5
echo GGG > g.txt
git add g.txt
git commit . -m "New file g.txt"

git checkout master


git merge f-4 f-5 -m "Merging f-4 AND f-5 into master"
git log --oneline

4.3 Repo remoto con due cloni


In questo esempio "simulo" il lavoro di due persone, ciascuna con la propria
working directory, che si utilizzano un repo remoto comune.

4.3.1 Creazione nuova directory top level e repo iniziali


rm -rf exa/fake-remote
mkdir -p exa

mkdir -p fake-remote
cd fake-remote

Creo un repo "nudo" e poi lo clono due volte

git init --bare repo


git clone repo alice
git clone repo bob

Initialized empty Git repository in /home/manini/scuola/topics/git/exa/fake-remote/re


Cloning into 'alice'...
warning: You appear to have cloned an empty repository.
done.
Cloning into 'bob'...
warning: You appear to have cloned an empty repository.
done.

20
4.3.2 Alice crea un le con tre commit e fa un push
Conguro nome e email di Alice (localmente in questo repo), in modo che
i suoi commit si distinguano da quelli di Bob anche se, dal punto di vista
dell'OS, sono eseguiti dallo stesso utente. Sarebbe forse meglio avere due
utenti separati ma ciò complicherebbe la scrittura di queste note.

git config user.name Alice


git config user.email alice@wonderland.net

Alice crea un singolo le di tre righe, aggiungendo una riga alla volta e
facendo sempre add e commit. Alla ne fa un push che, senza argomenti fa
il push del branch corrente verso il remote corrente.

echo 1 > a.txt


git status

On branch master

Initial commit

Untracked files:
(use "git add <file>..." to include in what will be committed)

a.txt

nothing added to commit but untracked files present (use "git add" to track)

git add a.txt


git status

On branch master

Initial commit

Changes to be committed:
(use "git rm --cached <file>..." to unstage)

new file: a.txt

git commit a.txt -m "Create a.txt"


git status

[master (root-commit) c2fbe2e] Create a.txt


1 file changed, 1 insertion(+)

21
create mode 100644 a.txt
On branch master
Your branch is based on 'origin/master', but the upstream is gone.
(use "git branch --unset-upstream" to fixup)
nothing to commit, working directory clean

echo 2 >> a.txt; git add a.txt; git commit a.txt -m "Add line 2."
echo 3 >> a.txt; git add a.txt; git commit a.txt -m "Add line 3."

git log --oneline

d096fa0 Add line 3.


7818e84 Add line 2.
c2fbe2e Create a.txt

git push

To /home/manini/scuola/topics/git/exa/fake-remote/repo
* [new branch] master -> master

4.3.3 Bob "scarica" i commit di Alice


Bob congura il proprio repo che è ovviamente vuoto e poi scarica, con
fetch, i commit di Alice, guarda un po' cosa riesce a sapere del remote e
poi fa un merge.

git config user.name Bob


git config user.email bob@home.net

git status

On branch master

Initial commit

nothing to commit (create/copy files and use "git add" to track)

git fetch

From /home/manini/scuola/topics/git/exa/fake-remote/repo
* [new branch] master -> origin/master

git remote

origin

22
git branch -r

origin/master

git log origin/master

commit d096fa0a1b556748b7050508a2f6b2237280f320
Author: Alice <alice@wonderland.net>
Date: Fri Mar 4 17:51:39 2016 +0100

Add line 3.

commit 7818e84ceea04f761c8979f2c1f1bafc49dedaf8
Author: Alice <alice@wonderland.net>
Date: Fri Mar 4 17:51:39 2016 +0100

Add line 2.

commit c2fbe2eaf15c4249169aeb61edf7ca80534636d1
Author: Alice <alice@wonderland.net>
Date: Fri Mar 4 17:51:39 2016 +0100

Create a.txt

git merge

4.3.4 Alice fa un branch, un merge con rebase e un push


git checkout -b feature

Switched to a new branch 'feature'

echo 4 >> a.txt; git add a.txt; git commit a.txt -m "Add 4 to a.txt"
echo 5 >> a.txt; git add a.txt; git commit a.txt -m "Add 5 to a.txt"
echo 6 >> a.txt; git add a.txt; git commit a.txt -m "Add 6 to a.txt"

[feature e243742] Add 4 to a.txt


1 file changed, 1 insertion(+)
[feature 72507ed] Add 5 to a.txt
1 file changed, 1 insertion(+)
[feature c24c367] Add 6 to a.txt
1 file changed, 1 insertion(+)

git log --oneline

23
c24c367 Add 6 to a.txt
72507ed Add 5 to a.txt
e243742 Add 4 to a.txt
d096fa0 Add line 3.
7818e84 Add line 2.
c2fbe2e Create a.txt

5 Workow
git ore le funzionalità di base per il controllo del codice, ma non impone
nessun workow (usso, metodo, protocollo di lavoro). Una possibile orga-
nizzazione si basa sull'esistenza contemporanea di vari "tipi" di branch.

1. Un singolo branch chiamato develop su cui si svolge la maggior parte


dello sviluppo, è il punto di riferimento per gli sviluppatori che però
lavorano su branch diversi...;
2. Vari branch detti feature branch (e quindi chiamati di solito feature-
xxx ) dedicati ciascuno allo sviluppo di una particolare funzionalità,
questi branch nascono (con "checkout -b") dalla versione "corrente" di
develop e poi si ricongiungono (con merge ") sempre con develop (che
nel frattempo sarà "andata avanti" grazie ai merge degli altri feature
branch.
3. Un singolo branch principale chiamato master (come da default git)
che dovrebbe contenere una versione sempre "funzionante" e quindi
sempre "pronta per il rilascio". master dovrebbe "prendere" ovvia-
mente da develop, ma questo è spesso troppo "attivo" per chi si deve
dedicare al "testing", per cui si crea...
4. Uno o più branch detti di release che nascono da develop e che non
accettano più merge (e relativi bachi!) dai feature branch, ma solo bug-
x, che di solito vengono subito integrati in develop. I branch sono più
di uno se si decide (come mi pare serio fare) di supportare non solo
l'ultima release ma anche le precedenti. In ogni caso le modiche per
correggere i bachi si svolgono in...
5. Tanti branch chiamati x-xxx, di solito di breve durata (si spera) che
di solito partono da release e poi ci ritornano, andando spesso anche
direttamente in develop e se sono "gravi", magari anche direttamente
in master (ed essere inviati come "update" ai clienti).

Come al solito, nella scelta e nella gestione del workow si deve trovare un
equilibrio tra la essibilità e complessità. Il workow è poi sicuramente
inuenzato dalla metodologia di sviluppo e di testing (ma questa è un'altra
storia).

24
6 github
Ho un account su github (prof-manini prof.manini.59@gmail.com switched
site me number).
Ho creato un progetto hello-world (come da tutorial), ho aggiunto un
le, fatto un branch, commit etc tutto dall'interfaccia web. Poi ho fatto un
clone locale, fatto delle modiche e poi ho cercato di fare un push. Non sono
riuscito e quindi sono passato a ssh.
Sul portatile di casa ho creato una chiave ssh (ss-genkey) con passphrase
(prof where number) e ho aggiunto la chiave all'account github.
Come suggerito su stackoverow, ho cambiato l'URL del remote con:

git remote set-url origin git@github.com:prof-manini/hello-world.git


Adesso posso fare push direttamente!

7 TODO Riassunto dei comandi


7.1 init
Create an empty Git repository or reinitialize an existing one
init inizializza (crea) un nuovo repository (una directory .git) nella
directory corrente. Se si fornisce anche un argomento, questo viene usato
come nome per creare una nuova directory in cui creare il repo. git init
foo; cd foo è quindi equivalente ad eseguire git init in una directory foo
vuota.

7.2 add
Add le contents to the index
add aggiunge uno o più le all'area di stage, dove risiedono i le che
saranno interessati dal prossimo commit. È importante notare che non
viene semplicemente aggiunto "il nome" del le, ma viene anche registrato
il suo "contenuto" corrente; e quindi se dopo una add il le viene modicato
e si desidera che queste modiche vengono inserite nel commit è necessario
ripetere il comando add !.
add può essere invocato in molti modi, passando come argomenti le o
directory ed ha anche molte opzioni. Ricordarsi che si può "sperimentare"
tranquillamente perché è sempre possibile rimuovere le dalla staging area
con il comando git rm --cached (XXX vero?).
È abbastanza frequente usare il comando git add . che aggiunge, ri-
corsivamente, tutti i le della directory corrente (anche nuovi).

25
7.3 commit
Record changes to the repository
commit "salva" lo stato corrente dell'area di stage nel repository. I con-
tenuti soggetti al commit sono specicati da:

1. ciò che è stato aggiunto allo stage con add ;


2. ciò che è stato rimosso dallo stage con rm ;
3. ciò che viene specicato esplicitamente nella linea di comando del com-
mit ;
4. se si usa l'opzione -a, tutti i le modicati (e non ignorati) presenti
nella directory di lavoro;

5. eventuali scelte interattive se si usa l'opzione --interactive= o


--patch.

Importante:
1. se subito dopo aver fatto un commit "ci si pente", si può cancellare
l'operazione con un reset;

2. se si vuole solo "vedere cosa succederebbe se..." si può usare l'opzione


--dry-run.

Opzioni utili:

1. amend: permette di cambiare il commit, per esempio cambiando il


messaggio di commento (se abbiamo sbagliato a scrivere) o aggiun-
gendo un le (che ci siamo dimenticati e per il quale non vogliamo fare
un commit separato). Uso:
git commit amend -m "Nuovo messaggio corretto" git commit amend
foo.txt
Ricordare che git deve comunque generare un nuovo commit (perché
è cambiato il contenuto e quindi anche l'hash) ma lo sostituisce al
precedente. Così ho però cammbiato la storia del mio repo cosa che
non devo fare se ho già fatto un push!

7.4 status
Show the working tree status
status mostra lo stato della working directory, elencando i le che:

1. sono diversi tra stage e working tree (probabili soggetti per un succes-
sivo add);

26
2. sono diversi tra stage e il commit "corrente" (probabili soggetti per un
successivo commit);

3. sono presenti nel working tree ma non nello stage (probabili soggetti
per un successivo add o rm o inclusione in .ignore).

7.5 log
Show commit logs
log mostra il log dei commit. Ha un sacco di opzioni per limitare l'output
e per scegliere il relativo formato.
La forma più frequente è sicuramente git log --oneline che mostra
una singola riga per ciascun commit con il nome del commit e la prima riga
del messaggio di commit.
Un'altra forma frequente è git log --oneline --branch --graph che
mostra anche i vari branch in forma di "grafo" (ASCII art).
Oppure git log --stat che mostra anche la lista dei le "cambiati" con
il conteggio delle righe cambiate.

7.6 checkout
Switch branches or restore working tree les
Il comando checkout può essere eseguito passando come argomento un
branch, un commit o una tag.
Il caso più normale è il branch ed ha due eetti "separati". Prima di
tutto la directory corrente viene modicata in modo che il suo contenuto
corrisponda alla situazione "registrata" nel commit. Inoltre il branch speci-
cato diventa il branch corrente. Si usa spesso nella forma git branch -b
<branch>, equivalente a git branch <branch>; git checkout <branch>.
Il checkout di un commit è pensato solo per ottenere i le per, ad esempio,
stamparli o spedirli a qualcuno, non per continuare a lavorarci. Infatti lascia
il working tree in uno stato un po' "pericoloso" detto detached head perché
associato ad un commit senza nome (anche se poi lo si può aggiungere più
tardi con un branch, ma è un po'un casino).

7.7 reset
Reset current HEAD to the specied state
reset permette di "ritornare" ad uno stato specico, denito da un com-
mit. È utile, per esempio, se per sbagli abbiamo fatto un commit nel branch
sbagliato (per esempio master), perché ci siamo dimenticati di fare un check-
out per cambiare branch (per esempio a x).
reset ha tre "livelli" di reset che posso scegliere da opzione. Tutti e tre
portano HEAD allo stesso commit (precedente) ma si dierenziano per lo

27
stato della working directory e della cache. Io in terpreto le dierenze come
misura di quanto "indietro" vado nella storia.

1. soft: i le di cui ho fatto commit vengono "tirati indietro" solo no
alla cache, quindi in un certo senso ho fatto l'undo solo del commit;

2. mixed (il default): i le vengono "tirati indietro" no alla working
directory, quindi ho fatto l'undo anche dell'add ;
3. hard: in questo caso tutti i le tracked vengono ripristinati allo stato
che avevano prima del commit (quindi perdo denitivamente tutte le
modiche) mentre eventuali nuovi le di cui non avevo nemmeno fatto
add vengono lasciati in pace.

7.8 revert
Revert some existing commits
revert, un po' come reset, permette di "tornare indietro" dopo aver fatto
qualche "errore". reset però lo fa "modicando la storia" e quindi non è
proprio il caso di usarlo se ho già fatto dei push. revert invece crea dei nuovi
commit facendo delle modiche che "annullano" quelle fatte dai commit
"sbagliati". In questo modo la storia precedente rimane invariata e posso
fare tranquillamente push.

7.9 branch
List, create, or delete branches
Il comando branch senza parametri mostra la lista dei branch, segnando
con un asterisco il branch corrente. Un primo argomento indica un nuovo
branch ed un secondo argomento il branch "padre" (default quello corrente).
Ricordarsi che non rende il nuovo branch corrente (per questo serve un check-
out).
Con l'opzione -d cancella un branch.

7.10 merge
Join two or more development histories together
Il comando merge serve ad "incorporare" i commit di uno o più branch
in un unico, tipicamente nuovo, commit. La tipica sequenza git checkout
master; git merge feature serve ad incorporare nel branch master i com-
mit del branch feature. merge senza argomenti funziona solo se il branch
corrente ha un "remote branch" (tipico il caso in cui il repo locale sia stato
creato clonando un repo remoto).
Ci sono vari "tipi" di merge :

28
1. Il più semplice è il fast forward che si realizza quando il branch corrente
(es: master) è "la base" dell'altro (es: feature), per cui il merge è
banale: basta spostare l'etichetta master al commit a cui punta feature
per cui non viene creato un nuovo commit. Ciò accade quando dopo il
branch di feature non sono stati fatti commit su master.

2. Il più generale è il three way merge in cui c'è stata una eettiva bi-
forcazione ed entrambi i branch hanno dei commit propri a valle del
parent comune. In questo caso git prova a creare un nuovo commit
che integri le "modiche" di entrambi i branch combianando il commit
di biforcazione (il parent comune) e i due commit "di testa" dei due
branch (da cui il nome three way ). Se non vi riesce per ciascun le con
conitti crea una versione "combinata": l'utente dovrà poi "mettere a
posto" il contenuto dei le, e fare add e commit normalmente.

3. Il terzo caso interessante si ottiene con il comando rebase (vedi oltre).

7.11 rebase
Forward-port local commits to the updated upstream head
Il comando rebase permette di "trasformare" una situazione che richiederebbe
un merge three way in una in cui è suciente un fast forward. Supponiamo di
avere la solita biforcazione master feature. Se da feature eseguo git rebase,
git costruisce, a partire dai commit di feature a valle della biforcazione, una
serie di commit "equivalenti" che partono però dalla testa di master. In
questo modo un successivo commit è un fast forward !
Ciò ha il vantaggio di rendere di nuovo lineare la storia di master, ma
d'altra parte fa "dimenticare" che c'è stato un branch.

7.12 fetch
git-fetch - Download objects and refs from another repository
Il caso forse più frequente è quello in cui il repository corrente è stato
creato clonando un altro repository che a questo punto è un suo remote iden-
ticato dal riferimento origin/master. In questo caso il comando git fetch
scarica dal repo remoto i commit (e i relativi riferimenti) necessari ad "ag-
giornare" il repo locale, commit si possono eventualmente successivamente
integrare con un merge.

7.13 pull
Fetch from and integrate with another repository or a local branch
Un pull è equivalente a fetch seguito da merge.

29
7.14 push
Update remote refs along with associated objects
In breve ... è come fare pull dall'altro lato!

7.15 clean
Cancella dalla working directory tutti gli oggetti che non sono tracked. Con
l'opzione -d cancella (ricorsivamente) le directory, con l'opzione -f cancella
i le.
Con l'opzione -x cancella anche i le ignored, con l'opzione -X (maius-
cola) cancella solo i le ignored.

7.16 reog
8 TODO Varie
8.1 .gitignore

30

Potrebbero piacerti anche