Close

Git refs: An overview

Scopri le basi di Git con questo tutorial a tema spaziale.

I tanti modi di fare riferimento a un commit

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:

Database
materiale correlato

Come spostare un repository Git completo

Logo di Bitbucket
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 a HEAD prima che venga sottoposto a modifiche drastiche.
  • MERGE_HEAD: i commit sottoposti a merge nel branch corrente con git 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 è il branch di origine nel repository locale e il parametro è il branch di destinazione nel repository remoto. Il segno + 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.

Accesso ai commit utilizzando riferimenti relativi

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 in main.
  • 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.

Le persone collaborano utilizzando una parete piena di strumenti

Blog di Bitbucket

Illustrazione su Devops

Percorso di apprendimento DevOps

Funzione Demo Den per demo con esperti Atlassian

Come Bitbucket Cloud funziona con Atlassian Open DevOps

Iscriviti alla nostra newsletter DevOps

Thank you for signing up