1use crate::buf::Buf;
2use crate::reference::Reference;
3use crate::repo::Repository;
4use crate::util::{self, Binding};
5use crate::{raw, Error};
6use std::os::raw::c_int;
7use std::path::Path;
8use std::ptr;
9use std::str;
10use std::{marker, mem};
11
12pub struct Worktree {
17 raw: *mut raw::git_worktree,
18}
19
20pub struct WorktreeAddOptions<'a> {
22 raw: raw::git_worktree_add_options,
23 _marker: marker::PhantomData<Reference<'a>>,
24}
25
26pub struct WorktreePruneOptions {
28 raw: raw::git_worktree_prune_options,
29}
30
31#[derive(PartialEq, Debug)]
33pub enum WorktreeLockStatus {
34 Unlocked,
36 Locked(Option<String>),
38}
39
40impl Worktree {
41 pub fn open_from_repository(repo: &Repository) -> Result<Worktree, Error> {
47 let mut raw = ptr::null_mut();
48 unsafe {
49 try_call!(raw::git_worktree_open_from_repository(&mut raw, repo.raw()));
50 Ok(Binding::from_raw(raw))
51 }
52 }
53
54 pub fn name(&self) -> Option<&str> {
60 unsafe {
61 crate::opt_bytes(self, raw::git_worktree_name(self.raw))
62 .and_then(|s| str::from_utf8(s).ok())
63 }
64 }
65
66 pub fn path(&self) -> &Path {
72 unsafe {
73 util::bytes2path(crate::opt_bytes(self, raw::git_worktree_path(self.raw)).unwrap())
74 }
75 }
76
77 pub fn validate(&self) -> Result<(), Error> {
82 unsafe {
83 try_call!(raw::git_worktree_validate(self.raw));
84 }
85 Ok(())
86 }
87
88 pub fn lock(&self, reason: Option<&str>) -> Result<(), Error> {
90 let reason = crate::opt_cstr(reason)?;
91 unsafe {
92 try_call!(raw::git_worktree_lock(self.raw, reason));
93 }
94 Ok(())
95 }
96
97 pub fn unlock(&self) -> Result<(), Error> {
99 unsafe {
100 try_call!(raw::git_worktree_unlock(self.raw));
101 }
102 Ok(())
103 }
104
105 pub fn is_locked(&self) -> Result<WorktreeLockStatus, Error> {
107 let buf = Buf::new();
108 unsafe {
109 match try_call!(raw::git_worktree_is_locked(buf.raw(), self.raw)) {
110 0 => Ok(WorktreeLockStatus::Unlocked),
111 _ => {
112 let v = buf.to_vec();
113 Ok(WorktreeLockStatus::Locked(match v.len() {
114 0 => None,
115 _ => Some(String::from_utf8(v).unwrap()),
116 }))
117 }
118 }
119 }
120 }
121
122 pub fn prune(&self, opts: Option<&mut WorktreePruneOptions>) -> Result<(), Error> {
124 unsafe {
127 try_call!(raw::git_worktree_prune(self.raw, opts.map(|o| o.raw())));
128 }
129 Ok(())
130 }
131
132 pub fn is_prunable(&self, opts: Option<&mut WorktreePruneOptions>) -> Result<bool, Error> {
134 unsafe {
135 let rv = try_call!(raw::git_worktree_is_prunable(
136 self.raw,
137 opts.map(|o| o.raw())
138 ));
139 Ok(rv != 0)
140 }
141 }
142}
143
144impl<'a> WorktreeAddOptions<'a> {
145 pub fn new() -> WorktreeAddOptions<'a> {
149 unsafe {
150 let mut raw = mem::zeroed();
151 assert_eq!(
152 raw::git_worktree_add_options_init(&mut raw, raw::GIT_WORKTREE_ADD_OPTIONS_VERSION),
153 0
154 );
155 WorktreeAddOptions {
156 raw,
157 _marker: marker::PhantomData,
158 }
159 }
160 }
161
162 pub fn lock(&mut self, enabled: bool) -> &mut WorktreeAddOptions<'a> {
164 self.raw.lock = enabled as c_int;
165 self
166 }
167
168 pub fn checkout_existing(&mut self, enabled: bool) -> &mut WorktreeAddOptions<'a> {
170 self.raw.checkout_existing = enabled as c_int;
171 self
172 }
173
174 pub fn reference(
176 &mut self,
177 reference: Option<&'a Reference<'_>>,
178 ) -> &mut WorktreeAddOptions<'a> {
179 self.raw.reference = if let Some(reference) = reference {
180 reference.raw()
181 } else {
182 ptr::null_mut()
183 };
184 self
185 }
186
187 pub fn raw(&self) -> *const raw::git_worktree_add_options {
189 &self.raw
190 }
191}
192
193impl WorktreePruneOptions {
194 pub fn new() -> WorktreePruneOptions {
199 unsafe {
200 let mut raw = mem::zeroed();
201 assert_eq!(
202 raw::git_worktree_prune_options_init(
203 &mut raw,
204 raw::GIT_WORKTREE_PRUNE_OPTIONS_VERSION
205 ),
206 0
207 );
208 WorktreePruneOptions { raw }
209 }
210 }
211
212 pub fn valid(&mut self, valid: bool) -> &mut WorktreePruneOptions {
217 self.flag(raw::GIT_WORKTREE_PRUNE_VALID, valid)
218 }
219
220 pub fn locked(&mut self, locked: bool) -> &mut WorktreePruneOptions {
224 self.flag(raw::GIT_WORKTREE_PRUNE_LOCKED, locked)
225 }
226
227 pub fn working_tree(&mut self, working_tree: bool) -> &mut WorktreePruneOptions {
231 self.flag(raw::GIT_WORKTREE_PRUNE_WORKING_TREE, working_tree)
232 }
233
234 fn flag(&mut self, flag: raw::git_worktree_prune_t, on: bool) -> &mut WorktreePruneOptions {
235 if on {
236 self.raw.flags |= flag as u32;
237 } else {
238 self.raw.flags &= !(flag as u32);
239 }
240 self
241 }
242
243 pub fn raw(&mut self) -> *mut raw::git_worktree_prune_options {
245 &mut self.raw
246 }
247}
248
249impl Binding for Worktree {
250 type Raw = *mut raw::git_worktree;
251 unsafe fn from_raw(ptr: *mut raw::git_worktree) -> Worktree {
252 Worktree { raw: ptr }
253 }
254 fn raw(&self) -> *mut raw::git_worktree {
255 self.raw
256 }
257}
258
259impl Drop for Worktree {
260 fn drop(&mut self) {
261 unsafe { raw::git_worktree_free(self.raw) }
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use crate::WorktreeAddOptions;
268 use crate::WorktreeLockStatus;
269
270 use tempfile::TempDir;
271
272 #[test]
273 fn smoke_add_no_ref() {
274 let (_td, repo) = crate::test::repo_init();
275
276 let wtdir = TempDir::new().unwrap();
277 let wt_path = wtdir.path().join("tree-no-ref-dir");
278 let opts = WorktreeAddOptions::new();
279
280 let wt = repo.worktree("tree-no-ref", &wt_path, Some(&opts)).unwrap();
281 assert_eq!(wt.name(), Some("tree-no-ref"));
282 assert_eq!(
283 wt.path().canonicalize().unwrap(),
284 wt_path.canonicalize().unwrap()
285 );
286 let status = wt.is_locked().unwrap();
287 assert_eq!(status, WorktreeLockStatus::Unlocked);
288 }
289
290 #[test]
291 fn smoke_add_locked() {
292 let (_td, repo) = crate::test::repo_init();
293
294 let wtdir = TempDir::new().unwrap();
295 let wt_path = wtdir.path().join("locked-tree");
296 let mut opts = WorktreeAddOptions::new();
297 opts.lock(true);
298
299 let wt = repo.worktree("locked-tree", &wt_path, Some(&opts)).unwrap();
300 assert!(wt.lock(Some("my reason")).is_err());
302 assert_eq!(wt.name(), Some("locked-tree"));
303 assert_eq!(
304 wt.path().canonicalize().unwrap(),
305 wt_path.canonicalize().unwrap()
306 );
307 assert_eq!(wt.is_locked().unwrap(), WorktreeLockStatus::Locked(None));
308 assert!(wt.unlock().is_ok());
309 assert!(wt.lock(Some("my reason")).is_ok());
310 assert_eq!(
311 wt.is_locked().unwrap(),
312 WorktreeLockStatus::Locked(Some("my reason".to_string()))
313 );
314 }
315
316 #[test]
317 fn smoke_add_from_branch() {
318 let (_td, repo) = crate::test::repo_init();
319
320 let (wt_top, branch) = crate::test::worktrees_env_init(&repo);
321 let wt_path = wt_top.path().join("test");
322 let mut opts = WorktreeAddOptions::new();
323 let reference = branch.into_reference();
324 opts.reference(Some(&reference));
325
326 let wt = repo
327 .worktree("test-worktree", &wt_path, Some(&opts))
328 .unwrap();
329 assert_eq!(wt.name(), Some("test-worktree"));
330 assert_eq!(
331 wt.path().canonicalize().unwrap(),
332 wt_path.canonicalize().unwrap()
333 );
334 let status = wt.is_locked().unwrap();
335 assert_eq!(status, WorktreeLockStatus::Unlocked);
336 }
337}