use crate::buf::Buf;
use crate::reference::Reference;
use crate::repo::Repository;
use crate::util::{self, Binding};
use crate::{raw, Error};
use std::os::raw::c_int;
use std::path::Path;
use std::ptr;
use std::str;
use std::{marker, mem};
pub struct Worktree {
raw: *mut raw::git_worktree,
}
pub struct WorktreeAddOptions<'a> {
raw: raw::git_worktree_add_options,
_marker: marker::PhantomData<Reference<'a>>,
}
pub struct WorktreePruneOptions {
raw: raw::git_worktree_prune_options,
}
#[derive(PartialEq, Debug)]
pub enum WorktreeLockStatus {
Unlocked,
Locked(Option<String>),
}
impl Worktree {
pub fn open_from_repository(repo: &Repository) -> Result<Worktree, Error> {
let mut raw = ptr::null_mut();
unsafe {
try_call!(raw::git_worktree_open_from_repository(&mut raw, repo.raw()));
Ok(Binding::from_raw(raw))
}
}
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())
}
}
pub fn path(&self) -> &Path {
unsafe {
util::bytes2path(crate::opt_bytes(self, raw::git_worktree_path(self.raw)).unwrap())
}
}
pub fn validate(&self) -> Result<(), Error> {
unsafe {
try_call!(raw::git_worktree_validate(self.raw));
}
Ok(())
}
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(())
}
pub fn unlock(&self) -> Result<(), Error> {
unsafe {
try_call!(raw::git_worktree_unlock(self.raw));
}
Ok(())
}
pub fn is_locked(&self) -> Result<WorktreeLockStatus, Error> {
let buf = Buf::new();
unsafe {
match try_call!(raw::git_worktree_is_locked(buf.raw(), self.raw)) {
0 => Ok(WorktreeLockStatus::Unlocked),
_ => {
let v = buf.to_vec();
Ok(WorktreeLockStatus::Locked(match v.len() {
0 => None,
_ => Some(String::from_utf8(v).unwrap()),
}))
}
}
}
}
pub fn prune(&self, opts: Option<&mut WorktreePruneOptions>) -> Result<(), Error> {
unsafe {
try_call!(raw::git_worktree_prune(self.raw, opts.map(|o| o.raw())));
}
Ok(())
}
pub fn is_prunable(&self, opts: Option<&mut WorktreePruneOptions>) -> Result<bool, Error> {
unsafe {
let rv = try_call!(raw::git_worktree_is_prunable(
self.raw,
opts.map(|o| o.raw())
));
Ok(rv != 0)
}
}
}
impl<'a> WorktreeAddOptions<'a> {
pub fn new() -> WorktreeAddOptions<'a> {
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,
}
}
}
pub fn lock(&mut self, enabled: bool) -> &mut WorktreeAddOptions<'a> {
self.raw.lock = enabled as c_int;
self
}
pub fn reference(
&mut self,
reference: Option<&'a Reference<'_>>,
) -> &mut WorktreeAddOptions<'a> {
self.raw.reference = if let Some(reference) = reference {
reference.raw()
} else {
ptr::null_mut()
};
self
}
pub fn raw(&self) -> *const raw::git_worktree_add_options {
&self.raw
}
}
impl WorktreePruneOptions {
pub fn new() -> WorktreePruneOptions {
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 }
}
}
pub fn valid(&mut self, valid: bool) -> &mut WorktreePruneOptions {
self.flag(raw::GIT_WORKTREE_PRUNE_VALID, valid)
}
pub fn locked(&mut self, locked: bool) -> &mut WorktreePruneOptions {
self.flag(raw::GIT_WORKTREE_PRUNE_LOCKED, locked)
}
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.raw.flags |= flag as u32;
} else {
self.raw.flags &= !(flag as u32);
}
self
}
pub fn raw(&mut self) -> *mut raw::git_worktree_prune_options {
&mut self.raw
}
}
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();
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(),
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();
opts.lock(true);
let wt = repo.worktree("locked-tree", &wt_path, Some(&opts)).unwrap();
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 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);
}
}