]> git.proxmox.com Git - rustc.git/blob - vendor/git2/src/worktree.rs
New upstream version 1.70.0+dfsg2
[rustc.git] / vendor / git2 / src / worktree.rs
1 use crate::buf::Buf;
2 use crate::reference::Reference;
3 use crate::repo::Repository;
4 use crate::util::{self, Binding};
5 use crate::{raw, Error};
6 use std::os::raw::c_int;
7 use std::path::Path;
8 use std::ptr;
9 use std::str;
10 use std::{marker, mem};
11
12 /// An owned git worktree
13 ///
14 /// This structure corresponds to a `git_worktree` in libgit2.
15 //
16 pub struct Worktree {
17 raw: *mut raw::git_worktree,
18 }
19
20 /// Options which can be used to configure how a worktree is initialized
21 pub struct WorktreeAddOptions<'a> {
22 raw: raw::git_worktree_add_options,
23 _marker: marker::PhantomData<Reference<'a>>,
24 }
25
26 /// Options to configure how worktree pruning is performed
27 pub struct WorktreePruneOptions {
28 raw: raw::git_worktree_prune_options,
29 }
30
31 /// Lock Status of a worktree
32 #[derive(PartialEq, Debug)]
33 pub enum WorktreeLockStatus {
34 /// Worktree is Unlocked
35 Unlocked,
36 /// Worktree is locked with the optional message
37 Locked(Option<String>),
38 }
39
40 impl Worktree {
41 /// Open a worktree of a the repository
42 ///
43 /// If a repository is not the main tree but a worktree, this
44 /// function will look up the worktree inside the parent
45 /// repository and create a new `git_worktree` structure.
46 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 /// Retrieves the name of the worktree
55 ///
56 /// This is the name that can be passed to repo::Repository::find_worktree
57 /// to reopen the worktree. This is also the name that would appear in the
58 /// list returned by repo::Repository::worktrees
59 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 /// Retrieves the path to the worktree
67 ///
68 /// This is the path to the top-level of the source and not the path to the
69 /// .git file within the worktree. This path can be passed to
70 /// repo::Repository::open.
71 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 /// Validates the worktree
78 ///
79 /// This checks that it still exists on the
80 /// filesystem and that the metadata is correct
81 pub fn validate(&self) -> Result<(), Error> {
82 unsafe {
83 try_call!(raw::git_worktree_validate(self.raw));
84 }
85 Ok(())
86 }
87
88 /// Locks the worktree
89 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 /// Unlocks the worktree
98 pub fn unlock(&self) -> Result<(), Error> {
99 unsafe {
100 try_call!(raw::git_worktree_unlock(self.raw));
101 }
102 Ok(())
103 }
104
105 /// Checks if worktree is locked
106 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 /// Prunes the worktree
123 pub fn prune(&self, opts: Option<&mut WorktreePruneOptions>) -> Result<(), Error> {
124 // When successful the worktree should be removed however the backing structure
125 // of the git_worktree should still be valid.
126 unsafe {
127 try_call!(raw::git_worktree_prune(self.raw, opts.map(|o| o.raw())));
128 }
129 Ok(())
130 }
131
132 /// Checks if the worktree is prunable
133 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
144 impl<'a> WorktreeAddOptions<'a> {
145 /// Creates a default set of add options.
146 ///
147 /// By default this will not lock the worktree
148 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 /// If enabled, this will cause the newly added worktree to be locked
163 pub fn lock(&mut self, enabled: bool) -> &mut WorktreeAddOptions<'a> {
164 self.raw.lock = enabled as c_int;
165 self
166 }
167
168 /// reference to use for the new worktree HEAD
169 pub fn reference(
170 &mut self,
171 reference: Option<&'a Reference<'_>>,
172 ) -> &mut WorktreeAddOptions<'a> {
173 self.raw.reference = if let Some(reference) = reference {
174 reference.raw()
175 } else {
176 ptr::null_mut()
177 };
178 self
179 }
180
181 /// Get a set of raw add options to be used with `git_worktree_add`
182 pub fn raw(&self) -> *const raw::git_worktree_add_options {
183 &self.raw
184 }
185 }
186
187 impl WorktreePruneOptions {
188 /// Creates a default set of pruning options
189 ///
190 /// By defaults this will prune only worktrees that are no longer valid
191 /// unlocked and not checked out
192 pub fn new() -> WorktreePruneOptions {
193 unsafe {
194 let mut raw = mem::zeroed();
195 assert_eq!(
196 raw::git_worktree_prune_options_init(
197 &mut raw,
198 raw::GIT_WORKTREE_PRUNE_OPTIONS_VERSION
199 ),
200 0
201 );
202 WorktreePruneOptions { raw }
203 }
204 }
205
206 /// Controls whether valid (still existing on the filesystem) worktrees
207 /// will be pruned
208 ///
209 /// Defaults to false
210 pub fn valid(&mut self, valid: bool) -> &mut WorktreePruneOptions {
211 self.flag(raw::GIT_WORKTREE_PRUNE_VALID, valid)
212 }
213
214 /// Controls whether locked worktrees will be pruned
215 ///
216 /// Defaults to false
217 pub fn locked(&mut self, locked: bool) -> &mut WorktreePruneOptions {
218 self.flag(raw::GIT_WORKTREE_PRUNE_LOCKED, locked)
219 }
220
221 /// Controls whether the actual working tree on the filesystem is recursively removed
222 ///
223 /// Defaults to false
224 pub fn working_tree(&mut self, working_tree: bool) -> &mut WorktreePruneOptions {
225 self.flag(raw::GIT_WORKTREE_PRUNE_WORKING_TREE, working_tree)
226 }
227
228 fn flag(&mut self, flag: raw::git_worktree_prune_t, on: bool) -> &mut WorktreePruneOptions {
229 if on {
230 self.raw.flags |= flag as u32;
231 } else {
232 self.raw.flags &= !(flag as u32);
233 }
234 self
235 }
236
237 /// Get a set of raw prune options to be used with `git_worktree_prune`
238 pub fn raw(&mut self) -> *mut raw::git_worktree_prune_options {
239 &mut self.raw
240 }
241 }
242
243 impl Binding for Worktree {
244 type Raw = *mut raw::git_worktree;
245 unsafe fn from_raw(ptr: *mut raw::git_worktree) -> Worktree {
246 Worktree { raw: ptr }
247 }
248 fn raw(&self) -> *mut raw::git_worktree {
249 self.raw
250 }
251 }
252
253 impl Drop for Worktree {
254 fn drop(&mut self) {
255 unsafe { raw::git_worktree_free(self.raw) }
256 }
257 }
258
259 #[cfg(test)]
260 mod tests {
261 use crate::WorktreeAddOptions;
262 use crate::WorktreeLockStatus;
263
264 use tempfile::TempDir;
265
266 #[test]
267 fn smoke_add_no_ref() {
268 let (_td, repo) = crate::test::repo_init();
269
270 let wtdir = TempDir::new().unwrap();
271 let wt_path = wtdir.path().join("tree-no-ref-dir");
272 let opts = WorktreeAddOptions::new();
273
274 let wt = repo.worktree("tree-no-ref", &wt_path, Some(&opts)).unwrap();
275 assert_eq!(wt.name(), Some("tree-no-ref"));
276 assert_eq!(
277 wt.path().canonicalize().unwrap(),
278 wt_path.canonicalize().unwrap()
279 );
280 let status = wt.is_locked().unwrap();
281 assert_eq!(status, WorktreeLockStatus::Unlocked);
282 }
283
284 #[test]
285 fn smoke_add_locked() {
286 let (_td, repo) = crate::test::repo_init();
287
288 let wtdir = TempDir::new().unwrap();
289 let wt_path = wtdir.path().join("locked-tree");
290 let mut opts = WorktreeAddOptions::new();
291 opts.lock(true);
292
293 let wt = repo.worktree("locked-tree", &wt_path, Some(&opts)).unwrap();
294 // shouldn't be able to lock a worktree that was created locked
295 assert!(wt.lock(Some("my reason")).is_err());
296 assert_eq!(wt.name(), Some("locked-tree"));
297 assert_eq!(
298 wt.path().canonicalize().unwrap(),
299 wt_path.canonicalize().unwrap()
300 );
301 assert_eq!(wt.is_locked().unwrap(), WorktreeLockStatus::Locked(None));
302 assert!(wt.unlock().is_ok());
303 assert!(wt.lock(Some("my reason")).is_ok());
304 assert_eq!(
305 wt.is_locked().unwrap(),
306 WorktreeLockStatus::Locked(Some("my reason".to_string()))
307 );
308 }
309
310 #[test]
311 fn smoke_add_from_branch() {
312 let (_td, repo) = crate::test::repo_init();
313
314 let (wt_top, branch) = crate::test::worktrees_env_init(&repo);
315 let wt_path = wt_top.path().join("test");
316 let mut opts = WorktreeAddOptions::new();
317 let reference = branch.into_reference();
318 opts.reference(Some(&reference));
319
320 let wt = repo
321 .worktree("test-worktree", &wt_path, Some(&opts))
322 .unwrap();
323 assert_eq!(wt.name(), Some("test-worktree"));
324 assert_eq!(
325 wt.path().canonicalize().unwrap(),
326 wt_path.canonicalize().unwrap()
327 );
328 let status = wt.is_locked().unwrap();
329 assert_eq!(status, WorktreeLockStatus::Unlocked);
330 }
331 }