From dfd5fc992dbba0ece2b303e7674f0e4fddb45f6a Mon Sep 17 00:00:00 2001 From: Matt Keeler Date: Sun, 26 Apr 2020 12:16:54 -0400 Subject: [PATCH 01/27] Intial support for working with worktrees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The systest changes were necessary due to the actual field name used in the C library being “ref” and not being able to name the field the same in the Rust struct because “ref” is a reserved keyword. --- libgit2-sys/lib.rs | 70 ++++++++++ src/call.rs | 6 + src/lib.rs | 2 + src/repo.rs | 61 +++++++++ src/test.rs | 10 +- src/worktree.rs | 325 +++++++++++++++++++++++++++++++++++++++++++++ systest/build.rs | 4 +- 7 files changed, 476 insertions(+), 2 deletions(-) create mode 100644 src/worktree.rs diff --git a/libgit2-sys/lib.rs b/libgit2-sys/lib.rs index 5bf894d07e..b61deb0839 100644 --- a/libgit2-sys/lib.rs +++ b/libgit2-sys/lib.rs @@ -1823,6 +1823,34 @@ git_enum! { } } +#[repr(C)] +pub struct git_worktree_add_options { + pub version: c_uint, + pub lock: c_int, + pub reference: *mut git_reference, +} + +pub const GIT_WORKTREE_ADD_OPTIONS_VERSION: u32 = 1; + +git_enum! { + pub enum git_worktree_prune_t { + /* Prune working tree even if working tree is valid */ + GIT_WORKTREE_PRUNE_VALID = 1 << 0, + /* Prune working tree even if it is locked */ + GIT_WORKTREE_PRUNE_LOCKED = 1 << 1, + /* Prune checked out working tree */ + GIT_WORKTREE_PRUNE_WORKING_TREE = 1 << 2, + } +} + +#[repr(C)] +pub struct git_worktree_prune_options { + pub version: c_uint, + pub flags: u32, +} + +pub const GIT_WORKTREE_PRUNE_OPTIONS_VERSION: u32 = 1; + extern "C" { // threads pub fn git_libgit2_init() -> c_int; @@ -3780,6 +3808,48 @@ extern "C" { ) -> c_int; pub fn git_libgit2_opts(option: c_int, ...) -> c_int; + + // Worktrees + pub fn git_worktree_list(out: *mut git_strarray, repo: *mut git_repository) -> c_int; + pub fn git_worktree_lookup( + out: *mut *mut git_worktree, + repo: *mut git_repository, + name: *const c_char, + ) -> c_int; + pub fn git_worktree_open_from_repository( + out: *mut *mut git_worktree, + repo: *mut git_repository, + ) -> c_int; + pub fn git_worktree_free(wt: *mut git_worktree); + pub fn git_worktree_validate(wt: *const git_worktree) -> c_int; + pub fn git_worktree_add_options_init( + opts: *mut git_worktree_add_options, + version: c_uint, + ) -> c_int; + pub fn git_worktree_add( + out: *mut *mut git_worktree, + repo: *mut git_repository, + name: *const c_char, + path: *const c_char, + opts: *const git_worktree_add_options, + ) -> c_int; + pub fn git_worktree_lock(wt: *mut git_worktree, reason: *const c_char) -> c_int; + pub fn git_worktree_unlock(wt: *mut git_worktree) -> c_int; + pub fn git_worktree_is_locked(reason: *mut git_buf, wt: *const git_worktree) -> c_int; + pub fn git_worktree_name(wt: *const git_worktree) -> *const c_char; + pub fn git_worktree_path(wt: *const git_worktree) -> *const c_char; + pub fn git_worktree_prune_options_init( + opts: *mut git_worktree_prune_options, + version: c_uint, + ) -> c_int; + pub fn git_worktree_is_prunable( + wt: *mut git_worktree, + opts: *mut git_worktree_prune_options, + ) -> c_int; + pub fn git_worktree_prune( + wt: *mut git_worktree, + opts: *mut git_worktree_prune_options, + ) -> c_int; } pub fn init() { diff --git a/src/call.rs b/src/call.rs index f1ade4a8f3..78f3df6a09 100644 --- a/src/call.rs +++ b/src/call.rs @@ -93,6 +93,12 @@ mod impls { } } + impl Convert<*mut libc::c_char> for CString { + fn convert(&self) -> *mut libc::c_char { + self.as_ptr() as *mut libc::c_char + } + } + impl> Convert<*const T> for Option { fn convert(&self) -> *const T { self.as_ref().map(|s| s.convert()).unwrap_or(ptr::null()) diff --git a/src/lib.rs b/src/lib.rs index ace4009230..cb2e871cca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,6 +131,7 @@ pub use crate::time::{IndexTime, Time}; pub use crate::tree::{Tree, TreeEntry, TreeIter, TreeWalkMode, TreeWalkResult}; pub use crate::treebuilder::TreeBuilder; pub use crate::util::IntoCString; +pub use crate::worktree::{Worktree, WorktreeAddOptions, WorktreeLockStatus, WorktreePruneOptions}; // Create a convinience method on bitflag struct which checks the given flag macro_rules! is_bit_set { @@ -686,6 +687,7 @@ mod tagforeach; mod time; mod tree; mod treebuilder; +mod worktree; fn init() { static INIT: Once = Once::new(); diff --git a/src/repo.rs b/src/repo.rs index 4f53ccb3fc..16ad2d29d6 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -16,6 +16,7 @@ use crate::stash::{stash_cb, StashApplyOptions, StashCbData}; use crate::string_array::StringArray; use crate::tagforeach::{tag_foreach_cb, TagForeachCB, TagForeachData}; use crate::util::{self, path_to_repo_path, Binding}; +use crate::worktree::{Worktree, WorktreeAddOptions}; use crate::CherrypickOptions; use crate::RevertOptions; use crate::{ @@ -2791,6 +2792,66 @@ impl Repository { Ok(Binding::from_raw(ret)) } } + + /// Lists all the worktrees for the repository + pub fn worktrees(&self) -> Result { + let mut arr = raw::git_strarray { + strings: 0 as *mut *mut c_char, + count: 0, + }; + unsafe { + try_call!(raw::git_worktree_list(&mut arr, self.raw)); + Ok(Binding::from_raw(arr)) + } + } + + /// Opens a worktree by name for the given repository + /// + /// This can open any worktree that the worktrees method returns. + pub fn worktree_lookup(&self, name: &str) -> Result { + let mut raw = ptr::null_mut(); + let raw_name = CString::new(name)?; + unsafe { + try_call!(raw::git_worktree_lookup(&mut raw, self.raw, raw_name)); + Ok(Binding::from_raw(raw)) + } + } + + /// Open a worktree of a the repository + /// + /// If a repository is not the main tree but a worktree, this + /// function will look up the worktree inside the parent + /// repository and create a new `git_worktree` structure. + pub fn worktree_open_from_repository(&self) -> Result { + let mut raw = ptr::null_mut(); + unsafe { + try_call!(raw::git_worktree_open_from_repository(&mut raw, self.raw)); + Ok(Binding::from_raw(raw)) + } + } + + /// Creates a new worktree for the repository + pub fn worktree_add( + &self, + name: &str, + path: &Path, + opts: &WorktreeAddOptions<'_>, + ) -> Result { + let mut raw = ptr::null_mut(); + let raw_name = CString::new(name)?; + let raw_path = path.into_c_string()?; + + unsafe { + try_call!(raw::git_worktree_add( + &mut raw, + self.raw, + raw_name, + raw_path, + &opts.raw() + )); + Ok(Binding::from_raw(raw)) + } + } } impl Binding for Repository { diff --git a/src/test.rs b/src/test.rs index f21690e355..89a13beafe 100644 --- a/src/test.rs +++ b/src/test.rs @@ -6,7 +6,7 @@ use std::ptr; use tempfile::TempDir; use url::Url; -use crate::{Oid, Repository, RepositoryInitOptions}; +use crate::{Branch, Oid, Repository, RepositoryInitOptions}; macro_rules! t { ($e:expr) => { @@ -56,6 +56,14 @@ pub fn path2url(path: &Path) -> String { Url::from_file_path(path).unwrap().to_string() } +pub fn worktrees_env_init(repo: &Repository) -> (TempDir, Branch<'_>) { + let oid = repo.head().unwrap().target().unwrap(); + let commit = repo.find_commit(oid).unwrap(); + let branch = repo.branch("wt-branch", &commit, true).unwrap(); + let wtdir = TempDir::new().unwrap(); + (wtdir, branch) +} + #[cfg(windows)] pub fn realpath(original: &Path) -> io::Result { Ok(original.to_path_buf()) diff --git a/src/worktree.rs b/src/worktree.rs new file mode 100644 index 0000000000..857379515a --- /dev/null +++ b/src/worktree.rs @@ -0,0 +1,325 @@ +use crate::buf::Buf; +use crate::reference::Reference; +use crate::repo::Repository; +use crate::util::{self, Binding}; +use crate::{call, raw, Error}; +use std::mem; +use std::path::Path; +use std::ptr; +use std::str; + +/// An owned git worktree +/// +/// This structure corresponds to a `git_worktree` in libgit2. +// +pub struct Worktree { + raw: *mut raw::git_worktree, +} + +// It is the current belief that a `Worktree` can be sent among threads, or +// even shared among threads in a mutex +unsafe impl Send for Worktree {} + +/// Options which can be used to configure how a repository is initialized +pub struct WorktreeAddOptions<'a> { + lock: i32, + reference: Option>, +} + +/// Options to configure how worktree pruning is performed +pub struct WorktreePruneOptions { + flags: u32, +} + +/// Lock Status of a worktree +#[derive(PartialEq, Debug)] +pub enum WorktreeLockStatus { + /// Worktree is Unlocked + Unlocked, + /// Worktree is locked with the optional message + Locked(Option), +} + +impl Worktree { + /// Retrieves the name of the worktree + /// + /// This is the name that can be passed to repo::Repository::worktree_lookup + /// to reopen the worktree. This is also the name that would appear in the + /// list returned by repo::Repository::worktrees + pub fn name(&self) -> Option<&str> { + unsafe { + crate::opt_bytes(self, raw::git_worktree_name(self.raw)) + .and_then(|s| str::from_utf8(s).ok()) + } + } + + /// Retrieves the path to the worktree + /// + /// This is the path to the top-level of the source and not the path to the + /// .git file within the worktree. This path can be passed to + /// repo::Repository::open. + pub fn path(&self) -> &Path { + unsafe { + util::bytes2path(crate::opt_bytes(self, raw::git_worktree_path(self.raw)).unwrap()) + } + } + + /// Validates the worktree + /// + /// This checks that it still exists on the + /// filesystem and that the metadata is correct + pub fn validate(&self) -> Result<(), Error> { + unsafe { + call::c_try(raw::git_worktree_validate(call::convert(&self.raw)))?; + } + Ok(()) + } + + /// Locks the worktree + pub fn lock(&self, reason: Option<&str>) -> Result<(), Error> { + let reason = crate::opt_cstr(reason)?; + unsafe { + try_call!(raw::git_worktree_lock(self.raw, reason)); + } + Ok(()) + } + + /// Unlocks the worktree + pub fn unlock(&self) -> Result<(), Error> { + unsafe { + call::c_try(raw::git_worktree_unlock(call::convert(&self.raw)))?; + } + Ok(()) + } + + /// Checks if worktree is locked + pub fn is_locked(&self) -> Result { + let buf = Buf::new(); + unsafe { + match try_call!(raw::git_worktree_is_locked(buf.raw(), self.raw)) { + 0 => Ok(WorktreeLockStatus::Unlocked), + _ => { + println!("Buf: {}", buf.as_str().unwrap()); + let v = buf.to_vec(); + println!("Length of v: {}", v.len()); + Ok(WorktreeLockStatus::Locked(match v.len() { + 0 => None, + _ => String::from_utf8(v).ok(), + })) + } + } + } + } + + /// Prunes the worktree + pub fn prune(&self, opts: WorktreePruneOptions) -> Result<(), Error> { + // When successful the worktree should be removed however the backing structure + // of the git_worktree should still be valid. + unsafe { + try_call!(raw::git_worktree_prune(self.raw, &mut opts.raw())); + } + Ok(()) + } + + /// Checks if the worktree is prunable + pub fn is_prunable(&self, opts: WorktreePruneOptions) -> bool { + unsafe { + if call!(raw::git_worktree_is_prunable(self.raw, &mut opts.raw())) <= 0 { + false + } else { + true + } + } + } + + /// Opens the repository from the worktree + pub fn open_repository(&self) -> Result { + let mut ret = ptr::null_mut(); + unsafe { + try_call!(raw::git_repository_open_from_worktree(&mut ret, self.raw)); + Ok(Binding::from_raw(ret)) + } + } +} + +impl<'a> WorktreeAddOptions<'a> { + /// Creates a default set of add options. + /// + /// By default this will not lock the worktree + pub fn new(reference: Option>) -> WorktreeAddOptions<'a> { + WorktreeAddOptions { + lock: 0, + reference: reference, + } + } + + /// If enabled, this will cause the newly added worktree to be locked + pub fn lock(&mut self, enabled: bool) -> &mut WorktreeAddOptions<'a> { + self.lock = match enabled { + true => 1, + false => 0, + }; + self + } + + /// Creates a set of raw add options to be used with `git_worktree_add` + /// + /// This method is unsafe as the returned value may have pointers to the + /// interior of this structure + pub unsafe fn raw(&self) -> raw::git_worktree_add_options { + let mut opts = mem::zeroed(); + assert_eq!( + raw::git_worktree_add_options_init(&mut opts, raw::GIT_WORKTREE_ADD_OPTIONS_VERSION), + 0 + ); + + opts.lock = self.lock; + opts.reference = if let Some(ref gref) = self.reference { + gref.raw() + } else { + ptr::null_mut() + }; + + opts + } +} + +impl WorktreePruneOptions { + /// Creates a default set of pruning options + /// + /// By defaults this will prune only worktrees that are no longer valid + /// unlocked and not checked out + pub fn new() -> WorktreePruneOptions { + WorktreePruneOptions { flags: 0 } + } + + /// Controls whether valid (still existing on the filesystem) worktrees + /// will be pruned + /// + /// Defaults to false + pub fn valid(&mut self, valid: bool) -> &mut WorktreePruneOptions { + self.flag(raw::GIT_WORKTREE_PRUNE_VALID, valid) + } + + /// Controls whether locked worktrees will be pruned + /// + /// Defaults to false + pub fn locked(&mut self, locked: bool) -> &mut WorktreePruneOptions { + self.flag(raw::GIT_WORKTREE_PRUNE_LOCKED, locked) + } + + /// Controls whether the actual working tree on the fs is recursively removed + /// + /// Defaults to false + pub fn working_tree(&mut self, working_tree: bool) -> &mut WorktreePruneOptions { + self.flag(raw::GIT_WORKTREE_PRUNE_WORKING_TREE, working_tree) + } + + fn flag(&mut self, flag: raw::git_worktree_prune_t, on: bool) -> &mut WorktreePruneOptions { + if on { + self.flags |= flag as u32; + } else { + self.flags &= !(flag as u32); + } + self + } + /// Creates a set of raw prune options to be used with `git_worktree_prune` + /// + /// This method is unsafe as the returned value may have pointers to the + /// interior of this structure + pub unsafe fn raw(&self) -> raw::git_worktree_prune_options { + let mut opts = mem::zeroed(); + assert_eq!( + raw::git_worktree_prune_options_init( + &mut opts, + raw::GIT_WORKTREE_PRUNE_OPTIONS_VERSION + ), + 0 + ); + + opts.flags = self.flags; + opts + } +} + +impl Binding for Worktree { + type Raw = *mut raw::git_worktree; + unsafe fn from_raw(ptr: *mut raw::git_worktree) -> Worktree { + Worktree { raw: ptr } + } + fn raw(&self) -> *mut raw::git_worktree { + self.raw + } +} + +impl Drop for Worktree { + fn drop(&mut self) { + unsafe { raw::git_worktree_free(self.raw) } + } +} + +#[cfg(test)] +mod tests { + use crate::WorktreeAddOptions; + use crate::WorktreeLockStatus; + use tempfile::TempDir; + + #[test] + fn smoke_add_no_ref() { + let (_td, repo) = crate::test::repo_init(); + let wtdir = TempDir::new().unwrap(); + let wt_path = wtdir.path().join("tree-no-ref-dir"); + let opts = WorktreeAddOptions::new(None); + + let wt = repo.worktree_add("tree-no-ref", &wt_path, &opts).unwrap(); + assert_eq!(wt.name(), Some("tree-no-ref")); + assert_eq!( + wt.path().canonicalize().unwrap(), + wt_path.canonicalize().unwrap() + ); + let status = wt.is_locked().unwrap(); + assert_eq!(status, WorktreeLockStatus::Unlocked); + } + + #[test] + fn smoke_add_locked() { + let (_td, repo) = crate::test::repo_init(); + let wtdir = TempDir::new().unwrap(); + let wt_path = wtdir.path().join("locked-tree"); + let mut opts = WorktreeAddOptions::new(None); + opts.lock(true); + + let wt = repo.worktree_add("locked-tree", &wt_path, &opts).unwrap(); + // shouldn't be able to lock a worktree that was created locked + assert!(wt.lock(Some("my reason")).is_err()); + assert_eq!(wt.name(), Some("locked-tree")); + assert_eq!( + wt.path().canonicalize().unwrap(), + wt_path.canonicalize().unwrap() + ); + assert_eq!(wt.is_locked().unwrap(), WorktreeLockStatus::Locked(None)); + assert!(wt.unlock().is_ok()); + assert!(wt.lock(Some("my reason")).is_ok()); + assert_eq!( + wt.is_locked().unwrap(), + WorktreeLockStatus::Locked(Some("my reason".to_string())) + ); + } + + #[test] + fn smoke_add_from_branch() { + let (_td, repo) = crate::test::repo_init(); + let (wt_top, branch) = crate::test::worktrees_env_init(&repo); + let wt_path = wt_top.path().join("test"); + let opts = WorktreeAddOptions::new(Some(branch.into_reference())); + + let wt = repo.worktree_add("test-worktree", &wt_path, &opts).unwrap(); + assert_eq!(wt.name(), Some("test-worktree")); + assert_eq!( + wt.path().canonicalize().unwrap(), + wt_path.canonicalize().unwrap() + ); + let status = wt.is_locked().unwrap(); + assert_eq!(status, WorktreeLockStatus::Unlocked); + } +} diff --git a/systest/build.rs b/systest/build.rs index 23a57bdc1e..eba93f3090 100644 --- a/systest/build.rs +++ b/systest/build.rs @@ -22,7 +22,9 @@ fn main() { }); cfg.skip_field(|struct_, f| { // this field is marked as const which ctest complains about - struct_ == "git_rebase_operation" && f == "id" + (struct_ == "git_rebase_operation" && f == "id") || + // the real name of this field is ref but that is a reserved keyword + (struct_ == "git_worktree_add_options" && f == "reference") }); cfg.skip_signededness(|s| { match s { From 8fca1eec69e1b3a314547eca5753ecf274c3ba35 Mon Sep 17 00:00:00 2001 From: Matt Keeler Date: Mon, 27 Apr 2020 09:20:11 -0400 Subject: [PATCH 02/27] Ignore the deprecated git_transfer_progress type alias in systest The type it aliases, git_indexer_progress, is still checked but this prevents spurious warnings about using deprecated fields. --- systest/build.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/systest/build.rs b/systest/build.rs index eba93f3090..64942afc64 100644 --- a/systest/build.rs +++ b/systest/build.rs @@ -43,5 +43,6 @@ fn main() { cfg.skip_roundtrip(|t| t == "git_clone_options" || t == "git_submodule_update_options"); cfg.skip_type(|t| t == "__enum_ty"); + cfg.skip_type(|t| t == "git_transfer_progress"); cfg.generate("../libgit2-sys/lib.rs", "all.rs"); } From 714beb656c5cd5d2ca7dfe3072314d5f9605e1c2 Mon Sep 17 00:00:00 2001 From: Matt Keeler Date: Thu, 14 May 2020 10:52:11 -0400 Subject: [PATCH 03/27] First round of experimental multi-repo testing --- Cargo.toml | 1 + src/blame.rs | 6 ++---- src/branch.rs | 5 ++--- src/test.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- src/worktree.rs | 18 ++++++------------ 5 files changed, 59 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a5c2a82ccc..2e2375a5b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ structopt = "0.3" time = "0.1.39" tempfile = "3.1.0" thread-id = "3.3.0" # remove when we work with minimal-versions without it +paste = "0.1.12" [features] unstable = [] diff --git a/src/blame.rs b/src/blame.rs index 5c582458df..94a500295d 100644 --- a/src/blame.rs +++ b/src/blame.rs @@ -290,9 +290,7 @@ mod tests { use std::fs::{self, File}; use std::path::Path; - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); + repo_test!(smoke, Typical, TypicalWorktree, BareWorktree { let mut index = repo.index().unwrap(); let root = repo.path().parent().unwrap(); @@ -322,5 +320,5 @@ mod tests { assert_eq!(hunk.path(), Some(Path::new("foo/bar"))); assert_eq!(hunk.lines_in_hunk(), 0); assert!(!hunk.is_boundary()) - } + }); } diff --git a/src/branch.rs b/src/branch.rs index 276bc534ac..a3829d9961 100644 --- a/src/branch.rs +++ b/src/branch.rs @@ -153,8 +153,7 @@ impl<'repo> Drop for Branches<'repo> { mod tests { use crate::BranchType; - #[test] - fn smoke() { + repo_test!(smoke, Typical, TypicalWorktree, BareWorktree { let (_td, repo) = crate::test::repo_init(); let head = repo.head().unwrap(); let target = head.target().unwrap(); @@ -174,5 +173,5 @@ mod tests { b1.set_upstream(None).unwrap(); b1.delete().unwrap(); - } + }); } diff --git a/src/test.rs b/src/test.rs index 89a13beafe..961d2b7813 100644 --- a/src/test.rs +++ b/src/test.rs @@ -17,7 +17,21 @@ macro_rules! t { }; } -pub fn repo_init() -> (TempDir, Repository) { +// `repo_test! will +macro_rules! repo_test { + ($test_name:ident, $($repo_type:ident),+ $test_body:block) => { + paste::item! { + $(#[test] + fn [<$test_name _ $repo_type:snake>]() { + #[allow(unused_variables)] + let (td, repo) = $crate::test::repo_init2($crate::test::RepoType::$repo_type); + $test_body + })+ + } + } +} + +pub fn repo_init_typical() -> (TempDir, Repository) { let td = TempDir::new().unwrap(); let mut opts = RepositoryInitOptions::new(); opts.initial_head("main"); @@ -37,6 +51,31 @@ pub fn repo_init() -> (TempDir, Repository) { (td, repo) } +pub fn repo_init_bare() -> (TempDir, Repository) { + panic!("unimplemented") +} + +pub fn repo_init_bare_worktree() -> (TempDir, Repository) { + panic!("unimplemented") +} + +pub fn repo_init_typical_worktree() -> (TempDir, Repository) { + panic!("unimplemented") +} + +pub fn repo_init() -> (TempDir, Repository) { + repo_init_typical() +} + +pub fn repo_init2(repo_type: RepoType) -> (TempDir, Repository) { + match repo_type { + RepoType::Typical => repo_init_typical(), + RepoType::Bare => repo_init_bare(), + RepoType::BareWorktree => repo_init_bare_worktree(), + RepoType::TypicalWorktree => repo_init_typical_worktree(), + } +} + pub fn commit(repo: &Repository) -> (Oid, Oid) { let mut index = t!(repo.index()); let root = repo.path().parent().unwrap(); @@ -64,6 +103,14 @@ pub fn worktrees_env_init(repo: &Repository) -> (TempDir, Branch<'_>) { (wtdir, branch) } +#[derive(Debug, Clone, Copy)] +pub enum RepoType { + Typical, + TypicalWorktree, + Bare, + BareWorktree, +} + #[cfg(windows)] pub fn realpath(original: &Path) -> io::Result { Ok(original.to_path_buf()) diff --git a/src/worktree.rs b/src/worktree.rs index 857379515a..262e6a18c7 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -264,9 +264,7 @@ mod tests { use crate::WorktreeLockStatus; use tempfile::TempDir; - #[test] - fn smoke_add_no_ref() { - let (_td, repo) = crate::test::repo_init(); + repo_test!(smoke_add_no_ref, Typical, Bare { let wtdir = TempDir::new().unwrap(); let wt_path = wtdir.path().join("tree-no-ref-dir"); let opts = WorktreeAddOptions::new(None); @@ -279,11 +277,9 @@ mod tests { ); let status = wt.is_locked().unwrap(); assert_eq!(status, WorktreeLockStatus::Unlocked); - } + }); - #[test] - fn smoke_add_locked() { - let (_td, repo) = crate::test::repo_init(); + repo_test!(smoke_add_locked, Typical, Bare { let wtdir = TempDir::new().unwrap(); let wt_path = wtdir.path().join("locked-tree"); let mut opts = WorktreeAddOptions::new(None); @@ -304,11 +300,9 @@ mod tests { wt.is_locked().unwrap(), WorktreeLockStatus::Locked(Some("my reason".to_string())) ); - } + }); - #[test] - fn smoke_add_from_branch() { - let (_td, repo) = crate::test::repo_init(); + repo_test!(smoke_add_from_branch, Typical, Bare { let (wt_top, branch) = crate::test::worktrees_env_init(&repo); let wt_path = wt_top.path().join("test"); let opts = WorktreeAddOptions::new(Some(branch.into_reference())); @@ -321,5 +315,5 @@ mod tests { ); let status = wt.is_locked().unwrap(); assert_eq!(status, WorktreeLockStatus::Unlocked); - } + }); } From 9e89b476834c3e9cb01cc4fb3580d2b455ea8a62 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Tue, 27 Oct 2020 12:10:01 +0900 Subject: [PATCH 04/27] Fix macro hygene error in `repo_test` --- src/blame.rs | 68 +++++++++++++++++++++++++++---------------------- src/branch.rs | 2 +- src/test.rs | 4 +-- src/worktree.rs | 37 +++++++++++++++------------ 4 files changed, 61 insertions(+), 50 deletions(-) diff --git a/src/blame.rs b/src/blame.rs index 94a500295d..55e8122c50 100644 --- a/src/blame.rs +++ b/src/blame.rs @@ -290,35 +290,41 @@ mod tests { use std::fs::{self, File}; use std::path::Path; - repo_test!(smoke, Typical, TypicalWorktree, BareWorktree { - let mut index = repo.index().unwrap(); - - let root = repo.path().parent().unwrap(); - fs::create_dir(&root.join("foo")).unwrap(); - File::create(&root.join("foo/bar")).unwrap(); - index.add_path(Path::new("foo/bar")).unwrap(); - - let id = index.write_tree().unwrap(); - let tree = repo.find_tree(id).unwrap(); - let sig = repo.signature().unwrap(); - let id = repo.refname_to_id("HEAD").unwrap(); - let parent = repo.find_commit(id).unwrap(); - let commit = repo - .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent]) - .unwrap(); - - let blame = repo.blame_file(Path::new("foo/bar"), None).unwrap(); - - assert_eq!(blame.len(), 1); - assert_eq!(blame.iter().count(), 1); - - let hunk = blame.get_index(0).unwrap(); - assert_eq!(hunk.final_commit_id(), commit); - assert_eq!(hunk.final_signature().name(), sig.name()); - assert_eq!(hunk.final_signature().email(), sig.email()); - assert_eq!(hunk.final_start_line(), 1); - assert_eq!(hunk.path(), Some(Path::new("foo/bar"))); - assert_eq!(hunk.lines_in_hunk(), 0); - assert!(!hunk.is_boundary()) - }); + use crate::Repository; + + repo_test!( + smoke, + (Typical, TypicalWorktree, BareWorktree), + |repo: &Repository| { + let mut index = repo.index().unwrap(); + + let root = repo.path().parent().unwrap(); + fs::create_dir(&root.join("foo")).unwrap(); + File::create(&root.join("foo/bar")).unwrap(); + index.add_path(Path::new("foo/bar")).unwrap(); + + let id = index.write_tree().unwrap(); + let tree = repo.find_tree(id).unwrap(); + let sig = repo.signature().unwrap(); + let id = repo.refname_to_id("HEAD").unwrap(); + let parent = repo.find_commit(id).unwrap(); + let commit = repo + .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent]) + .unwrap(); + + let blame = repo.blame_file(Path::new("foo/bar"), None).unwrap(); + + assert_eq!(blame.len(), 1); + assert_eq!(blame.iter().count(), 1); + + let hunk = blame.get_index(0).unwrap(); + assert_eq!(hunk.final_commit_id(), commit); + assert_eq!(hunk.final_signature().name(), sig.name()); + assert_eq!(hunk.final_signature().email(), sig.email()); + assert_eq!(hunk.final_start_line(), 1); + assert_eq!(hunk.path(), Some(Path::new("foo/bar"))); + assert_eq!(hunk.lines_in_hunk(), 0); + assert!(!hunk.is_boundary()) + } + ); } diff --git a/src/branch.rs b/src/branch.rs index a3829d9961..0c85972cf6 100644 --- a/src/branch.rs +++ b/src/branch.rs @@ -153,7 +153,7 @@ impl<'repo> Drop for Branches<'repo> { mod tests { use crate::BranchType; - repo_test!(smoke, Typical, TypicalWorktree, BareWorktree { + repo_test!(smoke, (Typical, TypicalWorktree, BareWorktree), |_| { let (_td, repo) = crate::test::repo_init(); let head = repo.head().unwrap(); let target = head.target().unwrap(); diff --git a/src/test.rs b/src/test.rs index 961d2b7813..d8bef9dfc3 100644 --- a/src/test.rs +++ b/src/test.rs @@ -19,13 +19,13 @@ macro_rules! t { // `repo_test! will macro_rules! repo_test { - ($test_name:ident, $($repo_type:ident),+ $test_body:block) => { + ($test_name:ident, ($($repo_type:ident),+), $test_body:expr) => { paste::item! { $(#[test] fn [<$test_name _ $repo_type:snake>]() { #[allow(unused_variables)] let (td, repo) = $crate::test::repo_init2($crate::test::RepoType::$repo_type); - $test_body + ($test_body)(&repo); })+ } } diff --git a/src/worktree.rs b/src/worktree.rs index 262e6a18c7..e9519bcf11 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -260,11 +260,12 @@ impl Drop for Worktree { #[cfg(test)] mod tests { + use crate::Repository; use crate::WorktreeAddOptions; use crate::WorktreeLockStatus; use tempfile::TempDir; - repo_test!(smoke_add_no_ref, Typical, Bare { + repo_test!(smoke_add_no_ref, (Typical, Bare), |repo: &Repository| { let wtdir = TempDir::new().unwrap(); let wt_path = wtdir.path().join("tree-no-ref-dir"); let opts = WorktreeAddOptions::new(None); @@ -279,7 +280,7 @@ mod tests { assert_eq!(status, WorktreeLockStatus::Unlocked); }); - repo_test!(smoke_add_locked, Typical, Bare { + repo_test!(smoke_add_locked, (Typical, Bare), |repo: &Repository| { let wtdir = TempDir::new().unwrap(); let wt_path = wtdir.path().join("locked-tree"); let mut opts = WorktreeAddOptions::new(None); @@ -302,18 +303,22 @@ mod tests { ); }); - repo_test!(smoke_add_from_branch, Typical, Bare { - let (wt_top, branch) = crate::test::worktrees_env_init(&repo); - let wt_path = wt_top.path().join("test"); - let opts = WorktreeAddOptions::new(Some(branch.into_reference())); - - let wt = repo.worktree_add("test-worktree", &wt_path, &opts).unwrap(); - assert_eq!(wt.name(), Some("test-worktree")); - assert_eq!( - wt.path().canonicalize().unwrap(), - wt_path.canonicalize().unwrap() - ); - let status = wt.is_locked().unwrap(); - assert_eq!(status, WorktreeLockStatus::Unlocked); - }); + repo_test!( + smoke_add_from_branch, + (Typical, Bare), + |repo: &Repository| { + let (wt_top, branch) = crate::test::worktrees_env_init(&repo); + let wt_path = wt_top.path().join("test"); + let opts = WorktreeAddOptions::new(Some(branch.into_reference())); + + let wt = repo.worktree_add("test-worktree", &wt_path, &opts).unwrap(); + assert_eq!(wt.name(), Some("test-worktree")); + assert_eq!( + wt.path().canonicalize().unwrap(), + wt_path.canonicalize().unwrap() + ); + let status = wt.is_locked().unwrap(); + assert_eq!(status, WorktreeLockStatus::Unlocked); + } + ); } From a4be5a95b093dca7ad784845c87dcacb80798169 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Wed, 5 Aug 2020 13:03:23 +0900 Subject: [PATCH 05/27] Match worktree version const types with other version consts --- libgit2-sys/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libgit2-sys/lib.rs b/libgit2-sys/lib.rs index b61deb0839..25a6adb380 100644 --- a/libgit2-sys/lib.rs +++ b/libgit2-sys/lib.rs @@ -1830,7 +1830,7 @@ pub struct git_worktree_add_options { pub reference: *mut git_reference, } -pub const GIT_WORKTREE_ADD_OPTIONS_VERSION: u32 = 1; +pub const GIT_WORKTREE_ADD_OPTIONS_VERSION: c_uint = 1; git_enum! { pub enum git_worktree_prune_t { @@ -1849,7 +1849,7 @@ pub struct git_worktree_prune_options { pub flags: u32, } -pub const GIT_WORKTREE_PRUNE_OPTIONS_VERSION: u32 = 1; +pub const GIT_WORKTREE_PRUNE_OPTIONS_VERSION: c_uint = 1; extern "C" { // threads From fbd13a17491b11a1cfd828ed4974afacb9c6c6fa Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Wed, 5 Aug 2020 13:09:53 +0900 Subject: [PATCH 06/27] Match worktree naming conventions with other entities e.g. branch uses branches, find_branch, branch to list, get, create branches. --- src/repo.rs | 4 ++-- src/worktree.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/repo.rs b/src/repo.rs index 16ad2d29d6..151566a229 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -2808,7 +2808,7 @@ impl Repository { /// Opens a worktree by name for the given repository /// /// This can open any worktree that the worktrees method returns. - pub fn worktree_lookup(&self, name: &str) -> Result { + pub fn find_worktree(&self, name: &str) -> Result { let mut raw = ptr::null_mut(); let raw_name = CString::new(name)?; unsafe { @@ -2831,7 +2831,7 @@ impl Repository { } /// Creates a new worktree for the repository - pub fn worktree_add( + pub fn worktree( &self, name: &str, path: &Path, diff --git a/src/worktree.rs b/src/worktree.rs index e9519bcf11..e3da52f214 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -270,7 +270,7 @@ mod tests { let wt_path = wtdir.path().join("tree-no-ref-dir"); let opts = WorktreeAddOptions::new(None); - let wt = repo.worktree_add("tree-no-ref", &wt_path, &opts).unwrap(); + let wt = repo.worktree("tree-no-ref", &wt_path, &opts).unwrap(); assert_eq!(wt.name(), Some("tree-no-ref")); assert_eq!( wt.path().canonicalize().unwrap(), @@ -286,7 +286,7 @@ mod tests { let mut opts = WorktreeAddOptions::new(None); opts.lock(true); - let wt = repo.worktree_add("locked-tree", &wt_path, &opts).unwrap(); + let wt = repo.worktree("locked-tree", &wt_path, &opts).unwrap(); // shouldn't be able to lock a worktree that was created locked assert!(wt.lock(Some("my reason")).is_err()); assert_eq!(wt.name(), Some("locked-tree")); @@ -311,7 +311,7 @@ mod tests { let wt_path = wt_top.path().join("test"); let opts = WorktreeAddOptions::new(Some(branch.into_reference())); - let wt = repo.worktree_add("test-worktree", &wt_path, &opts).unwrap(); + let wt = repo.worktree("test-worktree", &wt_path, &opts).unwrap(); assert_eq!(wt.name(), Some("test-worktree")); assert_eq!( wt.path().canonicalize().unwrap(), From c55c3341f04140360c29c700b86195328e1c50d0 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Wed, 5 Aug 2020 14:07:12 +0900 Subject: [PATCH 07/27] Move conversions to under its namespaces and match naming convention https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv --- src/repo.rs | 21 +++++++++++++++------ src/worktree.rs | 21 +++++++++++++++------ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/repo.rs b/src/repo.rs index 151566a229..35965ba7fc 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -163,6 +163,19 @@ impl Repository { } } + /// Attempt to open an already-existing repository from a worktree. + pub fn open_from_worktree(worktree: &Worktree) -> Result { + crate::init(); + let mut ret = ptr::null_mut(); + unsafe { + try_call!(raw::git_repository_open_from_worktree( + &mut ret, + worktree.raw() + )); + Ok(Binding::from_raw(ret)) + } + } + /// Attempt to open an already-existing repository at or above `path` /// /// This starts at `path` and looks up the filesystem hierarchy @@ -2822,12 +2835,8 @@ impl Repository { /// If a repository is not the main tree but a worktree, this /// function will look up the worktree inside the parent /// repository and create a new `git_worktree` structure. - pub fn worktree_open_from_repository(&self) -> Result { - let mut raw = ptr::null_mut(); - unsafe { - try_call!(raw::git_worktree_open_from_repository(&mut raw, self.raw)); - Ok(Binding::from_raw(raw)) - } + pub fn to_worktree(&self) -> Result { + Worktree::open_from_repository(self) } /// Creates a new worktree for the repository diff --git a/src/worktree.rs b/src/worktree.rs index e3da52f214..6666fac0f6 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -41,6 +41,19 @@ pub enum WorktreeLockStatus { } impl Worktree { + /// Open a worktree of a the repository + /// + /// If a repository is not the main tree but a worktree, this + /// function will look up the worktree inside the parent + /// repository and create a new `git_worktree` structure. + pub fn open_from_repository(repo: &Repository) -> Result { + let mut raw = ptr::null_mut(); + unsafe { + try_call!(raw::git_worktree_open_from_repository(&mut raw, repo.raw())); + Ok(Binding::from_raw(raw)) + } + } + /// Retrieves the name of the worktree /// /// This is the name that can be passed to repo::Repository::worktree_lookup @@ -133,12 +146,8 @@ impl Worktree { } /// Opens the repository from the worktree - pub fn open_repository(&self) -> Result { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_repository_open_from_worktree(&mut ret, self.raw)); - Ok(Binding::from_raw(ret)) - } + pub fn to_repository(&self) -> Result { + Repository::open_from_worktree(self) } } From 8a79cdd05e7a2406925fa8cb9d6f07637a87e5b5 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Wed, 5 Aug 2020 14:47:27 +0900 Subject: [PATCH 08/27] Match WorktreeAddOptions style with other options --- src/worktree.rs | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/worktree.rs b/src/worktree.rs index 6666fac0f6..905f8bdc75 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -4,6 +4,7 @@ use crate::repo::Repository; use crate::util::{self, Binding}; use crate::{call, raw, Error}; use std::mem; +use std::os::raw::c_int; use std::path::Path; use std::ptr; use std::str; @@ -22,7 +23,7 @@ unsafe impl Send for Worktree {} /// Options which can be used to configure how a repository is initialized pub struct WorktreeAddOptions<'a> { - lock: i32, + lock: bool, reference: Option>, } @@ -155,19 +156,22 @@ impl<'a> WorktreeAddOptions<'a> { /// Creates a default set of add options. /// /// By default this will not lock the worktree - pub fn new(reference: Option>) -> WorktreeAddOptions<'a> { + pub fn new() -> WorktreeAddOptions<'a> { WorktreeAddOptions { - lock: 0, - reference: reference, + lock: false, + reference: None, } } /// If enabled, this will cause the newly added worktree to be locked pub fn lock(&mut self, enabled: bool) -> &mut WorktreeAddOptions<'a> { - self.lock = match enabled { - true => 1, - false => 0, - }; + self.lock = enabled; + self + } + + /// reference to use for the new worktree HEAD + pub fn reference(&mut self, reference: Option>) -> &mut WorktreeAddOptions<'a> { + self.reference = reference; self } @@ -182,12 +186,8 @@ impl<'a> WorktreeAddOptions<'a> { 0 ); - opts.lock = self.lock; - opts.reference = if let Some(ref gref) = self.reference { - gref.raw() - } else { - ptr::null_mut() - }; + opts.lock = self.lock as c_int; + opts.reference = crate::call::convert(&self.reference.as_ref().map(|o| o.raw())); opts } @@ -277,9 +277,9 @@ mod tests { repo_test!(smoke_add_no_ref, (Typical, Bare), |repo: &Repository| { let wtdir = TempDir::new().unwrap(); let wt_path = wtdir.path().join("tree-no-ref-dir"); - let opts = WorktreeAddOptions::new(None); + let opts = WorktreeAddOptions::new(); - let wt = repo.worktree("tree-no-ref", &wt_path, &opts).unwrap(); + let wt = repo.worktree("tree-no-ref", &wt_path, Some(&opts)).unwrap(); assert_eq!(wt.name(), Some("tree-no-ref")); assert_eq!( wt.path().canonicalize().unwrap(), @@ -292,10 +292,10 @@ mod tests { repo_test!(smoke_add_locked, (Typical, Bare), |repo: &Repository| { let wtdir = TempDir::new().unwrap(); let wt_path = wtdir.path().join("locked-tree"); - let mut opts = WorktreeAddOptions::new(None); + let mut opts = WorktreeAddOptions::new(); opts.lock(true); - let wt = repo.worktree("locked-tree", &wt_path, &opts).unwrap(); + let wt = repo.worktree("locked-tree", &wt_path, Some(&opts)).unwrap(); // shouldn't be able to lock a worktree that was created locked assert!(wt.lock(Some("my reason")).is_err()); assert_eq!(wt.name(), Some("locked-tree")); @@ -318,9 +318,12 @@ mod tests { |repo: &Repository| { let (wt_top, branch) = crate::test::worktrees_env_init(&repo); let wt_path = wt_top.path().join("test"); - let opts = WorktreeAddOptions::new(Some(branch.into_reference())); + let mut opts = WorktreeAddOptions::new(); + opts.reference(Some(branch.into_reference())); - let wt = repo.worktree("test-worktree", &wt_path, &opts).unwrap(); + let wt = repo + .worktree("test-worktree", &wt_path, Some(&opts)) + .unwrap(); assert_eq!(wt.name(), Some("test-worktree")); assert_eq!( wt.path().canonicalize().unwrap(), From daba2aa6221835a0fea5292fcb84d07da7170f42 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Wed, 5 Aug 2020 14:48:06 +0900 Subject: [PATCH 09/27] Use ptr::null_mut() rather than 0 --- src/repo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/repo.rs b/src/repo.rs index 35965ba7fc..87694334e3 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -2809,7 +2809,7 @@ impl Repository { /// Lists all the worktrees for the repository pub fn worktrees(&self) -> Result { let mut arr = raw::git_strarray { - strings: 0 as *mut *mut c_char, + strings: ptr::null_mut(), count: 0, }; unsafe { From b8ab3a3106bb55271c7fd384cd3daed8e500d377 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Wed, 5 Aug 2020 14:56:07 +0900 Subject: [PATCH 10/27] Take `Option<>` where API may take NULL as opts --- src/repo.rs | 5 +++-- src/worktree.rs | 10 ++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/repo.rs b/src/repo.rs index 87694334e3..ca41487726 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -2844,19 +2844,20 @@ impl Repository { &self, name: &str, path: &Path, - opts: &WorktreeAddOptions<'_>, + opts: Option<&WorktreeAddOptions<'_>>, ) -> Result { let mut raw = ptr::null_mut(); let raw_name = CString::new(name)?; let raw_path = path.into_c_string()?; unsafe { + let opts = opts.map(|o| o.raw()); try_call!(raw::git_worktree_add( &mut raw, self.raw, raw_name, raw_path, - &opts.raw() + opts.as_ref() )); Ok(Binding::from_raw(raw)) } diff --git a/src/worktree.rs b/src/worktree.rs index 905f8bdc75..2eeeaa02b2 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -126,19 +126,21 @@ impl Worktree { } /// Prunes the worktree - pub fn prune(&self, opts: WorktreePruneOptions) -> Result<(), Error> { + pub fn prune(&self, opts: Option<&WorktreePruneOptions>) -> Result<(), Error> { // When successful the worktree should be removed however the backing structure // of the git_worktree should still be valid. unsafe { - try_call!(raw::git_worktree_prune(self.raw, &mut opts.raw())); + let mut opts = opts.map(|o| o.raw()); + try_call!(raw::git_worktree_prune(self.raw, opts.as_mut())); } Ok(()) } /// Checks if the worktree is prunable - pub fn is_prunable(&self, opts: WorktreePruneOptions) -> bool { + pub fn is_prunable(&self, opts: Option<&WorktreePruneOptions>) -> bool { unsafe { - if call!(raw::git_worktree_is_prunable(self.raw, &mut opts.raw())) <= 0 { + let mut opts = opts.map(|o| o.raw()); + if call!(raw::git_worktree_is_prunable(self.raw, opts.as_mut())) <= 0 { false } else { true From 8db70708e586cb53e40454d0baf12368778ecd18 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Wed, 5 Aug 2020 15:02:17 +0900 Subject: [PATCH 11/27] Create TempDirs to hold multiple `TempDir`s --- src/test.rs | 55 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/src/test.rs b/src/test.rs index d8bef9dfc3..87a1be58f6 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,5 +1,6 @@ use std::fs::File; use std::io; +use std::ops::Deref; use std::path::{Path, PathBuf}; #[cfg(unix)] use std::ptr; @@ -31,7 +32,41 @@ macro_rules! repo_test { } } -pub fn repo_init_typical() -> (TempDir, Repository) { +pub struct TempDirs { + main: TempDir, + _rest: Vec, +} + +impl Deref for TempDirs { + type Target = TempDir; + + fn deref(&self) -> &Self::Target { + &self.main + } +} + +pub fn repo_init_typical() -> (TempDirs, Repository) { + let (td, repo) = repo_init(); + let tds = TempDirs { + main: td, + _rest: vec![], + }; + (tds, repo) +} + +pub fn repo_init_bare() -> (TempDirs, Repository) { + panic!("unimplemented") +} + +pub fn repo_init_bare_worktree() -> (TempDirs, Repository) { + panic!("unimplemented") +} + +pub fn repo_init_typical_worktree() -> (TempDirs, Repository) { + panic!("unimplemented") +} + +pub fn repo_init() -> (TempDir, Repository) { let td = TempDir::new().unwrap(); let mut opts = RepositoryInitOptions::new(); opts.initial_head("main"); @@ -51,23 +86,7 @@ pub fn repo_init_typical() -> (TempDir, Repository) { (td, repo) } -pub fn repo_init_bare() -> (TempDir, Repository) { - panic!("unimplemented") -} - -pub fn repo_init_bare_worktree() -> (TempDir, Repository) { - panic!("unimplemented") -} - -pub fn repo_init_typical_worktree() -> (TempDir, Repository) { - panic!("unimplemented") -} - -pub fn repo_init() -> (TempDir, Repository) { - repo_init_typical() -} - -pub fn repo_init2(repo_type: RepoType) -> (TempDir, Repository) { +pub fn repo_init2(repo_type: RepoType) -> (TempDirs, Repository) { match repo_type { RepoType::Typical => repo_init_typical(), RepoType::Bare => repo_init_bare(), From 6d31b71fbce124410b176d42f372752ba3eae42f Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Wed, 5 Aug 2020 15:13:53 +0900 Subject: [PATCH 12/27] Implement multi-repo testing --- src/test.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/src/test.rs b/src/test.rs index 87a1be58f6..4adfd3240e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -55,15 +55,79 @@ pub fn repo_init_typical() -> (TempDirs, Repository) { } pub fn repo_init_bare() -> (TempDirs, Repository) { - panic!("unimplemented") + let td = TempDir::new().unwrap(); + let repo = Repository::init_bare(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(); + } + let tds = TempDirs { + main: td, + _rest: vec![], + }; + (tds, repo) } pub fn repo_init_bare_worktree() -> (TempDirs, Repository) { - panic!("unimplemented") + let td = TempDir::new().unwrap(); + let repo = Repository::init_bare(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(); + } + let worktree_td = TempDir::new().unwrap(); + std::fs::remove_dir(worktree_td.path()).unwrap(); // worktree will fail if the directory exists + let worktree = repo.worktree("worktree", worktree_td.path(), None).unwrap(); + let worktree_repo = worktree.to_repository().unwrap(); + + let tds = TempDirs { + main: worktree_td, + _rest: vec![td], + }; + (tds, worktree_repo) } pub fn repo_init_typical_worktree() -> (TempDirs, Repository) { - panic!("unimplemented") + 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(); + } + let worktree_td = TempDir::new().unwrap(); + std::fs::remove_dir(worktree_td.path()).unwrap(); // worktree will fail if the directory exists + let worktree = repo.worktree("worktree", worktree_td.path(), None).unwrap(); + let worktree_repo = worktree.to_repository().unwrap(); + + let tds = TempDirs { + main: worktree_td, + _rest: vec![td], + }; + (tds, worktree_repo) } pub fn repo_init() -> (TempDir, Repository) { From 8e17a984a45cae41955e6552198f77bdf09f248d Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Wed, 5 Aug 2020 16:02:08 +0900 Subject: [PATCH 13/27] Use repo.workdir() to get working directory --- src/blame.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blame.rs b/src/blame.rs index 55e8122c50..bd5ccd9771 100644 --- a/src/blame.rs +++ b/src/blame.rs @@ -298,7 +298,7 @@ mod tests { |repo: &Repository| { let mut index = repo.index().unwrap(); - let root = repo.path().parent().unwrap(); + let root = repo.workdir().unwrap(); fs::create_dir(&root.join("foo")).unwrap(); File::create(&root.join("foo/bar")).unwrap(); index.add_path(Path::new("foo/bar")).unwrap(); From b51bf5cdedf1189270fe941b7820fde7f41a9216 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Tue, 25 Aug 2020 13:09:18 +0900 Subject: [PATCH 14/27] Remove unnecessary and dangerous conversion for Cstring into *mut c_char --- src/call.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/call.rs b/src/call.rs index 78f3df6a09..f1ade4a8f3 100644 --- a/src/call.rs +++ b/src/call.rs @@ -93,12 +93,6 @@ mod impls { } } - impl Convert<*mut libc::c_char> for CString { - fn convert(&self) -> *mut libc::c_char { - self.as_ptr() as *mut libc::c_char - } - } - impl> Convert<*const T> for Option { fn convert(&self) -> *const T { self.as_ref().map(|s| s.convert()).unwrap_or(ptr::null()) From 7c820bce3f3b52fb8387021f84c74648bc60b92f Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Tue, 25 Aug 2020 14:21:49 +0900 Subject: [PATCH 15/27] Fix comments --- src/worktree.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/worktree.rs b/src/worktree.rs index 2eeeaa02b2..f50b7c9667 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -21,7 +21,7 @@ pub struct Worktree { // even shared among threads in a mutex unsafe impl Send for Worktree {} -/// Options which can be used to configure how a repository is initialized +/// Options which can be used to configure how a worktree is initialized pub struct WorktreeAddOptions<'a> { lock: bool, reference: Option>, @@ -57,7 +57,7 @@ impl Worktree { /// Retrieves the name of the worktree /// - /// This is the name that can be passed to repo::Repository::worktree_lookup + /// This is the name that can be passed to repo::Repository::find_worktree /// to reopen the worktree. This is also the name that would appear in the /// list returned by repo::Repository::worktrees pub fn name(&self) -> Option<&str> { From 42cd516ecf81e6cbca4c3a758e7a3f0b2b7777f9 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Tue, 25 Aug 2020 14:22:19 +0900 Subject: [PATCH 16/27] Remove println --- src/worktree.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/worktree.rs b/src/worktree.rs index f50b7c9667..a2d490f40f 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -113,9 +113,7 @@ impl Worktree { match try_call!(raw::git_worktree_is_locked(buf.raw(), self.raw)) { 0 => Ok(WorktreeLockStatus::Unlocked), _ => { - println!("Buf: {}", buf.as_str().unwrap()); let v = buf.to_vec(); - println!("Length of v: {}", v.len()); Ok(WorktreeLockStatus::Locked(match v.len() { 0 => None, _ => String::from_utf8(v).ok(), From a81f3ad1db46d207605e6a80a97418d30dc5e570 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Tue, 25 Aug 2020 14:43:51 +0900 Subject: [PATCH 17/27] Make Worktree::is_prunable return Result --- src/worktree.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/worktree.rs b/src/worktree.rs index a2d490f40f..2692b46d1f 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -135,14 +135,11 @@ impl Worktree { } /// Checks if the worktree is prunable - pub fn is_prunable(&self, opts: Option<&WorktreePruneOptions>) -> bool { + pub fn is_prunable(&self, opts: Option<&WorktreePruneOptions>) -> Result { unsafe { let mut opts = opts.map(|o| o.raw()); - if call!(raw::git_worktree_is_prunable(self.raw, opts.as_mut())) <= 0 { - false - } else { - true - } + let rv = try_call!(raw::git_worktree_is_prunable(self.raw, opts.as_mut())); + Ok(rv != 0) } } From fab731c45ed8fe4fce6efb3f336a3e0c46f6325c Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Sun, 1 Nov 2020 02:48:18 +0900 Subject: [PATCH 18/27] fixup! Remove init --- src/repo.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/repo.rs b/src/repo.rs index ca41487726..8cfc3c86e5 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -165,7 +165,6 @@ impl Repository { /// Attempt to open an already-existing repository from a worktree. pub fn open_from_worktree(worktree: &Worktree) -> Result { - crate::init(); let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_repository_open_from_worktree( From 966c34d66f9122bc313144f0bab7cd8104d81d36 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Sun, 1 Nov 2020 03:17:44 +0900 Subject: [PATCH 19/27] fixup! use try_call! --- src/worktree.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/worktree.rs b/src/worktree.rs index 2692b46d1f..f52aad6941 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -2,7 +2,7 @@ use crate::buf::Buf; use crate::reference::Reference; use crate::repo::Repository; use crate::util::{self, Binding}; -use crate::{call, raw, Error}; +use crate::{raw, Error}; use std::mem; use std::os::raw::c_int; use std::path::Path; @@ -84,7 +84,7 @@ impl Worktree { /// filesystem and that the metadata is correct pub fn validate(&self) -> Result<(), Error> { unsafe { - call::c_try(raw::git_worktree_validate(call::convert(&self.raw)))?; + try_call!(raw::git_worktree_validate(self.raw)); } Ok(()) } @@ -101,7 +101,7 @@ impl Worktree { /// Unlocks the worktree pub fn unlock(&self) -> Result<(), Error> { unsafe { - call::c_try(raw::git_worktree_unlock(call::convert(&self.raw)))?; + try_call!(raw::git_worktree_unlock(self.raw)); } Ok(()) } From 491efd4e8e617a2137080cdf9ebd018919117f02 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Sun, 1 Nov 2020 03:19:15 +0900 Subject: [PATCH 20/27] fixup! use unwrap since it's expected to always be utf-8 --- src/worktree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/worktree.rs b/src/worktree.rs index f52aad6941..108588b39d 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -116,7 +116,7 @@ impl Worktree { let v = buf.to_vec(); Ok(WorktreeLockStatus::Locked(match v.len() { 0 => None, - _ => String::from_utf8(v).ok(), + _ => Some(String::from_utf8(v).unwrap()), })) } } From 7ba981c0d97b3c48a519dbfad46f76db0355aea1 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Sun, 1 Nov 2020 03:23:05 +0900 Subject: [PATCH 21/27] fixup! Remove conversion aliases --- src/repo.rs | 9 --------- src/test.rs | 4 ++-- src/worktree.rs | 5 ----- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/repo.rs b/src/repo.rs index 8cfc3c86e5..c6dfba88cf 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -2829,15 +2829,6 @@ impl Repository { } } - /// Open a worktree of a the repository - /// - /// If a repository is not the main tree but a worktree, this - /// function will look up the worktree inside the parent - /// repository and create a new `git_worktree` structure. - pub fn to_worktree(&self) -> Result { - Worktree::open_from_repository(self) - } - /// Creates a new worktree for the repository pub fn worktree( &self, diff --git a/src/test.rs b/src/test.rs index 4adfd3240e..fd1696e418 100644 --- a/src/test.rs +++ b/src/test.rs @@ -94,7 +94,7 @@ pub fn repo_init_bare_worktree() -> (TempDirs, Repository) { let worktree_td = TempDir::new().unwrap(); std::fs::remove_dir(worktree_td.path()).unwrap(); // worktree will fail if the directory exists let worktree = repo.worktree("worktree", worktree_td.path(), None).unwrap(); - let worktree_repo = worktree.to_repository().unwrap(); + let worktree_repo = Repository::open_from_worktree(&worktree).unwrap(); let tds = TempDirs { main: worktree_td, @@ -121,7 +121,7 @@ pub fn repo_init_typical_worktree() -> (TempDirs, Repository) { let worktree_td = TempDir::new().unwrap(); std::fs::remove_dir(worktree_td.path()).unwrap(); // worktree will fail if the directory exists let worktree = repo.worktree("worktree", worktree_td.path(), None).unwrap(); - let worktree_repo = worktree.to_repository().unwrap(); + let worktree_repo = Repository::open_from_worktree(&worktree).unwrap(); let tds = TempDirs { main: worktree_td, diff --git a/src/worktree.rs b/src/worktree.rs index 108588b39d..e5268a7dca 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -142,11 +142,6 @@ impl Worktree { Ok(rv != 0) } } - - /// Opens the repository from the worktree - pub fn to_repository(&self) -> Result { - Repository::open_from_worktree(self) - } } impl<'a> WorktreeAddOptions<'a> { From 3327e21311bf181a0116670bae36823d9116df66 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Sun, 1 Nov 2020 03:45:10 +0900 Subject: [PATCH 22/27] fixup! Store raw git_worktree_add_options in WorktreeAddOptions --- src/repo.rs | 9 ++++----- src/worktree.rs | 50 ++++++++++++++++++++++++------------------------- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/repo.rs b/src/repo.rs index c6dfba88cf..113112e8a2 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -2830,24 +2830,23 @@ impl Repository { } /// Creates a new worktree for the repository - pub fn worktree( - &self, + pub fn worktree<'a>( + &'a self, name: &str, path: &Path, - opts: Option<&WorktreeAddOptions<'_>>, + opts: Option<&WorktreeAddOptions<'a>>, ) -> Result { let mut raw = ptr::null_mut(); let raw_name = CString::new(name)?; let raw_path = path.into_c_string()?; unsafe { - let opts = opts.map(|o| o.raw()); try_call!(raw::git_worktree_add( &mut raw, self.raw, raw_name, raw_path, - opts.as_ref() + opts.map(|o| o.raw()) )); Ok(Binding::from_raw(raw)) } diff --git a/src/worktree.rs b/src/worktree.rs index e5268a7dca..22412ccbc1 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -3,11 +3,11 @@ use crate::reference::Reference; use crate::repo::Repository; use crate::util::{self, Binding}; use crate::{raw, Error}; -use std::mem; use std::os::raw::c_int; use std::path::Path; use std::ptr; use std::str; +use std::{marker, mem}; /// An owned git worktree /// @@ -23,8 +23,8 @@ unsafe impl Send for Worktree {} /// Options which can be used to configure how a worktree is initialized pub struct WorktreeAddOptions<'a> { - lock: bool, - reference: Option>, + raw: raw::git_worktree_add_options, + _marker: marker::PhantomData>, } /// Options to configure how worktree pruning is performed @@ -149,39 +149,38 @@ impl<'a> WorktreeAddOptions<'a> { /// /// By default this will not lock the worktree pub fn new() -> WorktreeAddOptions<'a> { - WorktreeAddOptions { - lock: false, - reference: None, + unsafe { + let mut raw = mem::zeroed(); + assert_eq!( + raw::git_worktree_add_options_init(&mut raw, raw::GIT_WORKTREE_ADD_OPTIONS_VERSION), + 0 + ); + WorktreeAddOptions { + raw, + _marker: marker::PhantomData, + } } } /// If enabled, this will cause the newly added worktree to be locked pub fn lock(&mut self, enabled: bool) -> &mut WorktreeAddOptions<'a> { - self.lock = enabled; + self.raw.lock = enabled as c_int; self } /// reference to use for the new worktree HEAD - pub fn reference(&mut self, reference: Option>) -> &mut WorktreeAddOptions<'a> { - self.reference = reference; + pub fn reference(&mut self, reference: Option<&Reference<'a>>) -> &mut WorktreeAddOptions<'a> { + self.raw.reference = if let Some(reference) = reference { + reference.raw() + } else { + ptr::null_mut() + }; self } - /// Creates a set of raw add options to be used with `git_worktree_add` - /// - /// This method is unsafe as the returned value may have pointers to the - /// interior of this structure - pub unsafe fn raw(&self) -> raw::git_worktree_add_options { - let mut opts = mem::zeroed(); - assert_eq!( - raw::git_worktree_add_options_init(&mut opts, raw::GIT_WORKTREE_ADD_OPTIONS_VERSION), - 0 - ); - - opts.lock = self.lock as c_int; - opts.reference = crate::call::convert(&self.reference.as_ref().map(|o| o.raw())); - - opts + /// Get a set of raw add options to be used with `git_worktree_add` + pub fn raw(&self) -> *const raw::git_worktree_add_options { + &self.raw } } @@ -311,7 +310,8 @@ mod tests { let (wt_top, branch) = crate::test::worktrees_env_init(&repo); let wt_path = wt_top.path().join("test"); let mut opts = WorktreeAddOptions::new(); - opts.reference(Some(branch.into_reference())); + let reference = branch.into_reference(); + opts.reference(Some(&reference)); let wt = repo .worktree("test-worktree", &wt_path, Some(&opts)) From 79d44594a7607d9418cf7e6fea6882d5bee8db10 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Sun, 1 Nov 2020 03:57:38 +0900 Subject: [PATCH 23/27] fixup! Store raw git_worktree_prune_options in WorktreePruneOptions --- src/worktree.rs | 49 ++++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/src/worktree.rs b/src/worktree.rs index 22412ccbc1..1737267a3e 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -29,7 +29,7 @@ pub struct WorktreeAddOptions<'a> { /// Options to configure how worktree pruning is performed pub struct WorktreePruneOptions { - flags: u32, + raw: raw::git_worktree_prune_options, } /// Lock Status of a worktree @@ -124,21 +124,22 @@ impl Worktree { } /// Prunes the worktree - pub fn prune(&self, opts: Option<&WorktreePruneOptions>) -> Result<(), Error> { + pub fn prune(&self, opts: Option<&mut WorktreePruneOptions>) -> Result<(), Error> { // When successful the worktree should be removed however the backing structure // of the git_worktree should still be valid. unsafe { - let mut opts = opts.map(|o| o.raw()); - try_call!(raw::git_worktree_prune(self.raw, opts.as_mut())); + try_call!(raw::git_worktree_prune(self.raw, opts.map(|o| o.raw()))); } Ok(()) } /// Checks if the worktree is prunable - pub fn is_prunable(&self, opts: Option<&WorktreePruneOptions>) -> Result { + pub fn is_prunable(&self, opts: Option<&mut WorktreePruneOptions>) -> Result { unsafe { - let mut opts = opts.map(|o| o.raw()); - let rv = try_call!(raw::git_worktree_is_prunable(self.raw, opts.as_mut())); + let rv = try_call!(raw::git_worktree_is_prunable( + self.raw, + opts.map(|o| o.raw()) + )); Ok(rv != 0) } } @@ -190,7 +191,17 @@ impl WorktreePruneOptions { /// By defaults this will prune only worktrees that are no longer valid /// unlocked and not checked out pub fn new() -> WorktreePruneOptions { - WorktreePruneOptions { flags: 0 } + unsafe { + let mut raw = mem::zeroed(); + assert_eq!( + raw::git_worktree_prune_options_init( + &mut raw, + raw::GIT_WORKTREE_PRUNE_OPTIONS_VERSION + ), + 0 + ); + WorktreePruneOptions { raw } + } } /// Controls whether valid (still existing on the filesystem) worktrees @@ -217,28 +228,16 @@ impl WorktreePruneOptions { fn flag(&mut self, flag: raw::git_worktree_prune_t, on: bool) -> &mut WorktreePruneOptions { if on { - self.flags |= flag as u32; + self.raw.flags |= flag as u32; } else { - self.flags &= !(flag as u32); + self.raw.flags &= !(flag as u32); } self } - /// Creates a set of raw prune options to be used with `git_worktree_prune` - /// - /// This method is unsafe as the returned value may have pointers to the - /// interior of this structure - pub unsafe fn raw(&self) -> raw::git_worktree_prune_options { - let mut opts = mem::zeroed(); - assert_eq!( - raw::git_worktree_prune_options_init( - &mut opts, - raw::GIT_WORKTREE_PRUNE_OPTIONS_VERSION - ), - 0 - ); - opts.flags = self.flags; - opts + /// Get a set of raw prune options to be used with `git_worktree_prune` + pub fn raw(&mut self) -> *mut raw::git_worktree_prune_options { + &mut self.raw } } From dd529ae7ef789f5e25e9cdde4762f9c79c83ca58 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Thu, 5 Nov 2020 11:48:09 +0900 Subject: [PATCH 24/27] fixup! Remove repo_test! macros --- src/blame.rs | 70 ++++++++++++-------------- src/branch.rs | 5 +- src/test.rs | 130 ------------------------------------------------ src/worktree.rs | 61 ++++++++++++----------- 4 files changed, 69 insertions(+), 197 deletions(-) diff --git a/src/blame.rs b/src/blame.rs index bd5ccd9771..b45dc258c6 100644 --- a/src/blame.rs +++ b/src/blame.rs @@ -290,41 +290,37 @@ mod tests { use std::fs::{self, File}; use std::path::Path; - use crate::Repository; - - repo_test!( - smoke, - (Typical, TypicalWorktree, BareWorktree), - |repo: &Repository| { - let mut index = repo.index().unwrap(); - - let root = repo.workdir().unwrap(); - fs::create_dir(&root.join("foo")).unwrap(); - File::create(&root.join("foo/bar")).unwrap(); - index.add_path(Path::new("foo/bar")).unwrap(); - - let id = index.write_tree().unwrap(); - let tree = repo.find_tree(id).unwrap(); - let sig = repo.signature().unwrap(); - let id = repo.refname_to_id("HEAD").unwrap(); - let parent = repo.find_commit(id).unwrap(); - let commit = repo - .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent]) - .unwrap(); - - let blame = repo.blame_file(Path::new("foo/bar"), None).unwrap(); - - assert_eq!(blame.len(), 1); - assert_eq!(blame.iter().count(), 1); - - let hunk = blame.get_index(0).unwrap(); - assert_eq!(hunk.final_commit_id(), commit); - assert_eq!(hunk.final_signature().name(), sig.name()); - assert_eq!(hunk.final_signature().email(), sig.email()); - assert_eq!(hunk.final_start_line(), 1); - assert_eq!(hunk.path(), Some(Path::new("foo/bar"))); - assert_eq!(hunk.lines_in_hunk(), 0); - assert!(!hunk.is_boundary()) - } - ); + #[test] + fn smoke() { + let (_td, repo) = crate::test::repo_init(); + let mut index = repo.index().unwrap(); + + let root = repo.workdir().unwrap(); + fs::create_dir(&root.join("foo")).unwrap(); + File::create(&root.join("foo/bar")).unwrap(); + index.add_path(Path::new("foo/bar")).unwrap(); + + let id = index.write_tree().unwrap(); + let tree = repo.find_tree(id).unwrap(); + let sig = repo.signature().unwrap(); + let id = repo.refname_to_id("HEAD").unwrap(); + let parent = repo.find_commit(id).unwrap(); + let commit = repo + .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent]) + .unwrap(); + + let blame = repo.blame_file(Path::new("foo/bar"), None).unwrap(); + + assert_eq!(blame.len(), 1); + assert_eq!(blame.iter().count(), 1); + + let hunk = blame.get_index(0).unwrap(); + assert_eq!(hunk.final_commit_id(), commit); + assert_eq!(hunk.final_signature().name(), sig.name()); + assert_eq!(hunk.final_signature().email(), sig.email()); + assert_eq!(hunk.final_start_line(), 1); + assert_eq!(hunk.path(), Some(Path::new("foo/bar"))); + assert_eq!(hunk.lines_in_hunk(), 0); + assert!(!hunk.is_boundary()) + } } diff --git a/src/branch.rs b/src/branch.rs index 0c85972cf6..276bc534ac 100644 --- a/src/branch.rs +++ b/src/branch.rs @@ -153,7 +153,8 @@ impl<'repo> Drop for Branches<'repo> { mod tests { use crate::BranchType; - repo_test!(smoke, (Typical, TypicalWorktree, BareWorktree), |_| { + #[test] + fn smoke() { let (_td, repo) = crate::test::repo_init(); let head = repo.head().unwrap(); let target = head.target().unwrap(); @@ -173,5 +174,5 @@ mod tests { b1.set_upstream(None).unwrap(); b1.delete().unwrap(); - }); + } } diff --git a/src/test.rs b/src/test.rs index fd1696e418..89a13beafe 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,6 +1,5 @@ use std::fs::File; use std::io; -use std::ops::Deref; use std::path::{Path, PathBuf}; #[cfg(unix)] use std::ptr; @@ -18,118 +17,6 @@ macro_rules! t { }; } -// `repo_test! will -macro_rules! repo_test { - ($test_name:ident, ($($repo_type:ident),+), $test_body:expr) => { - paste::item! { - $(#[test] - fn [<$test_name _ $repo_type:snake>]() { - #[allow(unused_variables)] - let (td, repo) = $crate::test::repo_init2($crate::test::RepoType::$repo_type); - ($test_body)(&repo); - })+ - } - } -} - -pub struct TempDirs { - main: TempDir, - _rest: Vec, -} - -impl Deref for TempDirs { - type Target = TempDir; - - fn deref(&self) -> &Self::Target { - &self.main - } -} - -pub fn repo_init_typical() -> (TempDirs, Repository) { - let (td, repo) = repo_init(); - let tds = TempDirs { - main: td, - _rest: vec![], - }; - (tds, repo) -} - -pub fn repo_init_bare() -> (TempDirs, Repository) { - let td = TempDir::new().unwrap(); - let repo = Repository::init_bare(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(); - } - let tds = TempDirs { - main: td, - _rest: vec![], - }; - (tds, repo) -} - -pub fn repo_init_bare_worktree() -> (TempDirs, Repository) { - let td = TempDir::new().unwrap(); - let repo = Repository::init_bare(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(); - } - let worktree_td = TempDir::new().unwrap(); - std::fs::remove_dir(worktree_td.path()).unwrap(); // worktree will fail if the directory exists - let worktree = repo.worktree("worktree", worktree_td.path(), None).unwrap(); - let worktree_repo = Repository::open_from_worktree(&worktree).unwrap(); - - let tds = TempDirs { - main: worktree_td, - _rest: vec![td], - }; - (tds, worktree_repo) -} - -pub fn repo_init_typical_worktree() -> (TempDirs, 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(); - } - let worktree_td = TempDir::new().unwrap(); - std::fs::remove_dir(worktree_td.path()).unwrap(); // worktree will fail if the directory exists - let worktree = repo.worktree("worktree", worktree_td.path(), None).unwrap(); - let worktree_repo = Repository::open_from_worktree(&worktree).unwrap(); - - let tds = TempDirs { - main: worktree_td, - _rest: vec![td], - }; - (tds, worktree_repo) -} - pub fn repo_init() -> (TempDir, Repository) { let td = TempDir::new().unwrap(); let mut opts = RepositoryInitOptions::new(); @@ -150,15 +37,6 @@ pub fn repo_init() -> (TempDir, Repository) { (td, repo) } -pub fn repo_init2(repo_type: RepoType) -> (TempDirs, Repository) { - match repo_type { - RepoType::Typical => repo_init_typical(), - RepoType::Bare => repo_init_bare(), - RepoType::BareWorktree => repo_init_bare_worktree(), - RepoType::TypicalWorktree => repo_init_typical_worktree(), - } -} - pub fn commit(repo: &Repository) -> (Oid, Oid) { let mut index = t!(repo.index()); let root = repo.path().parent().unwrap(); @@ -186,14 +64,6 @@ pub fn worktrees_env_init(repo: &Repository) -> (TempDir, Branch<'_>) { (wtdir, branch) } -#[derive(Debug, Clone, Copy)] -pub enum RepoType { - Typical, - TypicalWorktree, - Bare, - BareWorktree, -} - #[cfg(windows)] pub fn realpath(original: &Path) -> io::Result { Ok(original.to_path_buf()) diff --git a/src/worktree.rs b/src/worktree.rs index 1737267a3e..492d837d73 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -259,12 +259,15 @@ impl Drop for Worktree { #[cfg(test)] mod tests { - use crate::Repository; use crate::WorktreeAddOptions; use crate::WorktreeLockStatus; + use tempfile::TempDir; - repo_test!(smoke_add_no_ref, (Typical, Bare), |repo: &Repository| { + #[test] + fn smoke_add_no_ref() { + let (_td, repo) = crate::test::repo_init(); + let wtdir = TempDir::new().unwrap(); let wt_path = wtdir.path().join("tree-no-ref-dir"); let opts = WorktreeAddOptions::new(); @@ -277,9 +280,12 @@ mod tests { ); let status = wt.is_locked().unwrap(); assert_eq!(status, WorktreeLockStatus::Unlocked); - }); + } + + #[test] + fn smoke_add_locked() { + let (_td, repo) = crate::test::repo_init(); - repo_test!(smoke_add_locked, (Typical, Bare), |repo: &Repository| { let wtdir = TempDir::new().unwrap(); let wt_path = wtdir.path().join("locked-tree"); let mut opts = WorktreeAddOptions::new(); @@ -300,28 +306,27 @@ mod tests { wt.is_locked().unwrap(), WorktreeLockStatus::Locked(Some("my reason".to_string())) ); - }); - - repo_test!( - smoke_add_from_branch, - (Typical, Bare), - |repo: &Repository| { - let (wt_top, branch) = crate::test::worktrees_env_init(&repo); - let wt_path = wt_top.path().join("test"); - let mut opts = WorktreeAddOptions::new(); - let reference = branch.into_reference(); - opts.reference(Some(&reference)); - - let wt = repo - .worktree("test-worktree", &wt_path, Some(&opts)) - .unwrap(); - assert_eq!(wt.name(), Some("test-worktree")); - assert_eq!( - wt.path().canonicalize().unwrap(), - wt_path.canonicalize().unwrap() - ); - let status = wt.is_locked().unwrap(); - assert_eq!(status, WorktreeLockStatus::Unlocked); - } - ); + } + + #[test] + fn smoke_add_from_branch() { + let (_td, repo) = crate::test::repo_init(); + + let (wt_top, branch) = crate::test::worktrees_env_init(&repo); + let wt_path = wt_top.path().join("test"); + let mut opts = WorktreeAddOptions::new(); + let reference = branch.into_reference(); + opts.reference(Some(&reference)); + + let wt = repo + .worktree("test-worktree", &wt_path, Some(&opts)) + .unwrap(); + assert_eq!(wt.name(), Some("test-worktree")); + assert_eq!( + wt.path().canonicalize().unwrap(), + wt_path.canonicalize().unwrap() + ); + let status = wt.is_locked().unwrap(); + assert_eq!(status, WorktreeLockStatus::Unlocked); + } } From f73d6a2704f2685e58999f1d7c7d06f558ea48fc Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Thu, 5 Nov 2020 12:04:39 +0900 Subject: [PATCH 25/27] fixup! Make sure `Reference` outlive `WorktreeAddOptions` --- src/worktree.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/worktree.rs b/src/worktree.rs index 492d837d73..b83fe454c8 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -170,7 +170,10 @@ impl<'a> WorktreeAddOptions<'a> { } /// reference to use for the new worktree HEAD - pub fn reference(&mut self, reference: Option<&Reference<'a>>) -> &mut WorktreeAddOptions<'a> { + pub fn reference( + &mut self, + reference: Option<&'a Reference<'_>>, + ) -> &mut WorktreeAddOptions<'a> { self.raw.reference = if let Some(reference) = reference { reference.raw() } else { From 858a446f24325f91496c8afc0ae899a8b63c5600 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Tue, 17 Nov 2020 11:38:25 +0900 Subject: [PATCH 26/27] fixup! Remove unnecessary `unsafe impl Send` --- src/worktree.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/worktree.rs b/src/worktree.rs index b83fe454c8..3cb75c6edf 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -17,10 +17,6 @@ pub struct Worktree { raw: *mut raw::git_worktree, } -// It is the current belief that a `Worktree` can be sent among threads, or -// even shared among threads in a mutex -unsafe impl Send for Worktree {} - /// Options which can be used to configure how a worktree is initialized pub struct WorktreeAddOptions<'a> { raw: raw::git_worktree_add_options, From d0418f70e8df5cf709029682e7e06a4dba953bc4 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Tue, 17 Nov 2020 11:39:37 +0900 Subject: [PATCH 27/27] fixup! Don't skip git_transfer_progress --- systest/build.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/systest/build.rs b/systest/build.rs index 64942afc64..eba93f3090 100644 --- a/systest/build.rs +++ b/systest/build.rs @@ -43,6 +43,5 @@ fn main() { cfg.skip_roundtrip(|t| t == "git_clone_options" || t == "git_submodule_update_options"); cfg.skip_type(|t| t == "__enum_ty"); - cfg.skip_type(|t| t == "git_transfer_progress"); cfg.generate("../libgit2-sys/lib.rs", "all.rs"); }