use std::path::Path;
use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use crate::gitcmd;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum EntryKind {
Tree,
Blob,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TreeEntry {
pub name: String,
pub kind: EntryKind,
pub sha: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlobContent {
pub content: String,
pub binary: bool,
pub sha: String,
}
pub struct BlobData {
pub sha: String,
pub content: Vec<u8>,
}
fn check_no_leading_dash(s: &str, what: &str) -> Result<()> {
if s.starts_with('-') {
bail!("invalid {what} {s:?}");
}
Ok(())
}
pub async fn list_tree(repo_path: &Path, git_ref: &str, path: &str) -> Result<Vec<TreeEntry>> {
check_no_leading_dash(git_ref, "ref")?;
check_no_leading_dash(path, "path")?;
let treeish = if path.is_empty() { git_ref.to_string() } else { format!("{git_ref}:{path}") };
let output = gitcmd::run_captured(repo_path, &["ls-tree", &treeish]).await?;
let mut entries = Vec::new();
for line in String::from_utf8_lossy(&output).lines() {
let Some((meta, name)) = line.split_once('\t') else {
continue;
};
let mut fields = meta.split(' ');
let (Some(_mode), Some(kind), Some(sha)) = (fields.next(), fields.next(), fields.next()) else {
continue;
};
let kind = match kind {
"tree" => EntryKind::Tree,
"blob" => EntryKind::Blob,
_ => continue,
};
entries.push(TreeEntry { name: name.to_string(), kind, sha: sha.to_string() });
}
Ok(entries)
}
pub async fn read_blob(repo_path: &Path, git_ref: &str, path: &str) -> Result<BlobData> {
check_no_leading_dash(git_ref, "ref")?;
check_no_leading_dash(path, "path")?;
if path.is_empty() {
bail!("empty path");
}
let treeish = format!("{git_ref}:{path}");
let sha = gitcmd::run_captured(repo_path, &["rev-parse", &treeish]).await?;
let sha = String::from_utf8_lossy(&sha).trim().to_string();
let content = gitcmd::run_captured(repo_path, &["show", &treeish]).await?;
Ok(BlobData { sha, content })
}
pub async fn list_all_files(repo_path: &Path, git_ref: &str) -> Result<Vec<String>> {
check_no_leading_dash(git_ref, "ref")?;
let output = gitcmd::run_captured(repo_path, &["ls-tree", "-r", "--name-only", git_ref]).await?;
Ok(String::from_utf8_lossy(&output).lines().map(str::to_string).collect())
}
pub async fn list_branches(repo_path: &Path) -> Result<Vec<String>> {
let output = gitcmd::run_captured(repo_path, &["for-each-ref", "--format=%(refname:short)", "refs/heads"]).await?;
Ok(String::from_utf8_lossy(&output).lines().map(str::to_string).collect())
}