Guida

I branch in Git: git branch, creare, cambiare ed eliminare un branch

Aggiornato: · Skillaria.Top

Un branch in Git è un puntatore leggero a un commit, e HEAD indica su quale branch vi trovate ora. git branch elenca, crea, rinomina ed elimina i branch, mentre git switch passa dall'uno all'altro; git switch -c nome crea un branch e ci si sposta subito.

Cos'è un branch in Git

Un branch in Git non è una copia del progetto, ma un piccolo file con l'hash di un singolo commit. Creare un branch richiede una frazione di secondo e quasi nessuno spazio, perché Git non duplica nulla: tutti i branch puntano agli stessi oggetti nel database del repository. Quando fate un commit, il puntatore del branch corrente si sposta semplicemente sul nuovo commit.

Questo cambia il modo di vedere i branch. Non bisogna averne paura né risparmiarli: un branch per ogni task, correzione o esperimento è una pratica normale. Se l'esperimento non riesce, il branch si elimina e la storia principale resta pulita.

Che un branch sia un file lo potete vedere con i vostri occhi:

$ cat .git/refs/heads/main
8b7d4e2a9f1c0d3e5b6a7c8d9e0f1a2b3c4d5e6f

(Nei repository grandi parte dei riferimenti è compattata in .git/packed-refs, quindi il file potrebbe non esserci: la sostanza non cambia.)

HEAD in Git: dove vi trovate ora

HEAD è il puntatore a ciò che in questo momento è «estratto» nella working directory. Di solito HEAD non punta a un commit, ma a un branch:

$ cat .git/HEAD
ref: refs/heads/main

La catena è questa: HEAD → main → commit. Il nuovo commit avrà come genitore il commit a cui guarda HEAD, dopodiché Git sposterà il branch a cui punta HEAD. Per questo i commit «finiscono» nel branch corrente.

Se vi spostate direttamente su un commit o un tag (git switch --detach v1.0 o git checkout 3f2a1c9), HEAD punterà direttamente al commit. Questo stato si chiama detached HEAD. Guardare il codice così è sicuro, ma i nuovi commit non sono legati ad alcun branch ed è facile perderli dopo un cambio. Se avete iniziato a modificare qualcosa, create un branch proprio lì: git switch -c fix-from-old-version.

Nelle espressioni HEAD è comodo come punto di riferimento: HEAD~1 è il commit precedente, HEAD~3 tre commit fa, HEAD^2 il secondo genitore di un commit di merge.

Come vedere i branch: git branch

git branch            # branch locali, quello corrente è segnato con *
git branch -v         # più l'ultimo commit di ciascuno
git branch -vv        # più upstream e ritardo/anticipo
git branch -r         # branch remoti (origin/...)
git branch -a         # tutti i branch
git branch --show-current   # solo il nome del branch corrente

Esempio di output di git branch -vv:

  feature/login 4c1e2d7 [origin/feature/login: ahead 2] Form di login
* main          8b7d4e2 [origin/main] Aggiorna il README
  old-spike     1a9b3c0 Esperimento con la cache

Si vede che in feature/login ci sono due commit non inviati, main coincide con il server, mentre old-spike non ha nessun corrispondente sul server.

