Git refs: An overview
Scopri le basi di Git con questo tutorial a tema spaziale.
Comprendendo i numerosi modi di fare riferimento a un commit, tutti questi comandi diventano molto più potenti. In questo capitolo, faremo luce sul funzionamento interno di comandi comuni come git checkout
, git branch
e git push
esplorando i numerosi metodi che consentono di fare riferimento a un commit.
Vedremo anche come ripristinare i commit apparentemente "persi" accedendovi tramite il meccanismo reflog di Git.
Hash
Il modo più diretto per fare riferimento a un commit è tramite il relativo hash SHA-1. Funge da ID univoco per ogni commit. Puoi trovare l'hash di tutti i tuoi commit nell'output di git log
.
commit 0c708fdec272bc4446c6cabea4f0022c2b616eba Author: Mary Johnson Date: Wed Jul 9 16:37:42 2014 -0500 Some commit message
Quando passi il commit ad altri comandi Git, devi solo specificare un numero sufficiente di caratteri per identificare in modo univoco il commit. Ad esempio, puoi controllare il commit precedente con git show
eseguendo il seguente comando:
git show 0c708f
A volte è necessario risolvere un branch, un tag o un altro riferimento indiretto nell'hash di commit corrispondente. A tale scopo, puoi utilizzare il comando git rev-parse
. Quanto segue restituisce l'hash del commit a cui punta il branch principale
:
materiale correlato
Come spostare un repository Git completo
Scopri la soluzione
Impara a utilizzare Git con Bitbucket Cloud
git rev-parse main
È particolarmente utile quando si scrivono script personalizzati che accettano un riferimento al commit. Invece di analizzare manualmente il riferimento al commit, puoi affidare a git rev-parse
la normalizzazione dell'input.
Riferimenti
Un riferimento rappresenta un modo indiretto di fare riferimento a un commit. Puoi considerarlo un alias intuitivo per un hash di commit. Questo è il meccanismo interno di Git per rappresentare branch e tag.
I riferimenti sono archiviati come normali file di testo nella directory .git/refs
, dove il file denominato .git
di solito è chiamato .git
. Per esplorare i riferimenti in uno dei tuoi repository, vai a .git/refs
. Dovresti vedere la seguente struttura, che tuttavia contiene file diversi a seconda dei branch, dei tag e dei remoti presenti nel repository:
.git/refs/ heads/ main some-feature remotes/ origin/ main tags/ v0.9
La directory head
definisce tutti i branch locali nel repository. Ogni nome di file corrisponde al nome del branch corrispondente e all'interno del file è presente un hash di commit, che è la posizione della punta del branch. Per verificarlo, prova a eseguire i due comandi seguenti dalla directory principale del repository Git:
# Output the contents of `refs/heads/main` file: cat .git/refs/heads/main # Inspect the commit at the tip of the `main` branch: git log -1 main
L'hash di commit restituito dal comando cat
deve corrispondere all'ID di commit visualizzato da git log
.
Per cambiare la posizione del branch main
, tutto ciò che Git deve fare è cambiare il contenuto del file refs/heads/main
. Allo stesso modo, per creare un nuovo branch è sufficiente scrivere un hash di commit in un nuovo file. Questo è uno dei motivi per cui i branch Git sono così leggeri rispetto a SVN.
La directory tags
funziona esattamente allo stesso modo, ma contiene tag anziché branch. La directory remotes
elenca tutti i repository remoti che hai creato con git remote
come sottodirectory separate. All'interno di ciascun repository troverai tutti i branch remoti che sono stati recuperati nel repository.
Specifying refs
Quando passi un riferimento a un comando Git, puoi definire il nome completo del riferimento o utilizzare un nome breve e lasciare che sia Git a eseguire la ricerca di un riferimento corrispondente. Dovresti già avere familiarità con i nomi brevi per i riferimenti, poiché sono quelli che utilizzi ogni volta che fai riferimento a un branch per nome.
git show some-feature
L'argomento some-feature
nel comando riportato sopra è in realtà un nome abbreviato per il branch. Git lo risolve in refs/heads/some-feature
prima di utilizzarlo. Puoi anche specificare il riferimento completo nella riga di comando, come indicato di seguito:
git show refs/heads/some-feature
In questo modo eviti qualsiasi ambiguità riguardo alla posizione del riferimento. È necessario, ad esempio, se fossero presenti sia un tag sia un branch denominati entrambi some-feature
. Tuttavia, se utilizzi convenzioni di denominazione corrette, l'ambiguità tra tag e branch in genere non dovrebbe costituire un problema.
Esamineremo altri nomi di riferimento completi nella sezione Specifiche dei riferimenti.
Packed refs
Per i repository di grandi dimensioni, Git esegue periodicamente una garbage collection per rimuovere oggetti non necessari e comprime i riferimenti in un unico file per ottimizzare le prestazioni. Puoi forzare questa compressione con il comando garbage collection:
git gc
Questo comando sposta tutti i singoli file dei branch e dei tag presenti nella cartella refs
in un unico file denominato packed-refs
, che si trova in cima alla directory .git
. Se apri questo file, troverai una mappatura degli hash di commit ai riferimenti:
00f54250cf4e549fdfcafe2cf9a2c90bc3800285 refs/heads/feature 0e25143693cfe9d5c2e83944bbaf6d3c4505eb17 refs/heads/main bb883e4c91c870b5fed88fd36696e752fb6cf8e6 refs/tags/v0.9
All'esterno, la normale funzionalità di Git non sarà in alcun modo interessata. Tuttavia, se ti stai chiedendo perché la cartella .git/refs
è vuota, sappi che i riferimenti sono finiti in questo file.
Special refs
Oltre alla directory refs
, sono presenti alcuni riferimenti speciali che risiedono nella directory .git
. Sono elencati di seguito:
HEAD
: il commit/branch attualmente sottoposto a checkout.FETCH_HEAD
: il branch recuperato più di recente da un repository remoto.ORIG_HEAD
: un riferimento di backup aHEAD
prima che venga sottoposto a modifiche drastiche.MERGE_HEAD
: i commit sottoposti a merge nel branch corrente congit merge
.CHERRY_PICK_HEAD
: il commit per il quale hai eseguito il cherry-pick.
Questi riferimenti sono tutti creati e aggiornati da Git quando necessario. Ad esempio, il comando git pull
esegue prima git fetch
, che aggiorna il riferimento FETCH_HEAD
. Quindi, esegue git merge FETCH_HEAD
per completare il checkout dei rami recuperati nel repository. Naturalmente, puoi utilizzarli tutti come qualsiasi altro riferimento, come sicuramente hai fatto con HEAD
.
Il contenuto di questi file è diverso a seconda del tipo e dello stato del repository. Il riferimento HEAD
può contenere un riferimento simbolico, che è semplicemente un riferimento a un altro riferimento anziché un hash di commit, o un hash di commit. Ad esempio, dai un'occhiata al contenuto di HEAD
quando sei nel branch main
:
git checkout main cat .git/HEAD
L'output sarà ref: refs/heads/main
, il che significa che HEAD
punta al riferimento refs/heads/main
. È in questo modo che Git sa che è stato effettuato il checkout del branch main
. Se dovessi passare a un altro branch, il contenuto di HEAD
verrebbe aggiornato per riflettere il nuovo branch. Tuttavia, se dovessi effettuare il checkout di un commit invece che di un branch, HEAD
conterrebbe un hash di commit invece di un riferimento simbolico. È in questo modo che Git sa di essere in uno stato HEAD scollegato.
Nella maggior parte dei casi, HEAD
è l'unico riferimento che utilizzerai direttamente. Gli altri in genere sono utili solo per la scrittura di script di livello inferiore che devono eseguire l'hook ai meccanismi interni di Git.
Specifiche dei riferimenti
Una specifica di riferimento mappa un branch nel repository locale a un branch in un repository remoto. In questo modo è possibile gestire i branch remoti utilizzando i comandi Git locali e configurare alcuni comportamenti git push
e git fetch
avanzati.
Una specifica di riferimento è specificata come [+]
<src>
:
<dst>
. Il parametro +
opzionale serve a forzare l'esecuzione di un aggiornamento non rapido da parte del repository remoto.
Le specifiche di riferimento possono essere utilizzate con il comando git push
per assegnare un nome diverso al branch remoto. Ad esempio, il comando seguente effettua il push del branch main
al repository remoto origin
come un normale comando git push
, ma utilizza qa-main
come nome del branch nel repository origin
. È una funzione utile per i team di controllo di qualità che devono eseguire il push dei propri branch a un repository remoto.
git push origin main:refs/heads/qa-main
You can also use refspecs for deleting remote branches. This is a common situation for feature-branch workflows that push the feature branches to a remote repo (e.g., for backup purposes). The remote feature branches still reside in the remote repo after they are deleted from the local repo, so you get a build-up of dead feature branches as your project progresses. You can delete them by pushing a refspec that has an empty parameter, like so:
git push origin :some-feature
Si tratta di un comando molto pratico perché ti permette di non accedere al tuo repository remoto per eliminare manualmente il branch remoto. Tieni presente che a partire da Git v1.7.0 puoi usare il flag --delete
invece del metodo precedente. Il comando riportato di seguito avrà lo stesso effetto del comando precedente:
git push origin --delete some-feature
Aggiungendo alcune righe al file di configurazione Git, puoi utilizzare le specifiche di riferimento per modificare il comportamento di git fetch
. Per impostazione predefinita, git fetch
recupera tutti i branch nel repository remoto. Il motivo è indicato nella seguente sezione del file .git/config
:
[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/*:refs/remotes/origin/*
La riga fetch
indica a git fetch
di scaricare tutti i branch dal repository origin
. Tuttavia, alcuni flussi di lavoro non li richiedono tutti. Ad esempio, molti flussi di lavoro di continuous integration utilizzano solo il branch main
. Per recuperare solo il branch main
, modifica la riga fetch
in modo che corrisponda a quanto segue:
[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/main:refs/remotes/origin/main
Puoi anche configurare git push
in modo simile. Ad esempio, se vuoi eseguire il push di un branch main
a qa-main
nell'elemento remoto origin
(come abbiamo fatto sopra), devi modificare il file di configurazione come segue:
[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/main:refs/remotes/origin/main push = refs/heads/main:refs/heads/qa-main
Le specifiche di riferimento ti offrono il controllo completo sul modo in cui i vari comandi Git trasferiscono i branch tra repository. Ti consentono di rinominare ed eliminare i branch dal repository locale, effettuare il recupero/il push a branch con nomi diversi e configurare git push
e git fetch
in modo che funzionino solo con i branch che desideri.
Relative refs
Puoi anche fare riferimento ai commit relativi a un altro commit. Il carattere ~
ti consente di raggiungere i commit principali. Ad esempio, il comando seguente mostra l'elemento principale di HEAD
:
git show HEAD~2
Tuttavia, quando si utilizzano i commit di merge, le cose si complicano un po'. Poiché i commit di merge hanno più di un elemento principale, puoi seguire più di un percorso. Per i merge a 3 vie, il primo elemento principale proviene dal branch in cui ti trovavi quando hai eseguito il merge e il secondo elemento principale proviene dal branch che hai passato al comando git merge
.
Il carattere ~
segue sempre il primo elemento principale di un commit di merge. Se vuoi seguire un elemento principale diverso, devi specificare quale con utilizzando il carattere ^
. Ad esempio, se HEAD
è un commit di merge, il comando riportato di seguito restituisce il secondo elemento principale di HEAD
.
git show HEAD^2
Puoi utilizzare più di un carattere ^
per spostare più di una generazione. Ad esempio, il comando riportato di seguito mostra l'elemento principale di HEAD
(supponendo che si tratti di un commit di merge) che si basa sul secondo elemento principale.
git show HEAD^2^1
Per chiarire come funzionano ~
e ^
, la figura seguente mostra come raggiungere qualsiasi commit da A
utilizzando riferimenti relativi. In alcuni casi, è possibile raggiungere un commit in diversi modi.
I riferimenti relativi possono essere utilizzati con gli stessi comandi che è possibile utilizzare con un riferimento normale. Ad esempio, tutti i comandi riportati di seguito utilizzano un riferimento relativo:
# Only list commits that are parent of the second parent of a merge commit git log HEAD^2 # Remove the last 3 commits from the current branch git reset HEAD~3 # Interactively rebase the last 3 commits on the current branch git rebase -i HEAD~3
The reflog
Il reflog è la rete di sicurezza di Git. Registra quasi tutte le modifiche apportate al tuo repository, indipendentemente dal fatto che tu abbia eseguito una snapshot o meno. Puoi considerarlo come una cronologia di tutte le operazioni effettuate nel repository locale. Per visualizzare il reflog, esegui il comando git reflog
. L'output dovrebbe avere il seguente aspetto:
400e4b7 HEAD@{0}: checkout: moving from main to HEAD~2 0e25143 HEAD@{1}: commit (amend): Integrate some awesome feature into `main` 00f5425 HEAD@{2}: commit (merge): Merge branch ';feature'; ad8621a HEAD@{3}: commit: Finish the feature
In altre parole:
- Hai appena effettuato il checkout di
HEAD~2
. - Prima di questa operazione hai modificato un messaggio di commit.
- Prima di questa operazione hai effettuato il merge del branch
feature
inmain
. - Prima di questa operazione hai effettuato il commit di una snapshot.
La sintassi HEAD{
ti consente di fare riferimento ai commit memorizzati nel reflog. Funziona in modo molto simile ai riferimenti HEAD~
della sezione precedente, ma
si riferisce a una voce nel reflog invece che alla cronologia dei commit.
Puoi utilizzarlo per effettuare il revert a uno stato che altrimenti andrebbe perduto. Ad esempio, supponiamo che tu abbia appena eliminato una nuova funzionalità con git reset
. Il reflog dovrebbe avere il seguente aspetto:
ad8621a HEAD@{0}: reset: moving to HEAD~3 298eb9f HEAD@{1}: commit: Some other commit message bbe9012 HEAD@{2}: commit: Continue the feature 9cb79fa HEAD@{3}: commit: Start a new feature
I tre commit prima di git reset
ora sono sospesi, il che significa che non è possibile utilizzarli come riferimento, tranne che attraverso il reflog. Ora, supponiamo che tu ti renda conto che buttare via tutto il tuo lavoro sia stato un errore. Tutto ciò che devi fare è effettuare il checkout del commit HEAD@ {1}
per tornare allo stato del repository prima che eseguissi git reset
.
git checkout HEAD@{1}
In questo modo, HEAD
è nello stato scollegato. Da qui, puoi creare un nuovo branch e continuare a lavorare sulla funzione.
Riepilogo
Ora dovresti aver acquisito una discreta conoscenza dell'utilizzo dei riferimenti ai commit in un repository Git. Abbiamo visto in che modo branch e tag vengono memorizzati come riferimenti nella sottodirectory .git
, come leggere un file packed-refs
, in che modo è rappresentato HEAD
, come utilizzare le specifiche dei riferimenti per operazioni avanzate di push e recupero, e come utilizzare i relativi operatori ~
e ^
per attraversare una gerarchia di branch.
Abbiamo anche dato un'occhiata al reflog, che rappresenta un modo per fare riferimento a commit non disponibili in nessun altro modo. È ideale per eseguire il ripristino da quelle piccole situazioni in cui ti rendi conto di aver commesso un errore.
Con questo articolo abbiamo voluto offrire gli strumenti giusti per poter scegliere esattamente il commit necessario in un determinato scenario di sviluppo. È molto facile sfruttare le competenze apprese in questo articolo rispetto alle tue conoscenze di Git esistenti, poiché alcuni dei comandi più comuni, tra cui git log
, git show
, git checkout
, git reset
, git revert
, git rebase
e molti altri, accettano i riferimenti come argomenti.
Condividi l'articolo
Argomento successivo
Letture consigliate
Aggiungi ai preferiti queste risorse per ricevere informazioni sui tipi di team DevOps e aggiornamenti continui su DevOps in Atlassian.