Skip to content

Commit

Permalink
Log view (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephan Dilly authored May 5, 2020
1 parent 4df7704 commit 1db1f00
Show file tree
Hide file tree
Showing 15 changed files with 817 additions and 147 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dirs = "2.0"
crossbeam-channel = "0.4"
scopeguard = "1.1"
bitflags = "1.2"
chrono = "0.4"
backtrace = { version = "0.3" }
scopetime = { path = "./scopetime", version = "0.1" }
asyncgit = { path = "./asyncgit", version = "0.2" }
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ install:
cargo install --path "."

install-debug:
cargo install --features=timing --path "."
cargo install --features=timing --path "." --offline
4 changes: 4 additions & 0 deletions asyncgit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
#![deny(clippy::all)]

mod diff;
mod revlog;
mod status;
pub mod sync;

pub use crate::{
diff::{AsyncDiff, DiffParams},
revlog::AsyncLog,
status::AsyncStatus,
sync::{
diff::{DiffLine, DiffLineType, FileDiff},
Expand All @@ -30,6 +32,8 @@ pub enum AsyncNotification {
Status,
///
Diff,
///
Log,
}

/// current working director `./`
Expand Down
105 changes: 105 additions & 0 deletions asyncgit/src/revlog.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use crate::{sync, AsyncNotification, CWD};
use crossbeam_channel::Sender;
use git2::Oid;
use scopetime::scope_time;
use std::{
iter::FromIterator,
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
},
};
use sync::{utils::repo, LogWalker};

///
pub struct AsyncLog {
current: Arc<Mutex<Vec<Oid>>>,
sender: Sender<AsyncNotification>,
pending: Arc<AtomicBool>,
}

static LIMIT_COUNT: usize = 1000;

impl AsyncLog {
///
pub fn new(sender: Sender<AsyncNotification>) -> Self {
Self {
current: Arc::new(Mutex::new(Vec::new())),
sender,
pending: Arc::new(AtomicBool::new(false)),
}
}

///
pub fn count(&mut self) -> usize {
self.current.lock().unwrap().len()
}

///
pub fn get_slice(
&self,
start_index: usize,
amount: usize,
) -> Vec<Oid> {
let list = self.current.lock().unwrap();
let list_len = list.len();
let min = start_index.min(list_len);
let max = min + amount;
let max = max.min(list_len);
Vec::from_iter(list[min..max].iter().cloned())
}

///
pub fn is_pending(&self) -> bool {
self.pending.load(Ordering::Relaxed)
}

///
pub fn fetch(&mut self) {
if !self.is_pending() {
self.clear();

let arc_current = Arc::clone(&self.current);
let sender = self.sender.clone();
let arc_pending = Arc::clone(&self.pending);
rayon_core::spawn(move || {
arc_pending.store(true, Ordering::Relaxed);

scope_time!("async::revlog");

let mut entries = Vec::with_capacity(LIMIT_COUNT);
let r = repo(CWD);
let mut walker = LogWalker::new(&r);
loop {
entries.clear();
let res_is_err = walker
.read(&mut entries, LIMIT_COUNT)
.is_err();

if !res_is_err {
let mut current = arc_current.lock().unwrap();
current.extend(entries.iter());
}

if res_is_err || entries.len() <= 1 {
break;
} else {
Self::notify(&sender);
}
}

arc_pending.store(false, Ordering::Relaxed);

Self::notify(&sender);
});
}
}

fn clear(&mut self) {
self.current.lock().unwrap().clear();
}

fn notify(sender: &Sender<AsyncNotification>) {
sender.send(AsyncNotification::Log).expect("error sending");
}
}
101 changes: 101 additions & 0 deletions asyncgit/src/sync/commits_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use super::utils::repo;
use git2::{Commit, Error, Oid};
use scopetime::scope_time;

///
#[derive(Debug)]
pub struct CommitInfo {
///
pub message: String,
///
pub time: i64,
///
pub author: String,
///
pub hash: String,
}

///
pub fn get_commits_info(
repo_path: &str,
ids: &[Oid],
) -> Result<Vec<CommitInfo>, Error> {
scope_time!("get_commits_info");

let repo = repo(repo_path);

let commits = ids.iter().map(|id| repo.find_commit(*id).unwrap());

let res = commits
.map(|c: Commit| {
let message = get_message(&c);
let author = if let Some(name) = c.author().name() {
String::from(name)
} else {
String::from("<unknown>")
};
CommitInfo {
message,
author,
time: c.time().seconds(),
hash: c.id().to_string(),
}
})
.collect::<Vec<_>>();

Ok(res)
}

fn get_message(c: &Commit) -> String {
if let Some(msg) = c.message() {
limit_str(msg, 50)
} else {
String::from("<unknown>")
}
}

fn limit_str(s: &str, limit: usize) -> String {
if let Some(first) = s.lines().next() {
first.chars().take(limit).collect::<String>()
} else {
String::new()
}
}

#[cfg(test)]
mod tests {

use super::get_commits_info;
use crate::sync::{
commit, stage_add_file, tests::repo_init_empty,
};
use std::{
fs::File,
io::{Error, Write},
path::Path,
};

#[test]
fn test_log() -> Result<(), Error> {
let file_path = Path::new("foo");
let (_td, repo) = repo_init_empty();
let root = repo.path().parent().unwrap();
let repo_path = root.as_os_str().to_str().unwrap();

File::create(&root.join(file_path))?.write_all(b"a")?;
stage_add_file(repo_path, file_path);
let c1 = commit(repo_path, "commit1");
File::create(&root.join(file_path))?.write_all(b"a")?;
stage_add_file(repo_path, file_path);
let c2 = commit(repo_path, "commit2");

let res = get_commits_info(repo_path, &vec![c2, c1]).unwrap();

assert_eq!(res.len(), 2);
assert_eq!(res[0].message.as_str(), "commit2");
assert_eq!(res[0].author.as_str(), "name");
assert_eq!(res[1].message.as_str(), "commit1");

Ok(())
}
}
117 changes: 117 additions & 0 deletions asyncgit/src/sync/logwalker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use git2::{Error, Oid, Repository, Revwalk};

///
pub struct LogWalker<'a> {
repo: &'a Repository,
revwalk: Option<Revwalk<'a>>,
}

impl<'a> LogWalker<'a> {
///
pub fn new(repo: &'a Repository) -> Self {
Self {
repo,
revwalk: None,
}
}

///
pub fn read(
&mut self,
out: &mut Vec<Oid>,
limit: usize,
) -> Result<usize, Error> {
let mut count = 0_usize;

if self.revwalk.is_none() {
let mut walk = self.repo.revwalk()?;
walk.push_head()?;
self.revwalk = Some(walk);
}

if let Some(ref mut walk) = self.revwalk {
for id in walk {
if let Ok(id) = id {
out.push(id);
count += 1;

if count == limit {
break;
}
}
}
}

Ok(count)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::sync::{
commit, get_commits_info, stage_add_file,
tests::repo_init_empty,
};
use std::{
fs::File,
io::{Error, Write},
path::Path,
};

#[test]
fn test_limit() -> Result<(), Error> {
let file_path = Path::new("foo");
let (_td, repo) = repo_init_empty();
let root = repo.path().parent().unwrap();
let repo_path = root.as_os_str().to_str().unwrap();

File::create(&root.join(file_path))?.write_all(b"a")?;
stage_add_file(repo_path, file_path);
commit(repo_path, "commit1");
File::create(&root.join(file_path))?.write_all(b"a")?;
stage_add_file(repo_path, file_path);
let oid2 = commit(repo_path, "commit2");

let mut items = Vec::new();
let mut walk = LogWalker::new(&repo);
walk.read(&mut items, 1).unwrap();

assert_eq!(items.len(), 1);
assert_eq!(items[0], oid2);

Ok(())
}

#[test]
fn test_logwalker() -> Result<(), Error> {
let file_path = Path::new("foo");
let (_td, repo) = repo_init_empty();
let root = repo.path().parent().unwrap();
let repo_path = root.as_os_str().to_str().unwrap();

File::create(&root.join(file_path))?.write_all(b"a")?;
stage_add_file(repo_path, file_path);
commit(repo_path, "commit1");
File::create(&root.join(file_path))?.write_all(b"a")?;
stage_add_file(repo_path, file_path);
let oid2 = commit(repo_path, "commit2");

let mut items = Vec::new();
let mut walk = LogWalker::new(&repo);
walk.read(&mut items, 100).unwrap();

let info = get_commits_info(repo_path, &items).unwrap();
dbg!(&info);

assert_eq!(items.len(), 2);
assert_eq!(items[0], oid2);

let mut items = Vec::new();
walk.read(&mut items, 100).unwrap();

assert_eq!(items.len(), 0);

Ok(())
}
}
4 changes: 4 additions & 0 deletions asyncgit/src/sync/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
//! sync git api
mod commits_info;
pub mod diff;
mod hooks;
mod hunks;
mod logwalker;
mod reset;
pub mod status;
pub mod utils;

pub use commits_info::{get_commits_info, CommitInfo};
pub use hooks::{hooks_commit_msg, hooks_post_commit, HookResult};
pub use hunks::{stage_hunk, unstage_hunk};
pub use logwalker::LogWalker;
pub use reset::{
reset_stage, reset_workdir_file, reset_workdir_folder,
};
Expand Down
Loading

0 comments on commit 1db1f00

Please sign in to comment.