mirror of
https://github.com/extrawurst/gitui.git
synced 2024-11-23 03:32:30 +03:00
show file size diff for binary files (#141)
This commit is contained in:
parent
f287d6a351
commit
76e52cc7a2
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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, ¤t_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, ¤t_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(¤t_hunk.unwrap(), ¤t_lines);
|
||||
}
|
||||
|
||||
if new_file_diff {
|
||||
res.borrow_mut().untracked = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !current_lines.is_empty() {
|
||||
adder(¤t_hunk.unwrap(), ¤t_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(())
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user