Skip to content

Commit 1db1f00

Browse files
author
Stephan Dilly
authored
Log view (#41)
1 parent 4df7704 commit 1db1f00

15 files changed

+817
-147
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dirs = "2.0"
2929
crossbeam-channel = "0.4"
3030
scopeguard = "1.1"
3131
bitflags = "1.2"
32+
chrono = "0.4"
3233
backtrace = { version = "0.3" }
3334
scopetime = { path = "./scopetime", version = "0.1" }
3435
asyncgit = { path = "./asyncgit", version = "0.2" }

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,4 @@ install:
3737
cargo install --path "."
3838

3939
install-debug:
40-
cargo install --features=timing --path "."
40+
cargo install --features=timing --path "." --offline

asyncgit/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
#![deny(clippy::all)]
66

77
mod diff;
8+
mod revlog;
89
mod status;
910
pub mod sync;
1011

1112
pub use crate::{
1213
diff::{AsyncDiff, DiffParams},
14+
revlog::AsyncLog,
1315
status::AsyncStatus,
1416
sync::{
1517
diff::{DiffLine, DiffLineType, FileDiff},
@@ -30,6 +32,8 @@ pub enum AsyncNotification {
3032
Status,
3133
///
3234
Diff,
35+
///
36+
Log,
3337
}
3438

3539
/// current working director `./`

asyncgit/src/revlog.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use crate::{sync, AsyncNotification, CWD};
2+
use crossbeam_channel::Sender;
3+
use git2::Oid;
4+
use scopetime::scope_time;
5+
use std::{
6+
iter::FromIterator,
7+
sync::{
8+
atomic::{AtomicBool, Ordering},
9+
Arc, Mutex,
10+
},
11+
};
12+
use sync::{utils::repo, LogWalker};
13+
14+
///
15+
pub struct AsyncLog {
16+
current: Arc<Mutex<Vec<Oid>>>,
17+
sender: Sender<AsyncNotification>,
18+
pending: Arc<AtomicBool>,
19+
}
20+
21+
static LIMIT_COUNT: usize = 1000;
22+
23+
impl AsyncLog {
24+
///
25+
pub fn new(sender: Sender<AsyncNotification>) -> Self {
26+
Self {
27+
current: Arc::new(Mutex::new(Vec::new())),
28+
sender,
29+
pending: Arc::new(AtomicBool::new(false)),
30+
}
31+
}
32+
33+
///
34+
pub fn count(&mut self) -> usize {
35+
self.current.lock().unwrap().len()
36+
}
37+
38+
///
39+
pub fn get_slice(
40+
&self,
41+
start_index: usize,
42+
amount: usize,
43+
) -> Vec<Oid> {
44+
let list = self.current.lock().unwrap();
45+
let list_len = list.len();
46+
let min = start_index.min(list_len);
47+
let max = min + amount;
48+
let max = max.min(list_len);
49+
Vec::from_iter(list[min..max].iter().cloned())
50+
}
51+
52+
///
53+
pub fn is_pending(&self) -> bool {
54+
self.pending.load(Ordering::Relaxed)
55+
}
56+
57+
///
58+
pub fn fetch(&mut self) {
59+
if !self.is_pending() {
60+
self.clear();
61+
62+
let arc_current = Arc::clone(&self.current);
63+
let sender = self.sender.clone();
64+
let arc_pending = Arc::clone(&self.pending);
65+
rayon_core::spawn(move || {
66+
arc_pending.store(true, Ordering::Relaxed);
67+
68+
scope_time!("async::revlog");
69+
70+
let mut entries = Vec::with_capacity(LIMIT_COUNT);
71+
let r = repo(CWD);
72+
let mut walker = LogWalker::new(&r);
73+
loop {
74+
entries.clear();
75+
let res_is_err = walker
76+
.read(&mut entries, LIMIT_COUNT)
77+
.is_err();
78+
79+
if !res_is_err {
80+
let mut current = arc_current.lock().unwrap();
81+
current.extend(entries.iter());
82+
}
83+
84+
if res_is_err || entries.len() <= 1 {
85+
break;
86+
} else {
87+
Self::notify(&sender);
88+
}
89+
}
90+
91+
arc_pending.store(false, Ordering::Relaxed);
92+
93+
Self::notify(&sender);
94+
});
95+
}
96+
}
97+
98+
fn clear(&mut self) {
99+
self.current.lock().unwrap().clear();
100+
}
101+
102+
fn notify(sender: &Sender<AsyncNotification>) {
103+
sender.send(AsyncNotification::Log).expect("error sending");
104+
}
105+
}

asyncgit/src/sync/commits_info.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use super::utils::repo;
2+
use git2::{Commit, Error, Oid};
3+
use scopetime::scope_time;
4+
5+
///
6+
#[derive(Debug)]
7+
pub struct CommitInfo {
8+
///
9+
pub message: String,
10+
///
11+
pub time: i64,
12+
///
13+
pub author: String,
14+
///
15+
pub hash: String,
16+
}
17+
18+
///
19+
pub fn get_commits_info(
20+
repo_path: &str,
21+
ids: &[Oid],
22+
) -> Result<Vec<CommitInfo>, Error> {
23+
scope_time!("get_commits_info");
24+
25+
let repo = repo(repo_path);
26+
27+
let commits = ids.iter().map(|id| repo.find_commit(*id).unwrap());
28+
29+
let res = commits
30+
.map(|c: Commit| {
31+
let message = get_message(&c);
32+
let author = if let Some(name) = c.author().name() {
33+
String::from(name)
34+
} else {
35+
String::from("<unknown>")
36+
};
37+
CommitInfo {
38+
message,
39+
author,
40+
time: c.time().seconds(),
41+
hash: c.id().to_string(),
42+
}
43+
})
44+
.collect::<Vec<_>>();
45+
46+
Ok(res)
47+
}
48+
49+
fn get_message(c: &Commit) -> String {
50+
if let Some(msg) = c.message() {
51+
limit_str(msg, 50)
52+
} else {
53+
String::from("<unknown>")
54+
}
55+
}
56+
57+
fn limit_str(s: &str, limit: usize) -> String {
58+
if let Some(first) = s.lines().next() {
59+
first.chars().take(limit).collect::<String>()
60+
} else {
61+
String::new()
62+
}
63+
}
64+
65+
#[cfg(test)]
66+
mod tests {
67+
68+
use super::get_commits_info;
69+
use crate::sync::{
70+
commit, stage_add_file, tests::repo_init_empty,
71+
};
72+
use std::{
73+
fs::File,
74+
io::{Error, Write},
75+
path::Path,
76+
};
77+
78+
#[test]
79+
fn test_log() -> Result<(), Error> {
80+
let file_path = Path::new("foo");
81+
let (_td, repo) = repo_init_empty();
82+
let root = repo.path().parent().unwrap();
83+
let repo_path = root.as_os_str().to_str().unwrap();
84+
85+
File::create(&root.join(file_path))?.write_all(b"a")?;
86+
stage_add_file(repo_path, file_path);
87+
let c1 = commit(repo_path, "commit1");
88+
File::create(&root.join(file_path))?.write_all(b"a")?;
89+
stage_add_file(repo_path, file_path);
90+
let c2 = commit(repo_path, "commit2");
91+
92+
let res = get_commits_info(repo_path, &vec![c2, c1]).unwrap();
93+
94+
assert_eq!(res.len(), 2);
95+
assert_eq!(res[0].message.as_str(), "commit2");
96+
assert_eq!(res[0].author.as_str(), "name");
97+
assert_eq!(res[1].message.as_str(), "commit1");
98+
99+
Ok(())
100+
}
101+
}

asyncgit/src/sync/logwalker.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use git2::{Error, Oid, Repository, Revwalk};
2+
3+
///
4+
pub struct LogWalker<'a> {
5+
repo: &'a Repository,
6+
revwalk: Option<Revwalk<'a>>,
7+
}
8+
9+
impl<'a> LogWalker<'a> {
10+
///
11+
pub fn new(repo: &'a Repository) -> Self {
12+
Self {
13+
repo,
14+
revwalk: None,
15+
}
16+
}
17+
18+
///
19+
pub fn read(
20+
&mut self,
21+
out: &mut Vec<Oid>,
22+
limit: usize,
23+
) -> Result<usize, Error> {
24+
let mut count = 0_usize;
25+
26+
if self.revwalk.is_none() {
27+
let mut walk = self.repo.revwalk()?;
28+
walk.push_head()?;
29+
self.revwalk = Some(walk);
30+
}
31+
32+
if let Some(ref mut walk) = self.revwalk {
33+
for id in walk {
34+
if let Ok(id) = id {
35+
out.push(id);
36+
count += 1;
37+
38+
if count == limit {
39+
break;
40+
}
41+
}
42+
}
43+
}
44+
45+
Ok(count)
46+
}
47+
}
48+
49+
#[cfg(test)]
50+
mod tests {
51+
use super::*;
52+
use crate::sync::{
53+
commit, get_commits_info, stage_add_file,
54+
tests::repo_init_empty,
55+
};
56+
use std::{
57+
fs::File,
58+
io::{Error, Write},
59+
path::Path,
60+
};
61+
62+
#[test]
63+
fn test_limit() -> Result<(), Error> {
64+
let file_path = Path::new("foo");
65+
let (_td, repo) = repo_init_empty();
66+
let root = repo.path().parent().unwrap();
67+
let repo_path = root.as_os_str().to_str().unwrap();
68+
69+
File::create(&root.join(file_path))?.write_all(b"a")?;
70+
stage_add_file(repo_path, file_path);
71+
commit(repo_path, "commit1");
72+
File::create(&root.join(file_path))?.write_all(b"a")?;
73+
stage_add_file(repo_path, file_path);
74+
let oid2 = commit(repo_path, "commit2");
75+
76+
let mut items = Vec::new();
77+
let mut walk = LogWalker::new(&repo);
78+
walk.read(&mut items, 1).unwrap();
79+
80+
assert_eq!(items.len(), 1);
81+
assert_eq!(items[0], oid2);
82+
83+
Ok(())
84+
}
85+
86+
#[test]
87+
fn test_logwalker() -> Result<(), Error> {
88+
let file_path = Path::new("foo");
89+
let (_td, repo) = repo_init_empty();
90+
let root = repo.path().parent().unwrap();
91+
let repo_path = root.as_os_str().to_str().unwrap();
92+
93+
File::create(&root.join(file_path))?.write_all(b"a")?;
94+
stage_add_file(repo_path, file_path);
95+
commit(repo_path, "commit1");
96+
File::create(&root.join(file_path))?.write_all(b"a")?;
97+
stage_add_file(repo_path, file_path);
98+
let oid2 = commit(repo_path, "commit2");
99+
100+
let mut items = Vec::new();
101+
let mut walk = LogWalker::new(&repo);
102+
walk.read(&mut items, 100).unwrap();
103+
104+
let info = get_commits_info(repo_path, &items).unwrap();
105+
dbg!(&info);
106+
107+
assert_eq!(items.len(), 2);
108+
assert_eq!(items[0], oid2);
109+
110+
let mut items = Vec::new();
111+
walk.read(&mut items, 100).unwrap();
112+
113+
assert_eq!(items.len(), 0);
114+
115+
Ok(())
116+
}
117+
}

asyncgit/src/sync/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
//! sync git api
22
3+
mod commits_info;
34
pub mod diff;
45
mod hooks;
56
mod hunks;
7+
mod logwalker;
68
mod reset;
79
pub mod status;
810
pub mod utils;
911

12+
pub use commits_info::{get_commits_info, CommitInfo};
1013
pub use hooks::{hooks_commit_msg, hooks_post_commit, HookResult};
1114
pub use hunks::{stage_hunk, unstage_hunk};
15+
pub use logwalker::LogWalker;
1216
pub use reset::{
1317
reset_stage, reset_workdir_file, reset_workdir_folder,
1418
};

0 commit comments

Comments
 (0)