mirror of
https://github.com/extrawurst/gitui.git
synced 2024-11-23 11:42:56 +03:00
Add folder actions unstage/stage/reset
This commit is contained in:
parent
d22571c919
commit
1ca04496cb
@ -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!(
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
|
||||||
|
32
src/app.rs
32
src/app.rs
@ -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
|
||||||
|
@ -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,
|
||||||
));
|
));
|
||||||
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
12
src/queue.rs
12
src/queue.rs
@ -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),
|
||||||
///
|
///
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
///
|
///
|
||||||
|
Loading…
Reference in New Issue
Block a user