show file size diff for binary files (#141)

This commit is contained in:
Stephan Dilly 2020-06-22 20:01:04 +02:00
parent f287d6a351
commit 76e52cc7a2
4 changed files with 309 additions and 182 deletions

7
Cargo.lock generated
View File

@ -101,6 +101,12 @@ dependencies = [
"constant_time_eq",
]
[[package]]
name = "bytesize"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81a18687293a1546b67c246452202bbbf143d239cb43494cc163da14979082da"
[[package]]
name = "cassowary"
version = "0.3.0"
@ -323,6 +329,7 @@ dependencies = [
"asyncgit",
"backtrace",
"bitflags",
"bytesize",
"chrono",
"clap",
"crossbeam-channel",

View File

@ -24,6 +24,7 @@ asyncgit = { path = "./asyncgit", version = "0.7" }
crossterm = "0.17"
clap = { version = "2.33", default-features = false }
tui = { version = "0.9", default-features = false, features = ['crossterm'] }
bytesize = { version = "1.0.1", default-features = false}
itertools = "0.9"
rayon-core = "1.7"
log = "0.4"

View File

@ -7,7 +7,7 @@ use git2::{
Repository,
};
use scopetime::scope_time;
use std::{fs, path::Path};
use std::{cell::RefCell, fs, path::Path, rc::Rc};
use utils::{get_head_repo, work_dir};
/// type of diff of a single line
@ -75,6 +75,10 @@ pub struct FileDiff {
pub lines: usize,
///
pub untracked: bool,
/// old and new file size in bytes
pub sizes: (u64, u64),
/// size delta in bytes
pub size_delta: i64,
}
pub(crate) fn get_diff_raw<'a>(
@ -152,110 +156,128 @@ fn raw_diff_to_file_diff<'a>(
diff: &'a Diff,
work_dir: &Path,
) -> Result<FileDiff> {
let mut res: FileDiff = FileDiff::default();
let mut current_lines = Vec::new();
let mut current_hunk: Option<HunkHeader> = None;
let res = Rc::new(RefCell::new(FileDiff::default()));
{
let mut current_lines = Vec::new();
let mut current_hunk: Option<HunkHeader> = None;
let mut adder = |header: &HunkHeader, lines: &Vec<DiffLine>| {
res.hunks.push(Hunk {
header_hash: hash(header),
lines: lines.clone(),
});
res.lines += lines.len();
};
let res_cell = Rc::clone(&res);
let adder = move |header: &HunkHeader,
lines: &Vec<DiffLine>| {
let mut res = res_cell.borrow_mut();
res.hunks.push(Hunk {
header_hash: hash(header),
lines: lines.clone(),
});
res.lines += lines.len();
};
let mut put = |hunk: Option<DiffHunk>, line: git2::DiffLine| {
if let Some(hunk) = hunk {
let hunk_header = HunkHeader::from(hunk);
match current_hunk {
None => current_hunk = Some(hunk_header),
Some(h) if h != hunk_header => {
adder(&h, &current_lines);
current_lines.clear();
current_hunk = Some(hunk_header)
}
_ => (),
}
let line_type = match line.origin() {
'H' => DiffLineType::Header,
'<' | '-' => DiffLineType::Delete,
'>' | '+' => DiffLineType::Add,
_ => DiffLineType::None,
};
let diff_line = DiffLine {
content: String::from_utf8_lossy(line.content())
.to_string(),
line_type,
};
current_lines.push(diff_line);
}
};
let new_file_diff = if diff.deltas().len() == 1 {
// it's safe to unwrap here because we check first that diff.deltas has a single element.
let delta: DiffDelta = diff.deltas().next().unwrap();
if delta.status() == Delta::Untracked {
let relative_path =
delta.new_file().path().ok_or_else(|| {
Error::Generic(
"new file path is unspecified.".to_string(),
)
})?;
let newfile_path = work_dir.join(relative_path);
if let Some(newfile_content) =
new_file_content(&newfile_path)
let res_cell = Rc::clone(&res);
let mut put = |delta: DiffDelta,
hunk: Option<DiffHunk>,
line: git2::DiffLine| {
{
let mut patch = Patch::from_buffers(
&[],
None,
newfile_content.as_bytes(),
Some(&newfile_path),
None,
)?;
let mut res = res_cell.borrow_mut();
res.sizes = (
delta.old_file().size(),
delta.new_file().size(),
);
res.size_delta = (res.sizes.1 as i64)
.saturating_sub(res.sizes.0 as i64);
}
if let Some(hunk) = hunk {
let hunk_header = HunkHeader::from(hunk);
patch
.print(&mut |_delta, hunk:Option<DiffHunk>, line: git2::DiffLine| {
put(hunk,line);
match current_hunk {
None => current_hunk = Some(hunk_header),
Some(h) if h != hunk_header => {
adder(&h, &current_lines);
current_lines.clear();
current_hunk = Some(hunk_header)
}
_ => (),
}
let line_type = match line.origin() {
'H' => DiffLineType::Header,
'<' | '-' => DiffLineType::Delete,
'>' | '+' => DiffLineType::Add,
_ => DiffLineType::None,
};
let diff_line = DiffLine {
content: String::from_utf8_lossy(line.content())
.to_string(),
line_type,
};
current_lines.push(diff_line);
}
};
let new_file_diff = if diff.deltas().len() == 1 {
// it's safe to unwrap here because we check first that diff.deltas has a single element.
let delta: DiffDelta = diff.deltas().next().unwrap();
if delta.status() == Delta::Untracked {
let relative_path =
delta.new_file().path().ok_or_else(|| {
Error::Generic(
"new file path is unspecified."
.to_string(),
)
})?;
let newfile_path = work_dir.join(relative_path);
if let Some(newfile_content) =
new_file_content(&newfile_path)
{
let mut patch = Patch::from_buffers(
&[],
None,
newfile_content.as_bytes(),
Some(&newfile_path),
None,
)?;
patch
.print(&mut |delta, hunk:Option<DiffHunk>, line: git2::DiffLine| {
put(delta,hunk,line);
true
})?;
true
true
} else {
false
}
} else {
false
}
} else {
false
};
if !new_file_diff {
diff.print(
DiffFormat::Patch,
move |delta, hunk, line: git2::DiffLine| {
put(delta, hunk, line);
true
},
)?;
}
} else {
false
};
if !new_file_diff {
diff.print(
DiffFormat::Patch,
|_, hunk, line: git2::DiffLine| {
put(hunk, line);
true
},
)?;
if !current_lines.is_empty() {
adder(&current_hunk.unwrap(), &current_lines);
}
if new_file_diff {
res.borrow_mut().untracked = true;
}
}
if !current_lines.is_empty() {
adder(&current_hunk.unwrap(), &current_lines);
}
if new_file_diff {
res.untracked = true;
}
Ok(res)
let res = Rc::try_unwrap(res).expect("rc error");
Ok(res.into_inner())
}
fn new_file_content(path: &Path) -> Option<String> {
@ -279,7 +301,7 @@ mod tests {
use super::get_diff;
use crate::error::Result;
use crate::sync::{
stage_add_file,
commit, stage_add_file,
status::{get_status, StatusType},
tests::{get_statuses, repo_init, repo_init_empty},
};
@ -455,4 +477,34 @@ mod tests {
Ok(())
}
#[test]
fn test_diff_delta_size() -> Result<()> {
let file_path = Path::new("bar");
let (_td, repo) = repo_init_empty().unwrap();
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"\x00")?;
stage_add_file(repo_path, file_path).unwrap();
commit(repo_path, "commit").unwrap();
File::create(&root.join(file_path))?
.write_all(b"\x00\x02")?;
let diff = get_diff(
repo_path,
String::from(file_path.to_str().unwrap()),
false,
)
.unwrap();
dbg!(&diff);
assert_eq!(diff.sizes, (1, 2));
assert_eq!(diff.size_delta, 1);
Ok(())
}
}

View File

@ -7,6 +7,7 @@ use crate::{
ui::{calc_scroll_top, style::SharedTheme},
};
use asyncgit::{hash, sync, DiffLine, DiffLineType, FileDiff, CWD};
use bytesize::ByteSize;
use crossterm::event::Event;
use std::{borrow::Cow, cmp, path::Path};
use strings::commands;
@ -29,7 +30,7 @@ struct Current {
///
pub struct DiffComponent {
diff: FileDiff,
diff: Option<FileDiff>,
selection: usize,
selected_hunk: Option<usize>,
current_size: (u16, u16),
@ -48,7 +49,7 @@ impl DiffComponent {
queue,
current: Current::default(),
selected_hunk: None,
diff: FileDiff::default(),
diff: None,
current_size: (0, 0),
selection: 0,
scroll_top: 0,
@ -56,8 +57,11 @@ impl DiffComponent {
}
}
///
const fn can_scroll(&self) -> bool {
self.diff.lines > 1
fn can_scroll(&self) -> bool {
self.diff
.as_ref()
.map(|diff| diff.lines > 1)
.unwrap_or_default()
}
///
pub fn current(&self) -> (String, bool) {
@ -66,7 +70,7 @@ impl DiffComponent {
///
pub fn clear(&mut self) -> Result<()> {
self.current = Current::default();
self.diff = FileDiff::default();
self.diff = None;
self.scroll_top = 0;
self.selection = 0;
self.selected_hunk = None;
@ -88,12 +92,13 @@ impl DiffComponent {
is_stage,
hash,
};
self.diff = diff;
self.scroll_top = 0;
self.selection = 0;
self.selected_hunk =
Self::find_selected_hunk(&self.diff, self.selection)?;
Self::find_selected_hunk(&diff, self.selection)?;
self.diff = Some(diff);
self.scroll_top = 0;
self.selection = 0;
}
Ok(())
@ -103,30 +108,34 @@ impl DiffComponent {
&mut self,
move_type: ScrollType,
) -> Result<()> {
let old = self.selection;
if let Some(diff) = &self.diff {
let old = self.selection;
let max = self.diff.lines.saturating_sub(1) as usize;
let max = diff.lines.saturating_sub(1) as usize;
self.selection = match move_type {
ScrollType::Down => old.saturating_add(1),
ScrollType::Up => old.saturating_sub(1),
ScrollType::Home => 0,
ScrollType::End => max,
ScrollType::PageDown => self.selection.saturating_add(
self.current_size.1.saturating_sub(1) as usize,
),
ScrollType::PageUp => self.selection.saturating_sub(
self.current_size.1.saturating_sub(1) as usize,
),
};
self.selection = match move_type {
ScrollType::Down => old.saturating_add(1),
ScrollType::Up => old.saturating_sub(1),
ScrollType::Home => 0,
ScrollType::End => max,
ScrollType::PageDown => {
self.selection.saturating_add(
self.current_size.1.saturating_sub(1)
as usize,
)
}
ScrollType::PageUp => self.selection.saturating_sub(
self.current_size.1.saturating_sub(1) as usize,
),
};
self.selection = cmp::min(max, self.selection);
self.selection = cmp::min(max, self.selection);
if old != self.selection {
self.selected_hunk =
Self::find_selected_hunk(&self.diff, self.selection)?;
if old != self.selection {
self.selected_hunk =
Self::find_selected_hunk(&diff, self.selection)?;
}
}
Ok(())
}
@ -154,49 +163,96 @@ impl DiffComponent {
}
fn get_text(&self, width: u16, height: u16) -> Result<Vec<Text>> {
let selection = self.selection;
let min = self.scroll_top;
let max = min + height as usize;
let mut res = Vec::new();
let mut line_cursor = 0_usize;
let mut lines_added = 0_usize;
if let Some(diff) = &self.diff {
if diff.hunks.is_empty() {
let is_positive = diff.size_delta >= 0;
let delta_byte_size =
ByteSize::b(diff.size_delta.abs() as u64);
let sign = if is_positive { "+" } else { "-" };
res.extend(vec![
Text::Raw(Cow::from("size: ")),
Text::Styled(
Cow::from(format!(
"{}",
ByteSize::b(diff.sizes.0)
)),
self.theme.text(false, false),
),
Text::Raw(Cow::from(" -> ")),
Text::Styled(
Cow::from(format!(
"{}",
ByteSize::b(diff.sizes.1)
)),
self.theme.text(false, false),
),
Text::Raw(Cow::from(" (")),
Text::Styled(
Cow::from(format!(
"{}{:}",
sign, delta_byte_size
)),
self.theme.diff_line(
if is_positive {
DiffLineType::Add
} else {
DiffLineType::Delete
},
false,
),
),
Text::Raw(Cow::from(")")),
]);
} else {
let selection = self.selection;
for (i, hunk) in self.diff.hunks.iter().enumerate() {
let hunk_selected =
self.selected_hunk.map_or(false, |s| s == i);
let min = self.scroll_top;
let max = min + height as usize;
if lines_added >= height as usize {
break;
}
let mut line_cursor = 0_usize;
let mut lines_added = 0_usize;
let hunk_len = hunk.lines.len();
let hunk_min = line_cursor;
let hunk_max = line_cursor + hunk_len;
for (i, hunk) in diff.hunks.iter().enumerate() {
let hunk_selected =
self.selected_hunk.map_or(false, |s| s == i);
if Self::hunk_visible(hunk_min, hunk_max, min, max) {
for (i, line) in hunk.lines.iter().enumerate() {
if line_cursor >= min && line_cursor <= max {
Self::add_line(
&mut res,
width,
line,
selection == line_cursor,
hunk_selected,
i == hunk_len as usize - 1,
&self.theme,
);
lines_added += 1;
if lines_added >= height as usize {
break;
}
line_cursor += 1;
let hunk_len = hunk.lines.len();
let hunk_min = line_cursor;
let hunk_max = line_cursor + hunk_len;
if Self::hunk_visible(
hunk_min, hunk_max, min, max,
) {
for (i, line) in hunk.lines.iter().enumerate()
{
if line_cursor >= min
&& line_cursor <= max
{
Self::add_line(
&mut res,
width,
line,
selection == line_cursor,
hunk_selected,
i == hunk_len as usize - 1,
&self.theme,
);
lines_added += 1;
}
line_cursor += 1;
}
} else {
line_cursor += hunk_len;
}
}
} else {
line_cursor += hunk_len;
}
}
Ok(res)
}
@ -272,26 +328,34 @@ impl DiffComponent {
}
fn unstage_hunk(&mut self) -> Result<()> {
if let Some(hunk) = self.selected_hunk {
let hash = self.diff.hunks[hunk].header_hash;
sync::unstage_hunk(CWD, self.current.path.clone(), hash)?;
self.queue_update();
if let Some(diff) = &self.diff {
if let Some(hunk) = self.selected_hunk {
let hash = diff.hunks[hunk].header_hash;
sync::unstage_hunk(
CWD,
self.current.path.clone(),
hash,
)?;
self.queue_update();
}
}
Ok(())
}
fn stage_hunk(&mut self) -> Result<()> {
if let Some(hunk) = self.selected_hunk {
let path = self.current.path.clone();
if self.diff.untracked {
sync::stage_add_file(CWD, Path::new(&path))?;
} else {
let hash = self.diff.hunks[hunk].header_hash;
sync::stage_hunk(CWD, path, hash)?;
}
if let Some(diff) = &self.diff {
if let Some(hunk) = self.selected_hunk {
let path = self.current.path.clone();
if diff.untracked {
sync::stage_add_file(CWD, Path::new(&path))?;
} else {
let hash = diff.hunks[hunk].header_hash;
sync::stage_hunk(CWD, path, hash)?;
}
self.queue_update();
self.queue_update();
}
}
Ok(())
@ -306,21 +370,22 @@ impl DiffComponent {
}
fn reset_hunk(&self) -> Result<()> {
if let Some(hunk) = self.selected_hunk {
let hash = self.diff.hunks[hunk].header_hash;
if let Some(diff) = &self.diff {
if let Some(hunk) = self.selected_hunk {
let hash = diff.hunks[hunk].header_hash;
self.queue
.as_ref()
.expect("try using queue in immutable diff")
.borrow_mut()
.push_back(InternalEvent::ConfirmAction(
Action::ResetHunk(
self.current.path.clone(),
hash,
),
));
self.queue
.as_ref()
.expect("try using queue in immutable diff")
.borrow_mut()
.push_back(InternalEvent::ConfirmAction(
Action::ResetHunk(
self.current.path.clone(),
hash,
),
));
}
}
Ok(())
}
@ -466,10 +531,12 @@ impl Component for DiffComponent {
if !self.is_immutable()
&& !self.is_stage() =>
{
if self.diff.untracked {
self.reset_untracked()?;
} else {
self.reset_hunk()?;
if let Some(diff) = &self.diff {
if diff.untracked {
self.reset_untracked()?;
} else {
self.reset_hunk()?;
}
}
Ok(true)
}