Non-corso per non prendere a martellate il monitor quando usate Git.
git clone https://git.lattuga.net/alberanid/git-crash-course.git
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License: http://creativecommons.org/licenses/by-sa/4.0/
A chi si trova a volere/dovere usare Git in un piccolo team, ed è ancora alle prime armi.
Le basi per lavorare in locale e con repository remoti.
Un workflow per lo sviluppo collaborativo, da applicare senza porsi troppe domande.
Una serie di strumenti avanzati, per chi ci ha preso gusto e vuole approfondire lo strumento.
Un sistema di controllo versione distribuito.
Serve per tener traccia dei cambiamenti al proprio codice e per facilitare lo sviluppo condiviso. Va ricordato che Git è nato soprattutto per aiutare chi deve integrare il codice altrui, e non tanto per il singolo sviluppatore.
Il resto lo spiega meglio Wikipedia: https://it.wikipedia.org/wiki/Git%5F%28software%29
Git non avrà segreti per voi, una volta compreso...
Tutto vero, ma la sua user interface è un mezzo disastro.
Working directory: i file su cui state lavorando
Staging area (o Index): dove mettiamo da parte le modifiche che finiranno nel prossimo commit
Commit: snapshot dello stato in un certo momento
(fare) Checkout: aggiornare i file nella working directory ad un dato branch/commit/...
HEAD: il punto a cui sarà collegato il prossimo commit (di norma, il branch corrente)
$ git config --global user.name "Davide Alberani"
$ git config --global user.email da@erlug.linux.it
$ git config --global color.ui auto
In cui forniamo le basi per lavorare in locale e con repository remoti.
Creare un nuovo repository partendo da una directory (vuota o meno):
$ git init
Clonare un repository remoto esistente:
$ git clone https://git.lattuga.net/user/repo.git
È stata creata la directory .git (il repository); se abbiamo fatto un clone, è stato aggiunto il riferimento al remote "origin".
Vedere lo stato del sistema (usatelo spesso!):
$ git status [-s]
Untracked: nuovi file nella working directory, non ancora aggiunti
Unmodified: file che non sono cambiati dal commit precedente
Modified: modificati nella working area e non ancora aggiunti alla staging area
Staged: nella staging area, pronti per il prossimo commit
Modifichiamo un file ed aggiungiamolo alla staging area:
$ git add prova.txt
Committiamolo:
$ git commit [-m "messaggio di commit"]
Verifichiamo quanto accaduto:
$ git log
Abbiamo aggiunto un file alla staging area, per poi salvare uno snapshot del nostro lavoro. Se - come normalmente accade - siamo in un branch, questo punta al nuovo commit (HEAD continua a puntare al branch, e di conseguenza anch'essa al nuovo commit).
Sono uno snapshot dell'intero stato del sistema in un dato momento, identificati da un hash (e.g.: 6d7696a8b8
).
I commit hash sono generati partendo da: messaggio, committer, author, dates, tree, parent hash.
Un tag è un puntatore ad un commit:
$ git tag -a v1.0
$ git log [--stat] [--patch] [--graph] [--decorate] [--color] [-2]
Rappresenta la storia dei commit fino al punto corrente (o da/a qualsiasi punto indicato).
Si può limitare agli ultimi N commit con -N
Modifichiamo un file, senza aggiungerlo alla staging area:
$ git diff
Per vedere quanto è stato posto in staging area (e motivo per cui è utile usarla):
$ git diff --staged
Modificare l'ultimo commit (aggiungere un file, modificare il commit message o l'autore, ...):
$ git commit --amend [--author="Name Surname <user@example.com>"]
Un file stato aggiunto per sbaglio alla staging area:
$ git reset HEAD -- file
Riportare un file modificato nell'ultimo stato committato/staged:
$ git checkout -- file
Ho fatto un casino nella working directory. Riportiamo tutto allo stato dell'ultimo commit:
$ git reset --hard HEAD
Voglio creare un nuovo commit che annulla le modifiche introdotte da un commit precedente:
$ git revert [-n] <commit>
Sono puntatori mobili, spostati ad ogni nuovo commit.
Servono a separare diversi filoni di sviluppo e ad integrare i contributi di altri.
Creare un branch:
$ git branch fix/bug-123
Visualizzare tutti i branch:
$ git branch -a -v
Cancellare un branch locale:
$ git branch -d [--force] fix/bug-123
Spostarsi su un branch:
$ git checkout fix/bug-123
Creare e spostarsi in un singolo comando:
$ git checkout -b fix/bug-123
master è solamente un default (di norma si considera master "stabile")
refs: nome collettivo per riferirsi ad HEAD, branches, tags
dare nomi significativi; usate prefissi come bugfix/, fix/, improvement/, feature/, task/) e issue di riferimento
possono essere logicamente suddivise: feature (o topic), release, integration branches e così via
$ git branch -b fix/bug-123
$ # editiamo nuovofile.txt
$ git add nuovofile.txt
$ git commit
$ git checkout master
$ git merge fix/bug-123
fast-forward! master era più indietro rispetto a fix/bug-123, e quindi abbiamo semplicemente spostato il puntatore master.
Non è stato neppure creato un nuovo commit.
Il comando commit ha le opzioni --ff-only e --no-ff per decidere come comportarsi.
$ git branch -b fix/bug-123
$ # editiamo file.txt
$ git add file.txt
$ git commit
$ git checkout master
$ # editiamo file.txt in maniera differente, sulle stesse righe
$ git add file.txt
$ git commit
Mergiamo:
$ git merge fix/bug-123
$ # risolviamo i conflitti
$ git add file.txt
$ git commit
Cercare sempre tutti i markers <<<<<<<, =======, >>>>>>>
$ git remote add origin https://git.lattuga.net/user/repo.git
$ git remote -v
Aggiornare il repository locale con i dati di un remoto:
$ git fetch --all --prune --tags origin
Commit che divergono tra il master locale e quello remoto:
$ git log --left-right master...origin/master
Scaricare gli aggiornamenti dal remoto e mergiare il branch corrente:
$ git pull origin
local branch: un branch che avete solo in locale
remote branch: un branch che esiste su un repository remoto
remote tracking branch: la copia locale di un remote branch; aggiornabile con fetch, non è possibile lavorarci sopra direttamente
local tracking branch: un branch locale su cui è possibile lavorare direttamente, che traccia un altro branch (di norma, un remote tracking branch)
l'associazione tra branch remoti e locali viene effettuata in automatico, in base al nome del branch: se nel repository remoto esiste origin/branch-1, il comando git checkout branch-1 crea un local tracking branch che traccia il remote tracking branch origin/branch-1
Aggiungere al repository remoto un branch locale:
$ git push --set-upstream origin local-branch-name
Inviare i cambiamenti locali ad un branch remoto:
$ git push --tags [origin [master]]
Cosa da non fare MAI (salvo non ne siate davvero convinti): modificare una history che sia già stata pushata.
Questo perché se qualcuno sta lavorando sullo stesso branch remoto, le altre persone si troveranno con dei repository non coerenti.
In cui forniamo un workflow precotto per chi non vuole porsi troppe domande, adatto allo sviluppo collaborativo.
Nello scegliere un workflow dovrete rispondere ad alcune domande, quali:
I principali sono:
Valide risorse:
Vediamo il forking workflow. Non perché sia intrinsecamente il migliore, ma perché quello più diffuso nello sviluppo su piattaforme come Github. Presupposti:
Il project maintainer ha creato il repository upstream remoto e il proprio clone locale.
$ git clone https://git.lattuga.net/maintainer/repo.git
Il developer ora:
Developer fa un clone locale del proprio repository remoto. È di norma buona idea aggiungere un remote "upstream" che punti al repository del maintainer:
$ git clone https://git.lattuga.net/developer/repo.git
$ git remote add upstream https://git.lattuga.net/maintainer/repo.git
Developer deve sviluppare un fix che andrà applicato sul branch master del repository upstream.
Prima di tutto è opportuno sincronizzare il proprio branch master con quello upstream, in modo da lavorare su codice recente:
$ git checkout master
$ git pull upstream master
$ git checkout -b fix/bug-123
$ # introdurre il fix
$ git commit
$ git push --set-upstream origin fix/bug-123
Ora va sulla pagina web del proprio fork e crea una pull request.
Pull request NON è un concetto base di Git (non esattamente, almeno). È qualcosa che vi è stato costruito sopra per facilitare la collaborazione tra sviluppatori.
La pull request creata in precedenza dice: "propongo di applicare i commit del branch developer:fix/bug-123 a maintainer:master" Ora developer, project maintainer e altri possono discuterne.
Se dovesse essere necessario, developer o altri possono aggiungere altri commit semplicemente con un nuovo push.
Una volta soddisfatti, project maintainer potrà effettuare il merge del codice su maintainer:master.
Se il merge non presenta conflitti, lo farà direttamente dalla GUI web sul repository upstream.
Altrimenti dovrà aggiungere un remote che punti al repository di developer, fare il fetch di developer:fix/bug-123, effettuare il merge su master per poi farne il push sul repository upstream.
Github e amici non suggeriranno di aggiungere lo sviluppatore come remote, ma di fare direttamente il pull del suo topic branch. È sicuramente più pulito se ricevete molte pull request da tante persone differenti.
Nel caso di Github, ad esempio:
Altrimenti:
In cui forniremo una serie di strumenti avanzati.
Salire di 3 livelli, seguendo sempre il primo parent commit (in caso di merge):
$ git show -s HEAD~3
Salire di un livello, seguendo il secondo parent commit (in caso di merge):
$ git show -s HEAD^2
Double dot range. Usando diff mostra i cambiamenti tra "master" e "branch"; usando log mostra i commit raggiungibili da "branch" ma non da "master":
$ git diff master..branch
Triple dot range. Usando diff mostra la differenza tra il punto di biforcazione tra "master" e "branch" e "branch" stesso; usando log mostra i commit raggiungibili da "master" o "branch", ma non da entrambi:
$ git log --left-right master...branch
Vedere anche: https://stackoverflow.com/questions/7251477/what-are-the-differences-between-double-dot-and-triple-dot-in-git-dif
$ git checkout master
$ git cherry-pick <commit>
$ # in caso di conflitti:
$ git cherry-pick --continue
Si sono prese le modifiche introdotte dai commit elencati, e sono state riapplicate sul branch corrente. Sono stati creati dei nuovi commit.
Ad esempio per backportare un fix su diversi release branch, o se vi siete accorti che un certo commit era da fare su un altro branch.
Poniamoci nella stessa situazione divergente dell'esempio in cui abbiamo usato merge, e poi:
$ git checkout fix/bug-123
$ git rebase master
$ # risolviamo eventuali conflitti
$ git rebase --continue
Abbiamo preso tutti i commit di fix/bug-123 e li abbiamo ri-applicati su master, che nel mentre era andato avanti. Tutti i commit specifici di fix/bug-123 sono cambiati. Volendo, ora si può fare un merge fast-forward in master.
Quando dovete spostare più commit e/o per porvi nella condizione di fare un merge pulito. Questo può essere fatto dal developer prima di aprire una pull request per semplificare il lavoro al maintainer e/o dal maintainer stesso prima del merge, per ottenere una history lineare.
Un rebase modifica i commit originali del branch: questo va evitato se quei commit sono già stati pushati ed altri sviluppatori li stanno usando come base per il proprio lavoro.
Creiamo un nuovo branch e committiamo 2 o 3 modifiche. Poi:
$ git rebase -i master
Abbiamo accorpato, scartato o invertito l'ordine dei commit.
È particolarmente utile quando abbiamo finito di lavorare su un branch, e vogliamo semplificare la history accorpando molti commit in uno solo.
Editiamo un file in vari punti, e poi aggiungiamolo alla staging area con --patch:
$ git add --patch
Ad esempio quando non si vuole includere in un commit una riga di debug, che però si vuole mantenere nella working directory.
è possibile creare e riapplicare una patch usando i comandi:
$ git format-patch [refs]
$ git apply patch-file.diff
Mettere da parte il lavoro nella working directory senza committare, e mostrare le modifiche stashed:
$ git stash
$ git stash list
Riapplicare una modifica messa in stash, ed eliminarne uno:
$ git stash pop stash@{0}
$ git stash drop stash@{0}
Ad esempio quando vogliamo passare ad un altro branch, accantonando le modifiche nella working directory.
La history mostra solo i commit inclusi in un branch.
Per vedere TUTTI gli spostamenti di HEAD:
$ git reflog [--relative-date]