Differenze tra le versioni di "Git"

Da GolemWiki.
Jump to navigation Jump to search
 
(2 versioni intermedie di uno stesso utente non sono mostrate)
Riga 211: Riga 211:
 
#Il patema è finito, si può fare la commit e pushare, sperando che nella lettura di questa lista nessuno abbia fatto altro. Altrimenti ripartire dal punto 1.
 
#Il patema è finito, si può fare la commit e pushare, sperando che nella lettura di questa lista nessuno abbia fatto altro. Altrimenti ripartire dal punto 1.
 
#: <code>git commit -m "commit interessante"<br/>git push</code>
 
#: <code>git commit -m "commit interessante"<br/>git push</code>
 +
 +
=== Rebase ===
 +
Generalmente ogni membro del team lavora sui feature o bugfix su un branch dedicato, che nasce da quello principale (di solito ''master'' o ''main'').
 +
Quando il lavoro è terminato, le modifiche vengono trasferite dal branch dedicato al branch principale.
 +
Se non ci sono state modifiche sul branch principale, questo può essere fatto avanzare per allinearsi al branch dedicato (operazione detta di ''fast-forward'').
 +
Tuttavia, spesso e volentieri capita che nel frattempo sono state trasferite altre modifiche sul branch principale (es. da un altro membro del team), per cui non è possibile eseguire un ''fast-forward'' del branch principale verso quello dedicato, perché quello dedicato "nasce" da una situazione precedente all'attuale stato del branch principale.
 +
 +
Ciò che si può fare per permettere il fast-forward, è di ''ribasare'' il branch dedicato sul nuovo stato del branch principale.
 +
Questo significa riscriverne la storia riapplicando tutti i suoi commit come se fossero nati dall'attuale stato del branch principale.
 +
Per ribasare un branch dedicato su un nuovo commit genitore (es. il branch ''main''), spostarsi sul branch dedicato, dopodiché:
 +
 +
$ git rebase <newparent> # es. git rebase main
 +
 +
''git'' non è sempre in grado di capire da quale commit è inizialmente nato il branch dedicato corrente, per cui potrebbe tentare di applicare su ''main'' anche altri commit precedenti alla diramazione - specialmente se si è riscritta la storia tramite altri rebase in qualche altro momento.
 +
Per suggerire a ''git'' qual è il commit genitore in maniera esplicita, utilizzare invece questo comando:
 +
 +
$ git rebase --onto <newparent> <oldparent>
 +
 +
Un modo esplicito per controllare cosa git abbia intenzione di fare dei nostri commit prima che provi davvero ad applicarli, è utilizzare la funzionalità di rebase interattivo, che, appunto, permette di vedere i commit che stanno per essere applicati - ed eventualmente di correggere al volo il loro numero, ordine e anche messaggio e contenuto, se proprio necessario.
 +
Per il rebase interattivo, utilizzare l'opzione <code>--interactive</code> (eventualmente combinata con le altre di cui sopra):
 +
 +
$ git rebase --interactive <newparent>
 +
 +
Nel caso in cui il branch su cui si sta ribasando sia cambiato in maniera sostanziale rispetto alla condizione originale da cui si è partiti (es. un altro membro del team ha cambiato le stesse linee nello stesso file), quando git applicherà il vostro commit problematico, si genererà un conflitto, che andrà corretto manualmente.
 +
 +
I conflitti sono contrassegnati nei file problematici con la seguente sintassi:
 +
 +
<<<<<
 +
contenuto nella nuova testa su cui si sta ribasando
 +
=====
 +
contenuto originale sulla vecchia testa su cui si era basati
 +
=====
 +
contenuto nel commit problematico
 +
>>>>>
 +
 +
Il contenuto dei file problematici può essere aggiustato in vari modi:
 +
- modificando manualmente il file
 +
- facendosi utilizzare da un ''merge tool'' (per vedere quelli supportati e installati usare <code>git mergetool --tool-help</code>)
 +
