1 use std::marker;
2 use std::mem;
3 use std::os::raw::c_int;
4 use std::path::Path;
5 use std::ptr;
6 use std::str;
7 
8 use crate::util::{self, Binding};
9 use crate::{build::CheckoutBuilder, SubmoduleIgnore, SubmoduleUpdate};
10 use 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
15 pub struct Submodule<'repo> {
16     raw: *mut raw::git_submodule,
17     _marker: marker::PhantomData<&'repo Repository>,
18 }
19 
20 impl<'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.
branch(&self) -> Option<&str>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.
branch_bytes(&self) -> Option<&[u8]>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.
clone( &mut self, opts: Option<&mut SubmoduleUpdateOptions<'_>>, ) -> Result<Repository, Error>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
url(&self) -> Option<&str>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`")]
url_bytes(&self) -> &[u8]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
opt_url_bytes(&self) -> Option<&[u8]>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
name(&self) -> Option<&str>81     pub fn name(&self) -> Option<&str> {
82         str::from_utf8(self.name_bytes()).ok()
83     }
84 
85     /// Get the name for the submodule.
name_bytes(&self) -> &[u8]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.
path(&self) -> &Path91     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.
head_id(&self) -> Option<Oid>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.
index_id(&self) -> Option<Oid>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.
workdir_id(&self) -> Option<Oid>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.
ignore_rule(&self) -> SubmoduleIgnore117     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.
update_strategy(&self) -> SubmoduleUpdate122     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.
init(&mut self, overwrite: bool) -> Result<(), Error>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     /// Open the repository for a submodule.
143     ///
144     /// This will only work if the submodule is checked out into the working
145     /// directory.
open(&self) -> Result<Repository, Error>146     pub fn open(&self) -> Result<Repository, Error> {
147         let mut raw = ptr::null_mut();
148         unsafe {
149             try_call!(raw::git_submodule_open(&mut raw, self.raw));
150             Ok(Binding::from_raw(raw))
151         }
152     }
153 
154     /// Reread submodule info from config, index, and HEAD.
155     ///
156     /// Call this to reread cached submodule information for this submodule if
157     /// you have reason to believe that it has changed.
158     ///
159     /// If `force` is `true`, then data will be reloaded even if it doesn't seem
160     /// out of date
reload(&mut self, force: bool) -> Result<(), Error>161     pub fn reload(&mut self, force: bool) -> Result<(), Error> {
162         unsafe {
163             try_call!(raw::git_submodule_reload(self.raw, force));
164         }
165         Ok(())
166     }
167 
168     /// Copy submodule remote info into submodule repo.
169     ///
170     /// This copies the information about the submodules URL into the checked
171     /// out submodule config, acting like "git submodule sync". This is useful
172     /// if you have altered the URL for the submodule (or it has been altered
173     /// by a fetch of upstream changes) and you need to update your local repo.
sync(&mut self) -> Result<(), Error>174     pub fn sync(&mut self) -> Result<(), Error> {
175         unsafe {
176             try_call!(raw::git_submodule_sync(self.raw));
177         }
178         Ok(())
179     }
180 
181     /// Add current submodule HEAD commit to index of superproject.
182     ///
183     /// If `write_index` is true, then the index file will be immediately
184     /// written. Otherwise you must explicitly call `write()` on an `Index`
185     /// later on.
add_to_index(&mut self, write_index: bool) -> Result<(), Error>186     pub fn add_to_index(&mut self, write_index: bool) -> Result<(), Error> {
187         unsafe {
188             try_call!(raw::git_submodule_add_to_index(self.raw, write_index));
189         }
190         Ok(())
191     }
192 
193     /// Resolve the setup of a new git submodule.
194     ///
195     /// This should be called on a submodule once you have called add setup and
196     /// done the clone of the submodule. This adds the .gitmodules file and the
197     /// newly cloned submodule to the index to be ready to be committed (but
198     /// doesn't actually do the commit).
add_finalize(&mut self) -> Result<(), Error>199     pub fn add_finalize(&mut self) -> Result<(), Error> {
200         unsafe {
201             try_call!(raw::git_submodule_add_finalize(self.raw));
202         }
203         Ok(())
204     }
205 
206     /// Update submodule.
207     ///
208     /// This will clone a missing submodule and check out the subrepository to
209     /// the commit specified in the index of the containing repository. If
210     /// the submodule repository doesn't contain the target commit, then the
211     /// submodule is fetched using the fetch options supplied in `opts`.
212     ///
213     /// `init` indicates if the submodule should be initialized first if it has
214     /// not been initialized yet.
update( &mut self, init: bool, opts: Option<&mut SubmoduleUpdateOptions<'_>>, ) -> Result<(), Error>215     pub fn update(
216         &mut self,
217         init: bool,
218         opts: Option<&mut SubmoduleUpdateOptions<'_>>,
219     ) -> Result<(), Error> {
220         unsafe {
221             let mut raw_opts = opts.map(|o| o.raw());
222             try_call!(raw::git_submodule_update(
223                 self.raw,
224                 init as c_int,
225                 raw_opts.as_mut().map_or(ptr::null_mut(), |o| o)
226             ));
227         }
228         Ok(())
229     }
230 }
231 
232 impl<'repo> Binding for Submodule<'repo> {
233     type Raw = *mut raw::git_submodule;
from_raw(raw: *mut raw::git_submodule) -> Submodule<'repo>234     unsafe fn from_raw(raw: *mut raw::git_submodule) -> Submodule<'repo> {
235         Submodule {
236             raw: raw,
237             _marker: marker::PhantomData,
238         }
239     }
raw(&self) -> *mut raw::git_submodule240     fn raw(&self) -> *mut raw::git_submodule {
241         self.raw
242     }
243 }
244 
245 impl<'repo> Drop for Submodule<'repo> {
drop(&mut self)246     fn drop(&mut self) {
247         unsafe { raw::git_submodule_free(self.raw) }
248     }
249 }
250 
251 /// Options to update a submodule.
252 pub struct SubmoduleUpdateOptions<'cb> {
253     checkout_builder: CheckoutBuilder<'cb>,
254     fetch_opts: FetchOptions<'cb>,
255     allow_fetch: bool,
256 }
257 
258 impl<'cb> SubmoduleUpdateOptions<'cb> {
259     /// Return default options.
new() -> Self260     pub fn new() -> Self {
261         SubmoduleUpdateOptions {
262             checkout_builder: CheckoutBuilder::new(),
263             fetch_opts: FetchOptions::new(),
264             allow_fetch: true,
265         }
266     }
267 
raw(&mut self) -> raw::git_submodule_update_options268     unsafe fn raw(&mut self) -> raw::git_submodule_update_options {
269         let mut checkout_opts: raw::git_checkout_options = mem::zeroed();
270         let init_res =
271             raw::git_checkout_init_options(&mut checkout_opts, raw::GIT_CHECKOUT_OPTIONS_VERSION);
272         assert_eq!(0, init_res);
273         self.checkout_builder.configure(&mut checkout_opts);
274         let opts = raw::git_submodule_update_options {
275             version: raw::GIT_SUBMODULE_UPDATE_OPTIONS_VERSION,
276             checkout_opts,
277             fetch_opts: self.fetch_opts.raw(),
278             allow_fetch: self.allow_fetch as c_int,
279         };
280         opts
281     }
282 
283     /// Set checkout options.
checkout(&mut self, opts: CheckoutBuilder<'cb>) -> &mut Self284     pub fn checkout(&mut self, opts: CheckoutBuilder<'cb>) -> &mut Self {
285         self.checkout_builder = opts;
286         self
287     }
288 
289     /// Set fetch options and allow fetching.
fetch(&mut self, opts: FetchOptions<'cb>) -> &mut Self290     pub fn fetch(&mut self, opts: FetchOptions<'cb>) -> &mut Self {
291         self.fetch_opts = opts;
292         self.allow_fetch = true;
293         self
294     }
295 
296     /// Allow or disallow fetching.
allow_fetch(&mut self, b: bool) -> &mut Self297     pub fn allow_fetch(&mut self, b: bool) -> &mut Self {
298         self.allow_fetch = b;
299         self
300     }
301 }
302 
303 impl<'cb> Default for SubmoduleUpdateOptions<'cb> {
default() -> Self304     fn default() -> Self {
305         Self::new()
306     }
307 }
308 
309 #[cfg(test)]
310 mod tests {
311     use std::fs;
312     use std::path::Path;
313     use tempfile::TempDir;
314     use url::Url;
315 
316     use crate::Repository;
317     use crate::SubmoduleUpdateOptions;
318 
319     #[test]
smoke()320     fn smoke() {
321         let td = TempDir::new().unwrap();
322         let repo = Repository::init(td.path()).unwrap();
323         let mut s1 = repo
324             .submodule("/path/to/nowhere", Path::new("foo"), true)
325             .unwrap();
326         s1.init(false).unwrap();
327         s1.sync().unwrap();
328 
329         let s2 = repo
330             .submodule("/path/to/nowhere", Path::new("bar"), true)
331             .unwrap();
332         drop((s1, s2));
333 
334         let mut submodules = repo.submodules().unwrap();
335         assert_eq!(submodules.len(), 2);
336         let mut s = submodules.remove(0);
337         assert_eq!(s.name(), Some("bar"));
338         assert_eq!(s.url(), Some("/path/to/nowhere"));
339         assert_eq!(s.branch(), None);
340         assert!(s.head_id().is_none());
341         assert!(s.index_id().is_none());
342         assert!(s.workdir_id().is_none());
343 
344         repo.find_submodule("bar").unwrap();
345         s.open().unwrap();
346         assert!(s.path() == Path::new("bar"));
347         s.reload(true).unwrap();
348     }
349 
350     #[test]
add_a_submodule()351     fn add_a_submodule() {
352         let (_td, repo1) = crate::test::repo_init();
353         let (td, repo2) = crate::test::repo_init();
354 
355         let url = Url::from_file_path(&repo1.workdir().unwrap()).unwrap();
356         let mut s = repo2
357             .submodule(&url.to_string(), Path::new("bar"), true)
358             .unwrap();
359         t!(fs::remove_dir_all(td.path().join("bar")));
360         t!(Repository::clone(&url.to_string(), td.path().join("bar")));
361         t!(s.add_to_index(false));
362         t!(s.add_finalize());
363     }
364 
365     #[test]
update_submodule()366     fn update_submodule() {
367         // -----------------------------------
368         // Same as `add_a_submodule()`
369         let (_td, repo1) = crate::test::repo_init();
370         let (td, repo2) = crate::test::repo_init();
371 
372         let url = Url::from_file_path(&repo1.workdir().unwrap()).unwrap();
373         let mut s = repo2
374             .submodule(&url.to_string(), Path::new("bar"), true)
375             .unwrap();
376         t!(fs::remove_dir_all(td.path().join("bar")));
377         t!(Repository::clone(&url.to_string(), td.path().join("bar")));
378         t!(s.add_to_index(false));
379         t!(s.add_finalize());
380         // -----------------------------------
381 
382         // Attempt to update submodule
383         let submodules = t!(repo1.submodules());
384         for mut submodule in submodules {
385             let mut submodule_options = SubmoduleUpdateOptions::new();
386             let init = true;
387             let opts = Some(&mut submodule_options);
388 
389             t!(submodule.update(init, opts));
390         }
391     }
392 
393     #[test]
clone_submodule()394     fn clone_submodule() {
395         // -----------------------------------
396         // Same as `add_a_submodule()`
397         let (_td, repo1) = crate::test::repo_init();
398         let (_td, repo2) = crate::test::repo_init();
399         let (_td, parent) = crate::test::repo_init();
400 
401         let url1 = Url::from_file_path(&repo1.workdir().unwrap()).unwrap();
402         let url3 = Url::from_file_path(&repo2.workdir().unwrap()).unwrap();
403         let mut s1 = parent
404             .submodule(&url1.to_string(), Path::new("bar"), true)
405             .unwrap();
406         let mut s2 = parent
407             .submodule(&url3.to_string(), Path::new("bar2"), true)
408             .unwrap();
409         // -----------------------------------
410 
411         t!(s1.clone(Some(&mut SubmoduleUpdateOptions::default())));
412         t!(s2.clone(None));
413     }
414 }
415