use std::convert::TryFrom as _;
use serde::{
ser::{SerializeStruct as _, Serializer},
Serialize,
};
use radicle_surf::{
diff,
vcs::git::{self, Browser, Rev},
};
use crate::{branch::Branch, error::Error, person::Person, revision::Revision};
#[derive(Clone, Serialize)]
pub struct Stats {
pub additions: u64,
pub deletions: u64,
}
#[derive(Clone, Serialize)]
pub struct Commit {
pub header: Header,
pub stats: Stats,
pub diff: diff::Diff,
pub branches: Vec<Branch>,
}
#[derive(Clone)]
pub struct Header {
pub sha1: git2::Oid,
pub author: Person,
pub summary: String,
pub message: String,
pub committer: Person,
pub committer_time: git2::Time,
}
impl Header {
#[must_use]
pub fn description(&self) -> &str {
self.message
.strip_prefix(&self.summary)
.unwrap_or(&self.message)
.trim()
}
}
impl From<&git::Commit> for Header {
fn from(commit: &git::Commit) -> Self {
Self {
sha1: commit.id,
author: Person {
name: commit.author.name.clone(),
email: commit.author.email.clone(),
},
summary: commit.summary.clone(),
message: commit.message.clone(),
committer: Person {
name: commit.committer.name.clone(),
email: commit.committer.email.clone(),
},
committer_time: commit.committer.time,
}
}
}
impl Serialize for Header {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("Header", 6)?;
state.serialize_field("sha1", &self.sha1.to_string())?;
state.serialize_field("author", &self.author)?;
state.serialize_field("summary", &self.summary)?;
state.serialize_field("description", &self.description())?;
state.serialize_field("committer", &self.committer)?;
state.serialize_field("committerTime", &self.committer_time.seconds())?;
state.end()
}
}
#[derive(Serialize)]
pub struct Commits {
pub headers: Vec<Header>,
pub stats: radicle_surf::vcs::git::Stats,
}
pub fn commit(browser: &mut Browser<'_>, sha1: git2::Oid) -> Result<Commit, Error> {
browser.commit(sha1)?;
let history = browser.get();
let commit = history.first();
let diff = if let Some(parent) = commit.parents.first() {
browser.diff(*parent, sha1)?
} else {
browser.initial_diff(sha1)?
};
let mut deletions = 0;
let mut additions = 0;
for file in &diff.modified {
if let diff::FileDiff::Plain { ref hunks } = file.diff {
for hunk in hunks.iter() {
for line in &hunk.lines {
match line {
diff::LineDiff::Addition { .. } => additions += 1,
diff::LineDiff::Deletion { .. } => deletions += 1,
_ => {},
}
}
}
}
}
for file in &diff.created {
if let diff::FileDiff::Plain { ref hunks } = file.diff {
for hunk in hunks.iter() {
for line in &hunk.lines {
if let diff::LineDiff::Addition { .. } = line {
additions += 1
}
}
}
}
}
for file in &diff.deleted {
if let diff::FileDiff::Plain { ref hunks } = file.diff {
for hunk in hunks.iter() {
for line in &hunk.lines {
if let diff::LineDiff::Deletion { .. } = line {
deletions += 1
}
}
}
}
}
let branches = browser
.revision_branches(sha1)?
.into_iter()
.map(Branch::from)
.collect();
Ok(Commit {
header: Header::from(commit),
stats: Stats {
additions,
deletions,
},
diff,
branches,
})
}
pub fn header(browser: &mut Browser<'_>, sha1: git2::Oid) -> Result<Header, Error> {
browser.commit(sha1)?;
let history = browser.get();
let commit = history.first();
Ok(Header::from(commit))
}
pub fn commits<P>(
browser: &mut Browser<'_>,
maybe_revision: Option<Revision<P>>,
) -> Result<Commits, Error>
where
P: ToString,
{
let maybe_revision = maybe_revision.map(Rev::try_from).transpose()?;
if let Some(revision) = maybe_revision {
browser.rev(revision)?;
}
let headers = browser.get().iter().map(Header::from).collect();
let stats = browser.get_stats()?;
Ok(Commits { headers, stats })
}