Skip to main content
  1. Comandi/

git - sistema di controllo versione distribuito

·13 mins
Alessio Barnini
Author
Alessio Barnini
Table of Contents

Cosa fa
#

Sistema di version control distribuito. Traccia modifiche ai file nel tempo, permette collaborazione, branching e rollback. Ogni sviluppatore ha una copia completa del repository, inclusa tutta la storia.

Sintassi
#

git [comando] [opzioni] [argomenti]

Comandi essenziali
#

ComandoFlagSignificato flagCosa fa
git initInizializza un nuovo repo nella cartella corr
git clone <url>Clona un repo remoto in
git statusMostra lo stato dei file (staged, modified, un
git add <file>-AallAggiunge tutti i file modificati allo st
git add -f <file>-fforceforza l'aggiunta di un file anche se è escluso da .gitignore.
git commit -m "msg"-mmessageCrea un commit c
git push-u origin mainupstreamPusha e imposta il t
git pull--rebaserebase invece di mergeAggiorna il branch locale c
git log--oneline --graphformato compatto + grafoVisualizza la
git logppatch Mostra ogni commit con il diff completo allegato — cosa è stato aggiunto (+) e cosa è stato rimosso (-) in ogni file.
git log -p | grep -i "password|secret|key|token|api" oken|api" ogni file.
git diff--stagedstagedMostra le differen
git stashpopripristinaSalva temporaneamente
git diff HASH1 HASH2Differenz
git push --forceSovrascrive la storia del remote — pe
git clone ssh://utente@host:2220/pathClone su porta SSH non standard — la port
git fetch --all --prune--prunepruneRimuove branch remoti locali che non esistono piu' sul remote
git show-ref patternMostra tutti i riferimenti che contengono il pattern

La cartella .git/
#

Important

.git/ è il repo. La cartella di lavoro sono solo i file estratti dall'ultimo commit. Se cancelli .git/ perdi tutta la storia — i file rimangono ma non sono più un repo Git.

# Struttura interna
.git/
├── HEAD          ← punta al branch corrente ("ref: refs/heads/main")
├── config        ← configurazione locale del repo (remote, user, ecc.)
├── index         ← staging area — file pronti per il prossimo commit
├── objects/      ← database di tutti i commit, tree, blob (i dati reali)
│   ├── ab/       ← primi 2 char dell'hash → subdirectory
│   └── cd/       ← ogni oggetto identificato dal suo SHA-1
├── refs/
│   ├── heads/    ← branch locali (main, feature, ecc.)
│   └── remotes/  ← branch remoti (origin/main, ecc.)
└── logs/         ← storia di tutti i movimenti di HEAD (reflog)
# Vedere a cosa punta HEAD
cat .git/HEAD

# Vedere l'hash dell'ultimo commit
cat .git/refs/heads/main

# Recuperare commit "persi" (anche dopo reset --hard)
git reflog

gitdir
#

Note

La cartella .git/ è chiamata gitdir — è la directory che contiene l'intero stato del repository. Il path del gitdir è sempre referenziato dalla variabile $GIT_DIR. Normalmente vive dentro la cartella di lavoro, ma può stare altrove (es. nei worktree o nei submodule).

# GIT_DIR può essere impostato manualmente per puntare
# a un gitdir in una posizione non standard
GIT_DIR=/path/al/gitdir git status

Cosa fa gitdir in pratica:

Il gitdir separa i metadati Git dai file di lavoro. Normalmente stanno insieme (progetto/.git/), ma possono stare separati — questo abilita scenari avanzati:

ScenarioCome funziona
Repo normale.git/ dentro la cartella di lavoro
Bare repoSolo il gitdir, nessuna cartella di lavoro — usato sui server remoti (GitHub, GitLab)
WorktreeStessa storia, due cartelle di lavoro diverse — git worktree add
SubmoduleIl gitdir del submodule vive in .git/modules/nome/ del repo padre, non dentro il submodule stesso
# Bare repo — solo il gitdir, nessun file di lavoro
# È quello che gira su GitHub dietro le quinte
git init --bare repo.git

# Verificare se sei in un bare repo
git rev-parse --is-bare-repository
Tip

Quando fai git clone di un repo privato su un server, il server ha un bare repo — solo il gitdir, nessun file estratto. Tu scarichi la storia e estrai i file nella tua cartella di lavoro.

# Vedere il path del gitdir corrente
git rev-parse --git-dir

# Output tipico:
# /Users/barno/progetti/obsidian-vault/.git

Esempio reale — gitdir fuori da iCloud:

# Problema: iCloud sincronizza .git/ → conflitti, file duplicati, corruzione repo
# Soluzione: cartella di lavoro su iCloud, gitdir fuori

# Crea il gitdir in una posizione fuori da iCloud
git init --separate-git-dir /Users/barno/gitdirs/obsidian-vault ~/Library/Mobile\ Documents/obsidian-vault

# Risultato:
# ~/Library/Mobile Documents/obsidian-vault/   ← su iCloud (file di lavoro)
#   └── .git                                   ← file di testo con path al gitdir
#                                                 NON una cartella, solo un puntatore
# /Users/barno/gitdirs/obsidian-vault/         ← fuori da iCloud (il vero gitdir)
# Il file .git nella cartella di lavoro contiene solo:
cat ~/Library/Mobile\ Documents/obsidian-vault/.git
# gitdir: /Users/barno/gitdirs/obsidian-vault
Tip

Con --separate-git-dir il .git nella cartella di lavoro non è più una cartella — è un file di testo con una sola riga che punta al gitdir reale. iCloud sincronizza solo i tuoi file, mai la storia Git.

Warning

Non committare mai file sensibili (password, token, chiavi SSH) — anche se li rimuovi nel commit successivo, restano per sempre in .git/objects/. Per rimuoverli dalla storia serve git filter-repo.

Branch
#

ComandoFlagSignificato flagCosa fa
git branchLista tutti i branch locali
git branch -a-aallLista branch locali e remoti
git branch -r-rremoteLista solo i branch remoti
git branch nomeCrea un nuovo branch
git checkout nomeSwitcha su un branch esistente
git checkout -b nome-bbranchCrea e switcha in un colpo solo
git merge nomeMergia il branch nome nel branch corrente
git branch -d nome-ddeleteElimina un branch (solo se gia' mergiato)
git branch -D nome-DDelete forzatoElimina un branch forzatamente

Rebase
#

Il rebase riscrive la storia dei commit. Invece di creare un commit di merge, "sposta" i tuoi commit sopra il branch target.

Prima del rebase — feature parte da B, main va avanti con C:

gitGraph
   commit id: "A"
   commit id: "B"
   branch feature
   checkout feature
   commit id: "D"
   commit id: "E"
   checkout main
   commit id: "C"

Dopo il rebase — i commit D ed E vengono riscritti come D' ed E' sopra C:

gitGraph
   commit id: "A"
   commit id: "B"
   commit id: "C"
   commit id: "D'"
   commit id: "E'"

Rebase interattivo — squash di 3 commit in 1:

# prima:   D───E───F    ← 3 commit separati
# dopo:    D'           ← squashati in 1 solo commit
git rebase -i HEAD~3
git checkout feature
git rebase main           # sposta i commit di feature sopra main
git rebase -i HEAD~3      # rebase interattivo: modifica/squash ultimi 3 commit
git rebase --abort        # annulla il rebase se ci sono conflitti
git rebase --continue     # continua dopo aver risolto i conflitti

Tag
#

ComandoFlagSignificato flagCosa fa
git tagLista tutti i tag del repository
git tag nomeCrea un tag leggero sul commit corrente
git tag -a nome -m "msg"-aannotatedCrea un tag annotato con messaggio
git show nomeMostra il contenuto del tag
git tag -d nome-ddeleteElimina un tag locale

I tag sono puntatori fissi a un commit — non si spostano come i branch. Usati normalmente per marcare versioni (v1.0, v2.3.1). In secret scanning vanno controllati sempre con git tag + git show <tag> — spesso dimenticati.

# Differenza tag leggero vs annotato
git tag v1.0              # leggero — solo un puntatore
git tag -a v1.0 -m "msg"  # annotato — ha metadati propri (autore, data, messaggio)

Merge vs Rebase
#

Merge — preserva la storia, crea un commit di unione:

gitGraph
   commit id: "A"
   commit id: "B"
   branch feature
   checkout feature
   commit id: "D"
   commit id: "E"
   checkout main
   commit id: "C"
   merge feature id: "M"

Rebase — riscrive la storia, lineare e pulita:

gitGraph
   commit id: "A"
   commit id: "B"
   commit id: "C"
   commit id: "D'"
   commit id: "E'"
MergeRebase
StoriaPreservata, con commit di mergeRiscritta, lineare
GrafoIntrecciatoPulito
Commit hashInvariatiCambiano (D→D')
Branch condivisi✅ Sicuro❌ Pericoloso
Branch locali✅ Ok✅ Preferibile
Traccia dell'unioneNo
ConflittiRisolti una volta nel commit MRisolti commit per commit
Tip

Rebase in privato, merge in pubblico. Finché il branch è solo tuo → rebase. Appena è condiviso → merge.

Warning

Il rebase riscrive i commit hash. Su un repo condiviso questo causa problemi — usalo solo su branch locali non ancora pushati. Se vedi commit hash che cambiano su un repo aziendale, potrebbe essere un segnale di manomissione della storia.

Force push — cosa risolve e cosa no

# Scenario: hai pushato una password per errore

# 1. Riscrivi la storia locale rimuovendo il commit con il segreto
git rebase -i HEAD~2

# 2. Sovrascrivi il remote con la storia riscritta
git push --force
# Il commit con hash 12345 non esiste piu' nella storia del remote.
# Chi fa git clone ORA non vedra' mai la password.

Ma il force push non raggiunge le copie locali esistenti:

SituazioneEffetto del force push
git clone dopo il force pushNon vede la password — storia pulita
Aveva gia' clonato primaIl commit e' in .git/objects/ sul suo disco
git pull dopo il force pushGit rifiuta — storia divergente. Deve fare git reset --hard origin/main per allinearsi, ma nel frattempo il commit e' ancora in locale
git fetch --allScarica i riferimenti aggiornati ma non tocca .git/objects/ — il vecchio commit rimane fino al prossimo git gc
Warning

Non puoi controllare le copie locali degli altri. Il force push pulisce il remote, non le macchine che hanno gia' fatto fetch. Ruota sempre la credenziale — il force push e' igiene, non sicurezza.

Cherry-pick
#

Prende un singolo commit da un altro branch e lo applica sul branch corrente, senza fare merge di tutto il branch.

gitGraph
   commit id: "A"
   commit id: "B"
   branch feature
   checkout feature
   commit id: "D"
   commit id: "E"
   commit id: "F"
   checkout main
   commit id: "C"
   cherry-pick id: "E"
git cherry-pick <hash-commit>   # applica quel commit sul branch corrente
git cherry-pick A..F            # range di commit
git cherry-pick --no-commit     # applica le modifiche senza creare il commit
git cherry-pick --abort         # annulla se ci sono conflitti
Tip

Scenario tipico: hai una hotfix su feature che serve subito su main, ma non vuoi portare tutto il branch — prendi solo quel commit con cherry-pick.


Reset
#

Sposta HEAD indietro nella storia. Tre modalità con impatto crescente sui file di lavoro.

git reset --soft HEAD~1    # annulla il commit → file restano in staging
git reset --mixed HEAD~1   # annulla il commit → file tornano a untracked
git reset --hard HEAD~1    # annulla il commit → file cancellati

Prima del reset:

gitGraph
   commit id: "A"
   commit id: "B"
   commit id: "C"

Dopo git reset HEAD~1:

gitGraph
   commit id: "A"
   commit id: "B"
ModalitàCommitStagingFile di lavoro
--soft✗ annullato✓ conservato✓ conservato
--mixed✗ annullato✗ svuotato✓ conservato
--hard✗ annullato✗ svuotato✗ cancellato
Warning

--hard cancella le modifiche ai file in modo irreversibile. L'unico modo per recuperare è git reflog — ma solo se il commit esisteva già prima del reset.

# Recuperare un commit perso dopo reset --hard
git reflog                        # trova l'hash del commit perso
git checkout <hash>               # ispeziona
git cherry-pick <hash>            # o ripristina sul branch corrente

Rename file/cartella
#

git mv vecchio-nome.md nuovo-nome.md
git add .
git commit -m "rename: vecchio-nome → nuovo-nome"

Su macOS il filesystem è case-insensitive — git non vede il rename guideGuide. Fix:

git mv guide guide_temp
git mv guide_temp Guide
git add .
git commit -m "fix: rename guide → Guide"
Note

Su macOS il filesystem è case-insensitive: git non rileva il rename guideGuide. Usa sempre git mv con passaggio intermedio _temp.

Submodule
#

Un submodule è un repo Git dentro un altro repo Git. Il repo padre non traccia i file del submodule — traccia solo un puntatore al commit specifico.

# Struttura tipica
cyber-appunti/               ← repo padre (pubblico, Hugo)
├── themes/
│   └── dark-theme-editor/   ← submodule (repo del tema)
├── content/
│   └── notes/               ← submodule (repo Obsidian privato)
└── hugo.toml

Aggiungere un submodule
#

git submodule add https://github.com/user/repo.git path/destinazione
git commit -m "feat: aggiunto submodule X"

Clonare un repo con submodule
#

# I submodule arrivano vuoti dopo il clone
git clone <url>
git submodule update --init                  # popola i submodule
git submodule update --init --recursive      # submodule annidati
Important

Dopo git clone i submodule arrivano vuoti — esegui sempre git submodule update --init per popolarli.

Git Clone e SCP

La sintassi host:porta/path ricorda scp perché entrambi usano lo stesso protocollo — SSH. scp usa : per separare host da path, git SSH usa la stessa convenzione nell'URL.

Aggiornare un submodule
#

git submodule update --remote path/submodule  # aggiorna all'ultimo commit remoto
git add .
git commit -m "update: submodule X aggiornato"

Comandi utili
#

git submodule status           # stato di tutti i submodule
git submodule foreach git pull # pull in tutti i submodule in un colpo

Rimuovere un submodule
#

git submodule deinit path/submodule
git rm path/submodule
rm -rf .git/modules/path/submodule
git commit -m "remove: submodule X rimosso"

Come funziona il puntatore
#

# repo padre vede:              non vede:
# .gitmodules     url + path   i file dentro il submodule
# commit hash     ABC123       le modifiche interne

Il repo padre sa solo "il submodule deve stare al commit ABC123". Se il submodule avanza di 3 commit, il padre non si aggiorna da solo — devi fare git submodule update --remote + commit nel padre.

Un repo pubblico ha rimosso una API key — ma la storia e' permanente:

git clone https://github.com/target/repo
git log --all --oneline   # cerca commit tipo "remove credentials", "fix config"
git show abc1234          # mostra il contenuto di quel commit

Se trovi password=, api_key=, SECRET= → segreto esposto Prima di pushare, verifica che non ci siano segreti:

git diff --staged | grep -i "password\|secret\|key\|token"

Secret scanning — segreti nella storia
#

Un segreto committato anche una sola volta resta recuperabile per sempre, anche se rimosso nel commit successivo.

# Ispeziona la storia cercando commit sospetti
git log --all --oneline
# cerca messaggi tipo: "fix info leak", "remove credentials", "fix config"

# Metodo 1 — mostra il diff di un commit specifico
git show abc1234

# Metodo 2 — confronta due commit esplicitamente
git diff abc1234 def5678

# Verifica prima del push
git diff --staged | grep -i "password\|secret\|key\|token\|api"

Strumenti automatici per la scansione: truffleHog, gitleaks, git-secrets.

Warning

Un segreto pushato va considerato compromesso, sempre. La pulizia della storia con git filter-repo e' igiene, non sicurezza. L'unica azione corretta e' ruotare immediatamente la credenziale.

.git/objects — il database fisico
#

Tutto cio' che git traccia — file, directory, commit — viene salvato in .git/objects/ come oggetto compresso identificato dal suo hash SHA-1. Sono

.git/objects/
├── d0/
│   └── cf2ab7dd7e...    # il commit con la password — sempre qui
├── b0/
│   └── 354c7be30f...    # il commit "fix info leak"
└── pack/                # oggetti compressi insieme dopo git gc

Quando fai git clone, git copia tutti questi oggetti sul tuo disco. Quando fai force push, cambi solo i puntatori sul remote — gli oggetti nelle copie locali restano finche' git gc non li pulisce.

# Vedere tutti gli oggetti nel database locale
git cat-file --batch-all-objects --batch-check

- `.pack` — gli oggetti compressi (i dati reali: commit, system, alberi)
- `.idx` — l'indice per trovare velocemente un oggetto nel pack per hash
- `.rev` — indice inverso, da posizione nel pack → hash

# Forzare la pulizia degli oggetti non referenziati
git gc --prune=now
Note

git gc rimuove solo gli oggetti non piu' referenziati da nessun branch o tag. Se un collega non ha ancora allineato il suo branch al remote dopo il force push, l'oggetto con la password e' ncora referenziato nella sua storia locale — git gc non lo tocca.

Caso reale
#

Un developer pusho' credenziali AWS su GitHub pubblico. Se ne accorse dopo 4 minuti e fece force push immediato. Bot automatici scansionano GitHub in tempo reale cercando pattern di API key. In quei 4 minuti il bot aveva gia' trovato le credenziali, creato istanze EC2 e iniziato a minare criptovaluta. Costo sulla fattura AWS: ~50.000 dollari. Il force push aveva pulito il repository. .git/objects/ sul server del bot aveva ancora tutto.

Collegato a
#

  • system — categoria
  • ssh — git usa SSH per autenticazione remota

Related