Articolo originale: Now that you’re not afraid of GIT anymore, here’s how to leverage what you know

La prima parte di questa serie ha esaminato il funzionamento interno di Git e ti ha mostrato come non aver paura di lavorare con Git.

Ora che abbiamo capito come funziona Git, passiamo alla sostanza: come sfruttare ciò che sappiamo nei nostri progetti.

Integrazione (merge)

Merge integra il tuo codice.

Ricordi come stavamo seguendo le buone pratiche Git, avendo branch per varie funzionalità su cui stavamo lavorando e non tutto su master? Verrà un momento in cui avrai finito con quella funzionalità e vorrai includerla nel tuo master. È qui che entra in gioco merge, quando vuoi integrare il tuo ramo in master.

Ci sono due tipi di azioni di merge:

Integrazione con avanzamento veloce (fast forward merge)

Ritorniamo al nostro esempio dall'ultima volta:

Questo è tanto semplice quanto spostare l'etichetta per master a the-ending. Git non ha dubbi su ciò che deve essere fatto esattamente, dal momento che il nostro "albero" aveva un'unica lista collegata di nodi.

$ git branch
  master
* the-ending
$ git checkout master
Switched to branch 'master'
$ git merge the-ending
Updating a39b9fd..b300387
Fast-forward
 byeworld | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 byeworld

Integrazione senza avanzamento veloce (non-fast forward merge)

Con questo tipo di integrazione Git non sa cosa fare. Ci sono state alcune modifiche sul branch base e altre modifiche sul branch che vogliamo integrare, il che genera i temuti conflitti di integrazione (merge conflicts)

Ecco la prima cosa da sapere circa i conflitti di integrazione: se non sai cosa sta succedendo, rinuncia all'operazione.

git merge --abort

Questo comando ti riporta allo stato originale, senza effetti collaterali. Hai semplicemente annullato il disastro che stavi per fare.

image-10
Non vuoi essere Brian?

Esaminiamo passo passo come risolvere i conflitti di integrazione.

$ git checkout -b the-middle
Switched to a new branch 'the-middle'

Continuando secondo il nostro stile, impariamo con un esempio. Ho modificato helloworld nel branch the-middle.

$ git diff
diff --git a/helloworld b/helloworld
index a042389..e702052 100644
--- a/helloworld
+++ b/helloworld
@@ -1 +1,3 @@
 hello world!
+
+Middle World

Poi ho eseguito un'azione di commit su the-middle.

Quindi sono passato a master e ho modificato helloworld. Ho aggiunto quanto segue:

$ git diff --cached
diff --git a/helloworld b/helloworld
index a042389..ac7a733 100644
--- a/helloworld
+++ b/helloworld
@@ -1 +1,3 @@
 hello world!
+
+Master World

Capisci come mai ho dovuto eseguire  git diff --cached qui? In caso contrario chiedimelo qui sotto!

Ora è tempo di integrare!

$ git merge the-middle
Auto-merging helloworld
CONFLICT (content): Merge conflict in helloworld
Automatic merge failed; fix conflicts and then commit the result.

Quando un'azione di merge fallisce, ecco cosa fa git: modifica i file oggetto di integrazione sui quali si sono verificati i conflitti per mostrarti esattamente come potresti agire.

Prima dell'azione di merge, in master il contenuto del file helloworld era:

$ cat helloworld 
hello world!

Dopo l'azione di merge, in master il contenuto del file helloworld è:

$ cat helloworld 
hello world!
<<<<<<< HEAD
Master World
=======
Middle World
>>>>>>> the-middle

Questo ha senso? La parte <<<<< HEAD è la nostra (il branch base) e la parte >>>>> the-middle  è la loro (il branch che si vuole integrare nel branch base).

Puoi semplicemente modificare il file per rimuovere il testo extra aggiunto da git, e scegliere cosa dovrebbe contenere in definitiva helloworld. Ci sono alcuni strumenti e integrazioni per editor per facilitare le cose, ma penso che sapere come funziona sotto il cofano possa  renderti più fiducioso quando non hai a disposizione il tuo editor preferito.

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)
Unmerged paths:
  (use "git add <file>..." to mark resolution)
both modified:   helloworld

Ho deciso di mantenere entrambe le modifiche.

$ cat helloworld 
hello world!
Master World
Middle World
N.d.T.
Da qui in avanti i commenti dei commit rilevanti sono stati tradotti per una maggiore comprensione dell'esposizione.

E il gioco è fatto:

$ git add helloworld 
$ git commit -m "risolto conflitto di integrazione"
[master c747e68] risolto conflitto di integrazione

Remoto

Visto che uno dei poteri del controllo della versione delle sorgenti è di salvare il tuo codice in caso di disastri, i remoti sono qui per essere di aiuto. Un remoto è una copia ospitata all'esterno del tuo repository. Per essere più precisi un remoto è un repository esterno (non necessariamente dello stesso codice che tu hai). Per esterno si intende una cartella differente nel tuo file system oppure nel cloud.

