feat(minga): minga-vfs — proyecta el repo como filesystem FUSE
minga-vfs deja de ser un stub: monta el repositorio direccionado por contenido como un filesystem FUSE de sólo lectura. roots/<hash> da el código fuente reconstruido (formato normalizado) de cada raíz del MST; cas/<hash> resuelve cualquier hash bajo demanda como S-expression. Capas separadas: render (SemanticNode→texto, puro) + source (contrato NodeSource, backends sled/memoria) + fs (único módulo con fuser). Nuevo subcomando `minga mount <punto>`. Dep fuser 0.15 sin libfuse-dev (default-features = false). 14 tests nuevos, sin regresión en minga-cli. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,2 +1,80 @@
|
||||
//! minga-vfs: proyección virtual del repositorio como filesystem (vía
|
||||
//! FUSE). Resuelve hashes a bloques de código bajo demanda. Pendiente.
|
||||
//! `minga-vfs`: proyecta el repositorio de Minga —direccionado por
|
||||
//! contenido semántico— como un filesystem FUSE de **sólo lectura**.
|
||||
//!
|
||||
//! Minga guarda código como un grafo de `StoredNode`s identificados por
|
||||
//! `ContentHash`; los archivos ingeridos son las raíces del MST. Este
|
||||
//! crate convierte ese grafo en algo que cualquier herramienta Unix
|
||||
//! (`ls`, `cat`, `grep`, un editor) puede recorrer, sin exponer `sled`
|
||||
//! ni la API del store.
|
||||
//!
|
||||
//! ## Layout del filesystem
|
||||
//!
|
||||
//! ```text
|
||||
//! <punto-de-montaje>/
|
||||
//! ├── README explicación del propio VFS
|
||||
//! ├── roots/ un archivo por raíz del MST (cada archivo ingerido)
|
||||
//! │ └── <hash64> código fuente reconstruido, formato normalizado
|
||||
//! └── cas/ cualquier nodo del store, resuelto bajo demanda
|
||||
//! └── <hash64> S-expression del subárbol con ese hash
|
||||
//! ```
|
||||
//!
|
||||
//! `roots/` **se enumera** (`ls` lista todas las raíces). `cas/` no se
|
||||
//! enumera —son potencialmente decenas de miles de nodos— pero
|
||||
//! `cas/<hash>` resuelve cualquier hash conocido: ése es el "blob por
|
||||
//! hash bajo demanda". El mismo hash bajo `roots/` y bajo `cas/` da dos
|
||||
//! vistas del mismo nodo: fuente reconstruida vs. árbol literal.
|
||||
//!
|
||||
//! ## Arquitectura (separabilidad)
|
||||
//!
|
||||
//! - [`render`] — `SemanticNode` → texto. Lógica pura, sin IO ni FUSE;
|
||||
//! reutilizable por un frontend web o TUI.
|
||||
//! - [`source`] — el contrato [`NodeSource`] y sus backends (`sled`
|
||||
//! vía [`RepoSource`], memoria vía [`MemSource`]).
|
||||
//! - `fs` — el único módulo acoplado a `fuser`: traduce el contrato a
|
||||
//! la `Filesystem` trait.
|
||||
|
||||
mod fs;
|
||||
pub mod render;
|
||||
pub mod source;
|
||||
|
||||
pub use fs::MingaFs;
|
||||
pub use source::{reconstruct, MemSource, NodeSource, RepoSource};
|
||||
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use fuser::MountOption;
|
||||
|
||||
/// Opciones de montaje comunes: sólo lectura, etiquetado como `minga`
|
||||
/// para que aparezca legible en `mount` / `df`.
|
||||
fn mount_options() -> Vec<MountOption> {
|
||||
vec![
|
||||
MountOption::RO,
|
||||
MountOption::FSName("minga".to_string()),
|
||||
MountOption::Subtype("minga".to_string()),
|
||||
]
|
||||
}
|
||||
|
||||
/// Monta el VFS en `mountpoint` y **bloquea** hasta que se desmonte
|
||||
/// (`fusermount -u <punto>`, `umount`, o una señal al proceso).
|
||||
///
|
||||
/// El punto de montaje debe ser un directorio existente. El filesystem
|
||||
/// es de sólo lectura: toda escritura falla con `EROFS`/`EACCES`.
|
||||
pub fn mount<S, P>(source: S, mountpoint: P) -> io::Result<()>
|
||||
where
|
||||
S: NodeSource,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
fuser::mount2(MingaFs::new(source), mountpoint, &mount_options())
|
||||
}
|
||||
|
||||
/// Como [`mount`] pero spawnea un hilo de fondo y retorna de inmediato.
|
||||
/// La sesión queda viva mientras el `BackgroundSession` no se dropee;
|
||||
/// dropearlo desmonta el filesystem.
|
||||
pub fn spawn_mount<S, P>(source: S, mountpoint: P) -> io::Result<fuser::BackgroundSession>
|
||||
where
|
||||
S: NodeSource + Send + 'static,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
fuser::spawn_mount2(MingaFs::new(source), mountpoint, &mount_options())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user