diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4254d434..321b2e71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,8 +19,8 @@ jobs: steps: - uses: actions/checkout@v2 - name: Build - run: cargo build --verbose + run: cargo build - name: Run tests - run: cargo test --workspace -- --test-threads=1 + run: cargo test --workspace - name: Run clippy run: cargo clean && cargo clippy --all-features diff --git a/Makefile b/Makefile index 7d7866b6..b934c341 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ debug: GITUI_LOGGING=true cargo run --features=timing test: - cargo test --workspace -- --test-threads=1 + cargo test --workspace clippy: cargo clean diff --git a/README.md b/README.md index 6f4325b4..f2904348 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,3 @@ GITUI_LOGGING=true gitui * https://github.com/jesseduffield/lazygit * https://github.com/jonas/tig * https://github.com/git-up/GitUp (would be nice to comeup with a way to have the map view available in a terminal tool) - - -asdad \ No newline at end of file diff --git a/asyncgit/src/diff.rs b/asyncgit/src/diff.rs index 4ace37ea..b60d0b41 100644 --- a/asyncgit/src/diff.rs +++ b/asyncgit/src/diff.rs @@ -1,4 +1,4 @@ -use crate::{hash, sync, AsyncNotification, Diff}; +use crate::{hash, sync, AsyncNotification, Diff, CWD}; use crossbeam_channel::Sender; use log::trace; use std::{ @@ -76,7 +76,7 @@ impl AsyncDiff { let sender = self.sender.clone(); rayon_core::spawn(move || { let res = - sync::diff::get_diff(params.0.clone(), params.1); + sync::diff::get_diff(CWD, params.0.clone(), params.1); let mut notify = false; { let mut current = arc_current.lock().unwrap(); diff --git a/asyncgit/src/lib.rs b/asyncgit/src/lib.rs index 45f9d726..c1dd238a 100644 --- a/asyncgit/src/lib.rs +++ b/asyncgit/src/lib.rs @@ -31,6 +31,9 @@ pub enum AsyncNotification { Diff, } +/// +pub static CWD: &str = "./"; + /// helper function to calculate the hash of an arbitrary type that implements the `Hash` trait pub fn hash(v: &T) -> u64 { let mut hasher = DefaultHasher::new(); diff --git a/asyncgit/src/status.rs b/asyncgit/src/status.rs index 5a2975a1..f190c39b 100644 --- a/asyncgit/src/status.rs +++ b/asyncgit/src/status.rs @@ -1,4 +1,4 @@ -use crate::{hash, sync, AsyncNotification, StatusItem}; +use crate::{hash, sync, AsyncNotification, StatusItem, CWD}; use crossbeam_channel::Sender; use log::trace; use std::{ @@ -85,8 +85,8 @@ impl AsyncStatus { fn get_status() -> Status { let work_dir = - sync::status::get_index(StatusType::WorkingDir); - let stage = sync::status::get_index(StatusType::Stage); + sync::status::get_status(CWD, StatusType::WorkingDir); + let stage = sync::status::get_status(CWD, StatusType::Stage); Status { stage, work_dir } } diff --git a/asyncgit/src/sync/diff.rs b/asyncgit/src/sync/diff.rs index 3f2e9c89..60db64a5 100644 --- a/asyncgit/src/sync/diff.rs +++ b/asyncgit/src/sync/diff.rs @@ -5,7 +5,7 @@ use git2::{ Delta, DiffDelta, DiffFormat, DiffHunk, DiffOptions, Patch, }; use scopetime::scope_time; -use std::fs; +use std::{fs, path::Path}; /// #[derive(Copy, Clone, PartialEq, Hash)] @@ -64,10 +64,10 @@ pub struct Hunk(pub Vec); pub struct Diff(pub Vec, pub u16); /// -pub fn get_diff(p: String, stage: bool) -> Diff { +pub fn get_diff(repo_path: &str, p: String, stage: bool) -> Diff { scope_time!("get_diff"); - let repo = utils::repo(); + let repo = utils::repo(repo_path); let mut opt = DiffOptions::new(); opt.pathspec(p); @@ -134,16 +134,18 @@ pub fn get_diff(p: String, stage: bool) -> Diff { let delta: DiffDelta = diff.deltas().next().unwrap(); if delta.status() == Delta::Untracked { - let newfile_path = delta.new_file().path().unwrap(); + let repo_path = Path::new(repo_path); + let newfile_path = + repo_path.join(delta.new_file().path().unwrap()); let newfile_content = - fs::read_to_string(newfile_path).unwrap(); + fs::read_to_string(&newfile_path).unwrap(); let mut patch = Patch::from_buffers( &[], None, newfile_content.as_bytes(), - Some(newfile_path), + Some(&newfile_path), Some(&mut opt), ) .unwrap(); @@ -186,51 +188,22 @@ mod tests { use super::get_diff; use crate::sync::{ stage_add, - status::{get_index, StatusType}, + status::{get_status, StatusType}, + tests::repo_init, }; - use git2::Repository; - use std::env; use std::{ fs::{self, File}, io::Write, path::Path, }; - use tempfile::TempDir; - - pub fn repo_init() -> (TempDir, Repository) { - let td = TempDir::new().unwrap(); - let repo = Repository::init(td.path()).unwrap(); - { - let mut config = repo.config().unwrap(); - config.set_str("user.name", "name").unwrap(); - config.set_str("user.email", "email").unwrap(); - - let mut index = repo.index().unwrap(); - let id = index.write_tree().unwrap(); - - let tree = repo.find_tree(id).unwrap(); - let sig = repo.signature().unwrap(); - repo.commit( - Some("HEAD"), - &sig, - &sig, - "initial", - &tree, - &[], - ) - .unwrap(); - } - (td, repo) - } #[test] fn test_untracked_subfolder() { let (_td, repo) = repo_init(); let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert!(env::set_current_dir(&root).is_ok()); - - let res = get_index(StatusType::WorkingDir); + let res = get_status(repo_path, StatusType::WorkingDir); assert_eq!(res.len(), 0); fs::create_dir(&root.join("foo")).unwrap(); @@ -239,10 +212,11 @@ mod tests { .write_all(b"test\nfoo") .unwrap(); - let res = get_index(StatusType::WorkingDir); + let res = get_status(repo_path, StatusType::WorkingDir); assert_eq!(res.len(), 1); - let diff = get_diff("foo/bar.txt".to_string(), false); + let diff = + get_diff(repo_path, "foo/bar.txt".to_string(), false); assert_eq!(diff.0.len(), 1); assert_eq!(diff.0[0].0[1].content, "test\n"); @@ -278,11 +252,9 @@ mod tests { fn test_hunks() { let (_td, repo) = repo_init(); let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - //TODO: this makes the test not threading safe - assert!(env::set_current_dir(&root).is_ok()); - - let res = get_index(StatusType::WorkingDir); + let res = get_status(repo_path, StatusType::WorkingDir); assert_eq!(res.len(), 0); let file_path = root.join("bar.txt"); @@ -294,14 +266,17 @@ mod tests { .unwrap(); } - let res = get_index(StatusType::WorkingDir); + let res = get_status(repo_path, StatusType::WorkingDir); assert_eq!(res.len(), 1); assert_eq!(res[0].path, "bar.txt"); - let res = stage_add(Path::new("bar.txt")); + let res = stage_add(repo_path, Path::new("bar.txt")); assert_eq!(res, true); - assert_eq!(get_index(StatusType::Stage).len(), 1); - assert_eq!(get_index(StatusType::WorkingDir).len(), 0); + assert_eq!(get_status(repo_path, StatusType::Stage).len(), 1); + assert_eq!( + get_status(repo_path, StatusType::WorkingDir).len(), + 0 + ); // overwrite with next content { @@ -311,10 +286,13 @@ mod tests { .unwrap(); } - assert_eq!(get_index(StatusType::Stage).len(), 1); - assert_eq!(get_index(StatusType::WorkingDir).len(), 1); + assert_eq!(get_status(repo_path, StatusType::Stage).len(), 1); + assert_eq!( + get_status(repo_path, StatusType::WorkingDir).len(), + 1 + ); - let res = get_diff("bar.txt".to_string(), false); + let res = get_diff(repo_path, "bar.txt".to_string(), false); assert_eq!(res.0.len(), 2) } diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index a1ee689f..e1783183 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -5,5 +5,75 @@ mod reset; pub mod status; pub mod utils; -pub use reset::{index_reset, stage_reset}; +pub use reset::{reset_stage, reset_workdir}; pub use utils::{commit, stage_add}; + +#[cfg(test)] +mod tests { + use git2::Repository; + use std::process::Command; + use tempfile::TempDir; + + pub fn repo_init() -> (TempDir, Repository) { + let td = TempDir::new().unwrap(); + let repo = Repository::init(td.path()).unwrap(); + { + let mut config = repo.config().unwrap(); + config.set_str("user.name", "name").unwrap(); + config.set_str("user.email", "email").unwrap(); + + let mut index = repo.index().unwrap(); + let id = index.write_tree().unwrap(); + + let tree = repo.find_tree(id).unwrap(); + let sig = repo.signature().unwrap(); + repo.commit( + Some("HEAD"), + &sig, + &sig, + "initial", + &tree, + &[], + ) + .unwrap(); + } + (td, repo) + } + + /// + pub fn debug_cmd_print(path: &str, cmd: &str) { + eprintln!("\n----\n{}", debug_cmd(path, cmd)) + } + + fn debug_cmd(path: &str, cmd: &str) -> String { + let output = if cfg!(target_os = "windows") { + Command::new("cmd") + .args(&["/C", cmd]) + .current_dir(path) + .output() + } else { + Command::new("sh") + .arg("-c") + .arg(cmd) + .current_dir(path) + .output() + }; + + let output = output.unwrap(); + let stdout = String::from_utf8(output.stdout).unwrap(); + let stderr = String::from_utf8(output.stderr).unwrap(); + format!( + "{}{}", + if stdout.is_empty() { + String::new() + } else { + format!("out:\n{}", stdout) + }, + if stderr.is_empty() { + String::new() + } else { + format!("err:\n{}", stderr) + } + ) + } +} diff --git a/asyncgit/src/sync/reset.rs b/asyncgit/src/sync/reset.rs index 9e90fe3f..876de398 100644 --- a/asyncgit/src/sync/reset.rs +++ b/asyncgit/src/sync/reset.rs @@ -1,18 +1,13 @@ -use super::utils::repo_at; -use git2::ObjectType; +use super::utils::repo; +use git2::{build::CheckoutBuilder, ObjectType}; use scopetime::scope_time; -use std::{path::Path, process::Command}; +use std::path::Path; /// -pub fn stage_reset(path: &Path) -> bool { - stage_reset_at("./", path) -} +pub fn reset_stage(repo_path: &str, path: &Path) -> bool { + scope_time!("reset_stage"); -/// -pub fn stage_reset_at(repo_path: &str, path: &Path) -> bool { - scope_time!("stage_reset_at"); - - let repo = repo_at(repo_path); + let repo = repo(repo_path); let reference = repo.head().unwrap(); let obj = repo @@ -30,94 +25,41 @@ pub fn stage_reset_at(repo_path: &str, path: &Path) -> bool { } /// -pub fn index_reset(path: &Path) -> bool { - index_reset_at("./", path) -} +pub fn reset_workdir(repo_path: &str, path: &Path) -> bool { + scope_time!("reset_workdir"); -/// -pub fn index_reset_at(repo_path: &str, path: &Path) -> bool { - let cmd = format!("git checkout {}", path.to_str().unwrap()); + let repo = repo(repo_path); - let output = if cfg!(target_os = "windows") { - Command::new("cmd") - .args(&["/C", cmd.as_str()]) - .current_dir(repo_path) - .output() - } else { - Command::new("sh") - .arg("-c") - .arg(cmd.as_str()) - .current_dir(repo_path) - .output() - }; + let mut checkout_opts = CheckoutBuilder::new(); + checkout_opts + .remove_untracked(true) + .force() + .update_index(false) + .path(&path); - if let Ok(out) = output { - // dbg!(String::from_utf8(out.stderr.clone()).unwrap()); - String::from_utf8(out.stderr).unwrap() - == "Updated 1 path from the index\n" - } else { - false - } + //first reset working dir file + repo.checkout_head(Some(&mut checkout_opts)).unwrap(); - //------------------------------------ - //TODO: why is this broken with libgit2 ??? - //------------------------------------ + let mut checkout_opts = CheckoutBuilder::new(); + checkout_opts + .update_index(true) // windows: needs this to be true WTF?! + .path(&path); - // scope_time!("index_reset"); + // now reset staged changes back to working dir + repo.checkout_index(None, Some(&mut checkout_opts)).unwrap(); - // let repo = repo_at(repo_path); - - // let mut checkout_opts = CheckoutBuilder::new(); - // checkout_opts - // .remove_untracked(true) - // .force() - // .update_index(false) - // .allow_conflicts(true) - // .path(&path); - - // if repo.checkout_head(Some(&mut checkout_opts)).is_ok() { - // return true; - // } - - // false + true } #[cfg(test)] mod tests { - use super::index_reset_at; + use super::reset_workdir; use crate::sync::{ - status::{get_index_at, StatusType}, - utils::stage_add_at, + status::{get_status, StatusType}, + tests::{debug_cmd_print, repo_init}, + utils::stage_add, }; - use git2::Repository; use std::{fs::File, io::Write, path::Path}; - use tempfile::TempDir; - - pub fn repo_init() -> (TempDir, Repository) { - let td = TempDir::new().unwrap(); - let repo = Repository::init(td.path()).unwrap(); - { - let mut config = repo.config().unwrap(); - config.set_str("user.name", "name").unwrap(); - config.set_str("user.email", "email").unwrap(); - - let mut index = repo.index().unwrap(); - let id = index.write_tree().unwrap(); - - let tree = repo.find_tree(id).unwrap(); - let sig = repo.signature().unwrap(); - repo.commit( - Some("HEAD"), - &sig, - &sig, - "initial", - &tree, - &[], - ) - .unwrap(); - } - (td, repo) - } static HUNK_A: &str = r" 1 start @@ -151,7 +93,7 @@ mod tests { let root = repo.path().parent().unwrap(); let repo_path = root.as_os_str().to_str().unwrap(); - let res = get_index_at(repo_path, StatusType::WorkingDir); + let res = get_status(repo_path, StatusType::WorkingDir); assert_eq!(res.len(), 0); let file_path = root.join("bar.txt"); @@ -163,7 +105,11 @@ mod tests { .unwrap(); } - stage_add_at(repo_path, Path::new("bar.txt")); + debug_cmd_print(repo_path, "git status"); + + stage_add(repo_path, Path::new("bar.txt")); + + debug_cmd_print(repo_path, "git status"); // overwrite with next content { @@ -173,24 +119,22 @@ mod tests { .unwrap(); } + debug_cmd_print(repo_path, "git status"); + + assert_eq!(get_status(repo_path, StatusType::Stage).len(), 1); assert_eq!( - get_index_at(repo_path, StatusType::Stage).len(), - 1 - ); - assert_eq!( - get_index_at(repo_path, StatusType::WorkingDir).len(), + get_status(repo_path, StatusType::WorkingDir).len(), 1 ); - let res = index_reset_at(repo_path, Path::new("bar.txt")); + let res = reset_workdir(repo_path, Path::new("bar.txt")); assert_eq!(res, true); + debug_cmd_print(repo_path, "git status"); + + assert_eq!(get_status(repo_path, StatusType::Stage).len(), 1); assert_eq!( - get_index_at(repo_path, StatusType::Stage).len(), - 1 - ); - assert_eq!( - get_index_at(repo_path, StatusType::WorkingDir).len(), + get_status(repo_path, StatusType::WorkingDir).len(), 0 ); } diff --git a/asyncgit/src/sync/status.rs b/asyncgit/src/sync/status.rs index 5239ec97..1e081797 100644 --- a/asyncgit/src/sync/status.rs +++ b/asyncgit/src/sync/status.rs @@ -63,18 +63,13 @@ impl Into for StatusType { } /// -pub fn get_index(status_type: StatusType) -> Vec { - get_index_at("./", status_type) -} - -/// -pub fn get_index_at( +pub fn get_status( repo_path: &str, status_type: StatusType, ) -> Vec { scope_time!("get_index"); - let repo = utils::repo_at(repo_path); + let repo = utils::repo(repo_path); let statuses = repo .statuses(Some( diff --git a/asyncgit/src/sync/utils.rs b/asyncgit/src/sync/utils.rs index 16d7e699..d4f87143 100644 --- a/asyncgit/src/sync/utils.rs +++ b/asyncgit/src/sync/utils.rs @@ -4,14 +4,8 @@ use git2::{IndexAddOption, Repository, RepositoryOpenFlags}; use scopetime::scope_time; use std::path::Path; -//TODO: get rid of this /// -pub fn repo() -> Repository { - repo_at("./") -} - -/// -pub fn repo_at(repo_path: &str) -> Repository { +pub fn repo(repo_path: &str) -> Repository { let repo = Repository::open_ext( repo_path, RepositoryOpenFlags::empty(), @@ -27,10 +21,10 @@ pub fn repo_at(repo_path: &str) -> Repository { } /// -pub fn commit(msg: &str) { +pub fn commit(repo_path: &str, msg: &str) { scope_time!("commit"); - let repo = repo(); + let repo = repo(repo_path); let signature = repo.signature().unwrap(); let reference = repo.head().unwrap(); @@ -52,15 +46,10 @@ pub fn commit(msg: &str) { } /// -pub fn stage_add(path: &Path) -> bool { - stage_add_at("./", path) -} - -/// -pub fn stage_add_at(repo_path: &str, path: &Path) -> bool { +pub fn stage_add(repo_path: &str, path: &Path) -> bool { scope_time!("stage_add"); - let repo = repo_at(repo_path); + let repo = repo(repo_path); let mut index = repo.index().unwrap(); diff --git a/src/app.rs b/src/app.rs index 2530ff84..84562458 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,7 +7,7 @@ use crate::{ }; use asyncgit::{ current_tick, sync, AsyncDiff, AsyncNotification, AsyncStatus, - DiffParams, + DiffParams, CWD, }; use crossbeam_channel::Sender; use crossterm::event::Event; @@ -431,14 +431,14 @@ impl App { if let Some(i) = self.index_wd.selection() { let path = Path::new(i.path.as_str()); - if sync::stage_add(path) { + if sync::stage_add(CWD, path) { self.update(); } } } else if let Some(i) = self.index.selection() { let path = Path::new(i.path.as_str()); - if sync::stage_reset(path) { + if sync::reset_stage(CWD, path) { self.update(); } } @@ -449,7 +449,7 @@ impl App { if let Some(i) = self.index_wd.selection() { let path = Path::new(i.path.as_str()); - if sync::index_reset(path) { + if sync::reset_workdir(CWD, path) { self.update(); } } diff --git a/src/components/commit.rs b/src/components/commit.rs index 905a9557..d936042f 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -1,6 +1,6 @@ use super::{CommandInfo, Component}; use crate::{strings, ui}; -use asyncgit::sync; +use asyncgit::{sync, CWD}; use crossterm::event::{Event, KeyCode}; use std::borrow::Cow; use tui::{ @@ -99,7 +99,7 @@ impl Component for CommitComponent { impl CommitComponent { fn commit(&mut self) { - sync::commit(&self.msg); + sync::commit(CWD, &self.msg); self.msg.clear(); self.hide();