Clonazione

Il comando clone esegue una clonazione del repository remoto all'interno della tua directory di lavoro corrente. Ciò avviene semplicemente creando una copia della cartella ./git, che ci fornisce l'intera cronologia e i file necessari per popolare la directory di lavoro.

git clone <repository-url>

Se non hai ancora eseguito un'azione di clonazione, probabilmente non hai un remoto. Puoi creare un remoto in questo modo:

git remote add <nome> <url>

Inviare e Scaricare (push e pull)

Le azioni di push e pull si applicano sul remoto.

Push invia le tue modifiche al remoto, vale a dire che stiamo inviando l'indice e gli oggetti corrispondenti dall'object store della nostra versione locale!

git push <nome del remoto> <nome del branch>

Pull scarica il code dal remoto. Esattamente come prima, stiamo copiando l'indice e gli oggetti corrispondenti dell'object store dalla versione su remoto!

git pull origin master

origin è il nome predefinito del remoto. Visto che master è il branch predefinito puoi vedere come il comando si trasforma nel semplice testo che troviamo ovunque: git pull origin master. Ora ne sai di più.

Reset

Reset ripristina il tuo codebase a una versione precedente. Questo comando ha 3 opzioni:

--soft, --hard e --mixed.

La bellezza di reset, è che è in grado di cambiare la cronologia. Diciamo che hai commesso un errore in un commit e ora il tuo log di git è sporcato da commit di questo tipo:

Risoluzione Bug

Risoluzione Finale Bug

Risoluzione Finale Finale Bug

Accidenti, perché questo non funziona ultimo tentativo di risoluzione bug

Se vuoi mantenere la cronologia del tuo master pulita, vorrai pulire questo log di commit.

Se stai inviando una pull request dove non c'è accorpamento dei commit, è auspicata anche una cronologia di commit pulita!

Ecco dove entra in gioco il comando reset. Potresti ripristinare tutti i tuoi commit e convertirli tutti in uno singolo: tutto sistemato!

(Per favore non usare questo come messaggio di commit, segui le migliori pratiche!)

Tornando al nostro esempio ecco quello che ho fatto :

$ git log
commit 959781ec78c970d4797c5e938ec154de44d0151b (HEAD -> master)
Author: Neil Kakkar
Date:   Mon Nov 5 07:32:55 2018 +0000
Accidenti, perché questo non funziona ultimo tentativo di risoluzione bug
commit affa90c0db78999d22c326fdbd6c1d5057228822
Author: Neil Kakkar
Date:   Mon Nov 5 07:32:19 2018 +0000
Risoluzione Finale Finale Bug
commit 2e9570cffc0a8206132d75c402d68351eda450bd
Author: Neil Kakkar
Date:   Mon Nov 5 07:31:49 2018 +0000
Risoluzione Finale Bug
commit 4560fc0ec6305d0b7bcfb4be1901438fd126d6d1
Author: Neil Kakkar
Date:   Mon Nov 5 07:31:21 2018 +0000
Risoluzione Bug
commit c747e6891af419119fd817dc69a2e122084aedae
Merge: 3d01508 fb8b2fc
Author: Neil Kakkar
Date:   Tue Oct 23 07:44:09 2018 +0100
risolto conflitto di integrazione

Ora che il bug è risolto, vorrei pulire la mia cronologia prima di inviare le modifiche al master. Questo funzionerebbe bene anche nel caso in cui realizzassi a posteriori di avere introdotto un altro bug e volessi ripristinare la versione precedente. In questo caso il commit c747e689 non ha il miglior messaggio di commit per capire questo.

$ git reset c747e6891af419119fd817dc69a2e122084aedae
$ git log
commit c747e6891af419119fd817dc69a2e122084aedae (HEAD -> master)
Merge: 3d01508 fb8b2fc
Author: Neil Kakkar
Date:   Tue Oct 23 07:44:09 2018 +0100
risolto conflitto di integrazione

Ecco, tutto sistemato?

$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
      clean.txt
nothing added to commit but untracked files present (use "git add" to track)

clean.txt è il file che ho usato nel commit per risolvere il bug. Ora tutto quello che devo fare è:

$ git add clean.txt 
$ git commit -m "risoluzione bug: non si riesce a pulire la cartella"
[master d8487ca] risoluzione bug: non si riesce a pulire la cartella
 1 file changed, 4 insertions(+)
 create mode 100644 clean.txt
$ git log
commit d8487ca8b9acfa9666bdf2c6b7fa27b3971bd957 (HEAD -> master)
Author: Neil Kakkar
Date:   Mon Nov 5 07:41:41 2018 +0000
risoluzione bug: non si riesce a pulire la cartella
commit c747e6891af419119fd817dc69a2e122084aedae
Merge: 3d01508 fb8b2fc
Author: Neil Kakkar
Date:   Tue Oct 23 07:44:09 2018 +0100
risolto conflitto di integrazione