Per filtrare i branch aiutano git branch --merged (già uniti al corrente: candidati all'eliminazione), git branch --no-merged (non ancora uniti) e git branch --list 'feature/*' (per pattern).

Come creare un branch

Ci sono due strade. git branch crea il branch ma vi lascia su quello attuale:

git branch feature/login            # dal commit corrente
git branch hotfix v1.2.0            # da un tag
git branch review 3f2a1c9           # da un commit specifico

Più spesso serve creare un branch e spostarsi subito su di esso. Per questo c'è git switch -c (introdotto in Git 2.23) o il vecchio equivalente git checkout -b:

git switch -c feature/login
git switch -c hotfix origin/main    # nuovo branch dallo stato aggiornato del server

Una buona abitudine è aggiornare la base prima di creare il branch per un task: git fetch, poi git switch -c feature/x origin/main. Così il branch parte dal codice attuale e non dal vostro main locale non aggiornato da tempo.

Come chiamare i branch

Convenzioni diffuse nei team: un prefisso per tipo di lavoro e una breve descrizione con trattini, come feature/user-avatar, fix/login-timeout, chore/update-deps, a volte con il numero del ticket: feature/SHOP-142-cart. Non si possono usare spazi, ~ ^ : ? * [, due punti consecutivi né la terminazione .lock.

Come cambiare branch: git switch

git switch main             # passare a un branch esistente
git switch -                # tornare al branch precedente
git switch feature/login    # se non esiste in locale ma c'è origin/feature/login,
                            # Git crea quello locale e lo collega al remoto

Al cambio di branch Git sostituisce i file della working directory con la versione del branch di destinazione. Le modifiche non committate vengono portate con voi, se non sono in conflitto con il branch di destinazione. Se lo sono, Git rifiuta:

error: Your local changes to the following files would be overwritten by checkout:
        app.js
Please commit your changes or stash them before you switch branches.

Soluzioni: fare commit, mettere da parte con git stash (e riprendere poi con git stash pop) oppure portare consapevolmente le modifiche tentando un merge: git switch -m main. L'opzione --discard-changes butta via le modifiche: usatela solo se siete sicuri che non vi servono.

Perché git switch e non checkout? Checkout sa sia cambiare branch sia sovrascrivere file, e per questo è facile sbagliare. Switch fa una cosa sola, lavora con i branch, quindi è più sicuro. Un confronto dettagliato tra checkout, switch e restore si trova in un articolo dedicato.

Come rinominare un branch

git branch -m new-name              # rinominare quello corrente
git branch -m old-name new-name     # rinominarne uno qualsiasi
git branch -M main                  # lo stesso, sovrascrivendo un branch esistente

Se il branch era già sul server, la rinomina è solo locale. Inviate quello nuovo ed eliminate quello vecchio:

git push -u origin new-name
git push origin --delete old-name

Come eliminare un branch

git branch -d feature/login     # elimina solo se il branch è stato unito
git branch -D old-spike         # elimina forzatamente
git push origin --delete feature/login   # eliminare sul server

-d protegge dalla perdita di lavoro: se nel branch ci sono commit che non sono nel branch corrente (o nel suo upstream), Git rifiuta con il messaggio «The branch 'x' is not fully merged». -D elimina senza domande. Il branch corrente non si può eliminare: prima spostatevi su un altro.

Avete eliminato troppo? I commit non spariscono subito. Trovate l'hash dell'ultimo commit del branch in git reflog e ripristinatelo: git branch old-spike 1a9b3c0.

I riferimenti obsoleti a branch remoti che non esistono più sul server si puliscono con git fetch --prune.

Collegare un branch locale a quello remoto

Un branch locale può avere un upstream, cioè un branch remoto con cui viene confrontato e sincronizzato. Serve perché git push e git pull funzionino senza argomenti e git status mostri «ahead/behind».

git push -u origin feature/login                 # al primo invio
git branch --set-upstream-to=origin/main main    # per un branch esistente
git branch --unset-upstream                      # rimuovere il collegamento

Unire i branch: in breve

I branch si creano per poi unirli. Quando il lavoro è pronto, spostatevi sul branch di destinazione e fateci il merge del vostro:

git switch main
git merge feature/login

Se main non è andato avanti da quando è stato creato il branch, Git fa un fast-forward: sposta semplicemente il puntatore di main sull'ultimo commit del branch, senza commit di merge. Se entrambi i branch hanno nuovi commit, avviene un merge a tre vie: Git trova l'antenato comune, confronta i due lati e crea un commit di merge con due genitori. Nei team il merge di solito si fa con una pull request sul server, e in locale ci si limita a scaricare il main aggiornato.

Promemoria sui branch

OperazioneComando
Elenco dei branchgit branch -vv
Creare un branchgit branch nome
Creare e spostarsigit switch -c nome
Cambiare branchgit switch nome
Tornare indietrogit switch -
Rinominaregit branch -m vecchio nuovo
Eliminare se unitogit branch -d nome
Eliminare forzatamentegit branch -D nome
Eliminare sul servergit push origin --delete nome
Branch eliminabiligit branch --merged
Ripristinare un branch eliminatogit reflog, poi git branch nome hash

Errori tipici dei principianti

  • Lavorare direttamente su main. Se i commit sono già fatti ma non inviati, create un branch nel punto attuale (git branch feature/x), poi riportate main allo stato del server: git reset --hard origin/main (verificate che sia tutto committato).
  • Branch da un main vecchio. Prima git fetch, poi create il branch da origin/main.
  • Commit in detached HEAD. Prima di cambiare, create un branch; se avete già cambiato, trovate i commit in git reflog.
  • Branch di lunga durata. Più a lungo un branch vive separato, più doloroso sarà il merge. Integrate regolarmente main e unite il lavoro a piccole porzioni.

I branch nel lavoro di squadra

I processi variano da team a team, ma quasi ovunque si trova uno di due schemi. In GitHub Flow c'è un solo branch di lunga durata, main, sempre pronto per il rilascio, e branch feature brevi che vengono uniti tramite pull request dopo la review e i controlli della CI. In Git Flow si aggiungono develop, i branch di release e quelli di hotfix; lo schema è più pesante e adatto a prodotti con rilasci versionati poco frequenti.

Indipendentemente dallo schema valgono regole comuni: un branch, un task; un branch vive giorni, non mesi; prima della pull request il branch è aggiornato con il main recente; dopo il merge il branch si elimina sia in locale sia sul server. Il branch principale si protegge con le impostazioni del repository: push diretto e force push vietati, merge solo tramite review.

Come vedere la storia dei branch

Il modo più semplice per capire come i branch si relazionano è il grafo: git log --oneline --graph --all disegna tutti i branch e i loro punti di divergenza direttamente nel terminale. Per vedere i commit presenti in un branch ma non ancora in main usate git log main..feature/login, e per la differenza nei file git diff main...feature/login (i tre punti confrontano con l'antenato comune, cioè mostrano solo le modifiche del branch stesso).

In sintesi

Un branch in Git è un puntatore economico a un commit, HEAD è il puntatore al branch corrente. git branch gestisce i branch (elenco, creazione, rinomina, eliminazione), git switch passa dall'uno all'altro e git switch -c fa entrambe le cose insieme. Branch, HEAD, fast-forward e merge a tre vie sono trattati con esempi reali nel corso su Git.

Domande frequenti

Quanti branch si possono creare in Git?

Praticamente quanti se ne vuole: ogni branch è un riferimento di qualche decina di byte, quindi non ci sono limiti di spazio.

Eliminando un branch si eliminano anche i commit?

Non subito. I commit a cui non punta più nulla restano conservati ancora per un po' e sono accessibili tramite git reflog, finché il garbage collector non li rimuove (di default non prima di 30 giorni per le voci di reflog irraggiungibili).

Come scopro da quale branch è stato creato quello corrente?

Git non lo memorizza: un branch ricorda solo il proprio commit. L'antenato comune con main si trova con il comando git merge-base HEAD main.

Che differenza c'è tra un branch e un tag?

Un branch avanza a ogni nuovo commit, mentre un tag resta fisso per sempre su un commit e si usa per segnare le release.

Perché main e non master?

È solo il nome del branch predefinito. Molti servizi sono passati a main; il nome per i nuovi repository si imposta con git config --global init.defaultBranch main.

Impara l’argomento in video

Questo argomento è trattato in modo approfondito, con pratica, nel corso «Git da zero a sviluppatore sicuro».

Vai al corso