- utilizzando esplicitamente una delle due versioni, ignorando il conflitto (eg. sovrascrivendo esplicitamente le proprie modifiche, o le modifiche dell'altro)
 +
 +
Per scegliere esplicitamente una versione ignorando totalmente le modifiche dell'altro:
 +
 +
$ git checkout --theirs path/file # se si ritiene che il commit che si sta applicando sia la versione corretta, essenzialmente sovrascrivendo le modifiche degli altri
 +
$ git checkout --ours path/file # se si ritiene che invece il commit che si sta applicando '''non''' sia corretto, essenzialmente confermando che il branch su cui si sta ribasando contenga la versione corretta, sovrascrivendo le proprie modifiche
 +
 +
Una volta modificato il contenuto problematico del/dei file in uno dei modi sopra suggeriti, si può continuare col rebase:
 +
 +
$ git add file_problematici
 +
$ git rebase --continue
 +
 +
Questo può chiaramente generare altri conflitti nei file successivi, che andranno risolti, finché il rebase non è completo.
 +
 +
Per interrompere un rebase a metà e ripartire da capo, utilizzare il comando per abortire:
 +
 +
$ git rebase --abort
  
 
=== Blame ===
 
=== Blame ===
Riga 295: Riga 350:
 
Per creare una nuova copia locale (''linked working tree'') associata ad un certo branch
 
Per creare una nuova copia locale (''linked working tree'') associata ad un certo branch
  
  $ git worktree add path [branch]
+
$ git worktree add path [branch]
  
 
Se non si specifica il branch ne viene creato uno nuovo, a partire dal commit corrente, prendendo il nome dalla directory di destinazione specificata nel path.
 
Se non si specifica il branch ne viene creato uno nuovo, a partire dal commit corrente, prendendo il nome dalla directory di destinazione specificata nel path.
 +
Per evitare confusione, specificare un path out-of-tree, per esempio:
 +
 +
$ cd myrepo_master
 +
$ git worktree add ../myrepo_branch
 +
 +
Una volta terminato il lavoro sul worktree separato, per rimuoverlo non eliminare direttamente la directory ad esso associata, ma utilizzare l'apposito comando git, così da mantenere consistente lo stato di <code>.git</code>.
 +
 +
$ git worktree delete ../myrepo_branch
  
 
Per approfondire, consultare la [https://git-scm.com/docs/git-worktree guida ufficiale].
 
Per approfondire, consultare la [https://git-scm.com/docs/git-worktree guida ufficiale].

Versione attuale delle 08:13, 1 ott 2024

Golem-template-note-info.png Questa pagina è in continua evoluzione


Git è un sistema di controllo versione nato nel 2005 ad opera di Linus Torvalds con l'obiettivo principale di versionare il Kernel Linux. Questa non vuole essere una guida esaustiva, ma un prontuario per i comandi di uso più comune, con l'aggiunta di qualche consiglio. Per una trattazione completa, si rimanda alla guida ufficiale, disponibile anche in italiano.

Installazione e configurazione

Su sistemi Debian-derivati:

# apt-get install git

Su ArchLinux:

# pacman -S git

Si consiglia l'uso di ZSH per i suggerimenti nel completamento dei comandi.

Configurazione

I file di configurazione si trovano nei seguenti percorsi:

<repository_git_attuale>/.git/config   # configurazione per singola cartella
~/.gitconfig                           # configurazione base per l'utente corrente
/etc/gitconfig                         # configurazione di sistema (non necessariamente presente)

Alcune configurazioni importanti:

$ git config user.name Tizio Caio
$ git config user.email tiziocaio@example.com
$ git config user.signingkey <FOOTPRINT GPG>
$ git config core.editor nano
$ git config alias.co checkout

Aggiungendo l'opzione --global, tali impostazioni diventeranno globali per l'utente corrente e non limitate al repository dal quale viene lanciato il comando.

Identità multiple

Nel caso in cui si volesse impostare una configurazione per tutti i repository all'interno di una determinata cartella, ad esempio per gestire separatamente un'identità personale ed una di lavoro, si può procedere come segue.

Nel ~/.gitconfig si imposta, opzionalmente, una identità di fallback. I .gitconfig supplementari vengono richiamati soltanto se il repository è all'interno delle cartelle specificate da includeIf.

[user]
        name = Tizio
        email = tiziocaio@example.com

[includeIf "gitdir:~/workspace/"]
        path = ~/workspace/.gitconfig
[includeIf "gitdir:~/personal/"]
        path = ~/personal/.gitconfig

Poi, nei file ~/workspace/.gitconfig e ~/personal/.gitconfig si esplicitano le identità da usare nel contesto di quelle sottodirectory:

[user]
        name = Tizio Caio
        email = tiziocaio@professional.example.com

Golem-template-note-warning.png Nell'opzione gitdir, è necessario specificare un path con un trailing slash, altrimenti prende solo la cartella specificata, e non anche le sottocartelle.


Comandi di base

init

Inizializzare un repository vuoto:

$ git init <sottocartella>

Se non viene specificata una sottocartella sarà inizializzato in quella attuale. Aggiungendo l'opzione --bare sarà creato un repository di tipo bare, ovvero privo della working directory, adatto per essere usato come repository remoto e non come cartella di lavoro.

Stato dei file

Con il comando

$ git status

si ottengono informazioni sui file attualmente presenti nella cartella di lavoro: se ce ne sono di nuovi (U), modificati (M), eliminati (D), etc... Darà inoltre informazioni sulla sincronizzazione tra la commit attuale e quella remota.

Aggiungere/Rimuovere files

Aggiungere un file nuovo (untracked) o modificato (rispetto all'ultima commit eseguita) nella Staging Area, ovvero pronto per la prossima commit:

$ git add <nome_file>

Rimuovere un file dall'indice dei file tracciati (risulterà quindi untracked):

$ git rm --cached <nome_file>

Omettendo --cached il file sarà anche eliminato dalla cartella (quindi definitivamente).

Rimuovere tutti i file dalla staging area:

git reset HEAD -- .

Rinominare un file: sebbene si possa utilizzare semplicemente il comando mv della shell, questo fa sì che git debba poi intuire automagicamente se il file è stato spostato, oppure se è stato eliminato e poi creato di nuovo. A volte lo spostamento del file non viene riconosciuto, e viene interpretato come eliminazione e successiva ri-aggiunta, generando lunghi e fastidiosi diff. Onde evitare questo problema, è opportuno segnalare l'operazione di spostamento/rinomina esplicitamente a git, tramite il comando apposito.

git mv source dest

Commit

Una volta aggiunti i file desiderati alla Staging Area, la prossima commit è pronta per essere "consolidata", cioè registrata nella cronologia del repository:

$ git commit

Sarà aperto l'editor di testo scelto (vedi sezione configurazione) per scrivere un breve messaggio rappresentativo della commit. Se il messaggio non è eccessivamente lungo (come spesso accade) è sufficiente accodare al comando di commit l'opzione -m "Messaggio per la commit". Un'altra scorciatoia utile può essere l'opzione -a, che aggiunge automaticamente tutti i file modificati (rispetto all'ultima commit eseguita) alla Staging Area senza dover eseguire add ogni volta. Questa opzione agisce solo su quelli modificati e non su quelli nuovi, per i quali la add è d'obbligo almeno la prima volta.

Il comando tipico di commit sarà quindi:

$ git commit -a -m "Riparato bug. Aggiunti commenti al codice."

L'esecuzione del comando commit senza l'aggiunta di file alla Staging Area (e quindi senza nemmeno l'opzione -a) non avrà alcun effetto.

Se si commette un piccolo errore in una commit e si vuole modificare un file o il messaggio di commit stesso, senza però dover creare una nuova commit, si può di nuovo fare add del file, e può essere aggiunta l'opzione:

$ git commit --amend

Golem-template-note-attention.png git commit --amend modifica la storia del repository, perciò prestare particolare attenzione se si è già pushato su un server remoto


Log

$ git log

elenca tutta la cronologia delle commit corredata di somma SHA-1, autore, data e ora di commit e messaggio.

È uno dei comandi col maggior numero di opzioni, dalla formattazione dell'output (più informazioni sull'autore, sulle commit firmate) al filtraggio (per autore, per data, per messaggio, etc...). Alcune delle più importanti sono:

  • --all: elenca anche le commit relative ad altri branches (vedi sezione relativa)
  • --oneline: elenco sintetico con solamente ID SHA-1 e messaggio di commit
  • --graph: insieme a --all realizza una rappresentazione ASCII art della ramificazione del repository

Combinando insieme le 3 opzioni sarà visualizzata in modo sintetico (oneline) ed efficace (graph) tutta la cronologia del repo. Può essere utile definire un alias per questo comando (vedi sezione configurazione), ad esempio:

$ git config alias.megalog "log --all --oneline --graph"

Git online

Utilizzare un server git (repository remoto) può servire per:

  • avere un backup del proprio progetto
  • condividere il proprio progetto con altri
  • lavorare in team sul progetto
  • fare un fork di un progetto già esistente

Configurare un repository remoto

Server privato

Se si ha a disposizione un server personale o aziendale (per esempio un VPS), questo può essere configurato come repository git remoto.

Per una configurazione base, è sufficiente avere a disposizione un server SSH (quasi sicuramente già presente) e installare git (vedi sezione installazione). Eseguire quindi

cd /home/utente/<cartella_git>
git init --bare

Si noti in questo caso l'utilità dell'opzione bare, che non crea una working directory su cui poter lavorare direttamente (spesso non necessaria su un server) ma solo il database dei file, risparmiando spazio di archiviazione.

Se si desidera avere un'interfaccia web più user-friendly, si può installare Gitea (al GOLEM abbiamo la nostra istanza), oppure GitLab, solo per citare due delle più famose piattaforme.

Servizio online

In alternativa ci si può affidare ad un servizio online, come GitHub (il più popolare), GitLab (che può essere anche usato on-premise) o BitBucket. Dopo il login (email o nome utente e password) si può procedere alla creazione di un nuovo repository con l'apposito pulsante. Saranno forniti quindi due URL per accedere:

https://github.com/NOME_UTENTE/NOME_REPOSITORY  
git@github.com:NOME_UTENTE/NOME_REPOSITORY.git  

Generalmente, l'URL https viene utilizzato per sola lettura, per clonare e scaricare repository pubblici, e per prove temporanee, mentre l'URL ssh può essere utilizzato, in maniera più flessibile e automatica, grazie all'autenticazione tramite chiave, anche per la scrittura (ammesso di avere l'autorizzazione da parte del proprietario).

Collegare il repo locale a quello remoto

Nuovo

In entrambi i casi, dopo aver creato un repo remoto si deve comunicare a git di collegare quell'URL al repo locale.

git remote add origin git@github.com:NOME_UTENTE/NOME_REPOSITORY.git  # Aggiungere l'URL
git push                                                              # Inviare il lavoro locale sul server

Nota: il nome origin (così come master per il ramo principale) è solamente una convenzione e può essere scelto a piacere.

Esistente

Se il repo a cui collegarsi esiste già, usiamo il comando

git clone git@github.com:NOME_UTENTE/NOME_REPOSITORY.git

Nel log, tutti i rami figureranno come origin/ramo-1. Per potersi agganciare con un ramo locale:

git checkout origin/ramo-1                       # Spostarsi sulla commit puntata da ramo-1 remoto
git checkout -b ramo-1                           # Creare un nuovo ramo locale su questa commit
git branch --set-upstream-to=origin/ramo-1       # Agganciare ramo-1 a origin/ramo-1

Nota: non è obbligatorio che il ramo locale abbia lo stesso nome del ramo remoto a cui è agganciato (ecco perché questi passaggi non sono automatici), ma fare il contrario sarebbe follia!

Push e Pull

A questo punto si può leggere e scrivere sui rami remoti del server origin/master, origin/ramo-1, etc. servendosi dei rami locali master, ramo-1, etc. coi quali eseguiamo l'ordinaria amministrazione del progetto. Usiamo per leggere e scrivere (rispettivamente) i comandi

git pull
git push

Per ottenere tutti gli oggetti remoti (rami, tags, ...) creati da collaboratori, è necessario aggiungere

git pull --all

e quindi per visualizzare i branch remoti

git branch --remotes

Lavorare con git

Evitare tracciamento file

Artefatti e configurazioni del repository

Talvolta è necessario evitare di tracciare alcuni file, ad esempio gli artifatti della compilazione (file oggetto e binari), file di configurazione, file che contengono dati sensibili, e così via. Per farlo, è sufficiente aggiungere il nome del file nel file nascosto .gitignore nella root del repository, e committarlo. Si possono aggiungere anche nomi di directory o pattern con wildcard.

File globali

Talvolta succede di "sporcare" il repository con file specifici di qualche applicazione che il singolo sviluppatore utilizza su tutti i repository, per esempio file di configurazione dell'editor di testo o di backup (*.swp, *.~). Si può dunque usare un file .gitignore globale per l'utente, che può essere messo nella ~ e specificato con:

git config --global core.excludesfile ~/.gitignore

Smettere di tracciare file ignorati

Può capitare di aggiungere un artefatto al repository, per sbaglio, e volerlo poi successivamente mettere tra i file ignorati. Però git ormai sta tenendo traccia di quel file, dunque come fare?

  • Aggiungere il file al .gitignore
  • Vedere il/i file che sono tracciati, ma che dovrebbero essere ignorati:
git ls-files -ci --exclude-standard
  • Rimuovere forzatamente i file indesiderati:
git rm --cached <file>
  • git commit

Lavorare in team

Windows

Quando capita di condividere il codice con altre persone che utilizzano Windows, onde evitare problemi subdoli causati dalla presenza di diversi terminatori di riga nel codice sorgente (CRLF su Windows e LF su Linux), la cosa corretta da fare è configurare l'opzione autocrlf a true. In questo modo, i file sul repository conterranno sempre LF come terminatore, e saranno opportunamente convertiti in CRLF (e viceversa) quando si fa il checkout (e il commit) su Windows.

git config core.autocrlf true

Per MAC OS X, questo problema non si pone.

Merge

Una piazzola

Quando si lavora in gruppo c'è sempre il rischio di "incrociarsi" nel pushare nuove commit, con conseguenti intrecci nella storia del progetto (amichevolmente detti "piazzole di sosta"). Per evitare ciò conviene seguire una procedura standard: non so se è la migliore però finora non ha mai dato problemi.

  1. Effettuare le proprie modifiche, al termine verificare se altri utenti hanno effettuato aggiornamenti nel mentre utilizzando
    git fetch
    Se non viene stampato alcunché si può saltare al punto 6 effettuando la commit e pushando regolarmente.
  2. Altrimenti, aggiungere i files modificati alla staging area ed "accantonarli" in una stash. È utile specificare un messaggio di stash (come fosse una commit) per individuarla più facilmente nel caso ci fossero altre stash.
    git stash push -m "merging in progress"
  3. A questo punto la cartella è stata riportata all'ultima commit scaricata. Effettuare il pull per scaricare le novità
    git pull
  4. Recuperare le modifiche dallo stash. Questa operazione è analoga ad un merge, ma al termine non rimarrà traccia nella storia, che proseguirà linearmente
    git stash pop
  5. Se il merge dallo stash va a buon fine si può passare al punto successivo, la stash viene eliminata automaticamente. Se invece le modifiche fatte confliggono con le novità appena scaricato (ciò viene notificato da git) è necessario integrare manualmente i punti (individuabili anche con git diff), ed eliminare la stash manualmente (git stash list per individuarla, git stash drop stash@{xxx} per eliminarla).
  6. Il patema è finito, si può fare la commit e pushare, sperando che nella lettura di questa lista nessuno abbia fatto altro. Altrimenti ripartire dal punto 1.
    git commit -m "commit interessante"
    git push

Rebase

Generalmente ogni membro del team lavora sui feature o bugfix su un branch dedicato, che nasce da quello principale (di solito master o main). Quando il lavoro è terminato, le modifiche vengono trasferite dal branch dedicato al branch principale. Se non ci sono state modifiche sul branch principale, questo può essere fatto avanzare per allinearsi al branch dedicato (operazione detta di fast-forward). Tuttavia, spesso e volentieri capita che nel frattempo sono state trasferite altre modifiche sul branch principale (es. da un altro membro del team), per cui non è possibile eseguire un fast-forward del branch principale verso quello dedicato, perché quello dedicato "nasce" da una situazione precedente all'attuale stato del branch principale.

Ciò che si può fare per permettere il fast-forward, è di ribasare il branch dedicato sul nuovo stato del branch principale. Questo significa riscriverne la storia riapplicando tutti i suoi commit come se fossero nati dall'attuale stato del branch principale. Per ribasare un branch dedicato su un nuovo commit genitore (es. il branch main), spostarsi sul branch dedicato, dopodiché:

$ git rebase <newparent> # es. git rebase main

git non è sempre in grado di capire da quale commit è inizialmente nato il branch dedicato corrente, per cui potrebbe tentare di applicare su main anche altri commit precedenti alla diramazione - specialmente se si è riscritta la storia tramite altri rebase in qualche altro momento. Per suggerire a git qual è il commit genitore in maniera esplicita, utilizzare invece questo comando:

$ git rebase --onto <newparent> <oldparent>

Un modo esplicito per controllare cosa git abbia intenzione di fare dei nostri commit prima che provi davvero ad applicarli, è utilizzare la funzionalità di rebase interattivo, che, appunto, permette di vedere i commit che stanno per essere applicati - ed eventualmente di correggere al volo il loro numero, ordine e anche messaggio e contenuto, se proprio necessario. Per il rebase interattivo, utilizzare l'opzione --interactive (eventualmente combinata con le altre di cui sopra):

$ git rebase --interactive <newparent>

Nel caso in cui il branch su cui si sta ribasando sia cambiato in maniera sostanziale rispetto alla condizione originale da cui si è partiti (es. un altro membro del team ha cambiato le stesse linee nello stesso file), quando git applicherà il vostro commit problematico, si genererà un conflitto, che andrà corretto manualmente.

I conflitti sono contrassegnati nei file problematici con la seguente sintassi:

<<<<<
contenuto nella nuova testa su cui si sta ribasando
=====
contenuto originale sulla vecchia testa su cui si era basati
=====
contenuto nel commit problematico
>>>>>

Il contenuto dei file problematici può essere aggiustato in vari modi:

- modificando manualmente il file
- facendosi utilizzare da un merge tool (per vedere quelli supportati e installati usare git mergetool --tool-help)
- utilizzando esplicitamente una delle due versioni, ignorando il conflitto (eg. sovrascrivendo esplicitamente le proprie modifiche, o le modifiche dell'altro)

Per scegliere esplicitamente una versione ignorando totalmente le modifiche dell'altro:

$ git checkout --theirs path/file # se si ritiene che il commit che si sta applicando sia la versione corretta, essenzialmente sovrascrivendo le modifiche degli altri
$ git checkout --ours path/file # se si ritiene che invece il commit che si sta applicando non sia corretto, essenzialmente confermando che il branch su cui si sta ribasando contenga la versione corretta, sovrascrivendo le proprie modifiche

Una volta modificato il contenuto problematico del/dei file in uno dei modi sopra suggeriti, si può continuare col rebase:

$ git add file_problematici
$ git rebase --continue

Questo può chiaramente generare altri conflitti nei file successivi, che andranno risolti, finché il rebase non è completo.

Per interrompere un rebase a metà e ripartire da capo, utilizzare il comando per abortire:

$ git rebase --abort

Blame

Per scoprire chi è stato l'ultimo che ha cambiato un pezzo di codice:

git blame path/file.c

A volte può capitare può capitare che tutte le ultime modifiche siano assegnate alla stessa persona. Questo, per esempio, avviene quando si fanno commit "batch" di riorganizzazione di tutto il repository, es. formattazione automatica di tutto il codice. Si può istruire git per ignorare questo tipo di commit quando si va a fare il blame.

Creare un file .git-blame-ignore-revs e popolarlo con l'elenco degli hash dei commit da ignorare, esempio:

930b609250f7bf0361ede392432f95b9b2a78fb2
7819dbca705d92d1551e58a82dd2ae9881c90e52

Dopodiché, istruire git per utilizzarlo:

git config blame.ignoreRevsFile .git-blame-ignore-revs

Fondere più commit in uno solo

La storia attuale. In blu i commit che si vogliono squashare in uno solo.

59e5834 moved files
37af4b7 added README.md and LICENSE file
cd37805 repository structured as an arduino library
ac95564 added comments for UDP part

Si sceglie l'ultimo commit appena prima:

git rebase --interactive ac95564

Si apre automaticamente l'editor, e si vedono i commit (in ordine inverso). Sostituire pick (p) con squash (s) per fondere i commit assieme, eccetto su quello più in alto.

Quando si chiude l'editor, si apre una nuova finestra dove è possibile editare il nuovo messaggio di commit.

Per annullare il rebase in corso, cancellare tutto il contenuto dell'editor e salvare.

Dividere un commit in più parti

La funzionalità di rebase può essere utilizzata anche per riscrivere dei commit già esistenti, e aggiungerne di nuovi.

Scegliere il commit che si vuole suddividere, poniamo che sia ac95564.

git rebase --interactive ac95564

Nell'editor, sostituire pick con edit in corrispondenza del commit che si desidera modificare, dopodiché avviare il rebase chiudendo l'editor. Ad un certo punto, il rebase si interrompe, in corrispondenza del commit scelto, che può quindi essere modificato.

Prima togliere le modifiche dalla staging area con:

git reset HEAD~

Dopo riaggiungere ciò che si desidera aggiungere al primo commit, e committare, poi procedere col secondo, e così via. Esempio:

git add <file1>
git commit
git add <file2> --patch
git commit
git add <file2>
git commit

Una volta soddisfatti:

git rebase --continue

Ignorare spazi bianchi

A volte capita di trovare codice scritto con editor non degni di questo nome, che non eliminano gli spazi vuoti in fondo alle righe. Per evitare di suscitare le ire degli scellerati manutentori, ed evitare di riempire il commit con centinaia di modifiche di spazi vuoti, usare:

git diff -U0 -w --no-color | git apply --cached --ignore-whitespace --unidiff-zero -

Eliminare branch

  • Locale
git branch -d branchname
  • Remoto
git push origin --delete branchname

Copie locali multiple

Quando si deve operare contemporaneamente su più di un branch dello stesso progetto è comodo clonare più volte il repository in cartelle separate, così da fare checkout come meglio si crede. Ma così facendo si creano due working copy indipendenti, con cartelle .git duplicate e branch locali a rigori differenti.

git worktree permette di effettuare, appunto, dei checkout in cartelle diverse dal progetto principale ma afferenti ad una stessa .git. Oltre a ridurre l'occupazione di disco, le due copie locali condivideranno l'albero dei commit rendendo più agevoli le operazioni di merge/log.

Per creare una nuova copia locale (linked working tree) associata ad un certo branch

$ git worktree add path [branch]

Se non si specifica il branch ne viene creato uno nuovo, a partire dal commit corrente, prendendo il nome dalla directory di destinazione specificata nel path. Per evitare confusione, specificare un path out-of-tree, per esempio:

$ cd myrepo_master
$ git worktree add ../myrepo_branch

Una volta terminato il lavoro sul worktree separato, per rimuoverlo non eliminare direttamente la directory ad esso associata, ma utilizzare l'apposito comando git, così da mantenere consistente lo stato di .git.

$ git worktree delete ../myrepo_branch

Per approfondire, consultare la guida ufficiale.

Trovare il commit che introduce un bug

A volte si sa che il software al commit taggato X funziona perfettamente, mentre a un successivo tag Y, presenta un bug. Il numero di commit tra X e Y può anche essere grande, ma tramite una ricerca binaria, in pochi passaggi è possibile identificare il commit che introduce il problema, avendo git che propone dei commit da testare, e fornendogli noi informazioni sulla loro bontà di funzionamento. Per farlo, si utilizza il comando git bisect.

Per avviare una sessione di git bisect:

git bisect start
git bisect good X
git bisect bad Y

A questo punto git sceglierà automaticamente il commit che cade nel mezzo (chiamiamolo commit M), e chiederà di provarlo. Una volta provato il software al commit M, saremo in grado di stabilire se ha il bug o meno, e lo si indicherà a git dando di nuovo un comando:

git bisect good HEAD

oppure

git bisect bad HEAD

a seconda che il commit M a cui ci troviamo sia buono, oppure presenti il bug.

Una volta dato il nuovo comando di git bisect, git sceglierà un nuovo commit N, di nuovo a metà tra i due commit estremi "buono" e "cattivo", dimezzando nuovamente lo spazio di ricerca: il commit andrà provato, e si continuerà a comunicare a git la sua bontà o meno, dando nuovi comandi git bisect good/bad.

Questi passaggi di bisect e test vanno ripetuti "un po' di volte", finché git non avrà sezionato tutti i commit tra X e Y, e a quel punto comunicherà quale è il primo commit che introduce il problema.

Il bug è adesso ben circoscritto e può essere studiato.

Quando si è trovato il commit incriminato, terminare la sessione di git bisect con:

git bisect reset

La ricerca avrà richiesto, al massimo, di controllare <math>log_{2}(C)</math> commit diversi, dove C è il numero di commit che separa X da Y.

Gestire files di grandi dimensioni (git lfs)

Git nasce con l'obiettivo di gestire dei files di codice sorgente, quindi plain-text; per questo non è molto efficace nella gestione di files contenenti dati in formato diverso quali possono essere, ad esempio, jpeg, png, pdf ecc.

Per ovviare a questa carenza sono stati sviluppati vari strumenti da installare come add-on; uno di questi è git-lfs

Installazione

Su sistemi Debian-derivati:

# apt-get install git-lfs

Su ArchLinux:

# pacman -S git-lfs

Su Windows: è già presente nell'installazione standard di git

Attivazione

Per poter usare git-lfs occorre che questo sia "attivato".

Attivazione globale a livello utente

Attiva git-lfs globalmente, questo significa che potrà essere usato in ogni repository:

git lfs install

L'attivazione locale è necessaria solo la prima volta.

Attivazione per un singolo repository

È possibile attivare git-lfs anche a livello di singolo repository:

git lfs install --local

Disattivazione globale a livello utente

È possibile disattivare globalmente git-lfs:

git lfs uninstall

in questo caso se si volesse tornare ad usarlo occorrerà attivarlo nuovamente.

Disattivazione per singolo repository

Se attivato per singolo repository è possibile disattivarlo

git lfs uninstall --local

Gestire i files da tracciare

Occorre impostare quali file (o tipi di file) tracciare. Se, ad esmpio, si volessero tracciare tutti i files di tipo pdf:

git lfs track "*.pdf"

Questo comando aggiunge le informazioni al file .gitattributes; occorre quindi ricordarsi di fare il commit di questo file (git add .gitattributes).

A questo punto è possibile lavorare normalmente, i commit e push riguardanti files che abbiamo definito tracciati da git-lfs saranno gestiti in modo trasparente.

Comandi aggiunti da git-lfs

Elenco dei comanti aggiunti da git-lfs:

git lfs --help