Ecco, fatto e finito. Riesci a indovinare ora, usando gli indizi del log, la sintassi del comando reset e le tue capacità tecniche per capire come funziona dietro le quinte?

Reset taglia l'alberatura dei commit al commit specificato. Tutte le etichette per quel branch, se sono più avanti, sono spostate indietro verso il commit specificato. I file esistenti rimangono comunque nell'object store? Sai come verificarlo ora.

I file sono anche stati rimossi dall'area di staging. Ora questo potrebbe essere un problema se hai molti file non tracciati o modificati che non vuoi aggiungere.

Come puoi fare?

Puoi recuperare l'indizio che ho lasciato all'inizio di questa sezione?

Le opzioni per gestire il comportamento del comando reset!

--soft mantiene tutti i file presenti nell'area di staging.

$ git reset --soft c747e6891af419119fd817dc69a2e122084aedae
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
new file:   clean.txt

--mixed è l'opzione predefinita: elimina anche tutti i file dall'area di staging.

--hard è brutale. Elimina i file dall'object store, comprese le directory. Usalo con estrema cautela. Ecco, ora sparisce la mia risoluzione del bug*. Andata.

$ git reset --hard c747e6891af419119fd817dc69a2e122084aedae
HEAD is now at c747e68 risolto conflitto di integrazione
$ git status
On branch master
nothing to commit, working tree clean

*Beh, non completamente. Git è stupefacente. Hai sentito parlare di metadati?. Un registro di ridondanza di quello che accade nel repository? Sì, naturalmente, Git lo mantiene!

$ git reflog
c747e68 (HEAD -> master) HEAD@{0}: reset: moving to c747e6891af419119fd817dc69a2e122084aedae
efc6d21 HEAD@{1}: commit: soft reset
c747e68 (HEAD -> master) HEAD@{2}: reset: moving to c747e6891af419119fd817dc69a2e122084aedae
d8487ca HEAD@{3}: commit: risoluzione bug: non si riesce a pulire la cartella
c747e68 (HEAD -> master) HEAD@{4}: reset: moving to c747e6891af419119fd817dc69a2e122084aedae
959781e HEAD@{5}: commit: Accidenti, perché questo non funziona ultimo tentativo di risoluzione bug
affa90c HEAD@{6}: commit: Risoluzione Finale Finale Bug
2e9570c HEAD@{7}: commit: Risoluzione Finale Bug
4560fc0 HEAD@{8}: commit: Risoluzione Bug
c747e68 (HEAD -> master) HEAD@{9}: commit (merge): risolto conflitto di integrazione
3d01508 HEAD@{10}: commit: add Master World
b300387 (the-ending) HEAD@{11}: checkout: moving from the-middle to master
fb8b2fc (the-middle) HEAD@{12}: commit: add Middle World
b300387 (the-ending) HEAD@{13}: checkout: moving from master to the-middle
b300387 (the-ending) HEAD@{14}: checkout: moving from the-middle to master
b300387 (the-ending) HEAD@{15}: checkout: moving from master to the-middle
b300387 (the-ending) HEAD@{16}: merge the-ending: Fast-forward
a39b9fd HEAD@{17}: checkout: moving from the-ending to master
b300387 (the-ending) HEAD@{18}: checkout: moving from master to the-ending
a39b9fd HEAD@{19}: checkout: moving from the-ending to master
b300387 (the-ending) HEAD@{20}: commit: add byeworld
a39b9fd HEAD@{21}: checkout: moving from master to the-ending
a39b9fd HEAD@{22}: commit (initial): Add helloworld

Questo è tutto il log dall'inizio dell'esempio dell'articolo precedente. Significa che sarei in grado di recuperare delle cose se avessi fatto un orribile errore?

$ git checkout d8487ca
Note: checking out 'd8487ca'.
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 d8487ca... fix bug: Unable to clean folder

$ ls
byeworld clean.txt  helloworld
N.d.T.
La traduzione del risultato del comando git qui sopra è: "Sei nello stato 'HEAD staccato'. Puoi guardarti attorno, fare modifiche sperimentali e inserirle in un commit, e puoi scartare qualunque commit fatto in questo stato senza influenzare alcun branch eseguendo un'altra azione di checkout.
Se vuoi creare un nuovo branch per mantenere i commit che hai creato, puoi farlo (ora o successivamente) usando nuovamente l'opzione -b con il comando checkout. Esempio: git checkout -b <nuovo-nome-branch>
HEAD è ora a d8487ca... risoluzione bug: non si riesce a pulire la cartella"

Ecco fatto.

Congratulazioni, sei un Git Ninja, apprendista ora.

C'è qualcos'altro che ti piacerebbe sapere? Qualcosa di Git che non ti è chiara? Fammelo sapere qui sotto! Cercherò di spiegartela nel modo nel quale l'ho imparata!

Soddisfatto? Non perdere ancora un altro post — iscriviti alla mia mailing list!