Skip to content

Commit 1bb11e6

Browse files
committed
get_logs() with gitoxide
1 parent 5d7b5c8 commit 1bb11e6

File tree

2 files changed

+90
-66
lines changed

2 files changed

+90
-66
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ bytecount = "0.6.2"
3333
clap = {version = "3.1.6", features = ["cargo", "wrap_help"]}
3434
color_quant = "1.1.0"
3535
git2 = {version = "0.14.2", default-features = false}
36-
git-repository = { version = "0.15.0", optional = true, default-features = false, git = "https://github.com/Byron/gitoxide", branch = "main" }
36+
git-repository = { version = "0.15.0", default-features = false, git = "https://github.com/Byron/gitoxide", branch = "main" }
3737
image = "0.24.1"
3838
owo-colors = "3.3.0"
3939
regex = "1.5.5"

src/info/repo.rs

Lines changed: 89 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ use anyhow::{Context, Result};
44
use byte_unit::Byte;
55
use git2::Time;
66
use git2::{
7-
BranchType, Commit, Repository, RepositoryOpenFlags, Signature, Status, StatusOptions,
8-
StatusShow,
7+
BranchType, Repository, RepositoryOpenFlags, Signature, Status, StatusOptions, StatusShow,
98
};
9+
use git_repository as git;
10+
use git_repository::bstr::ByteSlice;
1011
use regex::Regex;
1112
use std::collections::HashMap;
1213
use std::path::Path;
@@ -16,8 +17,9 @@ use time::OffsetDateTime;
1617
use time_humanize::HumanTime;
1718

1819
pub struct Repo<'a> {
19-
repo: &'a Repository,
20-
logs: Vec<Commit<'a>>,
20+
git2_repo: &'a Repository,
21+
repo: git::Repository,
22+
commits: Vec<git::DetachedObject>,
2123
}
2224

2325
#[derive(Hash, PartialEq, Eq)]
@@ -36,52 +38,68 @@ impl From<Signature<'_>> for Sig {
3638

