Skip to content

Commit

Permalink
refactor: use libgit2 for showing commits
Browse files Browse the repository at this point in the history
  • Loading branch information
altsem committed Feb 18, 2024
1 parent ad0ec41 commit cfea16f
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 42 deletions.
68 changes: 68 additions & 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 @@ -15,6 +15,7 @@ pprof = { version = "0.13.0", features = ["flamegraph", "criterion"] }

[dependencies]
ansi-to-tui = "3.1.0"
chrono = "0.4.34"
clap = { version = "4.4.16", features = ["derive"] }
crossterm = "0.27.0"
git2 = "0.18.2"
Expand Down
5 changes: 5 additions & 0 deletions src/git/commit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#[derive(Debug)]
pub(crate) struct Commit {
pub hash: String,
pub details: String,
}
70 changes: 50 additions & 20 deletions src/git/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
use git2::{DiffLineType::*, Repository};
use itertools::Itertools;

use self::{
commit::Commit,
diff::{Delta, Diff, Hunk},
merge_status::MergeStatus,
rebase_status::RebaseStatus,
};
use crate::Res;
use std::{
error::Error,
Expand All @@ -10,12 +17,7 @@ use std::{
str::{self, FromStr},
};

use self::{
diff::{Delta, Diff, Hunk},
merge_status::MergeStatus,
rebase_status::RebaseStatus,
};

pub(crate) mod commit;
pub(crate) mod diff;
pub(crate) mod merge_status;
mod parse;
Expand Down Expand Up @@ -90,14 +92,15 @@ fn branch_name(dir: &Path, hash: &str) -> Res<Option<String>> {
}

pub(crate) fn diff(dir: &Path, args: &[&str]) -> Res<Diff> {
assert!(args.is_empty(), "TODO handle args");
// TODO handle args?
let repo = &Repository::open(dir)?;
let diff = repo.diff_index_to_workdir(None, None)?;
convert_diff(diff)
}

// TODO Move elsewhere
pub(crate) fn convert_diff<'a>(diff: git2::Diff) -> Res<Diff> {
pub(crate) fn convert_diff(diff: git2::Diff) -> Res<Diff> {
let mut deltas = vec![];
let mut lines = String::new();

Expand Down Expand Up @@ -126,7 +129,7 @@ pub(crate) fn convert_diff<'a>(diff: git2::Diff) -> Res<Diff> {
if is_new_hunk {
let delta = deltas.last_mut().unwrap();

(*delta).hunks.push(Hunk {
delta.hunks.push(Hunk {
file_header: delta.file_header.clone(),
old_file: delta.old_file.clone(),
new_file: delta.new_file.clone(),
Expand All @@ -149,7 +152,6 @@ pub(crate) fn convert_diff<'a>(diff: git2::Diff) -> Res<Diff> {
}
ContextEOFNL => {
// TODO Handle '\ No newline at the end of file'
()
}
_ => (),
};
Expand Down Expand Up @@ -188,26 +190,54 @@ pub(crate) fn status(dir: &Path) -> Res<status::Status> {
}

pub(crate) fn show(dir: &Path, reference: &str) -> Res<Diff> {
// TODO Use libigt2
let repo = Repository::open(dir)?;
let object = &repo.revparse_single(reference)?;
let tree = object.peel_to_tree()?;
let prev = tree.iter().skip(1).next().unwrap();

let diff = repo.diff_tree_to_tree(
Some(&prev.to_object(&repo)?.into_tree().unwrap()),
Some(&object.peel_to_tree()?),
None,
)?;

let commit = object.peel_to_commit()?;
let tree = commit.tree()?;
let parent_tree = commit.parent(0)?.tree()?;
let diff = repo.diff_tree_to_tree(Some(&parent_tree), Some(&tree), None)?;
convert_diff(diff)
}

pub(crate) fn show_summary(dir: &Path, reference: &str) -> Res<String> {
pub(crate) fn show_summary(dir: &Path, reference: &str) -> Res<Commit> {
let repo = Repository::open(dir)?;
let object = &repo.revparse_single(reference)?;
let commit = object.peel_to_commit()?;

Ok(commit.message().unwrap_or("").to_string())
let author = commit.author();
let name = author.name().unwrap_or("");
let email = commit
.author()
.email()
.map(|email| format!("<{}>", email))
.unwrap_or("".to_string());

let message = commit
.message()
.unwrap_or("")
.to_string()
.lines()
.map(|line| format!(" {}", line))
.join("\n");

let offset = chrono::FixedOffset::east_opt(commit.time().offset_minutes() * 60).unwrap();
let time = chrono::DateTime::with_timezone(
&chrono::DateTime::from_timestamp(commit.time().seconds(), 0).unwrap(),
&offset,
);

let details = format!(
"Author: {}\nDate: {}\n\n{}",
[name, &email].join(" "),
time.to_rfc2822(),
message
);

Ok(Commit {
hash: commit.id().to_string(),
details,
})
}

// TODO Make this return a more useful type. Vec<Log>?
Expand Down
9 changes: 9 additions & 0 deletions src/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,12 @@ pub(crate) fn create_log_items(log: &str) -> impl Iterator<Item = Item> + '_ {
}
})
}

pub(crate) fn blank_line() -> Item {
Item {
display: Text::raw(""),
depth: 0,
unselectable: true,
..Default::default()
}
}
39 changes: 36 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,29 @@ mod tests {
insta::assert_snapshot!(redact_hashes(terminal, dir));
}

#[test]
fn log() {
let (ref mut terminal, ref mut state, dir) = setup(60, 20);
commit(&dir, "firstfile", "testing\ntesttest\n");
commit(&dir, "secondfile", "testing\ntesttest\n");
update(terminal, state, &[key('g'), key('l'), key('l')]).unwrap();
insta::assert_snapshot!(redact_hashes(terminal, dir));
}

#[test]
fn show() {
let (ref mut terminal, ref mut state, dir) = setup(60, 20);
commit(&dir, "firstfile", "This should not be visible\n");
commit(&dir, "secondfile", "This should be visible\n");
update(
terminal,
state,
&[key('g'), key('l'), key('l'), key_code(KeyCode::Enter)],
)
.unwrap();
insta::assert_snapshot!(redact_hashes(terminal, dir));
}

#[test]
fn rebase_conflict() {
let (ref mut terminal, ref mut state, dir) = setup(60, 20);
Expand Down Expand Up @@ -504,8 +527,8 @@ mod tests {
fn commit(dir: &TempDir, file_name: &str, contents: &str) {
let path = dir.child(file_name);
let message = match path.try_exists() {
Ok(true) => format!("modify {}", file_name),
_ => format!("add {}", file_name),
Ok(true) => format!("modify {}\n\nCommit body goes here\n", file_name),
_ => format!("add {}\n\nCommit body goes here\n", file_name),
};
fs::write(path, contents).expect("error writing to file");
run(dir, &["git", "add", file_name]);
Expand All @@ -516,6 +539,7 @@ mod tests {
String::from_utf8(
Command::new(cmd[0])
.args(&cmd[1..])
.env("GIT_COMMITTER_DATE", "Sun Feb 18 14:00 2024 +0100")
.current_dir(dir.path())
.output()
.unwrap_or_else(|_| panic!("failed to execute {:?}", cmd))
Expand All @@ -528,11 +552,20 @@ mod tests {
Event::Key(KeyEvent::new(KeyCode::Char(char), KeyModifiers::empty()))
}

fn key_code(code: KeyCode) -> Event {
Event::Key(KeyEvent::new(code, KeyModifiers::empty()))
}

fn redact_hashes(terminal: &mut Terminal<TestBackend>, dir: TempDir) -> String {
let mut debug_output = format!("{:#?}", terminal.backend().buffer());

for hash in run(&dir, &["git", "log", "--all", "--format=%H", "HEAD"]).lines() {
debug_output = debug_output.replace(hash, &"_".repeat(hash.len()));
}
for hash in run(&dir, &["git", "log", "--all", "--format=%h", "HEAD"]).lines() {
debug_output = debug_output.replace(hash, "_______");
debug_output = debug_output.replace(hash, &"_".repeat(hash.len()));
}

debug_output
}
}
29 changes: 20 additions & 9 deletions src/screen/show.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use crate::{
git,
items::{self, Item},
util, Config, Res,
theme::CURRENT_THEME,
Config, Res,
};
use ratatui::{
prelude::Rect,
style::Stylize,
text::{Line, Text},
};
use ansi_to_tui::IntoText;
use ratatui::{prelude::Rect, text::Text};

use super::Screen;

Expand All @@ -14,18 +18,25 @@ pub(crate) fn create(config: &Config, size: Rect, reference: String) -> Res<Scre
Screen::new(
size,
Box::new(move || {
let summary = git::show_summary(&config.dir, &reference)?;
let commit = git::show_summary(&config.dir, &reference)?;
let show = git::show(&config.dir.clone(), &reference)?;
let mut details = Text::from(commit.details);
details.lines.push(Line::raw(""));

let commit_text = summary.replace("", "").into_text()?;

Ok([
Ok(vec![
Item {
display: Text::from(commit_text.lines[0].clone()),
id: format!("commit_section_{}", commit.hash).into(),
display: Text::from(
format!("commit {}", commit.hash).fg(CURRENT_THEME.section),
),
section: true,
depth: 0,
..Default::default()
},
Item {
display: Text::from(commit_text.lines[1..].to_vec()),
id: format!("commit_{}", commit.hash).into(),
display: details,
depth: 1,
unselectable: true,
..Default::default()
},
Expand Down
Loading

0 comments on commit cfea16f

Please sign in to comment.