support opening submodule (#1298)

This commit is contained in:
extrawurst 2022-08-31 10:51:08 +02:00 committed by GitHub
parent aa9ed3349f
commit 986d34a5ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 392 additions and 51 deletions

20
Cargo.lock generated
View File

@ -67,6 +67,7 @@ version = "0.21.0"
dependencies = [
"crossbeam-channel",
"easy-cast",
"env_logger",
"git2",
"invalidstring",
"log",
@ -422,6 +423,19 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "env_logger"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "fancy-regex"
version = "0.7.1"
@ -692,6 +706,12 @@ dependencies = [
"libc",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "iana-time-zone"
version = "0.1.46"

View File

@ -51,6 +51,7 @@
- Browse commit log, diff committed changes
- Scalable terminal UI layout
- Async git API for fluid control
- Submodule support
## 2. <a name="motivation"></a> Motivation <small><sup>[Top ▲](#table-of-contents)</sup></small>

View File

@ -28,6 +28,7 @@ unicode-truncate = "0.2.0"
url = "2.2"
[dev-dependencies]
env_logger = "0.9"
invalidstring = { path = "../invalidstring", version = "0.1" }
pretty_assertions = "1.3"
serial_test = "0.9"

View File

@ -1,6 +1,9 @@
#![allow(renamed_and_removed_lints, clippy::unknown_clippy_lints)]
use std::{num::TryFromIntError, string::FromUtf8Error};
use std::{
num::TryFromIntError, path::StripPrefixError,
string::FromUtf8Error,
};
use thiserror::Error;
///
@ -50,6 +53,10 @@ pub enum Error {
#[error("git error:{0}")]
Git(#[from] git2::Error),
///
#[error("strip prefix error: {0}")]
StripPrefix(#[from] StripPrefixError),
///
#[error("utf8 error:{0}")]
Utf8Conversion(#[from] FromUtf8Error),

View File

@ -82,7 +82,8 @@ pub use stash::{
pub use state::{repo_state, RepoState};
pub use status::is_workdir_clean;
pub use submodules::{
get_submodules, update_submodule, SubmoduleInfo, SubmoduleStatus,
get_submodules, submodule_parent_info, update_submodule,
SubmoduleInfo, SubmoduleParentInfo, SubmoduleStatus,
};
pub use tags::{
delete_tag, get_tags, get_tags_with_metadata, CommitTags, Tag,
@ -209,6 +210,8 @@ mod tests {
///
pub fn repo_init_empty() -> Result<(TempDir, Repository)> {
init_log();
sandbox_config_files();
let td = TempDir::new()?;
@ -223,6 +226,8 @@ mod tests {
///
pub fn repo_init() -> Result<(TempDir, Repository)> {
init_log();
sandbox_config_files();
let td = TempDir::new()?;
@ -266,8 +271,18 @@ mod tests {
Ok((td, repo))
}
// init log
fn init_log() {
let _ = env_logger::builder()
.is_test(true)
.filter_level(log::LevelFilter::Trace)
.try_init();
}
/// Same as repo_init, but the repo is a bare repo (--bare)
pub fn repo_init_bare() -> Result<(TempDir, Repository)> {
init_log();
let tmp_repo_dir = TempDir::new()?;
let bare_repo = Repository::init_bare(tmp_repo_dir.path())?;
Ok((tmp_repo_dir, bare_repo))

View File

@ -11,7 +11,7 @@ use crate::error::Result;
pub type RepoPathRef = RefCell<RepoPath>;
///
#[derive(Clone)]
#[derive(Clone, Debug)]
pub enum RepoPath {
///
Path(PathBuf),

View File

@ -1,15 +1,24 @@
use std::path::PathBuf;
//TODO:
// #![allow(unused_variables, dead_code)]
use git2::SubmoduleUpdateOptions;
use std::path::{Path, PathBuf};
use git2::{
Repository, RepositoryOpenFlags, Submodule,
SubmoduleUpdateOptions,
};
use scopetime::scope_time;
use super::{repo, CommitId, RepoPath};
use crate::{error::Result, Error};
use crate::{error::Result, sync::utils::work_dir, Error};
pub use git2::SubmoduleStatus;
///
#[derive(Debug)]
pub struct SubmoduleInfo {
///
pub name: String,
///
pub path: PathBuf,
///
@ -22,6 +31,17 @@ pub struct SubmoduleInfo {
pub status: SubmoduleStatus,
}
///
#[derive(Debug)]
pub struct SubmoduleParentInfo {
/// where to find parent repo
pub parent_gitpath: PathBuf,
/// where to find submodule git path
pub submodule_gitpath: PathBuf,
/// `submodule_info` from perspective of parent repo
pub submodule_info: SubmoduleInfo,
}
impl SubmoduleInfo {
///
pub fn get_repo_path(
@ -35,6 +55,24 @@ impl SubmoduleInfo {
}
}
fn submodule_to_info(s: &Submodule, r: &Repository) -> SubmoduleInfo {
let status = r
.submodule_status(
s.name().unwrap_or_default(),
git2::SubmoduleIgnore::None,
)
.unwrap_or(SubmoduleStatus::empty());
SubmoduleInfo {
name: s.name().unwrap_or_default().into(),
path: s.path().to_path_buf(),
id: s.workdir_id().map(CommitId::from),
head_id: s.head_id().map(CommitId::from),
url: s.url().map(String::from),
status,
}
}
///
pub fn get_submodules(
repo_path: &RepoPath,
@ -46,22 +84,7 @@ pub fn get_submodules(
let res = r
.submodules()?
.iter()
.map(|s| {
let status = repo2
.submodule_status(
s.name().unwrap_or_default(),
git2::SubmoduleIgnore::None,
)
.unwrap_or(SubmoduleStatus::empty());
SubmoduleInfo {
path: s.path().to_path_buf(),
id: s.workdir_id().map(CommitId::from),
head_id: s.head_id().map(CommitId::from),
url: s.url().map(String::from),
status,
}
})
.map(|s| submodule_to_info(s, &repo2))
.collect();
Ok(res)
@ -82,3 +105,97 @@ pub fn update_submodule(
Ok(())
}
/// query whether `repo_path` points to a repo that is part of a parent git which contains it as a submodule
pub fn submodule_parent_info(
repo_path: &RepoPath,
) -> Result<Option<SubmoduleParentInfo>> {
scope_time!("submodule_parent_info");
let repo = repo(repo_path)?;
let repo_wd = work_dir(&repo)?.to_path_buf();
log::trace!("[sub] repo_wd: {:?}", repo_wd);
log::trace!("[sub] repo_path: {:?}", repo.path());
if let Some(parent_path) = repo_wd.parent() {
log::trace!("[sub] parent_path: {:?}", parent_path);
if let Ok(parent) = Repository::open_ext(
parent_path,
RepositoryOpenFlags::empty(),
Vec::<&Path>::new(),
) {
let parent_wd = work_dir(&parent)?.to_path_buf();
log::trace!("[sub] parent_wd: {:?}", parent_wd);
let submodule_name = repo_wd
.strip_prefix(parent_wd)?
.to_string_lossy()
.to_string();
log::trace!("[sub] submodule_name: {:?}", submodule_name);
if let Ok(submodule) =
parent.find_submodule(&submodule_name)
{
return Ok(Some(SubmoduleParentInfo {
parent_gitpath: parent.path().to_path_buf(),
submodule_gitpath: repo.path().to_path_buf(),
submodule_info: submodule_to_info(
&submodule, &parent,
),
}));
}
}
}
Ok(None)
}
#[cfg(test)]
mod tests {
use super::get_submodules;
use crate::sync::{
submodules::submodule_parent_info, tests::repo_init, RepoPath,
};
use git2::Repository;
use pretty_assertions::assert_eq;
use std::path::Path;
#[test]
fn test_smoke() {
let (dir, _r) = repo_init().unwrap();
{
let r = Repository::open(dir.path()).unwrap();
let mut s = r
.submodule(
//TODO: use local git
"https://github.com/extrawurst/brewdump.git",
Path::new("foo/bar"),
false,
)
.unwrap();
let _sub_r = s.clone(None).unwrap();
s.add_finalize().unwrap();
}
let repo_p = RepoPath::Path(dir.into_path());
let subs = get_submodules(&repo_p).unwrap();
assert_eq!(subs.len(), 1);
assert_eq!(&subs[0].name, "foo/bar");
let info = submodule_parent_info(
&subs[0].get_repo_path(&repo_p).unwrap(),
)
.unwrap()
.unwrap();
dbg!(&info);
assert_eq!(&info.submodule_info.name, "foo/bar");
}
}

View File

@ -28,7 +28,7 @@ use crate::{
};
use anyhow::{bail, Result};
use asyncgit::{
sync::{self, RepoPathRef},
sync::{self, utils::repo_work_dir, RepoPath, RepoPathRef},
AsyncGitNotification, PushType,
};
use crossbeam_channel::Sender;
@ -46,10 +46,17 @@ use tui::{
Frame,
};
#[derive(Clone)]
pub enum QuitState {
None,
Close,
OpenSubmodule(RepoPath),
}
/// the main app type
pub struct App {
repo: RepoPathRef,
do_quit: bool,
do_quit: QuitState,
help: HelpComponent,
msg: MsgComponent,
reset: ConfirmComponent,
@ -103,6 +110,8 @@ impl App {
theme: Theme,
key_config: KeyConfig,
) -> Self {
log::trace!("open repo at: {:?}", repo);
let queue = Queue::new();
let theme = Rc::new(theme);
let key_config = Rc::new(key_config);
@ -235,6 +244,7 @@ impl App {
),
submodule_popup: SubmodulesListComponent::new(
repo.clone(),
&queue,
theme.clone(),
key_config.clone(),
),
@ -243,7 +253,7 @@ impl App {
theme.clone(),
key_config.clone(),
),
do_quit: false,
do_quit: QuitState::None,
cmdbar: RefCell::new(CommandBar::new(
theme.clone(),
key_config.clone(),
@ -493,7 +503,13 @@ impl App {
///
pub fn is_quit(&self) -> bool {
self.do_quit || self.input.is_aborted()
!matches!(self.do_quit, QuitState::None)
|| self.input.is_aborted()
}
///
pub fn quit_state(&self) -> QuitState {
self.do_quit.clone()
}
///
@ -597,7 +613,7 @@ impl App {
}
if let Event::Key(e) = ev {
if key_match(e, self.key_config.keys.quit) {
self.do_quit = true;
self.do_quit = QuitState::Close;
return true;
}
}
@ -607,7 +623,7 @@ impl App {
fn check_hard_exit(&mut self, ev: &Event) -> bool {
if let Event::Key(e) = ev {
if key_match(e, self.key_config.keys.exit) {
self.do_quit = true;
self.do_quit = QuitState::Close;
return true;
}
}
@ -878,6 +894,15 @@ impl App {
flags
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
}
InternalEvent::OpenRepo { path } => {
let submodule_repo_path = RepoPath::Path(
Path::new(&repo_work_dir(&self.repo.borrow())?)
.join(path),
);
//TODO: validate this is a valid repo first, so we can show proper error otherwise
self.do_quit =
QuitState::OpenSubmodule(submodule_repo_path);
}
};
Ok(flags)

View File

@ -5,11 +5,15 @@ use super::{
};
use crate::{
keys::{key_match, SharedKeyConfig},
queue::{InternalEvent, Queue},
strings,
ui::{self, Size},
};
use anyhow::Result;
use asyncgit::sync::{get_submodules, RepoPathRef, SubmoduleInfo};
use asyncgit::sync::{
get_submodules, repo_dir, submodule_parent_info, RepoPathRef,
SubmoduleInfo, SubmoduleParentInfo,
};
use crossterm::event::Event;
use std::{cell::Cell, convert::TryInto};
use tui::{
@ -18,7 +22,7 @@ use tui::{
Alignment, Constraint, Direction, Layout, Margin, Rect,
},
text::{Span, Spans, Text},
widgets::{Block, BorderType, Borders, Clear, Paragraph},
widgets::{Block, Borders, Clear, Paragraph},
Frame,
};
use ui::style::SharedTheme;
@ -27,7 +31,10 @@ use unicode_truncate::UnicodeTruncateStr;
///
pub struct SubmodulesListComponent {
repo: RepoPathRef,
repo_path: String,
queue: Queue,
submodules: Vec<SubmoduleInfo>,
submodule_parent: Option<SubmoduleParentInfo>,
visible: bool,
current_height: Cell<u16>,
selection: u16,
@ -59,7 +66,6 @@ impl DrawableComponent for SubmodulesListComponent {
f.render_widget(
Block::default()
.title(strings::POPUP_TITLE_SUBMODULES)
.border_type(BorderType::Thick)
.borders(Borders::ALL),
area,
);
@ -69,16 +75,25 @@ impl DrawableComponent for SubmodulesListComponent {
horizontal: 1,
});
let chunks_vertical = Layout::default()
.direction(Direction::Vertical)
.constraints(
[Constraint::Min(1), Constraint::Length(5)]
.as_ref(),
)
.split(area);
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints(
[Constraint::Min(40), Constraint::Length(40)]
.as_ref(),
)
.split(area);
.split(chunks_vertical[0]);
self.draw_list(f, chunks[0])?;
self.draw_info(f, chunks[1]);
self.draw_local_info(f, chunks_vertical[1]);
}
Ok(())
@ -107,6 +122,20 @@ impl Component for SubmodulesListComponent {
true,
true,
));
out.push(CommandInfo::new(
strings::commands::open_submodule(&self.key_config),
self.is_valid_selection(),
true,
));
out.push(CommandInfo::new(
strings::commands::open_submodule_parent(
&self.key_config,
),
self.submodule_parent.is_some(),
true,
));
}
visibility_blocking(self)
}
@ -143,6 +172,21 @@ impl Component for SubmodulesListComponent {
return self
.move_selection(ScrollType::End)
.map(Into::into);
} else if key_match(e, self.key_config.keys.enter) {
if let Some(submodule) = self.selected_entry() {
self.queue.push(InternalEvent::OpenRepo {
path: submodule.path.clone(),
});
}
} else if key_match(
e,
self.key_config.keys.view_submodule_parent,
) {
if let Some(parent) = &self.submodule_parent {
self.queue.push(InternalEvent::OpenRepo {
path: parent.parent_gitpath.clone(),
});
}
} else if key_match(
e,
self.key_config.keys.cmd_bar_toggle,
@ -173,18 +217,22 @@ impl Component for SubmodulesListComponent {
impl SubmodulesListComponent {
pub fn new(
repo: RepoPathRef,
queue: &Queue,
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self {
submodules: Vec::new(),
submodule_parent: None,
scroll: VerticalScroll::new(),
queue: queue.clone(),
selection: 0,
visible: false,
theme,
key_config,
current_height: Cell::new(0),
repo,
repo_path: String::new(),
}
}
@ -201,6 +249,13 @@ impl SubmodulesListComponent {
if self.is_visible() {
self.submodules = get_submodules(&self.repo.borrow())?;
self.submodule_parent =
submodule_parent_info(&self.repo.borrow())?;
self.repo_path = repo_dir(&self.repo.borrow())
.map(|e| e.to_string_lossy().to_string())
.unwrap_or_default();
self.set_selection(self.selection)?;
}
Ok(())
@ -210,6 +265,10 @@ impl SubmodulesListComponent {
self.submodules.get(self.selection as usize)
}
fn is_valid_selection(&self) -> bool {
self.selected_entry().is_some()
}
//TODO: dedup this almost identical with BranchListComponent
fn move_selection(&mut self, scroll: ScrollType) -> Result<bool> {
let new_selection = match scroll {
@ -234,11 +293,11 @@ impl SubmodulesListComponent {
}
fn set_selection(&mut self, selection: u16) -> Result<()> {
let num_branches: u16 = self.submodules.len().try_into()?;
let num_branches = num_branches.saturating_sub(1);
let num_entriess: u16 = self.submodules.len().try_into()?;
let num_entriess = num_entriess.saturating_sub(1);
let selection = if selection > num_branches {
num_branches
let selection = if selection > num_entriess {
num_entriess
} else {
selection
};
@ -359,6 +418,32 @@ impl SubmodulesListComponent {
)
}
fn get_local_info_text(&self, theme: &SharedTheme) -> Text {
let mut spans = vec![
Spans::from(vec![Span::styled(
"Current:",
theme.text(false, false),
)]),
Spans::from(vec![Span::styled(
self.repo_path.to_string(),
theme.text(true, false),
)]),
Spans::from(vec![Span::styled(
"Parent:",
theme.text(false, false),
)]),
];
if let Some(parent_info) = &self.submodule_parent {
spans.push(Spans::from(vec![Span::styled(
parent_info.parent_gitpath.to_string_lossy(),
theme.text(true, false),
)]));
}
Text::from(spans)
}
fn draw_list<B: Backend>(
&self,
f: &mut Frame<B>,
@ -399,4 +484,13 @@ impl SubmodulesListComponent {
r,
);
}
fn draw_local_info<B: Backend>(&self, f: &mut Frame<B>, r: Rect) {
f.render_widget(
Paragraph::new(self.get_local_info_text(&self.theme))
.block(Block::default().borders(Borders::TOP))
.alignment(Alignment::Left),
r,
);
}
}

View File

@ -28,6 +28,7 @@ pub enum InputEvent {
}
///
#[derive(Clone)]
pub struct Input {
desired_state: Arc<NotifyableMutex<bool>>,
current_state: Arc<AtomicBool>,

View File

@ -11,7 +11,7 @@ use super::{
pub type SharedKeyConfig = Rc<KeyConfig>;
#[derive(Default)]
#[derive(Default, Clone)]
pub struct KeyConfig {
pub keys: KeysList,
symbols: KeySymbols,

View File

@ -34,6 +34,7 @@ impl From<&GituiKeyEvent> for KeyEvent {
}
}
#[derive(Clone)]
pub struct KeysList {
pub tab_status: GituiKeyEvent,
pub tab_log: GituiKeyEvent,
@ -108,6 +109,7 @@ pub struct KeysList {
pub stage_unstage_item: GituiKeyEvent,
pub tag_annotate: GituiKeyEvent,
pub view_submodules: GituiKeyEvent,
pub view_submodule_parent: GituiKeyEvent,
}
#[rustfmt::skip]
@ -187,7 +189,7 @@ impl Default for KeysList {
stage_unstage_item: GituiKeyEvent::new(KeyCode::Enter, KeyModifiers::empty()),
tag_annotate: GituiKeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL),
view_submodules: GituiKeyEvent::new(KeyCode::Char('S'), KeyModifiers::SHIFT),
view_submodule_parent: GituiKeyEvent::new(KeyCode::Char('p'), KeyModifiers::empty()),
}
}
}

View File

@ -168,6 +168,7 @@ impl KeysListFile {
stage_unstage_item: self.stage_unstage_item.unwrap_or(default.stage_unstage_item),
tag_annotate: self.tag_annotate.unwrap_or(default.tag_annotate),
view_submodules: self.view_submodules.unwrap_or(default.view_submodules),
view_submodule_parent: self.view_submodules.unwrap_or(default.view_submodule_parent),
}
}
}

View File

@ -3,7 +3,7 @@ use std::{fs::File, io::Read, path::PathBuf};
use anyhow::Result;
use serde::{Deserialize, Serialize};
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct KeySymbols {
pub enter: String,
pub left: String,

View File

@ -39,6 +39,7 @@ mod version;
use crate::{app::App, args::process_cmdline};
use anyhow::{bail, Result};
use app::QuitState;
use asyncgit::{sync::RepoPath, AsyncGitNotification};
use backtrace::Backtrace;
use crossbeam_channel::{tick, unbounded, Receiver, Select};
@ -114,7 +115,7 @@ fn main() -> Result<()> {
let key_config = KeyConfig::init()
.map_err(|e| eprintln!("KeyConfig loading error: {}", e))
.unwrap_or_default();
let theme = Theme::init(cliargs.theme)
let theme = Theme::init(&cliargs.theme)
.map_err(|e| eprintln!("Theme loading error: {}", e))
.unwrap_or_default();
@ -126,21 +127,48 @@ fn main() -> Result<()> {
set_panic_handlers()?;
let mut terminal = start_terminal(io::stdout())?;
let mut repo_path = cliargs.repo_path;
let input = Input::new();
loop {
let quit_state = run_app(
repo_path.clone(),
theme,
key_config.clone(),
&input,
&mut terminal,
)?;
match quit_state {
QuitState::OpenSubmodule(p) => {
repo_path = p;
}
_ => break,
}
}
Ok(())
}
fn run_app(
repo: RepoPath,
theme: Theme,
key_config: KeyConfig,
input: &Input,
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
) -> Result<QuitState, anyhow::Error> {
let (tx_git, rx_git) = unbounded();
let (tx_app, rx_app) = unbounded();
let input = Input::new();
let rx_input = input.receiver();
let ticker = tick(TICK_INTERVAL);
let spinner_ticker = tick(SPINNER_INTERVAL);
let mut app = App::new(
RefCell::new(cliargs.repo_path),
RefCell::new(repo),
&tx_git,
&tx_app,
input,
input.clone(),
theme,
key_config,
);
@ -165,7 +193,7 @@ fn main() -> Result<()> {
{
if let QueueEvent::SpinnerUpdate = event {
spinner.update();
spinner.draw(&mut terminal)?;
spinner.draw(terminal)?;
continue;
}
@ -194,10 +222,10 @@ fn main() -> Result<()> {
QueueEvent::SpinnerUpdate => unreachable!(),
}
draw(&mut terminal, &app)?;
draw(terminal, &app)?;
spinner.set_state(app.any_work_pending());
spinner.draw(&mut terminal)?;
spinner.draw(terminal)?;
if app.is_quit() {
break;
@ -205,7 +233,7 @@ fn main() -> Result<()> {
}
}
Ok(())
Ok(app.quit_state())
}
fn setup_terminal() -> Result<()> {

View File

@ -124,6 +124,8 @@ pub enum InternalEvent {
PopupStackPush(StackablePopupOpen),
///
ViewSubmodules,
///
OpenRepo { path: PathBuf },
}
/// single threaded simple queue for components to communicate with each other

View File

@ -716,6 +716,33 @@ pub mod commands {
)
}
pub fn open_submodule(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"Open [{}]",
key_config.get_hint(key_config.keys.enter),
),
"open submodule",
CMD_GROUP_GENERAL,
)
}
pub fn open_submodule_parent(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"Open Parent [{}]",
key_config
.get_hint(key_config.keys.view_submodule_parent),
),
"open submodule parent repo",
CMD_GROUP_GENERAL,
)
}
pub fn continue_rebase(
key_config: &SharedKeyConfig,
) -> CommandText {

View File

@ -15,7 +15,7 @@ use tui::style::{Color, Modifier, Style};
pub type SharedTheme = Rc<Theme>;
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
pub struct Theme {
selected_tab: Color,
#[serde(with = "Color")]
@ -279,7 +279,7 @@ impl Theme {
}
// This will only be called when theme.ron doesn't already exists
fn save(&self, theme_file: PathBuf) -> Result<()> {
fn save(&self, theme_file: &PathBuf) -> Result<()> {
let mut file = File::create(theme_file)?;
let data = to_string_pretty(self, PrettyConfig::default())?;
file.write_all(data.as_bytes())?;
@ -293,7 +293,7 @@ impl Theme {
Ok(from_bytes(&buffer)?)
}
pub fn init(file: PathBuf) -> Result<Self> {
pub fn init(file: &PathBuf) -> Result<Self> {
if file.exists() {
match Self::read_file(file.clone()) {
Err(e) => {