Architecture Decision Records - ADR260201c
2026/02/01Shell-out a git binary invece di libgit2
Autore: Marco Orlandin, Architect
Data: 01 Febbraio 2026
Status: Implementato
Progetto: git-side (open-source, sviluppato per Solexma LLC)
Constraint principali: Comportamento identico al git dell'utente, build semplice senza dipendenze native, distribuzione come singolo binario
Contesto e problema
git-side è scritto in Rust e ha bisogno di eseguire operazioni Git: init, add, commit, push, pull, log, rev-list, e altre. In Rust esistono diverse opzioni per interagire con Git programmaticamente, ognuna con trade-off significativi.
La scelta impatta direttamente su:
- Compatibilità: il comportamento deve essere identico a quello che l'utente si aspetta dal proprio
git. - Build: dipendenze native (come libgit2) complicano la compilazione cross-platform.
- Manutenzione: più astrazione = più superficie da mantenere.
Requisiti non funzionali
- Comportamento identico al
gitinstallato dall'utente - Build semplice, senza dipendenze native (C libraries)
- Singolo binario distribuibile
- Supporto per tutte le operazioni Git necessarie (porcelain e plumbing)
- Gestione errori chiara quando git non è installato
Opzioni valutate
Opzione 1: git2-rs (binding Rust per libgit2)
- Pro: API Rust-native, tipizzata, nessun parsing di output testuale. Ampiamente usata nell'ecosistema Rust.
- Contro: Dipendenza nativa da libgit2 (C library). Build più complessa, specialmente in cross-compilation. Comportamento potenzialmente diverso dal
gitdell'utente (libgit2 non è git). Alcune operazioni avanzate non supportate o con semantica diversa.
Opzione 2: gitoxide (implementazione Git pura in Rust)
- Pro: Puro Rust, nessuna dipendenza C. Prestazioni potenzialmente superiori.
- Contro: Progetto ancora in evoluzione, non tutte le operazioni sono stabili. API in continuo cambiamento. Feature parity con git non garantita.
Opzione 3: Shell-out al binario git
- Pro: Comportamento identico al git dell'utente, per definizione. Zero dipendenze native. Build banale. Supporta qualsiasi operazione git, incluse quelle plumbing.
- Contro: Richiede git installato sulla macchina. Parsing dell'output testuale. Gestione errori meno tipizzata (exit code + stderr).
Decisione
Scelto lo shell-out al binario git tramite std::process::Command.
Motivazioni principali:
- Garanzia di comportamento: se
gitfunziona per l'utente, funziona per git-side. - Zero dipendenze native: il binario Rust compilato è autosufficiente.
- Accesso completo: qualsiasi comando git è disponibile, inclusi plumbing commands come
hash-objecteupdate-index. - Semplicità: nessuna astrazione intermedia da mantenere.
Il presupposto è esplicito: git-side richiede git installato. Chi usa git-side ha già git, non è un vincolo reale.
Implementazione passo-passo
- Modulo git.rs: Wrapper sottili attorno a
std::process::Commandche impostanoGIT_DIR,GIT_WORK_TREEe puliscono variabili d'ambiente problematiche. - Gestione output: Cattura di stdout e stderr, check dell'exit code, conversione in tipo
Result<String, Error>. - Error handling: Enum custom via
thiserrorcon varianti specifiche per i diversi tipi di errore git. - Nessun parsing avanzato: L'output di git viene usato "as-is" dove possibile (log, status, diff passano direttamente all'utente).
- Plumbing diretto: Per operazioni come il self-versioning di
.side-tracked, si usano comandi plumbing (hash-object,update-index) senza intermediari.
Conseguenze osservate
- Build time del progetto ridotto rispetto a soluzioni con dipendenze native.
- Nessun problema di compatibilità cross-platform (testato macOS e Linux).
- Il parsing dell'output si è rivelato minimale: la maggior parte dei comandi non richiede interpretazione complessa.
- Lezioni apprese:
- Shell-out non è una scorciatoia, è la scelta giusta quando il comportamento "reale" di git è il requisito.
- La pulizia delle variabili d'ambiente è cruciale: senza, i comandi git ereditano contesti sbagliati (specialmente nelle hook).
- Wrapper sottili > wrapper profondi. Meno si astrae, meno si rompe.
Stack utilizzato
- Linguaggio: Rust (Edition 2024, MSRV 1.85)
- Interfaccia Git:
std::process::Command - Error handling: crate
thiserror - Linting: Clippy con
all,pedantic,nursery
Quando considerare questo approccio
Se il tuo tool deve interagire con Git e:
- Il comportamento deve essere identico al
gitdell'utente - Non vuoi dipendenze native
- Hai bisogno di accesso a comandi plumbing
- La build semplice è un requisito
...shell-out è quasi sempre la scelta giusta. Aggiungi libgit2 o gitoxide solo se hai bisogno di performance su operazioni massive (migliaia di commit, diff su grandi alberi).
Hai un caso simile? Contattami. Valutiamo insieme il trade-off per il tuo contesto.