summaryrefslogtreecommitdiffstats
path: root/vendor/git2/src/worktree.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/git2/src/worktree.rs')
-rw-r--r--vendor/git2/src/worktree.rs331
1 files changed, 331 insertions, 0 deletions
diff --git a/vendor/git2/src/worktree.rs b/vendor/git2/src/worktree.rs
new file mode 100644
index 0000000..569b639
--- /dev/null
+++ b/vendor/git2/src/worktree.rs
@@ -0,0 +1,331 @@
+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};
+
+/// An owned git worktree
+///
+/// This structure corresponds to a `git_worktree` in libgit2.
+//
+pub struct Worktree {
+ raw: *mut raw::git_worktree,
+}
+
+/// Options which can be used to configure how a worktree is initialized
+pub struct WorktreeAddOptions<'a> {
+ raw: raw::git_worktree_add_options,
+ _marker: marker::PhantomData<Reference<'a>>,
+}
+
+/// Options to configure how worktree pruning is performed
+pub struct WorktreePruneOptions {
+ raw: raw::git_worktree_prune_options,
+}
+
+/// Lock Status of a worktree
+#[derive(PartialEq, Debug)]
+pub enum WorktreeLockStatus {
+ /// Worktree is Unlocked
+ Unlocked,
+ /// Worktree is locked with the optional message
+ Locked(Option<String>),
+}
+
+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<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))
+ }
+ }
+
+ /// Retrieves the name of the worktree
+ ///
+ /// 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> {
+ 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 {
+ try_call!(raw::git_worktree_validate(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 {
+ try_call!(raw::git_worktree_unlock(self.raw));
+ }
+ Ok(())
+ }
+
+ /// Checks if worktree is locked
+ 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()),
+ }))
+ }
+ }
+ }
+ }
+
+ /// Prunes the worktree
+ 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 {
+ 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<&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> {
+ /// Creates a default set of add options.
+ ///
+ /// By default this will not lock the worktree
+ 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,
+ }
+ }
+ }
+
+ /// If enabled, this will cause the newly added worktree to be locked
+ pub fn lock(&mut self, enabled: bool) -> &mut WorktreeAddOptions<'a> {
+ self.raw.lock = enabled as c_int;
+ self
+ }
+
+ /// reference to use for the new worktree HEAD
+ 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
+ }
+
+ /// 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
+ }
+}
+
+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 {
+ 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
+ /// 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 filesystem 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.raw.flags |= flag as u32;
+ } else {
+ self.raw.flags &= !(flag as u32);
+ }
+ self
+ }
+
+ /// 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
+ }
+}
+
+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();
+ // 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 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);
+ }
+}