git2/
submodule.rs

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
12/// A structure to represent a git [submodule][1]
13///
14/// [1]: http://git-scm.com/book/en/Git-Tools-Submodules
15pub struct Submodule<'repo> {
16    raw: *mut raw::git_submodule,
17    _marker: marker::PhantomData<&'repo Repository>,
18}
19
20impl<'repo> Submodule<'repo> {
21    /// Get the submodule's branch.
22    ///
23    /// Returns `None` if the branch is not valid utf-8 or if the branch is not
24    /// yet available.
25    pub fn branch(&self) -> Option<&str> {
26        self.branch_bytes().and_then(|s| str::from_utf8(s).ok())
27    }
28
29    /// Get the branch for the submodule.
30    ///
31    /// Returns `None` if the branch is not yet available.
32    pub fn branch_bytes(&self) -> Option<&[u8]> {
33        unsafe { crate::opt_bytes(self, raw::git_submodule_branch(self.raw)) }
34    }
35
36    /// Perform the clone step for a newly created submodule.
37    ///
38    /// This performs the necessary `git_clone` to setup a newly-created submodule.
39    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    /// Get the submodule's URL.
56    ///
57    /// Returns `None` if the URL is not valid utf-8 or if the URL isn't present
58    pub fn url(&self) -> Option<&str> {
59        self.opt_url_bytes().and_then(|b| str::from_utf8(b).ok())
60    }
61
62    /// Get the URL for the submodule.
63    #[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    /// Get the URL for the submodule.
70    ///
71    /// Returns `None` if the URL isn't present
72    // TODO: delete this method and fix the signature of `url_bytes` on next
73    // major version bump
74    pub fn opt_url_bytes(&self) -> Option<&[u8]> {
75        unsafe { crate::opt_bytes(self, raw::git_submodule_url(self.raw)) }
76    }
77
78    /// Get the submodule's name.
79    ///
80    /// Returns `None` if the name is not valid utf-8
81    pub fn name(&self) -> Option<&str> {
82        str::from_utf8(self.name_bytes()).ok()
83    }
84
85    /// Get the name for the submodule.
86    pub fn name_bytes(&self) -> &[u8] {
87        unsafe { crate::opt_bytes(self, raw::git_submodule_name(self.raw)).unwrap() }
88    }
89
90    /// Get the path for the submodule.
91    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    /// Get the OID for the submodule in the current HEAD tree.
98    pub fn head_id(&self) -> Option<Oid> {
99        unsafe { Binding::from_raw_opt(raw::git_submodule_head_id(self.raw)) }
100    }
101
102    /// Get the OID for the submodule in the index.
103    pub fn index_id(&self) -> Option<Oid> {
104        unsafe { Binding::from_raw_opt(raw::git_submodule_index_id(self.raw)) }
105    }
106
107    /// Get the OID for the submodule in the current working directory.
108    ///
109    /// This returns the OID that corresponds to looking up 'HEAD' in the
110    /// checked out submodule. If there are pending changes in the index or
111    /// anything else, this won't notice that.
112    pub fn workdir_id(&self) -> Option<Oid> {
113        unsafe { Binding::from_raw_opt(raw::git_submodule_wd_id(self.raw)) }
114    }
115
116    /// Get the ignore rule that will be used for the submodule.
117    pub fn ignore_rule(&self) -> SubmoduleIgnore {
118        SubmoduleIgnore::from_raw(unsafe { raw::git_submodule_ignore(self.raw) })
119    }
120
121    /// Get the update rule that will be used for the submodule.
122    pub fn update_strategy(&self) -> SubmoduleUpdate {
123        SubmoduleUpdate::from_raw(unsafe { raw::git_submodule_update_strategy(self.raw) })
124    }
125
126    /// Copy submodule info into ".git/config" file.
127    ///
128    /// Just like "git submodule init", this copies information about the
129    /// submodule into ".git/config". You can use the accessor functions above
130    /// to alter the in-memory git_submodule object and control what is written
131    /// to the config, overriding what is in .gitmodules.
132    ///
133    /// By default, existing entries will not be overwritten, but passing `true`
134    /// for `overwrite` forces them to be updated.
135    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    /// Set up the subrepository for a submodule in preparation for clone.
143    ///
144    /// This function can be called to init and set up a submodule repository
145    /// from a submodule in preparation to clone it from its remote.
146
147    /// use_gitlink: Should the workdir contain a gitlink to the repo in
148    /// .git/modules vs. repo directly in workdir.
149    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    /// Open the repository for a submodule.
162    ///
163    /// This will only work if the submodule is checked out into the working
164    /// directory.
165    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    /// Reread submodule info from config, index, and HEAD.
174    ///
175    /// Call this to reread cached submodule information for this submodule if
176    /// you have reason to believe that it has changed.
177    ///
178    /// If `force` is `true`, then data will be reloaded even if it doesn't seem
179    /// out of date
180    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    /// Copy submodule remote info into submodule repo.
188    ///
189    /// This copies the information about the submodules URL into the checked
190    /// out submodule config, acting like "git submodule sync". This is useful
191    /// if you have altered the URL for the submodule (or it has been altered
192    /// by a fetch of upstream changes) and you need to update your local repo.
193    pub fn sync(&mut self) -> Result<(), Error> {
194        unsafe {
195            try_call!(raw::git_submodule_sync(self.raw));
196        }
197        Ok(())
198    }
199
200    /// Add current submodule HEAD commit to index of superproject.
201    ///
202    /// If `write_index` is true, then the index file will be immediately
203    /// written. Otherwise you must explicitly call `write()` on an `Index`
204    /// later on.
205    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    /// Resolve the setup of a new git submodule.
213    ///
214    /// This should be called on a submodule once you have called add setup and
215    /// done the clone of the submodule. This adds the .gitmodules file and the
216    /// newly cloned submodule to the index to be ready to be committed (but
217    /// doesn't actually do the commit).
218    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    /// Update submodule.
226    ///
227    /// This will clone a missing submodule and check out the subrepository to
228    /// the commit specified in the index of the containing repository. If
229    /// the submodule repository doesn't contain the target commit, then the
230    /// submodule is fetched using the fetch options supplied in `opts`.
231    ///
232    /// `init` indicates if the submodule should be initialized first if it has
233    /// not been initialized yet.
234    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
270/// Options to update a submodule.
271pub struct SubmoduleUpdateOptions<'cb> {
272    checkout_builder: CheckoutBuilder<'cb>,
273    fetch_opts: FetchOptions<'cb>,
274    allow_fetch: bool,
275}
276
277impl<'cb> SubmoduleUpdateOptions<'cb> {
278    /// Return default options.
279    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    /// Set checkout options.
303    pub fn checkout(&mut self, opts: CheckoutBuilder<'cb>) -> &mut Self {
304        self.checkout_builder = opts;
305        self
306    }
307
308    /// Set fetch options and allow fetching.
309    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    /// Allow or disallow fetching.
316    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        // -----------------------------------
387        // Same as `add_a_submodule()`
388        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        // -----------------------------------
400
401        // Attempt to update submodule
402        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        // -----------------------------------
415        // Same as `add_a_submodule()`
416        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        // -----------------------------------
429
430        t!(s1.clone(Some(&mut SubmoduleUpdateOptions::default())));
431        t!(s2.clone(None));
432    }
433
434    #[test]
435    fn repo_init_submodule() {
436        // -----------------------------------
437        // Same as `clone_submodule()`
438        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        // -----------------------------------
448        // Let's commit the submodule for later clone
449        t!(sub.clone(None));
450        t!(sub.add_to_index(true));
451        t!(sub.add_finalize());
452
453        crate::test::commit(&parent);
454
455        // Clone the parent to init its submodules
456        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        // First init child
463        t!(child.init(false));
464        assert_eq!(child.url().unwrap(), url_child.as_str());
465
466        // open() is not possible before initializing the repo
467        assert!(child.open().is_err());
468        t!(child.repo_init(true));
469        assert!(child.open().is_ok());
470    }
471}