Skip to content

Commit cff720a

Browse files
authored
Merge pull request #315 from o2sh/replace-git-sys-calls
Replace git sys calls with git2-rs (libgit2)
2 parents 8dd4255 + b8859a4 commit cff720a

File tree

11 files changed

+493
-510
lines changed

11 files changed

+493
-510
lines changed

Diff for: src/main.rs

+5-12
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,23 @@
22
#![recursion_limit = "1024"]
33
#![cfg_attr(feature = "fail-on-deprecated", deny(deprecated))]
44

5-
use onefetch::{cli::Cli, cli_utils, error::*, git_utils, info};
5+
use onefetch::{cli::Cli, cli_utils, error::*, info, printer, repo};
66

7-
use {
8-
process::{Command, Stdio},
9-
std::{io, process},
10-
};
7+
use std::{io, process};
118

129
mod onefetch;
1310

1411
fn run() -> Result<()> {
1512
#[cfg(windows)]
1613
let _ = ansi_term::enable_ansi_support();
1714

18-
if !is_git_installed() {
15+
if !cli_utils::is_git_installed() {
1916
return Err("git failed to execute!".into());
2017
}
2118

2219
let config = Cli::new()?;
2320

24-
if !git_utils::is_valid_repo(&config.repo_path)? {
21+
if !repo::is_valid(&config.repo_path)? {
2522
return Err("please run onefetch inside of a non-bare git repository".into());
2623
}
2724

@@ -35,7 +32,7 @@ fn run() -> Result<()> {
3532

3633
let info = info::Info::new(config)?;
3734

38-
let mut printer = cli_utils::Printer::new(io::BufWriter::new(io::stdout()), info);
35+
let mut printer = printer::Printer::new(io::BufWriter::new(io::stdout()), info);
3936

4037
printer.print()?;
4138

@@ -55,7 +52,3 @@ fn main() {
5552
}
5653
}
5754
}
58-
59-
fn is_git_installed() -> bool {
60-
Command::new("git").arg("--version").stdout(Stdio::null()).status().is_ok()
61-
}

Diff for: src/onefetch/cli_utils.rs

+11-84
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,8 @@
1-
use crate::onefetch::{
2-
ascii_art::AsciiArt, deps::package_manager::PackageManager, error::*, info::Info,
3-
language::Language,
4-
};
5-
use colored::Color;
1+
use crate::onefetch::{deps::package_manager::PackageManager, error::*, language::Language};
62
use std::env;
7-
use std::io::Write;
3+
use std::process::{Command, Stdio};
84
use strum::IntoEnumIterator;
95

10-
pub struct Printer<W> {
11-
writer: W,
12-
info: Info,
13-
}
14-
15-
impl<W: Write> Printer<W> {
16-
pub fn new(writer: W, info: Info) -> Self {
17-
Self { writer, info }
18-
}
19-
20-
pub fn print(&mut self) -> Result<()> {
21-
let center_pad = " ";
22-
let info_str = format!("{}", &self.info);
23-
let mut info_lines = info_str.lines();
24-
let colors: Vec<Color> = Vec::new();
25-
let mut buf = String::new();
26-
27-
if self.info.config.art_off {
28-
buf.push_str(&info_str);
29-
} else if let Some(custom_image) = &self.info.config.image {
30-
buf.push_str(
31-
&self
32-
.info
33-
.config
34-
.image_backend
35-
.as_ref()
36-
.unwrap()
37-
.add_image(
38-
info_lines.map(|s| format!("{}{}", center_pad, s)).collect(),
39-
custom_image,
40-
self.info.config.image_color_resolution,
41-
)
42-
.chain_err(|| "Error while drawing image")?,
43-
);
44-
} else {
45-
let mut logo_lines = if let Some(custom_ascii) = &self.info.config.ascii_input {
46-
AsciiArt::new(custom_ascii, &colors, !self.info.config.no_bold)
47-
} else {
48-
AsciiArt::new(self.get_ascii(), &self.info.ascii_colors, !self.info.config.no_bold)
49-
};
50-
51-
loop {
52-
match (logo_lines.next(), info_lines.next()) {
53-
(Some(logo_line), Some(info_line)) => {
54-
buf.push_str(&format!("{}{}{:^}\n", logo_line, center_pad, info_line))
55-
}
56-
(Some(logo_line), None) => buf.push_str(&format!("{}\n", logo_line)),
57-
(None, Some(info_line)) => buf.push_str(&format!(
58-
"{:<width$}{}{:^}\n",
59-
"",
60-
center_pad,
61-
info_line,
62-
width = logo_lines.width()
63-
)),
64-
(None, None) => {
65-
buf.push('\n');
66-
break;
67-
}
68-
}
69-
}
70-
}
71-
72-
write!(self.writer, "{}", buf)?;
73-
74-
Ok(())
75-
}
76-
77-
fn get_ascii(&self) -> &str {
78-
let language = if let Some(ascii_language) = &self.info.config.ascii_language {
79-
ascii_language
80-
} else {
81-
&self.info.dominant_language
82-
};
83-
84-
language.get_ascii_art()
85-
}
86-
}
87-
886
pub fn print_supported_languages() -> Result<()> {
897
for l in Language::iter() {
908
println!("{}", l);
@@ -106,3 +24,12 @@ pub fn is_truecolor_terminal() -> bool {
10624
.map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
10725
.unwrap_or(false)
10826
}
27+
28+
pub fn get_git_version() -> Result<String> {
29+
let version = Command::new("git").arg("--version").output()?;
30+
Ok(String::from_utf8_lossy(&version.stdout).replace('\n', ""))
31+
}
32+
33+
pub fn is_git_installed() -> bool {
34+
Command::new("git").arg("--version").stdout(Stdio::null()).status().is_ok()
35+
}

Diff for: src/onefetch/git_utils.rs

+104-64
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,120 @@
1-
use crate::onefetch::{commit_info::CommitInfo, error::*};
2-
use git2::{Repository, RepositoryOpenFlags};
3-
use regex::Regex;
4-
use std::path::Path;
5-
6-
pub fn get_repo_work_dir(repo: &Repository) -> Result<String> {
7-
if let Some(workdir) = work_dir(&repo)?.to_str() {
8-
Ok(workdir.to_string())
9-
} else {
10-
Err("invalid workdir".into())
1+
use crate::onefetch::error::*;
2+
use std::process::Command;
3+
4+
pub fn get_git_history(dir: &str, no_merges: bool) -> Result<Vec<String>> {
5+
let mut args = vec!["-C", dir, "log"];
6+
if no_merges {
7+
args.push("--no-merges");
118
}
12-
}
139

14-
fn work_dir(repo: &Repository) -> Result<&Path> {
15-
repo.workdir().ok_or_else(|| "unable to query workdir".into())
10+
args.push("--pretty=%cr\t%ae\t%an");
11+
12+
let output = Command::new("git").args(args).output()?;
13+
14+
let output = String::from_utf8_lossy(&output.stdout);
15+
Ok(output.lines().map(|x| x.to_string()).collect::<Vec<_>>())
1616
}
1717

18-
pub fn get_repo_name_and_url(repo: &Repository) -> Result<(String, String)> {
19-
let config = repo.config()?;
20-
let mut remote_origin_url: Option<String> = None;
21-
let mut remote_url_fallback = String::new();
22-
let mut repository_name = String::new();
23-
let remote_regex = Regex::new(r"remote\.[a-zA-Z0-9]+\.url").unwrap();
24-
25-
for entry in &config.entries(None).unwrap() {
26-
let entry = entry?;
27-
let entry_name = entry.name().unwrap();
28-
if entry_name == "remote.origin.url" {
29-
remote_origin_url = Some(entry.value().unwrap().to_string());
30-
} else if remote_regex.is_match(entry_name) {
31-
remote_url_fallback = entry.value().unwrap().to_string()
32-
}
18+
pub fn get_authors(git_history: &[String], n: usize) -> Vec<(String, usize, usize)> {
19+
let mut authors = std::collections::HashMap::new();
20+
let mut author_name_by_email = std::collections::HashMap::new();
21+
let mut total_commits = 0;
22+
for line in git_history {
23+
let author_email = line.split('\t').collect::<Vec<_>>()[1].to_string();
24+
let author_name = line.split('\t').collect::<Vec<_>>()[2].to_string();
25+
let commit_count = authors.entry(author_email.to_string()).or_insert(0);
26+
author_name_by_email.entry(author_email.to_string()).or_insert(author_name);
27+
*commit_count += 1;
28+
total_commits += 1;
3329
}
3430

35-
let remote_url = if let Some(url) = remote_origin_url { url } else { remote_url_fallback };
31+
let mut authors: Vec<(String, usize)> = authors.into_iter().collect();
32+
authors.sort_by(|(_, a_count), (_, b_count)| b_count.cmp(a_count));
3633

37-
let name_parts: Vec<&str> = remote_url.split('/').collect();
34+
authors.truncate(n);
3835

39-
if !name_parts.is_empty() {
40-
let mut i = 1;
41-
while repository_name.is_empty() && i <= name_parts.len() {
42-
repository_name = name_parts[name_parts.len() - i].to_string();
43-
i += 1;
44-
}
45-
}
36+
let authors: Vec<(String, usize, usize)> = authors
37+
.into_iter()
38+
.map(|(author, count)| {
39+
(
40+
author_name_by_email.get(&author).unwrap().trim_matches('\'').to_string(),
41+
count,
42+
count * 100 / total_commits,
43+
)
44+
})
45+
.collect();
4646

47-
if repository_name.contains(".git") {
48-
let repo_name = repository_name.clone();
49-
let parts: Vec<&str> = repo_name.split(".git").collect();
50-
repository_name = parts[0].to_string();
51-
}
47+
authors
48+
}
49+
50+
pub fn get_date_of_last_commit(git_history: &[String]) -> Result<String> {
51+
let last_commit = git_history.first();
52+
53+
let output = match last_commit {
54+
Some(date) => date.split('\t').collect::<Vec<_>>()[0].to_string(),
55+
None => "??".into(),
56+
};
5257

53-
Ok((repository_name, remote_url))
58+
Ok(output)
5459
}
5560

56-
pub fn get_current_commit_info(repo: &Repository) -> Result<CommitInfo> {
57-
let head = repo.head()?;
58-
let head_oid = head.target().ok_or("")?;
59-
let refs = repo.references()?;
60-
let refs_info = refs
61-
.filter_map(|reference| match reference {
62-
Ok(reference) => match (reference.target(), reference.shorthand()) {
63-
(Some(oid), Some(shorthand)) if oid == head_oid => Some(if reference.is_tag() {
64-
String::from("tags/") + shorthand
65-
} else {
66-
String::from(shorthand)
67-
}),
68-
_ => None,
69-
},
70-
Err(_) => None,
71-
})
72-
.collect::<Vec<String>>();
73-
Ok(CommitInfo::new(head_oid, refs_info))
61+
pub fn get_creation_date(git_history: &[String]) -> Result<String> {
62+
let first_commit = git_history.last();
63+
64+
let output = match first_commit {
65+
Some(creation_time) => creation_time.split('\t').collect::<Vec<_>>()[0].to_string(),
66+
None => "??".into(),
67+
};
68+
69+
Ok(output)
7470
}
7571

76-
pub fn is_valid_repo(repo_path: &str) -> Result<bool> {
77-
let repo = Repository::open_ext(repo_path, RepositoryOpenFlags::empty(), Vec::<&Path>::new());
72+
pub fn get_number_of_commits(git_history: &[String]) -> String {
73+
let number_of_commits = git_history.len();
74+
number_of_commits.to_string()
75+
}
76+
77+
pub fn get_packed_size(dir: &str) -> Result<String> {
78+
let output = Command::new("git")
79+
.arg("-C")
80+
.arg(dir)
81+
.arg("count-objects")
82+
.arg("-vH")
83+
.output()
84+
.expect("Failed to execute git.");
85+
86+
let output = String::from_utf8_lossy(&output.stdout);
87+
let lines = output.to_string();
88+
let size_line = lines.split('\n').find(|line| line.starts_with("size-pack:"));
7889

79-
Ok(repo.is_ok() && !repo?.is_bare())
90+
let repo_size = match size_line {
91+
None => "??",
92+
Some(size_str) => &(size_str[11..]),
93+
};
94+
95+
let output = Command::new("git")
96+
.arg("-C")
97+
.arg(dir)
98+
.arg("ls-files")
99+
.output()
100+
.expect("Failed to execute git.");
101+
// To check if command executed successfully or not
102+
let error = &output.stderr;
103+
104+
if error.is_empty() {
105+
let output = String::from_utf8_lossy(&output.stdout);
106+
107+
let lines = output.to_string();
108+
let files_list = lines.split('\n');
109+
let mut files_count: u128 = 0;
110+
for _file in files_list {
111+
files_count += 1;
112+
}
113+
files_count -= 1; // As splitting giving one line extra(blank).
114+
let res = repo_size.to_owned() + (" (") + &(files_count.to_string()) + (" files)");
115+
Ok(res)
116+
} else {
117+
let res = repo_size;
118+
Ok(res.into())
119+
}
80120
}

0 commit comments

Comments
 (0)