4b5d834
use std::path::Path;
use gix::ObjectId;
use tokio::io::{AsyncBufReadExt, AsyncReadExt};
use crate::GitBaby;
use crate::command::error::CmdError;
use crate::diff::helper;
use crate::diff::types::{
ChangedPath, ChangedPathDiff, ChangedPathRequest, DiffOptions, DiffRequest, NumStat,
RangeDiffSpec, ShortStat,
};
use crate::error::BabyError;
impl GitBaby {
pub async fn raw_diff(
&self,
req: &DiffRequest,
hex_len: Option<u8>,
) -> Result<String, BabyError> {
let opts = DiffOptions::default();
let mut cmd = self
.spawn_diff_cmd(req, hex_len, &opts, helper::build_raw_diff_cmd)
.await?;
run_capture(&mut cmd, &req.left.to_string(), &req.right.to_string()).await
}
pub async fn raw_diff_stream<F>(
&self,
req: &DiffRequest,
hex_len: Option<u8>,
#[allow(unused_mut)] mut callback: F,
) -> Result<(), BabyError>
where
F: FnMut(&[u8]) + Send,
{
let opts = DiffOptions::default();
let mut cmd = self
.spawn_diff_cmd(req, hex_len, &opts, helper::build_raw_diff_cmd)
.await?;
run_stream(
&mut cmd,
&req.left.to_string(),
&req.right.to_string(),
callback,
)
.await
}
pub async fn patch_diff(
&self,
req: &DiffRequest,
hex_len: Option<u8>,
) -> Result<String, BabyError> {
let opts = DiffOptions::default();
let mut cmd = self
.spawn_diff_cmd(req, hex_len, &opts, helper::build_patch_diff_cmd)
.await?;
run_capture(&mut cmd, &req.left.to_string(), &req.right.to_string()).await
}
pub async fn patch_diff_stream<F>(
&self,
req: &DiffRequest,
hex_len: Option<u8>,
#[allow(unused_mut)] mut callback: F,
) -> Result<(), BabyError>
where
F: FnMut(&[u8]) + Send,
{
let opts = DiffOptions::default();
let mut cmd = self
.spawn_diff_cmd(req, hex_len, &opts, helper::build_patch_diff_cmd)
.await?;
run_stream(
&mut cmd,
&req.left.to_string(),
&req.right.to_string(),
callback,
)
.await
}
pub async fn commit_delta(
&self,
req: &DiffRequest,
opts: &DiffOptions,
hex_len: Option<u8>,
) -> Result<Vec<ChangedPath>, BabyError> {
if let Some(paths) = &opts.paths {
helper::validate_paths(paths)?;
}
let hex_len = match hex_len {
Some(n) => n,
None => self.repo_hex_len().await?,
};
let mut cmd = self
.spawn_diff_cmd(req, Some(hex_len), opts, helper::build_commit_delta_cmd)
.await?;
let output = run_output(&mut cmd, &req.left.to_string(), &req.right.to_string()).await?;
let abbrev = hex_len as usize;
let mut paths: Vec<ChangedPath> = Vec::new();
for line in String::from_utf8_lossy(&output.stdout).lines() {
if line.is_empty() {
continue;
}
paths.push(helper::parse_raw_diff_line(line, abbrev)?);
}
Ok(paths)
}
pub async fn commit_diff(
&self,
req: &DiffRequest,
opts: &DiffOptions,
hex_len: Option<u8>,
) -> Result<Vec<ChangedPathDiff>, BabyError> {
if let Some(paths) = &opts.paths {
helper::validate_paths(paths)?;
}
let hex_len = match hex_len {
Some(n) => n,
None => self.repo_hex_len().await?,
};
let mut cmd = self
.spawn_diff_cmd(req, Some(hex_len), opts, helper::build_commit_diff_cmd)
.await?;
let output = run_output(&mut cmd, &req.left.to_string(), &req.right.to_string()).await?;
let abbrev = hex_len as usize;
helper::parse_commit_diff_output(&output.stdout, abbrev)
}
pub async fn find_changed_paths(
&self,
requests: Vec<ChangedPathRequest>,
filter_status: Option<char>,
detect_renames: Option<u8>,
merge_parents: bool,
) -> Result<Vec<ChangedPath>, BabyError> {
let mut cmd = self
.spawn_diff_state_cmd(&requests, filter_status, detect_renames, merge_parents)
.await?;
let payload = helper::build_diff_tree_stdin(&requests);
let output = match cmd.feed(&payload).await {
Ok(()) => match cmd.run().await {
Ok(o) => o,
Err(CmdError::NonZeroExit { stderr, .. }) => {
return Err(helper::classify_stderr(
&stderr,
&requests.first().map_or_else(String::new, |r| match r {
ChangedPathRequest::Commit(o) => o.to_hex().to_string(),
ChangedPathRequest::TreePair(l, _) => l.to_hex().to_string(),
}),
"",
));
}
Err(e) => return Err(BabyError::from(e)),
},
Err(e) => return Err(BabyError::from(e)),
};
let abbrev = self.repo_hex_len().await? as usize;
helper::parse_diff_tree_z_stream(&output.stdout, abbrev)
}
pub async fn diff_stats(
&self,
req: &DiffRequest,
opts: &DiffOptions,
) -> Result<Vec<NumStat>, BabyError> {
if let Some(paths) = &opts.paths {
helper::validate_paths(paths)?;
}
let mut cmd = self
.spawn_diff_cmd_no_hex(req, opts, helper::build_numstat_cmd)
.await?;
let output = run_output(&mut cmd, &req.left.to_string(), &req.right.to_string()).await?;
helper::parse_numstat_output(&output.stdout)
}
pub async fn shortstat(
&self,
req: &DiffRequest,
opts: &DiffOptions,
) -> Result<ShortStat, BabyError> {
if let Some(paths) = &opts.paths {
helper::validate_paths(paths)?;
}
let mut cmd = self
.spawn_diff_cmd_no_hex(req, opts, helper::build_shortstat_cmd)
.await?;
let output = run_output(&mut cmd, &req.left.to_string(), &req.right.to_string()).await?;
helper::parse_shortstat_output(&output.stdout)
}
pub async fn range_diff(
&self,
spec: &RangeDiffSpec,
opts: &DiffOptions,
hex_len: Option<u8>,
) -> Result<String, BabyError> {
let mut cmd = self.spawn_range_diff_cmd(spec, opts, hex_len).await?;
let (left, right) = match spec {
RangeDiffSpec::Range(r1, r2) | RangeDiffSpec::RevisionRange(r1, r2) => {
(r1.clone(), r2.clone())
}
RangeDiffSpec::BaseWithRevisions(_, r1, r2) => (r1.clone(), r2.clone()),
};
run_capture(&mut cmd, &left, &right).await
}
async fn spawn_diff_cmd<F>(
&self,
req: &DiffRequest,
hex_len: Option<u8>,
opts: &DiffOptions,
builder: F,
) -> Result<crate::command::cmd::Cmd, BabyError>
where
F: FnOnce(
&Path,
crate::command::env::Env,
&DiffRequest,
&DiffOptions,
Option<u8>,
) -> crate::command::cmd::Cmd,
{
let dir = self
.facade
.git_repo_dir()
.await
.map_err(crate::share::facade_error)?;
let alternates = self
.facade
.git_alternate_object_directories()
.await
.map_err(crate::share::facade_error)?;
let resolved = match hex_len {
Some(n) => n,
None => self.repo_hex_len().await?,
};
let env = crate::share::build_env_safe(&alternates);
Ok(builder(&dir, env, req, opts, Some(resolved)))
}
async fn spawn_diff_cmd_no_hex<F>(
&self,
req: &DiffRequest,
opts: &DiffOptions,
builder: F,
) -> Result<crate::command::cmd::Cmd, BabyError>
where
F: FnOnce(
&Path,
crate::command::env::Env,
&DiffRequest,
&DiffOptions,
) -> crate::command::cmd::Cmd,
{
let dir = self
.facade
.git_repo_dir()
.await
.map_err(crate::share::facade_error)?;
let alternates = self
.facade
.git_alternate_object_directories()
.await
.map_err(crate::share::facade_error)?;
let env = crate::share::build_env_safe(&alternates);
Ok(builder(&dir, env, req, opts))
}
async fn spawn_diff_state_cmd(
&self,
requests: &[ChangedPathRequest],
filter_status: Option<char>,
detect_renames: Option<u8>,
merge_parents: bool,
) -> Result<crate::command::cmd::Cmd, BabyError> {
let dir = self
.facade
.git_repo_dir()
.await
.map_err(crate::share::facade_error)?;
let alternates = self
.facade
.git_alternate_object_directories()
.await
.map_err(crate::share::facade_error)?;
let env = crate::share::build_env_safe(&alternates);
Ok(helper::build_find_changed_paths_cmd(
&dir,
env,
requests,
filter_status,
detect_renames,
merge_parents,
))
}
async fn spawn_range_diff_cmd(
&self,
spec: &RangeDiffSpec,
opts: &DiffOptions,
hex_len: Option<u8>,
) -> Result<crate::command::cmd::Cmd, BabyError> {
let dir = self
.facade
.git_repo_dir()
.await
.map_err(crate::share::facade_error)?;
let alternates = self
.facade
.git_alternate_object_directories()
.await
.map_err(crate::share::facade_error)?;
let resolved = match hex_len {
Some(n) => n,
None => self.repo_hex_len().await?,
};
let env = crate::share::build_env_safe(&alternates);
Ok(helper::build_range_diff_cmd(
&dir,
env,
spec,
opts,
Some(resolved),
))
}
async fn repo_hex_len(&self) -> Result<u8, BabyError> {
let repo = self
.facade
.gix_repo()
.await
.map_err(crate::share::facade_error)?;
Ok(repo.object_hash().len_in_hex() as u8)
}
}
async fn run_capture(
cmd: &mut crate::command::cmd::Cmd,
left: &str,
right: &str,
) -> Result<String, BabyError> {
let output = match cmd.run().await {
Ok(o) => o,
Err(CmdError::NonZeroExit { ref stderr, .. }) => {
return Err(helper::classify_stderr(stderr, left, right));
}
Err(e) => return Err(helper::classify_cmd_error(e, left, right)),
};
if !output.status.success() {
return Err(helper::classify_stderr(
&String::from_utf8_lossy(&output.stderr),
left,
right,
));
}
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
}
async fn run_output(
cmd: &mut crate::command::cmd::Cmd,
left: &str,
right: &str,
) -> Result<crate::command::cmd::CmdOutput, BabyError> {
let output = match cmd.run().await {
Ok(o) => o,
Err(CmdError::NonZeroExit { ref stderr, .. }) => {
return Err(helper::classify_stderr(stderr, left, right));
}
Err(e) => return Err(helper::classify_cmd_error(e, left, right)),
};
if !output.status.success() {
return Err(helper::classify_stderr(
&String::from_utf8_lossy(&output.stderr),
left,
right,
));
}
Ok(output)
}
async fn run_stream<F>(
cmd: &mut crate::command::cmd::Cmd,
left: &str,
right: &str,
mut callback: F,
) -> Result<(), BabyError>
where
F: FnMut(&[u8]) + Send,
{
if let Err(e) = cmd.spawn().await {
return Err(helper::classify_cmd_error(e, left, right));
}
let stdout = std::mem::replace(&mut cmd.stdout, Box::new(tokio::io::empty()));
let stderr = std::mem::replace(&mut cmd.stderr, Box::new(tokio::io::empty()));
// Drain stderr concurrently so a chatty child cannot fill the pipe buffer
// and deadlock while we consume stdout.
let stderr_task = tokio::spawn(async move {
let mut stderr = stderr;
let mut sbuf = Vec::new();
let _ = stderr.read_to_end(&mut sbuf).await;
sbuf
});
let mut reader = tokio::io::BufReader::new(stdout);
let mut buf = Vec::with_capacity(8192);
loop {
buf.clear();
let n = match reader.read_until(b'\n', &mut buf).await {
Ok(n) => n,
Err(source) => {
return Err(BabyError::Custom(format!("stream read failed: {}", source)));
}
};
if n == 0 {
break;
}
callback(&buf);
}
let status = match cmd.wait().await {
Ok(s) => s,
Err(e) => return Err(helper::classify_cmd_error(e, left, right)),
};
let sbuf = match stderr_task.await {
Ok(v) => v,
Err(e) => {
return Err(BabyError::Custom(format!(
"stderr drain task failed: {}",
e
)));
}
};
if !status.success() {
return Err(helper::classify_stderr(
&String::from_utf8_lossy(&sbuf),
left,
right,
));
}
Ok(())
}
#[allow(dead_code)]
fn _oid_marker(_o: ObjectId) {}