Skip to main content

ralph/git/lfs/
detect.rs

1//! Git LFS detection helpers.
2//!
3//! Responsibilities:
4//! - Detect whether a repository is configured for Git LFS.
5//! - List LFS-tracked files via `git lfs ls-files`.
6//!
7//! Not handled here:
8//! - Filter validation or pointer parsing.
9//! - Aggregate health reporting.
10//!
11//! Invariants/assumptions:
12//! - Known "LFS not installed" failures return empty results instead of errors.
13
14use crate::git::error::{GitError, git_output};
15use anyhow::{Context, Result};
16use std::fs;
17use std::path::Path;
18
19pub fn has_lfs(repo_root: &Path) -> Result<bool> {
20    let git_lfs_dir = repo_root.join(".git/lfs");
21    if git_lfs_dir.is_dir() {
22        return Ok(true);
23    }
24
25    let gitattributes = repo_root.join(".gitattributes");
26    if gitattributes.is_file() {
27        let content = fs::read_to_string(&gitattributes)
28            .with_context(|| format!("read .gitattributes in {}", repo_root.display()))?;
29        return Ok(content.contains("filter=lfs"));
30    }
31
32    Ok(false)
33}
34
35pub fn list_lfs_files(repo_root: &Path) -> Result<Vec<String>> {
36    let output = git_output(repo_root, &["lfs", "ls-files"])
37        .with_context(|| format!("run git lfs ls-files in {}", repo_root.display()))?;
38
39    if !output.status.success() {
40        let stderr = String::from_utf8_lossy(&output.stderr);
41        if stderr.contains("not a git lfs repository")
42            || stderr.contains("git: lfs is not a git command")
43        {
44            return Ok(Vec::new());
45        }
46        return Err(GitError::CommandFailed {
47            args: "lfs ls-files".to_string(),
48            code: output.status.code(),
49            stderr: stderr.trim().to_string(),
50        }
51        .into());
52    }
53
54    let stdout = String::from_utf8_lossy(&output.stdout);
55    let mut files = Vec::new();
56    for line in stdout.lines() {
57        if let Some((_, path)) = line.rsplit_once(" * ") {
58            files.push(path.to_string());
59        }
60    }
61    Ok(files)
62}