1use std::marker;
2use std::mem;
3use std::os::raw::c_int;
4use std::path::Path;
5use std::ptr;
6use std::str;
7
8use crate::util::{self, Binding};
9use crate::{build::CheckoutBuilder, SubmoduleIgnore, SubmoduleUpdate};
10use crate::{raw, Error, FetchOptions, Oid, Repository};
11
12pub struct Submodule<'repo> {
16 raw: *mut raw::git_submodule,
17 _marker: marker::PhantomData<&'repo Repository>,
18}
19
20impl<'repo> Submodule<'repo> {
21 pub fn branch(&self) -> Option<&str> {
26 self.branch_bytes().and_then(|s| str::from_utf8(s).ok())
27 }
28
29 pub fn branch_bytes(&self) -> Option<&[u8]> {
33 unsafe { crate::opt_bytes(self, raw::git_submodule_branch(self.raw)) }
34 }
35
36 pub fn clone(
40 &mut self,
41 opts: Option<&mut SubmoduleUpdateOptions<'_>>,
42 ) -> Result<Repository, Error> {
43 unsafe {
44 let raw_opts = opts.map(|o| o.raw());
45 let mut raw_repo = ptr::null_mut();
46 try_call!(raw::git_submodule_clone(
47 &mut raw_repo,
48 self.raw,
49 raw_opts.as_ref()
50 ));
51 Ok(Binding::from_raw(raw_repo))
52 }
53 }
54
55 pub fn url(&self) -> Option<&str> {
59 self.opt_url_bytes().and_then(|b| str::from_utf8(b).ok())
60 }
61
62 #[doc(hidden)]
64 #[deprecated(note = "renamed to `opt_url_bytes`")]
65 pub fn url_bytes(&self) -> &[u8] {
66 self.opt_url_bytes().unwrap()
67 }
68
69 pub fn opt_url_bytes(&self) -> Option<&[u8]> {
75 unsafe { crate::opt_bytes(self, raw::git_submodule_url(self.raw)) }
76 }
77
78 pub fn name(&self) -> Option<&str> {
82 str::from_utf8(self.name_bytes()).ok()
83 }
84
85 pub fn name_bytes(&self) -> &[u8] {
87 unsafe { crate::opt_bytes(self, raw::git_submodule_name(self.raw)).unwrap() }
88 }
89
90 pub fn path(&self) -> &Path {
92 util::bytes2path(unsafe {
93 crate::opt_bytes(self, raw::git_submodule_path(self.raw)).unwrap()
94 })
95 }
96
97 pub fn head_id(&self) -> Option<Oid> {
99 unsafe { Binding::from_raw_opt(raw::git_submodule_head_id(self.raw)) }
100 }
101
102 pub fn index_id(&self) -> Option<Oid> {
104 unsafe { Binding::from_raw_opt(raw::git_submodule_index_id(self.raw)) }
105 }
106
107 pub fn workdir_id(&self) -> Option<Oid> {
113 unsafe { Binding::from_raw_opt(raw::git_submodule_wd_id(self.raw)) }
114 }
115
116 pub fn ignore_rule(&self) -> SubmoduleIgnore {
118 SubmoduleIgnore::from_raw(unsafe { raw::git_submodule_ignore(self.raw) })
119 }
120
121 pub fn update_strategy(&self) -> SubmoduleUpdate {
123 SubmoduleUpdate::from_raw(unsafe { raw::git_submodule_update_strategy(self.raw) })
124 }
125
126 pub fn init(&mut self, overwrite: bool) -> Result<(), Error> {
136 unsafe {
137 try_call!(raw::git_submodule_init(self.raw, overwrite));
138 }
139 Ok(())
140 }
141
142 pub fn repo_init(&mut self, use_gitlink: bool) -> Result<Repository, Error> {
150 unsafe {
151 let mut raw_repo = ptr::null_mut();
152 try_call!(raw::git_submodule_repo_init(
153 &mut raw_repo,
154 self.raw,
155 use_gitlink
156 ));
157 Ok(Binding::from_raw(raw_repo))
158 }
159 }
160
161 pub fn open(&self) -> Result<Repository, Error> {
166 let mut raw = ptr::null_mut();
167 unsafe {
168 try_call!(raw::git_submodule_open(&mut raw, self.raw));
169 Ok(Binding::from_raw(raw))
170 }
171 }
172
173 pub fn reload(&mut self, force: bool) -> Result<(), Error> {
181 unsafe {
182 try_call!(raw::git_submodule_reload(self.raw, force));
183 }
184 Ok(())
185 }
186
187 pub fn sync(&mut self) -> Result<(), Error> {
194 unsafe {
195 try_call!(raw::git_submodule_sync(self.raw));
196 }
197 Ok(())
198 }
199
200 pub fn add_to_index(&mut self, write_index: bool) -> Result<(), Error> {
206 unsafe {
207 try_call!(raw::git_submodule_add_to_index(self.raw, write_index));
208 }
209 Ok(())
210 }
211
212 pub fn add_finalize(&mut self) -> Result<(), Error> {
219 unsafe {
220 try_call!(raw::git_submodule_add_finalize(self.raw));
221 }
222 Ok(())
223 }
224
225 pub fn update(
235 &mut self,
236 init: bool,
237 opts: Option<&mut SubmoduleUpdateOptions<'_>>,
238 ) -> Result<(), Error> {
239 unsafe {
240 let mut raw_opts = opts.map(|o| o.raw());
241 try_call!(raw::git_submodule_update(
242 self.raw,
243 init as c_int,
244 raw_opts.as_mut().map_or(ptr::null_mut(), |o| o)
245 ));
246 }
247 Ok(())
248 }
249}
250
251impl<'repo> Binding for Submodule<'repo> {
252 type Raw = *mut raw::git_submodule;
253 unsafe fn from_raw(raw: *mut raw::git_submodule) -> Submodule<'repo> {
254 Submodule {
255 raw,
256 _marker: marker::PhantomData,
257 }
258 }
259 fn raw(&self) -> *mut raw::git_submodule {
260 self.raw
261 }
262}
263
264impl<'repo> Drop for Submodule<'repo> {
265 fn drop(&mut self) {
266 unsafe { raw::git_submodule_free(self.raw) }
267 }
268}
269
270pub struct SubmoduleUpdateOptions<'cb> {
272 checkout_builder: CheckoutBuilder<'cb>,
273 fetch_opts: FetchOptions<'cb>,
274 allow_fetch: bool,
275}
276
277impl<'cb> SubmoduleUpdateOptions<'cb> {
278 pub fn new() -> Self {
280 SubmoduleUpdateOptions {
281 checkout_builder: CheckoutBuilder::new(),
282 fetch_opts: FetchOptions::new(),
283 allow_fetch: true,
284 }
285 }
286
287 unsafe fn raw(&mut self) -> raw::git_submodule_update_options {
288 let mut checkout_opts: raw::git_checkout_options = mem::zeroed();
289 let init_res =
290 raw::git_checkout_init_options(&mut checkout_opts, raw::GIT_CHECKOUT_OPTIONS_VERSION);
291 assert_eq!(0, init_res);
292 self.checkout_builder.configure(&mut checkout_opts);
293 let opts = raw::git_submodule_update_options {
294 version: raw::GIT_SUBMODULE_UPDATE_OPTIONS_VERSION,
295 checkout_opts,
296 fetch_opts: self.fetch_opts.raw(),
297 allow_fetch: self.allow_fetch as c_int,
298 };
299 opts
300 }
301
302 pub fn checkout(&mut self, opts: CheckoutBuilder<'cb>) -> &mut Self {
304 self.checkout_builder = opts;
305 self
306 }
307
308 pub fn fetch(&mut self, opts: FetchOptions<'cb>) -> &mut Self {
310 self.fetch_opts = opts;
311 self.allow_fetch = true;
312 self
313 }
314
315 pub fn allow_fetch(&mut self, b: bool) -> &mut Self {
317 self.allow_fetch = b;
318 self
319 }
320}
321
322impl<'cb> Default for SubmoduleUpdateOptions<'cb> {
323 fn default() -> Self {
324 Self::new()
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use std::fs;
331 use std::path::Path;
332 use tempfile::TempDir;
333 use url::Url;
334
335 use crate::Repository;
336 use crate::SubmoduleUpdateOptions;
337
338 #[test]
339 fn smoke() {
340 let td = TempDir::new().unwrap();
341 let repo = Repository::init(td.path()).unwrap();
342 let mut s1 = repo
343 .submodule("/path/to/nowhere", Path::new("foo"), true)
344 .unwrap();
345 s1.init(false).unwrap();
346 s1.sync().unwrap();
347
348 let s2 = repo
349 .submodule("/path/to/nowhere", Path::new("bar"), true)
350 .unwrap();
351 drop((s1, s2));
352
353 let mut submodules = repo.submodules().unwrap();
354 assert_eq!(submodules.len(), 2);
355 let mut s = submodules.remove(0);
356 assert_eq!(s.name(), Some("bar"));
357 assert_eq!(s.url(), Some("/path/to/nowhere"));
358 assert_eq!(s.branch(), None);
359 assert!(s.head_id().is_none());
360 assert!(s.index_id().is_none());
361 assert!(s.workdir_id().is_none());
362
363 repo.find_submodule("bar").unwrap();
364 s.open().unwrap();
365 assert!(s.path() == Path::new("bar"));
366 s.reload(true).unwrap();
367 }
368
369 #[test]
370 fn add_a_submodule() {
371 let (_td, repo1) = crate::test::repo_init();
372 let (td, repo2) = crate::test::repo_init();
373
374 let url = Url::from_file_path(&repo1.workdir().unwrap()).unwrap();
375 let mut s = repo2
376 .submodule(&url.to_string(), Path::new("bar"), true)
377 .unwrap();
378 t!(fs::remove_dir_all(td.path().join("bar")));
379 t!(Repository::clone(&url.to_string(), td.path().join("bar")));
380 t!(s.add_to_index(false));
381 t!(s.add_finalize());
382 }
383
384 #[test]
385 fn update_submodule() {
386 let (_td, repo1) = crate::test::repo_init();
389 let (td, repo2) = crate::test::repo_init();
390
391 let url = Url::from_file_path(&repo1.workdir().unwrap()).unwrap();
392 let mut s = repo2
393 .submodule(&url.to_string(), Path::new("bar"), true)
394 .unwrap();
395 t!(fs::remove_dir_all(td.path().join("bar")));
396 t!(Repository::clone(&url.to_string(), td.path().join("bar")));
397 t!(s.add_to_index(false));
398 t!(s.add_finalize());
399 let submodules = t!(repo1.submodules());
403 for mut submodule in submodules {
404 let mut submodule_options = SubmoduleUpdateOptions::new();
405 let init = true;
406 let opts = Some(&mut submodule_options);
407
408 t!(submodule.update(init, opts));
409 }
410 }
411
412 #[test]
413 fn clone_submodule() {
414 let (_td, repo1) = crate::test::repo_init();
417 let (_td, repo2) = crate::test::repo_init();
418 let (_td, parent) = crate::test::repo_init();
419
420 let url1 = Url::from_file_path(&repo1.workdir().unwrap()).unwrap();
421 let url2 = Url::from_file_path(&repo2.workdir().unwrap()).unwrap();
422 let mut s1 = parent
423 .submodule(&url1.to_string(), Path::new("bar"), true)
424 .unwrap();
425 let mut s2 = parent
426 .submodule(&url2.to_string(), Path::new("bar2"), true)
427 .unwrap();
428 t!(s1.clone(Some(&mut SubmoduleUpdateOptions::default())));
431 t!(s2.clone(None));
432 }
433
434 #[test]
435 fn repo_init_submodule() {
436 let (_td, child) = crate::test::repo_init();
439 let (_td, parent) = crate::test::repo_init();
440
441 let url_child = Url::from_file_path(&child.workdir().unwrap()).unwrap();
442 let url_parent = Url::from_file_path(&parent.workdir().unwrap()).unwrap();
443 let mut sub = parent
444 .submodule(&url_child.to_string(), Path::new("bar"), true)
445 .unwrap();
446
447 t!(sub.clone(None));
450 t!(sub.add_to_index(true));
451 t!(sub.add_finalize());
452
453 crate::test::commit(&parent);
454
455 let td = TempDir::new().unwrap();
457 let new_parent = Repository::clone(&url_parent.to_string(), &td).unwrap();
458
459 let mut submodules = new_parent.submodules().unwrap();
460 let child = submodules.first_mut().unwrap();
461
462 t!(child.init(false));
464 assert_eq!(child.url().unwrap(), url_child.as_str());
465
466 assert!(child.open().is_err());
468 t!(child.repo_init(true));
469 assert!(child.open().is_ok());
470 }
471}