Add folder actions unstage/stage/reset

This commit is contained in:
Stephan Dilly 2020-04-28 11:49:14 +02:00
parent d22571c919
commit 1ca04496cb
11 changed files with 273 additions and 131 deletions

View File

@ -238,7 +238,7 @@ fn new_file_content(path: &Path) -> String {
mod tests { mod tests {
use super::get_diff; use super::get_diff;
use crate::sync::{ use crate::sync::{
stage_add, stage_add_file,
status::{get_status, StatusType}, status::{get_status, StatusType},
tests::{repo_init, repo_init_empty}, tests::{repo_init, repo_init_empty},
}; };
@ -288,7 +288,7 @@ mod tests {
.write_all(b"test\nfoo") .write_all(b"test\nfoo")
.unwrap(); .unwrap();
assert_eq!(stage_add(repo_path, file_path), true); assert_eq!(stage_add_file(repo_path, file_path), true);
let diff = get_diff( let diff = get_diff(
repo_path, repo_path,
@ -347,7 +347,7 @@ mod tests {
assert_eq!(res.len(), 1); assert_eq!(res.len(), 1);
assert_eq!(res[0].path, "bar.txt"); assert_eq!(res[0].path, "bar.txt");
let res = stage_add(repo_path, Path::new("bar.txt")); let res = stage_add_file(repo_path, Path::new("bar.txt"));
assert_eq!(res, true); assert_eq!(res, true);
assert_eq!(get_status(repo_path, StatusType::Stage).len(), 1); assert_eq!(get_status(repo_path, StatusType::Stage).len(), 1);
assert_eq!( assert_eq!(

View File

@ -9,8 +9,12 @@ pub mod utils;
pub use hooks::{hooks_commit_msg, hooks_post_commit, HookResult}; pub use hooks::{hooks_commit_msg, hooks_post_commit, HookResult};
pub use hunks::{stage_hunk, unstage_hunk}; pub use hunks::{stage_hunk, unstage_hunk};
pub use reset::{reset_stage, reset_workdir}; pub use reset::{
pub use utils::{commit, stage_add, stage_addremoved}; reset_stage, reset_workdir_file, reset_workdir_folder,
};
pub use utils::{
commit, stage_add_all, stage_add_file, stage_addremoved,
};
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -26,14 +26,14 @@ pub fn reset_stage(repo_path: &str, path: &Path) -> bool {
} }
/// ///
pub fn reset_workdir(repo_path: &str, path: &Path) -> bool { pub fn reset_workdir_file(repo_path: &str, path: &str) -> bool {
scope_time!("reset_workdir"); scope_time!("reset_workdir_file");
let repo = repo(repo_path); let repo = repo(repo_path);
// Note: early out for removing untracked files, due to bug in checkout_head code: // Note: early out for removing untracked files, due to bug in checkout_head code:
// see https://github.com/libgit2/libgit2/issues/5089 // see https://github.com/libgit2/libgit2/issues/5089
if let Ok(status) = repo.status_file(&path) { if let Ok(status) = repo.status_file(Path::new(path)) {
let removed_file_wd = if status == Status::WT_NEW let removed_file_wd = if status == Status::WT_NEW
|| (status == Status::WT_MODIFIED | Status::INDEX_NEW) || (status == Status::WT_MODIFIED | Status::INDEX_NEW)
{ {
@ -51,7 +51,7 @@ pub fn reset_workdir(repo_path: &str, path: &Path) -> bool {
.update_index(true) // windows: needs this to be true WTF?! .update_index(true) // windows: needs this to be true WTF?!
.allow_conflicts(true) .allow_conflicts(true)
.force() .force()
.path(&path); .path(path);
repo.checkout_index(None, Some(&mut checkout_opts)).is_ok() repo.checkout_index(None, Some(&mut checkout_opts)).is_ok()
} else { } else {
@ -59,17 +59,36 @@ pub fn reset_workdir(repo_path: &str, path: &Path) -> bool {
} }
} }
///
pub fn reset_workdir_folder(repo_path: &str, path: &str) -> bool {
scope_time!("reset_workdir_folder");
let repo = repo(repo_path);
let mut checkout_opts = CheckoutBuilder::new();
checkout_opts
.update_index(true) // windows: needs this to be true WTF?!
.allow_conflicts(true)
.remove_untracked(true)
.force()
.path(path);
repo.checkout_index(None, Some(&mut checkout_opts)).is_ok()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{reset_stage, reset_workdir}; use super::{
reset_stage, reset_workdir_file, reset_workdir_folder,
};
use crate::sync::{ use crate::sync::{
status::{get_status, StatusType}, status::{get_status, StatusType},
tests::{debug_cmd_print, repo_init, repo_init_empty}, tests::{debug_cmd_print, repo_init, repo_init_empty},
utils::stage_add, utils::{commit, stage_add_all, stage_add_file},
}; };
use std::{ use std::{
fs::{self, File}, fs::{self, File},
io::Write, io::{Error, Write},
path::Path, path::Path,
}; };
@ -119,7 +138,7 @@ mod tests {
debug_cmd_print(repo_path, "git status"); debug_cmd_print(repo_path, "git status");
stage_add(repo_path, Path::new("bar.txt")); stage_add_file(repo_path, Path::new("bar.txt"));
debug_cmd_print(repo_path, "git status"); debug_cmd_print(repo_path, "git status");
@ -139,7 +158,7 @@ mod tests {
1 1
); );
let res = reset_workdir(repo_path, Path::new("bar.txt")); let res = reset_workdir_file(repo_path, "bar.txt");
assert_eq!(res, true); assert_eq!(res, true);
debug_cmd_print(repo_path, "git status"); debug_cmd_print(repo_path, "git status");
@ -172,7 +191,7 @@ mod tests {
1 1
); );
let res = reset_workdir(repo_path, Path::new("foo/bar.txt")); let res = reset_workdir_file(repo_path, "foo/bar.txt");
assert_eq!(res, true); assert_eq!(res, true);
debug_cmd_print(repo_path, "git status"); debug_cmd_print(repo_path, "git status");
@ -183,6 +202,56 @@ mod tests {
); );
} }
#[test]
fn test_reset_folder() -> Result<(), Error> {
let (_td, repo) = repo_init();
let root = repo.path().parent().unwrap();
let repo_path = root.as_os_str().to_str().unwrap();
{
fs::create_dir(&root.join("foo"))?;
File::create(&root.join("foo/file1.txt"))?
.write_all(b"file1")?;
File::create(&root.join("foo/file2.txt"))?
.write_all(b"file1")?;
File::create(&root.join("file3.txt"))?
.write_all(b"file3")?;
}
assert!(stage_add_all(repo_path, "*"));
commit(repo_path, "msg");
{
File::create(&root.join("foo/file1.txt"))?
.write_all(b"file1\nadded line")?;
fs::remove_file(&root.join("foo/file2.txt"))?;
File::create(&root.join("foo/file4.txt"))?
.write_all(b"file4")?;
File::create(&root.join("foo/file5.txt"))?
.write_all(b"file5")?;
File::create(&root.join("file3.txt"))?
.write_all(b"file3\nadded line")?;
}
stage_add_file(repo_path, Path::new("foo/file5.txt"));
assert_eq!(
get_status(repo_path, StatusType::WorkingDir).len(),
4
);
assert_eq!(get_status(repo_path, StatusType::Stage).len(), 1);
assert!(reset_workdir_folder(repo_path, "foo"));
assert_eq!(
get_status(repo_path, StatusType::WorkingDir).len(),
1
);
assert_eq!(get_status(repo_path, StatusType::Stage).len(), 1);
Ok(())
}
#[test] #[test]
fn test_reset_untracked_in_subdir_and_index() { fn test_reset_untracked_in_subdir_and_index() {
let (_td, repo) = repo_init(); let (_td, repo) = repo_init();
@ -219,7 +288,7 @@ mod tests {
1 1
); );
let res = reset_workdir(repo_path, Path::new(file)); let res = reset_workdir_file(repo_path, file);
assert_eq!(res, true); assert_eq!(res, true);
debug_cmd_print(repo_path, "git status"); debug_cmd_print(repo_path, "git status");
@ -243,7 +312,7 @@ mod tests {
.write_all(b"test\nfoo") .write_all(b"test\nfoo")
.unwrap(); .unwrap();
assert_eq!(stage_add(repo_path, file_path), true); assert_eq!(stage_add_file(repo_path, file_path), true);
assert_eq!(reset_stage(repo_path, file_path), true); assert_eq!(reset_stage(repo_path, file_path), true);
} }

View File

@ -1,6 +1,6 @@
//! sync git api (various methods) //! sync git api (various methods)
use git2::{Repository, RepositoryOpenFlags}; use git2::{IndexAddOption, Repository, RepositoryOpenFlags};
use scopetime::scope_time; use scopetime::scope_time;
use std::path::Path; use std::path::Path;
@ -63,8 +63,8 @@ pub fn commit(repo_path: &str, msg: &str) {
} }
/// add a file diff from workingdir to stage (will not add removed files see `stage_addremoved`) /// add a file diff from workingdir to stage (will not add removed files see `stage_addremoved`)
pub fn stage_add(repo_path: &str, path: &Path) -> bool { pub fn stage_add_file(repo_path: &str, path: &Path) -> bool {
scope_time!("stage_add"); scope_time!("stage_add_file");
let repo = repo(repo_path); let repo = repo(repo_path);
@ -78,6 +78,25 @@ pub fn stage_add(repo_path: &str, path: &Path) -> bool {
false false
} }
/// like `stage_add_file` but uses a pattern to match/glob multiple files/folders
pub fn stage_add_all(repo_path: &str, pattern: &str) -> bool {
scope_time!("stage_add_all");
let repo = repo(repo_path);
let mut index = repo.index().unwrap();
if index
.add_all(vec![pattern], IndexAddOption::DEFAULT, None)
.is_ok()
{
index.write().unwrap();
return true;
}
false
}
/// stage a removed file /// stage a removed file
pub fn stage_addremoved(repo_path: &str, path: &Path) -> bool { pub fn stage_addremoved(repo_path: &str, path: &Path) -> bool {
scope_time!("stage_addremoved"); scope_time!("stage_addremoved");
@ -98,13 +117,12 @@ pub fn stage_addremoved(repo_path: &str, path: &Path) -> bool {
mod tests { mod tests {
use super::*; use super::*;
use crate::sync::{ use crate::sync::{
stage_add,
status::{get_status, StatusType}, status::{get_status, StatusType},
tests::{repo_init, repo_init_empty}, tests::{repo_init, repo_init_empty},
}; };
use std::{ use std::{
fs::{remove_file, File}, fs::{self, remove_file, File},
io::Write, io::{Error, Write},
path::Path, path::Path,
}; };
@ -126,7 +144,7 @@ mod tests {
assert_eq!(status_count(StatusType::WorkingDir), 1); assert_eq!(status_count(StatusType::WorkingDir), 1);
assert_eq!(stage_add(repo_path, file_path), true); assert_eq!(stage_add_file(repo_path, file_path), true);
assert_eq!(status_count(StatusType::WorkingDir), 0); assert_eq!(status_count(StatusType::WorkingDir), 0);
assert_eq!(status_count(StatusType::Stage), 1); assert_eq!(status_count(StatusType::Stage), 1);
@ -149,7 +167,7 @@ mod tests {
.write_all(b"test\nfoo") .write_all(b"test\nfoo")
.unwrap(); .unwrap();
assert_eq!(stage_add(repo_path, file_path), true); assert_eq!(stage_add_file(repo_path, file_path), true);
commit(repo_path, "commit msg"); commit(repo_path, "commit msg");
} }
@ -161,7 +179,7 @@ mod tests {
let root = repo.path().parent().unwrap(); let root = repo.path().parent().unwrap();
let repo_path = root.as_os_str().to_str().unwrap(); let repo_path = root.as_os_str().to_str().unwrap();
assert_eq!(stage_add(repo_path, file_path), false); assert_eq!(stage_add_file(repo_path, file_path), false);
} }
#[test] #[test]
@ -187,12 +205,40 @@ mod tests {
assert_eq!(status_count(StatusType::WorkingDir), 2); assert_eq!(status_count(StatusType::WorkingDir), 2);
assert_eq!(stage_add(repo_path, file_path), true); assert_eq!(stage_add_file(repo_path, file_path), true);
assert_eq!(status_count(StatusType::WorkingDir), 1); assert_eq!(status_count(StatusType::WorkingDir), 1);
assert_eq!(status_count(StatusType::Stage), 1); assert_eq!(status_count(StatusType::Stage), 1);
} }
#[test]
fn test_staging_folder() -> Result<(), Error> {
let (_td, repo) = repo_init();
let root = repo.path().parent().unwrap();
let repo_path = root.as_os_str().to_str().unwrap();
let status_count = |s: StatusType| -> usize {
get_status(repo_path, s).len()
};
fs::create_dir_all(&root.join("a/d"))?;
File::create(&root.join(Path::new("a/d/f1.txt")))?
.write_all(b"foo")?;
File::create(&root.join(Path::new("a/d/f2.txt")))?
.write_all(b"foo")?;
File::create(&root.join(Path::new("a/f3.txt")))?
.write_all(b"foo")?;
assert_eq!(status_count(StatusType::WorkingDir), 3);
assert_eq!(stage_add_all(repo_path, "a/d"), true);
assert_eq!(status_count(StatusType::WorkingDir), 1);
assert_eq!(status_count(StatusType::Stage), 2);
Ok(())
}
#[test] #[test]
fn test_staging_deleted_file() { fn test_staging_deleted_file() {
let file_path = Path::new("file1.txt"); let file_path = Path::new("file1.txt");
@ -211,7 +257,7 @@ mod tests {
.write_all(b"test file1 content") .write_all(b"test file1 content")
.unwrap(); .unwrap();
assert_eq!(stage_add(repo_path, file_path), true); assert_eq!(stage_add_file(repo_path, file_path), true);
commit(repo_path, "commit msg"); commit(repo_path, "commit msg");

View File

@ -17,7 +17,7 @@ use crossbeam_channel::Sender;
use crossterm::event::Event; use crossterm::event::Event;
use itertools::Itertools; use itertools::Itertools;
use log::trace; use log::trace;
use std::{borrow::Cow, path::Path}; use std::borrow::Cow;
use strings::commands; use strings::commands;
use tui::{ use tui::{
backend::Backend, backend::Backend,
@ -299,7 +299,7 @@ impl App {
loop { loop {
let front = self.queue.borrow_mut().pop_front(); let front = self.queue.borrow_mut().pop_front();
if let Some(e) = front { if let Some(e) = front {
flags.insert(self.process_internal_event(&e)); flags.insert(self.process_internal_event(e));
} else { } else {
break; break;
} }
@ -311,35 +311,45 @@ impl App {
fn process_internal_event( fn process_internal_event(
&mut self, &mut self,
ev: &InternalEvent, ev: InternalEvent,
) -> NeedsUpdate { ) -> NeedsUpdate {
let mut flags = NeedsUpdate::empty(); let mut flags = NeedsUpdate::empty();
match ev { match ev {
InternalEvent::ResetFile(p) => { InternalEvent::ResetItem(reset_item) => {
if sync::reset_workdir(CWD, Path::new(p.as_str())) { if reset_item.is_folder {
if sync::reset_workdir_folder(
CWD,
reset_item.path.as_str(),
) {
flags.insert(NeedsUpdate::ALL);
}
} else if sync::reset_workdir_file(
CWD,
reset_item.path.as_str(),
) {
flags.insert(NeedsUpdate::ALL); flags.insert(NeedsUpdate::ALL);
} }
} }
InternalEvent::ConfirmResetFile(p) => { InternalEvent::ConfirmResetItem(reset_item) => {
self.reset.open_for_path(p); self.reset.open_for_path(reset_item);
flags.insert(NeedsUpdate::COMMANDS); flags.insert(NeedsUpdate::COMMANDS);
} }
InternalEvent::AddHunk(hash) => { InternalEvent::AddHunk(hash) => {
if let Some((path, is_stage)) = self.selected_path() { if let Some((path, is_stage)) = self.selected_path() {
if is_stage { if is_stage {
if sync::unstage_hunk(CWD, path, *hash) { if sync::unstage_hunk(CWD, path, hash) {
flags.insert(NeedsUpdate::ALL); flags.insert(NeedsUpdate::ALL);
} }
} else if sync::stage_hunk(CWD, path, *hash) { } else if sync::stage_hunk(CWD, path, hash) {
flags.insert(NeedsUpdate::ALL); flags.insert(NeedsUpdate::ALL);
} }
} }
} }
InternalEvent::ShowMsg(msg) => { InternalEvent::ShowMsg(msg) => {
self.msg.show_msg(msg); self.msg.show_msg(msg.as_str());
flags.insert(NeedsUpdate::ALL); flags.insert(NeedsUpdate::ALL);
} }
InternalEvent::Update(u) => flags.insert(*u), InternalEvent::Update(u) => flags.insert(u),
}; };
flags flags

View File

@ -6,12 +6,11 @@ use super::{
use crate::{ use crate::{
components::{CommandInfo, Component}, components::{CommandInfo, Component},
keys, keys,
queue::{InternalEvent, NeedsUpdate, Queue}, queue::{InternalEvent, NeedsUpdate, Queue, ResetItem},
strings, ui, strings, ui,
}; };
use asyncgit::{hash, sync, StatusItem, StatusItemType, CWD}; use asyncgit::{hash, sync, StatusItem, StatusItemType, CWD};
use crossterm::event::Event; use crossterm::event::Event;
use log::trace;
use std::{borrow::Cow, convert::From, path::Path}; use std::{borrow::Cow, convert::From, path::Path};
use strings::commands; use strings::commands;
use tui::{ use tui::{
@ -103,25 +102,28 @@ impl ChangesComponent {
fn index_add_remove(&mut self) -> bool { fn index_add_remove(&mut self) -> bool {
if let Some(tree_item) = self.selection() { if let Some(tree_item) = self.selection() {
if let FileTreeItemKind::File(i) = tree_item.kind { if self.is_working_dir {
if self.is_working_dir { if let FileTreeItemKind::File(i) = tree_item.kind {
if let Some(status) = i.status { if let Some(status) = i.status {
let path = Path::new(i.path.as_str()); let path = Path::new(i.path.as_str());
return match status { return match status {
StatusItemType::Deleted => { StatusItemType::Deleted => {
sync::stage_addremoved(CWD, path) sync::stage_addremoved(CWD, path)
} }
_ => sync::stage_add(CWD, path), _ => sync::stage_add_file(CWD, path),
}; };
} }
} else { } else {
let path = Path::new(i.path.as_str()); //TODO: check if we can handle the one file case with it aswell
return sync::stage_add_all(
return sync::reset_stage(CWD, path); CWD,
tree_item.info.full_path.as_str(),
);
} }
} else { } else {
//TODO: let path =
trace!("tbd"); Path::new(tree_item.info.full_path.as_str());
return sync::reset_stage(CWD, path);
} }
} }
@ -130,16 +132,16 @@ impl ChangesComponent {
fn dispatch_reset_workdir(&mut self) -> bool { fn dispatch_reset_workdir(&mut self) -> bool {
if let Some(tree_item) = self.selection() { if let Some(tree_item) = self.selection() {
if let FileTreeItemKind::File(i) = tree_item.kind { let is_folder =
self.queue.borrow_mut().push_back( matches!(tree_item.kind, FileTreeItemKind::Path(_));
InternalEvent::ConfirmResetFile(i.path), self.queue.borrow_mut().push_back(
); InternalEvent::ConfirmResetItem(ResetItem {
path: tree_item.info.full_path,
is_folder,
}),
);
return true; return true;
} else {
//TODO:
trace!("tbd");
}
} }
false false
} }
@ -282,22 +284,22 @@ impl Component for ChangesComponent {
out: &mut Vec<CommandInfo>, out: &mut Vec<CommandInfo>,
_force_all: bool, _force_all: bool,
) -> CommandBlocking { ) -> CommandBlocking {
let some_selection = let some_selection = self.selection().is_some();
self.selection().is_some() && self.is_file_seleted();
if self.is_working_dir { if self.is_working_dir {
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::STAGE_FILE, commands::STAGE_ITEM,
some_selection, some_selection,
self.focused, self.focused,
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::RESET_FILE, commands::RESET_ITEM,
some_selection, some_selection,
self.focused, self.focused,
)); ));
} else { } else {
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::UNSTAGE_FILE, commands::UNSTAGE_ITEM,
some_selection, some_selection,
self.focused, self.focused,
)); ));

View File

@ -151,17 +151,37 @@ impl FileTreeItems {
self.0.len() self.0.len()
} }
fn push_dirs( ///
item_path: &Path, pub(crate) fn find_parent_index(
&self,
path: &str,
index: usize,
) -> usize {
if let Some(parent_path) = Path::new(path).parent() {
let parent_path = parent_path.to_str().unwrap();
for i in (0..=index).rev() {
let item = &self.0[i];
let item_path = &item.info.full_path;
if item_path == parent_path {
return i;
}
}
}
0
}
fn push_dirs<'a>(
item_path: &'a Path,
nodes: &mut BinaryHeap<FileTreeItem>, nodes: &mut BinaryHeap<FileTreeItem>,
paths_added: &mut BTreeSet<String>, //TODO: use a ref string here paths_added: &mut BTreeSet<&'a Path>,
collapsed: &BTreeSet<&String>, collapsed: &BTreeSet<&String>,
) { ) {
for c in item_path.ancestors().skip(1) { for c in item_path.ancestors().skip(1) {
if c.parent().is_some() { if c.parent().is_some() {
let path_string = String::from(c.to_str().unwrap()); let path_string = String::from(c.to_str().unwrap());
if !paths_added.contains(&path_string) { if !paths_added.contains(c) {
paths_added.insert(path_string.clone()); paths_added.insert(c);
let is_collapsed = let is_collapsed =
collapsed.contains(&path_string); collapsed.contains(&path_string);
nodes.push(FileTreeItem::new_path( nodes.push(FileTreeItem::new_path(
@ -292,4 +312,25 @@ mod tests {
] ]
); );
} }
#[test]
fn test_find_parent() {
//0 a/
//1 b/
//2 c
//3 d
let res = FileTreeItems::new(
&string_vec_to_status(&[
"a/b/c", //
"a/b/d", //
]),
&BTreeSet::new(),
);
assert_eq!(
res.find_parent_index(&String::from("a/b/c"), 3),
1
);
}
} }

View File

@ -3,7 +3,7 @@ use super::{
DrawableComponent, DrawableComponent,
}; };
use crate::{ use crate::{
queue::{InternalEvent, Queue}, queue::{InternalEvent, Queue, ResetItem},
strings, ui, strings, ui,
}; };
@ -20,7 +20,7 @@ use tui::{
/// ///
pub struct ResetComponent { pub struct ResetComponent {
path: String, target: Option<ResetItem>,
visible: bool, visible: bool,
queue: Queue, queue: Queue,
} }
@ -107,21 +107,24 @@ impl ResetComponent {
/// ///
pub fn new(queue: Queue) -> Self { pub fn new(queue: Queue) -> Self {
Self { Self {
path: String::default(), target: None,
visible: false, visible: false,
queue, queue,
} }
} }
/// ///
pub fn open_for_path(&mut self, path: &str) { pub fn open_for_path(&mut self, item: ResetItem) {
self.path = path.to_string(); self.target = Some(item);
self.show(); self.show();
} }
/// ///
pub fn confirm(&mut self) { pub fn confirm(&mut self) {
if let Some(target) = self.target.take() {
self.queue
.borrow_mut()
.push_back(InternalEvent::ResetItem(target));
}
self.hide(); self.hide();
self.queue
.borrow_mut()
.push_back(InternalEvent::ResetFile(self.path.clone()));
} }
} }

View File

@ -2,7 +2,7 @@ use super::filetree::{
FileTreeItem, FileTreeItemKind, FileTreeItems, PathCollapsed, FileTreeItem, FileTreeItemKind, FileTreeItems, PathCollapsed,
}; };
use asyncgit::StatusItem; use asyncgit::StatusItem;
use std::{cmp, collections::BTreeSet, path::Path}; use std::{cmp, collections::BTreeSet};
/// ///
#[derive(Default)] #[derive(Default)]
@ -193,7 +193,8 @@ impl StatusTree {
if collapsed) if collapsed)
{ {
SelectionChange::new( SelectionChange::new(
self.find_parent_index(&item_path, current_selection), self.tree
.find_parent_index(&item_path, current_selection),
false, false,
) )
} else if matches!(item_kind, FileTreeItemKind::Path(PathCollapsed(collapsed)) } else if matches!(item_kind, FileTreeItemKind::Path(PathCollapsed(collapsed))
@ -290,27 +291,6 @@ impl StatusTree {
} }
} }
} }
fn find_parent_index(
&self,
path: &str,
current_index: usize,
) -> usize {
let path = Path::new(path);
if let Some(path) = path.parent() {
for i in (0..=current_index).rev() {
let item = self.tree.items().get(i).unwrap();
let item_path = &item.info.full_path;
//TODO: use parameter path here
if item_path.ends_with(path.to_str().unwrap()) {
return i;
}
}
}
0
}
} }
#[cfg(test)] #[cfg(test)]
@ -353,27 +333,6 @@ mod tests {
assert_eq!(res.selection, Some(0)); assert_eq!(res.selection, Some(0));
} }
#[test]
fn test_select_parent() {
let items = string_vec_to_status(&[
"a/b/c", //
"a/b/d", //
]);
//0 a/
//1 b/
//2 c
//3 d
let mut res = StatusTree::default();
res.update(&items);
assert_eq!(
res.find_parent_index(&String::from("a/b/c"), 3),
1
);
}
#[test] #[test]
fn test_keep_selected_item() { fn test_keep_selected_item() {
let mut res = StatusTree::default(); let mut res = StatusTree::default();

View File

@ -13,12 +13,20 @@ bitflags! {
} }
} }
/// data of item that is supposed to be reset
pub struct ResetItem {
/// path to the item (folder/file)
pub path: String,
/// are talking about a folder here? otherwise it's a single file
pub is_folder: bool,
}
/// ///
pub enum InternalEvent { pub enum InternalEvent {
/// ///
ConfirmResetFile(String), ConfirmResetItem(ResetItem),
/// ///
ResetFile(String), ResetItem(ResetItem),
/// ///
AddHunk(u64), AddHunk(u64),
/// ///

View File

@ -79,21 +79,21 @@ pub mod commands {
CMD_GROUP_COMMIT, CMD_GROUP_COMMIT,
); );
/// ///
pub static STAGE_FILE: CommandText = CommandText::new( pub static STAGE_ITEM: CommandText = CommandText::new(
"Stage File [enter]", "Stage Item [enter]",
"stage currently selected file", "stage currently selected file or entire path",
CMD_GROUP_CHANGES, CMD_GROUP_CHANGES,
); );
/// ///
pub static UNSTAGE_FILE: CommandText = CommandText::new( pub static UNSTAGE_ITEM: CommandText = CommandText::new(
"Unstage File [enter]", "Unstage Item [enter]",
"remove currently selected file from stage", "unstage currently selected file or entire path",
CMD_GROUP_CHANGES, CMD_GROUP_CHANGES,
); );
/// ///
pub static RESET_FILE: CommandText = CommandText::new( pub static RESET_ITEM: CommandText = CommandText::new(
"Reset File [D]", "Reset Item [D]",
"revert changes in selected file", "revert changes in selected file or entire path",
CMD_GROUP_CHANGES, CMD_GROUP_CHANGES,
); );
/// ///