1use std::convert::TryFrom as _;
19
20use serde::{
21 ser::{SerializeStruct as _, Serializer},
22 Serialize,
23};
24
25use radicle_surf::{
26 diff,
27 vcs::git::{self, Browser, Rev},
28};
29
30use crate::{branch::Branch, error::Error, person::Person, revision::Revision};
31
32#[derive(Clone, Serialize)]
34pub struct Stats {
35 pub additions: u64,
37 pub deletions: u64,
39}
40
41#[derive(Clone, Serialize)]
43pub struct Commit {
44 pub header: Header,
46 pub stats: Stats,
48 pub diff: diff::Diff,
50 pub branches: Vec<Branch>,
52}
53
54#[derive(Clone)]
56pub struct Header {
57 pub sha1: git2::Oid,
60 pub author: Person,
62 pub summary: String,
64 pub message: String,
66 pub committer: Person,
68 pub committer_time: git2::Time,
71}
72
73impl Header {
74 #[must_use]
77 pub fn description(&self) -> &str {
78 self.message
79 .strip_prefix(&self.summary)
80 .unwrap_or(&self.message)
81 .trim()
82 }
83}
84
85impl From<&git::Commit> for Header {
86 fn from(commit: &git::Commit) -> Self {
87 Self {
88 sha1: commit.id,
89 author: Person {
90 name: commit.author.name.clone(),
91 email: commit.author.email.clone(),
92 },
93 summary: commit.summary.clone(),
94 message: commit.message.clone(),
95 committer: Person {
96 name: commit.committer.name.clone(),
97 email: commit.committer.email.clone(),
98 },
99 committer_time: commit.committer.time,
100 }
101 }
102}
103
104impl Serialize for Header {
105 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
106 where
107 S: Serializer,
108 {
109 let mut state = serializer.serialize_struct("Header", 6)?;
110 state.serialize_field("sha1", &self.sha1.to_string())?;
111 state.serialize_field("author", &self.author)?;
112 state.serialize_field("summary", &self.summary)?;
113 state.serialize_field("description", &self.description())?;
114 state.serialize_field("committer", &self.committer)?;
115 state.serialize_field("committerTime", &self.committer_time.seconds())?;
116 state.end()
117 }
118}
119
120#[derive(Serialize)]
122pub struct Commits {
123 pub headers: Vec<Header>,
125 pub stats: radicle_surf::vcs::git::Stats,
127}
128
129pub fn commit(browser: &mut Browser<'_>, sha1: git2::Oid) -> Result<Commit, Error> {
136 browser.commit(sha1)?;
137
138 let history = browser.get();
139 let commit = history.first();
140
141 let diff = if let Some(parent) = commit.parents.first() {
142 browser.diff(*parent, sha1)?
143 } else {
144 browser.initial_diff(sha1)?
145 };
146
147 let mut deletions = 0;
148 let mut additions = 0;
149
150 for file in &diff.modified {
151 if let diff::FileDiff::Plain { ref hunks } = file.diff {
152 for hunk in hunks.iter() {
153 for line in &hunk.lines {
154 match line {
155 diff::LineDiff::Addition { .. } => additions += 1,
156 diff::LineDiff::Deletion { .. } => deletions += 1,
157 _ => {},
158 }
159 }
160 }
161 }
162 }
163
164 for file in &diff.created {
165 if let diff::FileDiff::Plain { ref hunks } = file.diff {
166 for hunk in hunks.iter() {
167 for line in &hunk.lines {
168 if let diff::LineDiff::Addition { .. } = line {
169 additions += 1
170 }
171 }
172 }
173 }
174 }
175
176 for file in &diff.deleted {
177 if let diff::FileDiff::Plain { ref hunks } = file.diff {
178 for hunk in hunks.iter() {
179 for line in &hunk.lines {
180 if let diff::LineDiff::Deletion { .. } = line {
181 deletions += 1
182 }
183 }
184 }
185 }
186 }
187
188 let branches = browser
189 .revision_branches(sha1)?
190 .into_iter()
191 .map(Branch::from)
192 .collect();
193
194 Ok(Commit {
195 header: Header::from(commit),
196 stats: Stats {
197 additions,
198 deletions,
199 },
200 diff,
201 branches,
202 })
203}
204
205pub fn header(browser: &mut Browser<'_>, sha1: git2::Oid) -> Result<Header, Error> {
212 browser.commit(sha1)?;
213
214 let history = browser.get();
215 let commit = history.first();
216
217 Ok(Header::from(commit))
218}
219
220pub fn commits<P>(
227 browser: &mut Browser<'_>,
228 maybe_revision: Option<Revision<P>>,
229) -> Result<Commits, Error>
230where
231 P: ToString,
232{
233 let maybe_revision = maybe_revision.map(Rev::try_from).transpose()?;
234
235 if let Some(revision) = maybe_revision {
236 browser.rev(revision)?;
237 }
238
239 let headers = browser.get().iter().map(Header::from).collect();
240 let stats = browser.get_stats()?;
241
242 Ok(Commits { headers, stats })
243}