3739
impl<'a> Repo<'a> {
3840
pub fn new(
39-
repo: &'a Repository,
41+
git2_repo: &'a Repository,
4042
no_merges: bool,
4143
bot_regex_pattern: &Option<Regex>,
4244
) -> Result<Self> {
43-
let logs = Self::get_logs(repo, no_merges, bot_regex_pattern)?;
44-
Ok(Self { repo, logs })
45+
let mut repo = git::open(git2_repo.path())?;
46+
let logs = Self::get_logs(&mut repo, no_merges, bot_regex_pattern)?;
47+
Ok(Self {
48+
git2_repo,
49+
repo,
50+
commits: logs,
51+
})
4552
}
4653

54+
// TODO: avoid allocating/copying buffers. Instead gather the desired information
55+
// during traversal and keep only author names for processing.
4756
fn get_logs(
48-
repo: &'a Repository,
57+
repo: &mut git::Repository,
4958
no_merges: bool,
5059
bot_regex_pattern: &Option<Regex>,
51-
) -> Result<Vec<Commit<'a>>> {
52-
let mut revwalk = repo.revwalk()?;
53-
revwalk.push_head()?;
54-
let logs: Vec<Commit<'a>> = revwalk
55-
.filter_map(|r| match r {
56-
Err(_) => None,
57-
Ok(r) => repo
58-
.find_commit(r)
59-
.ok()
60-
.filter(|commit| !(no_merges && commit.parents().len() > 1))
61-
.filter(|commit| {
62-
!(bot_regex_pattern.is_some() && is_bot(commit.author(), bot_regex_pattern))
63-
}),
60+
) -> Result<Vec<git::DetachedObject>> {
61+
// assure that objects we just traversed are coming from cache
62+
// when we read the commit right after.
63+
repo.object_cache_size(128 * 1024);
64+
let commits = repo
65+
.head()?
66+
.peel_to_commit_in_place()?
67+
.ancestors()
68+
.all()
69+
.filter_map(|commit_id| {
70+
let commit: git::Commit = commit_id
71+
.ok()?
72+
.object()
73+
.expect("commit is still present/comes from cache")
74+
.into_commit();
75+
if no_merges && commit.parent_ids().take(2).count() > 1 {
76+
return None;
77+
}
78+
if is_bot(commit.iter().author(), bot_regex_pattern) {
79+
return None;
80+
}
81+
commit.detach().into()
6482
})
6583
.collect();
66-
67-
Ok(logs)
84+
Ok(commits)
6885
}
6986

7087
pub fn get_creation_date(&self, iso_time: bool) -> Result<String> {
71-
let first_commit = self.logs.last();
72-
let output = match first_commit {
73-
Some(commit) => {
74-
let time = commit.time();
75-
git_time_to_formatted_time(&time, iso_time)
76-
}
77-
None => "".into(),
78-
};
79-
80-
Ok(output)
88+
let first_commit = self.commits.last();
89+
todo!()
90+
// let output = match first_commit {
91+
// Some(commit) => {
92+
// let time = commit.time();
93+
// git_time_to_formatted_time(&time, iso_time)
94+
// }
95+
// None => "".into(),
96+
// };
97+
//
98+
// Ok(output)
8199
}
82100

83101
pub fn get_number_of_commits(&self) -> String {
84-
let number_of_commits = self.logs.len();
102+
let number_of_commits = self.commits.len();
85103
number_of_commits.to_string()
86104
}
87105

@@ -92,16 +110,16 @@ impl<'a> Repo<'a> {
92110
) -> Result<(Vec<Author>, usize)> {
93111
let mut author_to_number_of_commits: HashMap<Sig, usize> = HashMap::new();
94112
let mut total_nbr_of_commits = 0;
95-
let mailmap = self.repo.mailmap()?;
96-
for commit in &self.logs {
97-
let author = match commit.author_with_mailmap(&mailmap) {
98-
Ok(val) => val,
99-
Err(_) => commit.author(),
100-
};
101-
let author_nbr_of_commits = author_to_number_of_commits
102-
.entry(Sig::from(author))
103-
.or_insert(0);
104-
*author_nbr_of_commits += 1;
113+
let mailmap = self.git2_repo.mailmap()?;
114+
for commit in &self.commits {
115+
// let author = match commit.author_with_mailmap(&mailmap) {
116+
// Ok(val) => val,
117+
// Err(_) => commit.author(),
118+
// };
119+
// let author_nbr_of_commits = author_to_number_of_commits
120+
// .entry(Sig::from(author))
121+
// .or_insert(0);
122+
// *author_nbr_of_commits += 1;
105123
total_nbr_of_commits += 1;
106124
}
107125

@@ -132,17 +150,18 @@ impl<'a> Repo<'a> {
132150
}
133151

134152
pub fn get_date_of_last_commit(&self, iso_time: bool) -> String {
135-
let last_commit = self.logs.first();
153+
let last_commit = self.commits.first();
136154

137-
match last_commit {
138-
Some(commit) => git_time_to_formatted_time(&commit.time(), iso_time),
139-
None => "".into(),
140-
}
155+
todo!()
156+
// match last_commit {
157+
// Some(commit) => git_time_to_formatted_time(&commit.time(), iso_time),
158+
// None => "".into(),
159+
// }
141160
}
142161

143162
// This collects the repo size excluding .git
144163
pub fn get_repo_size(&self) -> (String, u64) {
145-
let (repo_size, file_count) = match self.repo.index() {
164+
let (repo_size, file_count) = match self.git2_repo.index() {
146165
Ok(index) => index.iter().fold(
147166
(0, 0),
148167
|(repo_size, file_count): (u128, u64), index_entry| -> (u128, u64) {
@@ -164,11 +183,11 @@ impl<'a> Repo<'a> {
164183
}
165184

166185
pub fn get_number_of_tags(&self) -> Result<usize> {
167-
Ok(self.repo.tag_names(None)?.len())
186+
Ok(self.git2_repo.tag_names(None)?.len())
168187
}
169188

170189
pub fn get_number_of_branches(&self) -> Result<usize> {
171-
let mut number_of_branches = self.repo.branches(Some(BranchType::Remote))?.count();
190+
let mut number_of_branches = self.git2_repo.branches(Some(BranchType::Remote))?.count();
172191
if number_of_branches > 0 {
173192
//Exclude origin/HEAD -> origin/main
174193
number_of_branches -= 1;
@@ -177,7 +196,7 @@ impl<'a> Repo<'a> {
177196
}
178197

179198
pub fn get_git_username(&self) -> Result<String> {
180-
let config = self.repo.config()?;
199+
let config = self.git2_repo.config()?;
181200
let username = match config.get_entry("user.name") {
182201
Ok(v) => v.value().unwrap_or("").into(),
183202
Err(_) => "".into(),
@@ -190,14 +209,14 @@ impl<'a> Repo<'a> {
190209
let mut version_name = String::new();
191210
let mut most_recent: i64 = 0;
192211

193-
self.repo.tag_foreach(|id, name| {
212+
self.git2_repo.tag_foreach(|id, name| {
194213
if let Ok(name) = String::from_utf8(name[10..].into()) {
195214
let mut current_time: i64 = 0;
196-
if let Ok(tag) = self.repo.find_tag(id) {
197-
if let Ok(c) = self.repo.find_commit(tag.target_id()) {
215+
if let Ok(tag) = self.git2_repo.find_tag(id) {
216+
if let Ok(c) = self.git2_repo.find_commit(tag.target_id()) {
198217
current_time = c.time().seconds();
199218
}
200-
} else if let Ok(c) = self.repo.find_commit(id) {
219+
} else if let Ok(c) = self.git2_repo.find_commit(id) {
201220
current_time = c.time().seconds();
202221
}
203222
if current_time > most_recent {
@@ -214,7 +233,7 @@ impl<'a> Repo<'a> {
214233
}
215234

216235
pub fn get_pending_changes(&self) -> Result<String> {
217-
let statuses = self.repo.statuses(Some(
236+
let statuses = self.git2_repo.statuses(Some(
218237
StatusOptions::default()
219238
.show(StatusShow::Workdir)
220239
.update_index(true)
@@ -254,7 +273,7 @@ impl<'a> Repo<'a> {
254273
}
255274

256275
pub fn get_name_and_url(&self) -> Result<(String, String)> {
257-
let config = self.repo.config()?;
276+
let config = self.git2_repo.config()?;
258277
let mut remote_origin_url: Option<String> = None;
259278
let mut remote_url_fallback = String::new();
260279
let mut repository_name = String::new();
@@ -304,9 +323,9 @@ impl<'a> Repo<'a> {
304323
}
305324

306325
pub fn get_head_refs(&self) -> Result<HeadRefs> {
307-
let head = self.repo.head()?;
326+
let head = self.git2_repo.head()?;
308327
let head_oid = head.target().with_context(|| "Could not read HEAD")?;
309-
let refs = self.repo.references()?;
328+
let refs = self.git2_repo.references()?;
310329
let refs_info = refs
311330
.filter_map(|reference| match reference {
312331
Ok(reference) => match (reference.target(), reference.shorthand()) {
@@ -322,15 +341,20 @@ impl<'a> Repo<'a> {
322341
}
323342

324343
fn work_dir(&self) -> Result<&Path> {
325-
self.repo
344+
self.git2_repo
326345
.workdir()
327346
.with_context(|| "unable to query workdir")
328347
}
329348
}
330349

331-
fn is_bot(author: Signature, bot_regex_pattern: &Option<Regex>) -> bool {
332-
let author_name = String::from_utf8_lossy(author.name_bytes()).into_owned();
333-
bot_regex_pattern.as_ref().unwrap().is_match(&author_name)
350+
fn is_bot(author: Option<git::actor::SignatureRef<'_>>, bot_regex_pattern: &Option<Regex>) -> bool {
351+
author
352+
.and_then(|author| {
353+
bot_regex_pattern
354+
.as_ref()
355+
.map(|regex| regex.is_match(author.name.to_str_lossy().as_ref()))
356+
})
357+
.unwrap_or(false)
334358
}
335359

336360
fn bytes_to_human_readable(bytes: u128) -> String {

0 commit comments

Comments
 (0)