Skip to content
Clone
80f9e01
413 lines 13.5 KB Rust
use std::path::Path;
use crate::GitBaby;
use crate::commit::helper;
use crate::commit::types::{
CommitInfo, CommitShortStat, CommitStats, ListCommitsOptions, TreeEntry,
};
use crate::error::BabyError;
impl GitBaby {
pub async fn get_commit(&self, revision: &str) -> Result<CommitInfo, BabyError> {
helper::validate_revision(revision)?;
let id = self
.rev_parse(revision)
.await
.map_err(helper::facade_error)?;
let pool = crate::cache::try_global_cache();
let key = crate::cache::object_key(crate::cache::NS_COMMIT, &id);
if let Some(p) = &pool
&& let Ok(Some(v)) = p.store().get(&key).await
&& let Ok(info) = serde_json::from_slice(&v)
{
return Ok(info);
}
let mut cmd = self
.spawn_commit_cmd(revision, |dir, env| {
helper::build_get_commit_cmd(dir, env, revision)
})
.await?;
let ctx = helper::StderrCtx {
revision,
path: None,
};
let output = cmd
.run()
.await
.map_err(|e| helper::classify_cmd_error(e, &ctx))?;
if !output.status.success() {
return Err(helper::classify_stderr(
&String::from_utf8_lossy(&output.stderr),
&ctx,
));
}
let info = helper::parse_commit_porcelain(id, &output.stdout)?;
if let Some(p) = &pool
&& let Ok(v) = serde_json::to_vec(&info)
{
p.store().insert(key, v);
}
Ok(info)
}
pub async fn rev_parse(&self, revision: &str) -> Result<gix::ObjectId, BabyError> {
helper::validate_revision(revision)?;
let pool = crate::cache::try_global_cache();
let key = if pool.is_some() {
let dir = self
.facade
.git_repo_dir()
.await
.map_err(helper::facade_error)?;
let dir = std::fs::canonicalize(&dir)
.map_err(|source| BabyError::Io { path: dir, source })?;
Some(crate::cache::rev_key(&dir, revision))
} else {
None
};
if let (Some(p), Some(key)) = (&pool, &key)
&& let Ok(Some(v)) = p.store().get(key).await
&& let Ok(hex) = String::from_utf8(v)
&& let Ok(oid) = gix::ObjectId::from_hex(hex.trim().as_bytes())
{
return Ok(oid);
}
let mut cmd = self
.spawn_commit_cmd(revision, |dir, env| {
helper::build_rev_parse_cmd(dir, env, revision)
})
.await?;
let ctx = helper::StderrCtx {
revision,
path: None,
};
let output = cmd
.run()
.await
.map_err(|e| helper::classify_cmd_error(e, &ctx))?;
if !output.status.success() {
return Err(helper::classify_stderr(
&String::from_utf8_lossy(&output.stderr),
&ctx,
));
}
let text = String::from_utf8_lossy(&output.stdout);
let hex = text.trim();
let oid = gix::ObjectId::from_hex(hex.as_bytes()).map_err(|source| {
BabyError::Custom(format!(
"rev-parse returned invalid oid `{}`: {}",
hex, source
))
})?;
if let (Some(p), Some(key)) = (&pool, &key) {
p.store().insert(key.clone(), hex.as_bytes().to_vec());
}
Ok(oid)
}
pub async fn is_ancestor(&self, ancestor: &str, descendant: &str) -> Result<bool, BabyError> {
helper::validate_revision(ancestor)?;
helper::validate_revision(descendant)?;
let mut cmd = self
.spawn_commit_cmd(descendant, |dir, env| {
helper::build_is_ancestor_cmd(dir, env, ancestor, descendant)
})
.await?;
let ctx = helper::StderrCtx {
revision: descendant,
path: None,
};
let output = match cmd.run().await {
Ok(o) => o,
Err(CmdError::NonZeroExit { stderr, .. }) => {
if stderr.trim().is_empty() {
return Ok(false);
}
return Err(helper::classify_stderr(&stderr, &ctx));
}
Err(e) => return Err(helper::classify_cmd_error(e, &ctx)),
};
if output.status.success() {
Ok(true)
} else {
Ok(false)
}
}
pub async fn list_commits(
&self,
opts: ListCommitsOptions,
) -> Result<Vec<CommitInfo>, BabyError> {
helper::validate_list_commits_options(&opts)?;
let rev = opts.revision.clone();
let mut cmd = self
.spawn_commit_cmd(&rev, |dir, env| {
helper::build_list_commits_cmd(dir, env, &opts)
})
.await?;
let ctx = helper::StderrCtx {
revision: &rev,
path: opts.path.as_deref(),
};
let output = cmd
.run()
.await
.map_err(|e| helper::classify_cmd_error(e, &ctx))?;
if !output.status.success() {
return Err(helper::classify_stderr(
&String::from_utf8_lossy(&output.stderr),
&ctx,
));
}
helper::parse_log_porcelain(&output.stdout)
}
pub async fn count_commits(&self, opts: ListCommitsOptions) -> Result<u64, BabyError> {
helper::validate_list_commits_options(&opts)?;
let rev = opts.revision.clone();
let mut cmd = self
.spawn_commit_cmd(&rev, |dir, env| {
helper::build_count_commits_cmd(dir, env, &opts)
})
.await?;
let ctx = helper::StderrCtx {
revision: &rev,
path: opts.path.as_deref(),
};
let output = cmd
.run()
.await
.map_err(|e| helper::classify_cmd_error(e, &ctx))?;
if !output.status.success() {
return Err(helper::classify_stderr(
&String::from_utf8_lossy(&output.stderr),
&ctx,
));
}
let s = String::from_utf8_lossy(&output.stdout);
s.trim().parse::<u64>().map_err(|_| BabyError::CommitParse {
line_number: 0,
message: format!("rev-list --count returned `{}`", s.trim()),
})
}
pub async fn commit_stats(
&self,
revision: &str,
path: Option<&Path>,
) -> Result<CommitStats, BabyError> {
helper::validate_revision(revision)?;
if let Some(p) = path {
helper::validate_path_local_no_escape(p)?;
}
let mut cmd = self
.spawn_commit_cmd(revision, |dir, env| {
helper::build_commit_stats_cmd(dir, env, revision, path)
})
.await?;
let ctx = helper::StderrCtx { revision, path };
let output = cmd
.run()
.await
.map_err(|e| helper::classify_cmd_error(e, &ctx))?;
if !output.status.success() {
return Err(helper::classify_stderr(
&String::from_utf8_lossy(&output.stderr),
&ctx,
));
}
helper::parse_shortstat(&output.stdout)
}
pub async fn commit_short_stats(
&self,
revision: &str,
) -> Result<Vec<CommitShortStat>, BabyError> {
helper::validate_revision(revision)?;
let mut cmd = self
.spawn_commit_cmd(revision, |dir, env| {
helper::build_commit_short_stats_cmd(dir, env, revision)
})
.await?;
let ctx = helper::StderrCtx {
revision,
path: None,
};
let output = cmd
.run()
.await
.map_err(|e| helper::classify_cmd_error(e, &ctx))?;
if !output.status.success() {
return Err(helper::classify_stderr(
&String::from_utf8_lossy(&output.stderr),
&ctx,
));
}
helper::parse_commit_short_stats(&output.stdout)
}
pub async fn get_tree_entries(
&self,
revision: &str,
path: &Path,
) -> Result<Vec<TreeEntry>, BabyError> {
helper::validate_revision(revision)?;
helper::validate_path_local_no_escape(path)?;
let pool = crate::cache::try_global_cache();
let key = if pool.is_some() {
match self.rev_parse(revision).await.map_err(helper::facade_error) {
Ok(oid) => {
let mut k =
Vec::with_capacity(oid.as_bytes().len() + 1 + path.as_os_str().len());
k.extend_from_slice(oid.as_bytes());
k.push(0);
k.extend_from_slice(path.to_string_lossy().as_bytes());
Some(crate::cache::encode(crate::cache::NS_TREE_ENTRIES, &k))
}
Err(_) => None,
}
} else {
None
};
if let (Some(p), Some(key)) = (&pool, &key)
&& let Ok(Some(v)) = p.store().get(key).await
&& let Ok(entries) = serde_json::from_slice::<Vec<TreeEntry>>(&v)
{
return Ok(entries);
}
let mut cmd = self
.spawn_commit_cmd(revision, |dir, env| {
helper::build_tree_entries_cmd(dir, env, revision, path)
})
.await?;
let ctx = helper::StderrCtx {
revision,
path: Some(path),
};
let output = cmd
.run()
.await
.map_err(|e| helper::classify_cmd_error(e, &ctx))?;
if !output.status.success() {
return Err(helper::classify_stderr(
&String::from_utf8_lossy(&output.stderr),
&ctx,
));
}
let entries = helper::parse_tree_entries(&output.stdout)?;
if let (Some(p), Some(key)) = (&pool, &key)
&& let Ok(v) = serde_json::to_vec(&entries)
{
p.store().insert(key.clone(), v);
}
Ok(entries)
}
pub async fn tree_entry_at(
&self,
revision: &str,
path: &Path,
) -> Result<Option<TreeEntry>, BabyError> {
helper::validate_revision(revision)?;
helper::validate_path_local_no_escape(path)?;
let raw = path.to_string_lossy();
let clean = raw.trim().trim_start_matches("./");
if clean.is_empty() || clean == "." {
return Ok(None);
}
let (parent, base) = match clean.rsplit_once('/') {
Some((p, b)) => (p, b),
None => ("", clean),
};
if base.is_empty() {
return Ok(None);
}
let treeish = if parent.is_empty() {
format!("{}:", revision)
} else {
format!("{}:{}", revision, parent)
};
let mut cmd = self
.spawn_commit_cmd(revision, |dir, env| {
let args: Vec<String> = ["-c", "core.quotepath=off", "ls-tree", treeish.as_str()]
.into_iter()
.map(String::from)
.collect();
crate::share::git_cmd(helper::CALLER, dir, env, None, args)
})
.await?;
let ctx = helper::StderrCtx {
revision,
path: Some(path),
};
let output = cmd
.run()
.await
.map_err(|e| helper::classify_cmd_error(e, &ctx))?;
if !output.status.success() {
return Err(helper::classify_stderr(
&String::from_utf8_lossy(&output.stderr),
&ctx,
));
}
let entries = helper::parse_tree_entries(&output.stdout)?;
Ok(entries.into_iter().find(|e| e.name == base))
}
pub async fn last_commit_for_path(
&self,
revision: &str,
path: &Path,
) -> Result<CommitInfo, BabyError> {
helper::validate_revision(revision)?;
helper::validate_path_local_no_escape(path)?;
let mut cmd = self
.spawn_commit_cmd(revision, |dir, env| {
helper::build_last_commit_for_path_cmd(dir, env, revision, path)
})
.await?;
let ctx = helper::StderrCtx {
revision,
path: Some(path),
};
let output = cmd
.run()
.await
.map_err(|e| helper::classify_cmd_error(e, &ctx))?;
if !output.status.success() {
return Err(helper::classify_stderr(
&String::from_utf8_lossy(&output.stderr),
&ctx,
));
}
let mut commits = helper::parse_log_porcelain(&output.stdout)?;
commits
.pop()
.ok_or_else(|| BabyError::RevisionNotFound(revision.to_string()))
}
async fn spawn_commit_cmd<F>(
&self,
rev: &str,
builder: F,
) -> Result<crate::command::cmd::Cmd, BabyError>
where
F: FnOnce(&Path, crate::command::env::Env) -> crate::command::cmd::Cmd,
{
let _ = rev;
let dir = self
.facade
.git_repo_dir()
.await
.map_err(helper::facade_error)?;
let alternates = self
.facade
.git_alternate_object_directories()
.await
.map_err(helper::facade_error)?;
let env = helper::build_env(&alternates);
Ok(builder(&dir, env))
}
}
use crate::command::error::CmdError;