M0: compilable skeleton — Kigi 0.1.0 fork surgery
Hard fork of xai-org/grok-build (Apache-2.0) re-targeted as Kigi, an
unofficial Kimi Code CLI community build.
Rename & identity
- 72 xai-*/xai-grok-* crates -> kigi-* (explicit: xai-grok-pager-bin ->
kigi-bin [binary `kigi`], xai-grok-pager -> kigi-tui; rest mechanical);
ptyctl, ptyctl-cli, third_party/ unchanged; proto package
xai.grok.tools.v1 -> kigi.tools.v1
- Config home ~/.kigi (KIGI_SHARE_DIR override), env prefix GROK_* ->
KIGI_*, `kigi --version` carries the unofficial-community-build notice
- clap identity, help text, startup banner, prompt templates rebranded
(templates re-encrypted)
Deletions (PRD removal list #5/#6/#7/#9/#10)
- voice input (xai-grok-voice) and all TUI wiring
- telemetry: Mixpanel client, external OTel stream, Sentry, OTLP layers,
trace/GCS/S3 upload queues (kigi-file-utils halved), workspace upload
module & dc_log, heap-profile uploader, auth-diagnostics uploader,
session-analytics halves of feedback; local zero-egress observability
preserved in new kigi-log crate (unified log, --debug firehose,
subsystem file logs, opt-in instrumentation)
- announcements (crate, remote-settings fields, TUI surfaces)
- plugin marketplace (crate, sources/browse/CTA/extensions-modal tab);
direct plugin install/uninstall/update via kigi-agent git_install kept
- relay/gateway/assets endpoints and features (agent relay, headless
relay transport, gateway bridge, LeaderEnvUrls); leader IPC socket now
~/.kigi/leader.sock + KIGI_LEADER_SOCKET, no ws-url derivation
- functional types rehomed instead of deleted: PermissionMode ->
kigi-config-types, McpInitStrategy -> kigi-mcp, PrCreationSource ->
session signals, TerminalDiagnostics -> kigi-pager-render, agent_id ->
shell util
Endpoints
- kigi-env rewritten: single production KigiEndpoints {coding_api_base_url
https://api.kimi.com/coding/v1 (KIGI_CODE_BASE_URL), oauth_host
https://auth.kimi.com (KIGI_OAUTH_HOST), update_base_url (GitHub
Releases API), upgrade_page_url}; GrokBuildEnvironment enum deleted
Toolchain & workspace hygiene
- Rust 1.97.0 pinned; edition 2024; full cargo update; git2 hoisted to
workspace at 0.21 (Option->Result API migration), quick-xml 0.41
- Root Cargo.toml hand-maintained (PRD §8.1): version 0.1.0 inherited by
all members, members sorted, unused deps pruned
- cargo-deny advisories gate (deny.toml with documented transitive
exceptions); CI workflow (check/clippy/fmt/deny/test, macOS+Linux)
- cross-crate test seams re-gated behind `test-support` cargo feature;
insta snapshot baselines renamed to the kigi_tui prefix
- clippy --workspace --all-targets: zero warnings; fmt clean
Fixes surfaced by the port
- updater probe/installer divergence (bin/kigi vs bin/grok symlink set)
- idle model-metadata refresh dead under KIGI_CODE_BASE_URL override
(new is_effective_coding_endpoint_url, loopback+override aware)
- macOS symlinked-TMPDIR fixture canonicalization (foreign_sessions,
fast-worktree); RSS measurement tests serialized via serial_test
Docs & legal (Apache §4)
- NOTICE added (upstream attribution + change statement); THIRD-PARTY
notices sustained; kigi-tools ported-code notices extended; README,
CONTRIBUTING, SECURITY, AGENTS.md rewritten
Out of scope for M0 (tracked): Kimi auth/inference (M1), search/fetch,
command parity, config import (M2), Computer Hub excision & final
brand-token sweep (M2), distribution & self-update rewrite (M3).
This commit is contained in:
commit
d6c20fc13f
2612 files changed
+1353757
No files matched your search
@@ -0,0 +1,728 @@
|
||||
//! BTRFS filesystem and subvolume detection.
|
||||
//!
|
||||
//! This module handles detection of BTRFS filesystems and subvolumes, including
|
||||
//! the case where a BTRFS subvolume is bind-mounted to another location (e.g.
|
||||
//! when the working-tree path is not itself on BTRFS).
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use nix::sys::statfs::{BTRFS_SUPER_MAGIC, statfs};
|
||||
|
||||
/// Information about a BTRFS subvolume.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BtrfsInfo {
|
||||
/// Root path of the subvolume as seen by the user.
|
||||
/// This may be a bind mount target (e.g., `/workspace/repo`).
|
||||
pub subvolume_root: PathBuf,
|
||||
|
||||
/// If the subvolume is accessed via a bind mount, this contains the actual
|
||||
/// source path on the btrfs filesystem (e.g., `/mnt/btrfs/repo`).
|
||||
/// None if the path is directly on btrfs without a bind mount.
|
||||
pub bind_mount_source: Option<PathBuf>,
|
||||
|
||||
/// The btrfs mount point where snapshots can be created.
|
||||
/// For bind mounts, this is the parent of bind_mount_source.
|
||||
/// For direct btrfs paths, this is determined from the mount table.
|
||||
pub btrfs_mount_point: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Information about a bind mount.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BindMountInfo {
|
||||
/// The target path (where the bind mount is visible)
|
||||
#[allow(dead_code)]
|
||||
pub target: PathBuf,
|
||||
/// The source path (actual location of the data)
|
||||
pub source: PathBuf,
|
||||
/// The filesystem type of the source
|
||||
pub fs_type: String,
|
||||
}
|
||||
|
||||
/// Check if a path is on a BTRFS filesystem.
|
||||
pub fn is_btrfs(path: &Path) -> Result<bool> {
|
||||
let stat = statfs(path).with_context(|| format!("statfs failed for {}", path.display()))?;
|
||||
Ok(stat.filesystem_type() == BTRFS_SUPER_MAGIC)
|
||||
}
|
||||
|
||||
/// Get bind mount information for a path.
|
||||
///
|
||||
/// Uses `findmnt` to check if a path is a bind mount and retrieve its source.
|
||||
/// Returns `Ok(Some(BindMountInfo))` if the path is a bind mount.
|
||||
/// Returns `Ok(None)` if not a bind mount or if detection fails.
|
||||
pub fn get_bind_mount_info(path: &Path) -> Result<Option<BindMountInfo>> {
|
||||
// Use findmnt to get mount information
|
||||
// -n: no headers, -o: output fields, -T: target path
|
||||
let mut cmd = Command::new("findmnt");
|
||||
kigi_tty_utils::detach_std_command(&mut cmd);
|
||||
cmd.stdin(Stdio::null());
|
||||
let output = cmd
|
||||
.args(["-n", "-o", "SOURCE,TARGET,FSTYPE,OPTIONS", "-T"])
|
||||
// OsStr arg: a non-UTF-8 path must not silently collapse to ".".
|
||||
.arg(path)
|
||||
.output();
|
||||
|
||||
let output = match output {
|
||||
Ok(output) if output.status.success() => output,
|
||||
Ok(_) => {
|
||||
tracing::debug!(path = %path.display(), "findmnt failed or path not mounted");
|
||||
return Ok(None);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::debug!(path = %path.display(), error = %e, "failed to run findmnt");
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let line = stdout.trim();
|
||||
|
||||
if line.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Parse findmnt output: SOURCE TARGET FSTYPE OPTIONS
|
||||
// Fields are separated by whitespace
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() < 3 {
|
||||
tracing::debug!(path = %path.display(), line = %line, "unexpected findmnt output format");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let source = parts[0];
|
||||
let target = parts[1];
|
||||
let fs_type = parts[2];
|
||||
let options = parts.get(3).unwrap_or(&"");
|
||||
|
||||
// Check if this is a bind mount by looking for "bind" in options
|
||||
// or by checking if source contains a subpath (e.g., /dev/loop0[/repo])
|
||||
let is_bind = options.contains("bind")
|
||||
|| source.contains('[')
|
||||
|| (source.starts_with('/') && !source.starts_with("/dev/"));
|
||||
|
||||
if !is_bind {
|
||||
tracing::debug!(
|
||||
path = %path.display(),
|
||||
source = %source,
|
||||
"not a bind mount"
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// For bind mounts, the source might be in format like "/dev/loop0[/repo]"
|
||||
// We need to resolve the actual path
|
||||
let actual_source = resolve_bind_mount_source(path)?;
|
||||
|
||||
if let Some(actual_source) = actual_source {
|
||||
tracing::debug!(
|
||||
path = %path.display(),
|
||||
source = %actual_source.display(),
|
||||
"detected bind mount"
|
||||
);
|
||||
Ok(Some(BindMountInfo {
|
||||
target: PathBuf::from(target),
|
||||
source: actual_source,
|
||||
fs_type: fs_type.to_string(),
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve the actual source path for a bind mount by parsing /proc/self/mountinfo.
|
||||
///
|
||||
/// This is more reliable than parsing findmnt output for getting the actual path.
|
||||
fn resolve_bind_mount_source(target: &Path) -> Result<Option<PathBuf>> {
|
||||
let mountinfo = std::fs::read_to_string("/proc/self/mountinfo")
|
||||
.context("failed to read /proc/self/mountinfo")?;
|
||||
|
||||
let target_str = target.to_string_lossy();
|
||||
|
||||
// Find the mount entry for our target
|
||||
// mountinfo format: ID PARENT_ID MAJOR:MINOR ROOT MOUNTPOINT OPTIONS - FSTYPE SOURCE SUPER_OPTIONS
|
||||
for line in mountinfo.lines() {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() < 10 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mount_point = parts[4];
|
||||
if mount_point != target_str {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Found our mount point
|
||||
let root = parts[3]; // The root within the filesystem
|
||||
let fstype_idx = parts.iter().position(|&p| p == "-").map(|i| i + 1);
|
||||
|
||||
if let Some(fstype_idx) = fstype_idx {
|
||||
let fstype = parts.get(fstype_idx).unwrap_or(&"");
|
||||
let source = parts.get(fstype_idx + 1).unwrap_or(&"");
|
||||
|
||||
// For btrfs bind mounts, we need to find where the btrfs is mounted
|
||||
// and construct the full path
|
||||
if *fstype == "btrfs" {
|
||||
// Try 1: Find a btrfs root mount (root="/") for this device.
|
||||
// Common when the btrfs volume root is mounted separately
|
||||
// (e.g., `/mnt/btrfs/`).
|
||||
if let Some(btrfs_mount) = find_btrfs_mount_for_source(source, &mountinfo)? {
|
||||
// Construct the full path: btrfs_mount + root
|
||||
let full_source = if root == "/" {
|
||||
btrfs_mount
|
||||
} else {
|
||||
btrfs_mount.join(root.trim_start_matches('/'))
|
||||
};
|
||||
return Ok(Some(full_source));
|
||||
}
|
||||
|
||||
// Try 2: Resolve using a subvolume mount (no root mount exists).
|
||||
// This handles the case where only a btrfs subvolume is mounted
|
||||
// (e.g., `mount -o subvol=/repo /dev/loop0 /workspace/repo`)
|
||||
// without a separate mount for the btrfs volume root.
|
||||
match resolve_via_subvol_mount(source, root, &mountinfo) {
|
||||
Ok(Some(full_source)) => return Ok(Some(full_source)),
|
||||
Ok(None) => {
|
||||
tracing::debug!(
|
||||
device = %source,
|
||||
root = %root,
|
||||
target = %target_str,
|
||||
"subvol mount fallback found no matching mount for btrfs device"
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::debug!(
|
||||
device = %source,
|
||||
root = %root,
|
||||
target = %target_str,
|
||||
error = %e,
|
||||
"subvol mount fallback failed"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Find the mount point for a btrfs device/source.
|
||||
fn find_btrfs_mount_for_source(source: &str, mountinfo: &str) -> Result<Option<PathBuf>> {
|
||||
// Look for a mount of this source that has root="/"
|
||||
for line in mountinfo.lines() {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() < 10 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let root = parts[3];
|
||||
let mount_point = parts[4];
|
||||
|
||||
let fstype_idx = parts.iter().position(|&p| p == "-").map(|i| i + 1);
|
||||
if let Some(fstype_idx) = fstype_idx {
|
||||
let fstype = parts.get(fstype_idx).unwrap_or(&"");
|
||||
let mount_source = parts.get(fstype_idx + 1).unwrap_or(&"");
|
||||
|
||||
if *fstype == "btrfs" && *mount_source == source && root == "/" {
|
||||
return Ok(Some(PathBuf::from(mount_point)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Resolve a btrfs path using an existing subvolume mount when no root mount exists.
|
||||
///
|
||||
/// On some hosts the btrfs volume root is not mounted separately — only a
|
||||
/// specific subvolume is mounted (e.g.,
|
||||
/// `mount -o subvol=/repo /dev/loop0 /workspace/repo`). In that case,
|
||||
/// `find_btrfs_mount_for_source` returns `None` because there's no `root="/"` mount.
|
||||
///
|
||||
/// This function finds any mount of the same btrfs device and computes the filesystem
|
||||
/// path by adjusting for the mount's root offset.
|
||||
///
|
||||
/// For example, with mount entry `root=/repo mount_point=/workspace/repo` and target
|
||||
/// root `/repo/.kigi-snapshots/wt-123`, this returns
|
||||
/// `/workspace/repo/.kigi-snapshots/wt-123`.
|
||||
fn resolve_via_subvol_mount(
|
||||
device: &str,
|
||||
target_root: &str,
|
||||
mountinfo: &str,
|
||||
) -> Result<Option<PathBuf>> {
|
||||
for line in mountinfo.lines() {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() < 10 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mount_root = parts[3];
|
||||
let mount_point = parts[4];
|
||||
|
||||
let fstype_idx = parts.iter().position(|&p| p == "-").map(|i| i + 1);
|
||||
if let Some(fstype_idx) = fstype_idx {
|
||||
let fstype = parts.get(fstype_idx).unwrap_or(&"");
|
||||
let mount_source = parts.get(fstype_idx + 1).unwrap_or(&"");
|
||||
|
||||
if *fstype == "btrfs" && *mount_source == device {
|
||||
// Found a mount of this btrfs device.
|
||||
// Check if target_root starts with (or equals) this mount's root.
|
||||
if let Some(relative) = target_root.strip_prefix(mount_root) {
|
||||
// Ensure we matched at a path boundary, not a partial name
|
||||
// (e.g., mount_root="/repo" matches "/repo/foo" but not "/repo-other")
|
||||
if relative.is_empty() || relative.starts_with('/') {
|
||||
let relative = relative.trim_start_matches('/');
|
||||
let full_source = if relative.is_empty() {
|
||||
PathBuf::from(mount_point)
|
||||
} else {
|
||||
PathBuf::from(mount_point).join(relative)
|
||||
};
|
||||
return Ok(Some(full_source));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Get the btrfs mount point that contains a given path.
|
||||
///
|
||||
/// This walks up the path hierarchy to find the nearest btrfs mount point.
|
||||
pub fn get_btrfs_mount_point(path: &Path) -> Result<Option<PathBuf>> {
|
||||
let mountinfo = std::fs::read_to_string("/proc/self/mountinfo")
|
||||
.context("failed to read /proc/self/mountinfo")?;
|
||||
|
||||
let canonical = dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
|
||||
|
||||
// Find the longest matching mount point that is btrfs
|
||||
let mut best_match: Option<PathBuf> = None;
|
||||
let mut best_len = 0;
|
||||
|
||||
for line in mountinfo.lines() {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() < 10 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mount_point = parts[4];
|
||||
let fstype_idx = parts.iter().position(|&p| p == "-").map(|i| i + 1);
|
||||
|
||||
if let Some(fstype_idx) = fstype_idx {
|
||||
let fstype = parts.get(fstype_idx).unwrap_or(&"");
|
||||
|
||||
if *fstype == "btrfs" && canonical.starts_with(mount_point) {
|
||||
let len = mount_point.len();
|
||||
if len > best_len {
|
||||
best_len = len;
|
||||
best_match = Some(PathBuf::from(mount_point));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(best_match)
|
||||
}
|
||||
|
||||
/// Check if a path is a BTRFS subvolume and return info.
|
||||
///
|
||||
/// Returns `Ok(Some(BtrfsInfo))` if the path is a BTRFS subvolume root.
|
||||
/// Returns `Ok(None)` if not on BTRFS or not a subvolume.
|
||||
///
|
||||
/// This function also detects bind-mounted BTRFS subvolumes. For example, if
|
||||
/// `/workspace/repo` is bind-mounted from `/mnt/btrfs/repo`, this function will
|
||||
/// detect it as a BTRFS subvolume and populate the `bind_mount_source` field.
|
||||
///
|
||||
/// Note: This checks if the path itself is a subvolume root, not if it's
|
||||
/// contained within a subvolume.
|
||||
pub fn is_btrfs_subvolume(path: &Path) -> Result<Option<BtrfsInfo>> {
|
||||
let on_btrfs = is_btrfs(path)?;
|
||||
|
||||
if !on_btrfs {
|
||||
// Not on BTRFS at all (statfs says different fs type).
|
||||
// Check if it's a bind mount from a BTRFS subvolume anyway
|
||||
// (this handles the rare case where statfs doesn't report btrfs).
|
||||
tracing::debug!(
|
||||
path = %path.display(),
|
||||
"path not on BTRFS, checking for bind mount from BTRFS"
|
||||
);
|
||||
|
||||
if let Some(bind_info) = get_bind_mount_info(path)?
|
||||
&& bind_info.fs_type == "btrfs"
|
||||
&& check_is_subvolume_cmd(&bind_info.source)
|
||||
{
|
||||
let btrfs_mount = get_btrfs_mount_point(&bind_info.source).ok().flatten();
|
||||
tracing::info!(
|
||||
path = %path.display(),
|
||||
source = %bind_info.source.display(),
|
||||
btrfs_mount = ?btrfs_mount,
|
||||
"path is a bind-mounted BTRFS subvolume"
|
||||
);
|
||||
return Ok(Some(BtrfsInfo {
|
||||
subvolume_root: path.to_path_buf(),
|
||||
bind_mount_source: Some(bind_info.source),
|
||||
btrfs_mount_point: btrfs_mount,
|
||||
}));
|
||||
}
|
||||
|
||||
tracing::debug!(path = %path.display(), "not a BTRFS subvolume");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Path is on BTRFS (statfs reports btrfs). Check if it's a subvolume.
|
||||
if !check_is_subvolume_cmd(path) {
|
||||
tracing::debug!(
|
||||
path = %path.display(),
|
||||
"path is on BTRFS but not a subvolume root"
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// It's a BTRFS subvolume. Now check if it's accessed via a bind mount.
|
||||
//
|
||||
// A path like `/workspace/repo` can be a bind mount FROM a btrfs subvolume
|
||||
// at `/mnt/btrfs/repo`. In that case, statfs reports btrfs (because the data
|
||||
// IS on btrfs), but the snapshot destination (e.g. `~/.kigi/worktrees/...`)
|
||||
// is NOT on btrfs. We need to detect the bind mount so we can create
|
||||
// snapshots inside the actual btrfs mount point and expose them at the
|
||||
// destination via a symlink.
|
||||
if let Some(bind_info) = get_bind_mount_info(path)?
|
||||
&& bind_info.fs_type == "btrfs"
|
||||
{
|
||||
let btrfs_mount = get_btrfs_mount_point(&bind_info.source).ok().flatten();
|
||||
tracing::info!(
|
||||
path = %path.display(),
|
||||
source = %bind_info.source.display(),
|
||||
btrfs_mount = ?btrfs_mount,
|
||||
"path is a bind-mounted BTRFS subvolume"
|
||||
);
|
||||
return Ok(Some(BtrfsInfo {
|
||||
subvolume_root: path.to_path_buf(),
|
||||
bind_mount_source: Some(bind_info.source),
|
||||
btrfs_mount_point: btrfs_mount,
|
||||
}));
|
||||
}
|
||||
|
||||
// Direct BTRFS subvolume (not bind-mounted)
|
||||
let btrfs_mount = get_btrfs_mount_point(path).ok().flatten();
|
||||
tracing::debug!(
|
||||
path = %path.display(),
|
||||
btrfs_mount = ?btrfs_mount,
|
||||
"path is a direct BTRFS subvolume"
|
||||
);
|
||||
Ok(Some(BtrfsInfo {
|
||||
subvolume_root: path.to_path_buf(),
|
||||
bind_mount_source: None,
|
||||
btrfs_mount_point: btrfs_mount,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Run `btrfs subvolume show` to check if a path is a subvolume.
|
||||
fn check_is_subvolume_cmd(path: &Path) -> bool {
|
||||
let mut cmd = Command::new("btrfs");
|
||||
kigi_tty_utils::detach_std_command(&mut cmd);
|
||||
cmd.stdin(Stdio::null());
|
||||
// OsStr arg: a non-UTF-8 path must not silently collapse to ".".
|
||||
cmd.arg("subvolume")
|
||||
.arg("show")
|
||||
.arg(path)
|
||||
.output()
|
||||
.map(|output| output.status.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Helper to check if we're running on a BTRFS filesystem.
|
||||
/// Returns the BTRFS path if available, None otherwise.
|
||||
fn get_btrfs_test_path() -> Option<PathBuf> {
|
||||
// Check environment variable first
|
||||
if let Ok(path) = std::env::var("BTRFS_TEST_PATH") {
|
||||
let path = PathBuf::from(path);
|
||||
if path.exists() && is_btrfs(&path).unwrap_or(false) {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
|
||||
// Check common BTRFS mount points
|
||||
for candidate in &["/", "/home", "/btrfs", "/mnt/btrfs"] {
|
||||
let path = Path::new(candidate);
|
||||
if path.exists() && is_btrfs(path).unwrap_or(false) {
|
||||
return Some(path.to_path_buf());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Helper to check if a path is a BTRFS subvolume for testing.
|
||||
fn get_btrfs_subvolume_test_path() -> Option<PathBuf> {
|
||||
if let Some(btrfs_path) = get_btrfs_test_path()
|
||||
&& is_btrfs_subvolume(&btrfs_path).ok().flatten().is_some()
|
||||
{
|
||||
return Some(btrfs_path);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_btrfs_info_debug() {
|
||||
let info = BtrfsInfo {
|
||||
subvolume_root: PathBuf::from("/test/path"),
|
||||
bind_mount_source: None,
|
||||
btrfs_mount_point: None,
|
||||
};
|
||||
let debug_str = format!("{:?}", info);
|
||||
assert!(debug_str.contains("BtrfsInfo"));
|
||||
assert!(debug_str.contains("/test/path"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_btrfs_info_clone() {
|
||||
let info = BtrfsInfo {
|
||||
subvolume_root: PathBuf::from("/original/path"),
|
||||
bind_mount_source: Some(PathBuf::from("/btrfs/source")),
|
||||
btrfs_mount_point: Some(PathBuf::from("/btrfs")),
|
||||
};
|
||||
let cloned = info.clone();
|
||||
assert_eq!(info.subvolume_root, cloned.subvolume_root);
|
||||
assert_eq!(info.bind_mount_source, cloned.bind_mount_source);
|
||||
assert_eq!(info.btrfs_mount_point, cloned.btrfs_mount_point);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_btrfs_info_with_bind_mount() {
|
||||
let info = BtrfsInfo {
|
||||
subvolume_root: PathBuf::from("/workspace/repo"),
|
||||
bind_mount_source: Some(PathBuf::from("/mnt/btrfs/repo")),
|
||||
btrfs_mount_point: Some(PathBuf::from("/mnt/btrfs")),
|
||||
};
|
||||
assert!(info.bind_mount_source.is_some());
|
||||
assert_eq!(
|
||||
info.bind_mount_source.as_ref().unwrap(),
|
||||
&PathBuf::from("/mnt/btrfs/repo")
|
||||
);
|
||||
assert_eq!(
|
||||
info.btrfs_mount_point.as_ref().unwrap(),
|
||||
&PathBuf::from("/mnt/btrfs")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_btrfs_on_root() {
|
||||
// Test on root filesystem - should not panic regardless of fs type
|
||||
let result = is_btrfs(Path::new("/"));
|
||||
assert!(result.is_ok());
|
||||
// We can't assert the value since it depends on the system
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_btrfs_on_tmp() {
|
||||
// /tmp is typically not on BTRFS (tmpfs or ext4)
|
||||
// This test just verifies the function doesn't panic
|
||||
let result = is_btrfs(Path::new("/tmp"));
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_btrfs_nonexistent_path() {
|
||||
let result = is_btrfs(Path::new("/nonexistent/path/that/does/not/exist"));
|
||||
assert!(result.is_err());
|
||||
let err_msg = result.unwrap_err().to_string();
|
||||
assert!(err_msg.contains("statfs failed"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_btrfs_subvolume_on_tmp() {
|
||||
// Should return None for non-BTRFS paths (or paths that aren't subvolumes)
|
||||
let result = is_btrfs_subvolume(Path::new("/tmp"));
|
||||
assert!(result.is_ok());
|
||||
// On most systems /tmp is not a BTRFS subvolume
|
||||
// But we can't assert None because some systems might have BTRFS /tmp
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_btrfs_subvolume_nonexistent_path() {
|
||||
// Nonexistent paths should error in is_btrfs before reaching btrfs command
|
||||
let result = is_btrfs_subvolume(Path::new("/nonexistent/path/xyz"));
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_btrfs_subvolume_on_root() {
|
||||
// Test on root - should not panic
|
||||
let result = is_btrfs_subvolume(Path::new("/"));
|
||||
assert!(result.is_ok());
|
||||
// Result depends on whether / is a BTRFS subvolume
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_btrfs_detection_on_real_btrfs() {
|
||||
// This test automatically skips if no BTRFS is detected
|
||||
let Some(btrfs_path) = get_btrfs_test_path() else {
|
||||
eprintln!("Skipping test: no BTRFS filesystem detected");
|
||||
return;
|
||||
};
|
||||
|
||||
let is_btrfs_result = is_btrfs(&btrfs_path);
|
||||
assert!(is_btrfs_result.is_ok());
|
||||
assert!(is_btrfs_result.unwrap(), "Expected path to be on BTRFS");
|
||||
eprintln!("BTRFS detected at: {}", btrfs_path.display());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_btrfs_subvolume_detection_on_real_btrfs() {
|
||||
// This test automatically skips if no BTRFS subvolume is detected
|
||||
let Some(subvol_path) = get_btrfs_subvolume_test_path() else {
|
||||
eprintln!("Skipping test: no BTRFS subvolume detected");
|
||||
return;
|
||||
};
|
||||
|
||||
let result = is_btrfs_subvolume(&subvol_path);
|
||||
assert!(result.is_ok());
|
||||
let info = result.unwrap();
|
||||
assert!(info.is_some(), "Expected path to be a BTRFS subvolume");
|
||||
eprintln!("BTRFS subvolume detected at: {}", subvol_path.display());
|
||||
}
|
||||
|
||||
/// Test that a bind-mounted btrfs path (which reports as btrfs via statfs)
|
||||
/// correctly detects the bind mount and populates bind_mount_source.
|
||||
///
|
||||
/// Regression: a bind-mounted working tree can be mis-detected as a "direct"
|
||||
/// btrfs subvolume (`bind_mount_source=None`), which then makes snapshot
|
||||
/// creation fail because the destination path is not on btrfs.
|
||||
///
|
||||
/// Optional live check: set `BTRFS_BIND_TEST_PATH` to a bind-mounted btrfs
|
||||
/// path on the host. Skips when the env var is unset or the path is not a
|
||||
/// bind-mounted btrfs location.
|
||||
#[test]
|
||||
fn test_bind_mounted_btrfs_detects_bind_mount_source() {
|
||||
let path = match std::env::var_os("BTRFS_BIND_TEST_PATH") {
|
||||
Some(p) => PathBuf::from(p),
|
||||
None => {
|
||||
eprintln!("Skipping test: BTRFS_BIND_TEST_PATH not set");
|
||||
return;
|
||||
}
|
||||
};
|
||||
if !path.exists() {
|
||||
eprintln!(
|
||||
"Skipping test: BTRFS_BIND_TEST_PATH={} does not exist",
|
||||
path.display()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if it's on btrfs
|
||||
if !is_btrfs(&path).unwrap_or(false) {
|
||||
eprintln!("Skipping test: {} is not on btrfs", path.display());
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if it's a bind mount
|
||||
let bind_info = get_bind_mount_info(&path);
|
||||
let is_bind_mount = bind_info.as_ref().ok().and_then(|o| o.as_ref()).is_some();
|
||||
|
||||
if !is_bind_mount {
|
||||
eprintln!("Skipping test: {} is not a bind mount", path.display());
|
||||
return;
|
||||
}
|
||||
|
||||
// The critical test: is_btrfs_subvolume should detect the bind mount
|
||||
let result = is_btrfs_subvolume(&path);
|
||||
assert!(result.is_ok());
|
||||
let info = result.unwrap();
|
||||
assert!(
|
||||
info.is_some(),
|
||||
"Expected {} to be a BTRFS subvolume",
|
||||
path.display()
|
||||
);
|
||||
|
||||
let info = info.unwrap();
|
||||
assert!(
|
||||
info.bind_mount_source.is_some(),
|
||||
"Expected bind_mount_source to be Some for bind-mounted btrfs path {}, \
|
||||
but got None. This would cause snapshot creation to fail because the destination \
|
||||
is not on btrfs.",
|
||||
path.display()
|
||||
);
|
||||
assert!(
|
||||
info.btrfs_mount_point.is_some(),
|
||||
"Expected btrfs_mount_point to be Some"
|
||||
);
|
||||
|
||||
eprintln!(
|
||||
"Bind-mounted btrfs correctly detected:\n path: {}\n bind_source: {:?}\n btrfs_mount: {:?}",
|
||||
info.subvolume_root.display(),
|
||||
info.bind_mount_source,
|
||||
info.btrfs_mount_point
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Unit tests for resolve_via_subvol_mount ─────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_resolve_via_subvol_mount_exact_match() {
|
||||
// Simulates: mount -o subvol=/repo /dev/loop0 /workspace/repo
|
||||
// Target root is /repo (the subvolume itself)
|
||||
let mountinfo =
|
||||
"8267 8961 0:813 /repo /workspace/repo rw,relatime - btrfs /dev/loop0 rw,ssd";
|
||||
let result = resolve_via_subvol_mount("/dev/loop0", "/repo", mountinfo).unwrap();
|
||||
assert_eq!(result, Some(PathBuf::from("/workspace/repo")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_via_subvol_mount_nested_path() {
|
||||
// Simulates: target root /repo/.kigi-snapshots/wt-123 resolved via
|
||||
// mount with root=/repo at /workspace/repo
|
||||
let mountinfo =
|
||||
"8267 8961 0:813 /repo /workspace/repo rw,relatime - btrfs /dev/loop0 rw,ssd";
|
||||
let result =
|
||||
resolve_via_subvol_mount("/dev/loop0", "/repo/.kigi-snapshots/wt-123", mountinfo)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(PathBuf::from("/workspace/repo/.kigi-snapshots/wt-123"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_via_subvol_mount_no_match() {
|
||||
// Target root doesn't start with mount root
|
||||
let mountinfo =
|
||||
"8267 8961 0:813 /repo /workspace/repo rw,relatime - btrfs /dev/loop0 rw,ssd";
|
||||
let result = resolve_via_subvol_mount("/dev/loop0", "/other", mountinfo).unwrap();
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_via_subvol_mount_partial_name_no_match() {
|
||||
// /repo-other should NOT match mount_root=/repo
|
||||
let mountinfo =
|
||||
"8267 8961 0:813 /repo /workspace/repo rw,relatime - btrfs /dev/loop0 rw,ssd";
|
||||
let result = resolve_via_subvol_mount("/dev/loop0", "/repo-other", mountinfo).unwrap();
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_via_subvol_mount_wrong_device() {
|
||||
// Different device should not match
|
||||
let mountinfo =
|
||||
"8267 8961 0:813 /repo /workspace/repo rw,relatime - btrfs /dev/loop0 rw,ssd";
|
||||
let result = resolve_via_subvol_mount("/dev/loop1", "/repo", mountinfo).unwrap();
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_via_subvol_mount_non_btrfs_ignored() {
|
||||
// ext4 mount should not match
|
||||
let mountinfo = "8970 8961 259:7 /mnt/local /local rw,relatime - ext4 /dev/nvme0n1p2 rw";
|
||||
let result = resolve_via_subvol_mount("/dev/nvme0n1p2", "/mnt/local", mountinfo).unwrap();
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//! BTRFS snapshot support for fast worktree creation.
|
||||
//!
|
||||
//! On Linux systems where the source repo is on a BTRFS subvolume,
|
||||
//! we can use BTRFS snapshots for O(1) worktree creation instead of
|
||||
//! file-by-file CoW cloning.
|
||||
//!
|
||||
//! The snapshot creates a complete standalone git repository (not a
|
||||
//! git worktree), which is immediately usable without any fixup.
|
||||
//!
|
||||
//! This module also handles the case where the BTRFS subvolume is accessed
|
||||
//! via a bind mount (e.g. when the working-tree path is not itself on BTRFS).
|
||||
//! In such cases, snapshots are created inside the BTRFS mount point and
|
||||
//! exposed at the expected destination via a symlink.
|
||||
|
||||
pub mod detect;
|
||||
pub mod snapshot;
|
||||
|
||||
pub use detect::{BtrfsInfo, is_btrfs, is_btrfs_subvolume};
|
||||
pub use snapshot::{
|
||||
BTRFS_META_SUFFIX, BTRFS_SNAPSHOT_SUBDIRS, BtrfsSnapshotMetadata, SnapshotMetaState,
|
||||
btrfs_meta_path, create_snapshot, create_snapshot_with_symlink, create_worktree_symlink,
|
||||
delete_snapshot, is_safe_snapshot_delete_target, remove_btrfs_metadata, snapshot_dest_path,
|
||||
snapshot_meta_state, snapshot_meta_targets, write_btrfs_metadata,
|
||||
};
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user