| use std::marker; |
| use std::mem; |
| use std::os::raw::c_int; |
| use std::path::Path; |
| use std::ptr; |
| use std::str; |
| |
| use crate::util::{self, Binding}; |
| use crate::{build::CheckoutBuilder, SubmoduleIgnore, SubmoduleUpdate}; |
| use crate::{raw, Error, FetchOptions, Oid, Repository}; |
| |
| /// A structure to represent a git [submodule][1] |
| /// |
| /// [1]: http://git-scm.com/book/en/Git-Tools-Submodules |
| pub struct Submodule<'repo> { |
| raw: *mut raw::git_submodule, |
| _marker: marker::PhantomData<&'repo Repository>, |
| } |
| |
| impl<'repo> Submodule<'repo> { |
| /// Get the submodule's branch. |
| /// |
| /// Returns `None` if the branch is not valid utf-8 or if the branch is not |
| /// yet available. |
| pub fn branch(&self) -> Option<&str> { |
| self.branch_bytes().and_then(|s| str::from_utf8(s).ok()) |
| } |
| |
| /// Get the branch for the submodule. |
| /// |
| /// Returns `None` if the branch is not yet available. |
| pub fn branch_bytes(&self) -> Option<&[u8]> { |
| unsafe { crate::opt_bytes(self, raw::git_submodule_branch(self.raw)) } |
| } |
| |
| /// Perform the clone step for a newly created submodule. |
| /// |
| /// This performs the necessary `git_clone` to setup a newly-created submodule. |
| pub fn clone( |
| &mut self, |
| opts: Option<&mut SubmoduleUpdateOptions<'_>>, |
| ) -> Result<Repository, Error> { |
| unsafe { |
| let raw_opts = opts.map(|o| o.raw()); |
| let mut raw_repo = ptr::null_mut(); |
| try_call!(raw::git_submodule_clone( |
| &mut raw_repo, |
| self.raw, |
| raw_opts.as_ref() |
| )); |
| Ok(Binding::from_raw(raw_repo)) |
| } |
| } |
| |
| /// Get the submodule's url. |
| /// |
| /// Returns `None` if the url is not valid utf-8 or if the URL isn't present |
| pub fn url(&self) -> Option<&str> { |
| self.opt_url_bytes().and_then(|b| str::from_utf8(b).ok()) |
| } |
| |
| /// Get the url for the submodule. |
| #[doc(hidden)] |
| #[deprecated(note = "renamed to `opt_url_bytes`")] |
| pub fn url_bytes(&self) -> &[u8] { |
| self.opt_url_bytes().unwrap() |
| } |
| |
| /// Get the url for the submodule. |
| /// |
| /// Returns `None` if the URL isn't present |
| // TODO: delete this method and fix the signature of `url_bytes` on next |
| // major version bump |
| pub fn opt_url_bytes(&self) -> Option<&[u8]> { |
| unsafe { crate::opt_bytes(self, raw::git_submodule_url(self.raw)) } |
| } |
| |
| /// Get the submodule's name. |
| /// |
| /// Returns `None` if the name is not valid utf-8 |
| pub fn name(&self) -> Option<&str> { |
| str::from_utf8(self.name_bytes()).ok() |
| } |
| |
| /// Get the name for the submodule. |
| pub fn name_bytes(&self) -> &[u8] { |
| unsafe { crate::opt_bytes(self, raw::git_submodule_name(self.raw)).unwrap() } |
| } |
| |
| /// Get the path for the submodule. |
| pub fn path(&self) -> &Path { |
| util::bytes2path(unsafe { |
| crate::opt_bytes(self, raw::git_submodule_path(self.raw)).unwrap() |
| }) |
| } |
| |
| /// Get the OID for the submodule in the current HEAD tree. |
| pub fn head_id(&self) -> Option<Oid> { |
| unsafe { Binding::from_raw_opt(raw::git_submodule_head_id(self.raw)) } |
| } |
| |
| /// Get the OID for the submodule in the index. |
| pub fn index_id(&self) -> Option<Oid> { |
| unsafe { Binding::from_raw_opt(raw::git_submodule_index_id(self.raw)) } |
| } |
| |
| /// Get the OID for the submodule in the current working directory. |
| /// |
| /// This returns the OID that corresponds to looking up 'HEAD' in the |
| /// checked out submodule. If there are pending changes in the index or |
| /// anything else, this won't notice that. |
| pub fn workdir_id(&self) -> Option<Oid> { |
| unsafe { Binding::from_raw_opt(raw::git_submodule_wd_id(self.raw)) } |
| } |
| |
| /// Get the ignore rule that will be used for the submodule. |
| pub fn ignore_rule(&self) -> SubmoduleIgnore { |
| SubmoduleIgnore::from_raw(unsafe { raw::git_submodule_ignore(self.raw) }) |
| } |
| |
| /// Get the update rule that will be used for the submodule. |
| pub fn update_strategy(&self) -> SubmoduleUpdate { |
| SubmoduleUpdate::from_raw(unsafe { raw::git_submodule_update_strategy(self.raw) }) |
| } |
| |
| /// Copy submodule info into ".git/config" file. |
| /// |
| /// Just like "git submodule init", this copies information about the |
| /// submodule into ".git/config". You can use the accessor functions above |
| /// to alter the in-memory git_submodule object and control what is written |
| /// to the config, overriding what is in .gitmodules. |
| /// |
| /// By default, existing entries will not be overwritten, but passing `true` |
| /// for `overwrite` forces them to be updated. |
| pub fn init(&mut self, overwrite: bool) -> Result<(), Error> { |
| unsafe { |
| try_call!(raw::git_submodule_init(self.raw, overwrite)); |
| } |
| Ok(()) |
| } |
| |
| /// Open the repository for a submodule. |
| /// |
| /// This will only work if the submodule is checked out into the working |
| /// directory. |
| pub fn open(&self) -> Result<Repository, Error> { |
| let mut raw = ptr::null_mut(); |
| unsafe { |
| try_call!(raw::git_submodule_open(&mut raw, self.raw)); |
| Ok(Binding::from_raw(raw)) |
| } |
| } |
| |
| /// Reread submodule info from config, index, and HEAD. |
| /// |
| /// Call this to reread cached submodule information for this submodule if |
| /// you have reason to believe that it has changed. |
| /// |
| /// If `force` is `true`, then data will be reloaded even if it doesn't seem |
| /// out of date |
| pub fn reload(&mut self, force: bool) -> Result<(), Error> { |
| unsafe { |
| try_call!(raw::git_submodule_reload(self.raw, force)); |
| } |
| Ok(()) |
| } |
| |
| /// Copy submodule remote info into submodule repo. |
| /// |
| /// This copies the information about the submodules URL into the checked |
| /// out submodule config, acting like "git submodule sync". This is useful |
| /// if you have altered the URL for the submodule (or it has been altered |
| /// by a fetch of upstream changes) and you need to update your local repo. |
| pub fn sync(&mut self) -> Result<(), Error> { |
| unsafe { |
| try_call!(raw::git_submodule_sync(self.raw)); |
| } |
| Ok(()) |
| } |
| |
| /// Add current submodule HEAD commit to index of superproject. |
| /// |
| /// If `write_index` is true, then the index file will be immediately |
| /// written. Otherwise you must explicitly call `write()` on an `Index` |
| /// later on. |
| pub fn add_to_index(&mut self, write_index: bool) -> Result<(), Error> { |
| unsafe { |
| try_call!(raw::git_submodule_add_to_index(self.raw, write_index)); |
| } |
| Ok(()) |
| } |
| |
| /// Resolve the setup of a new git submodule. |
| /// |
| /// This should be called on a submodule once you have called add setup and |
| /// done the clone of the submodule. This adds the .gitmodules file and the |
| /// newly cloned submodule to the index to be ready to be committed (but |
| /// doesn't actually do the commit). |
| pub fn add_finalize(&mut self) -> Result<(), Error> { |
| unsafe { |
| try_call!(raw::git_submodule_add_finalize(self.raw)); |
| } |
| Ok(()) |
| } |
| |
| /// Update submodule. |
| /// |
| /// This will clone a missing submodule and check out the subrepository to |
| /// the commit specified in the index of the containing repository. If |
| /// the submodule repository doesn't contain the target commit, then the |
| /// submodule is fetched using the fetch options supplied in `opts`. |
| /// |
| /// `init` indicates if the submodule should be initialized first if it has |
| /// not been initialized yet. |
| pub fn update( |
| &mut self, |
| init: bool, |
| opts: Option<&mut SubmoduleUpdateOptions<'_>>, |
| ) -> Result<(), Error> { |
| unsafe { |
| let mut raw_opts = opts.map(|o| o.raw()); |
| try_call!(raw::git_submodule_update( |
| self.raw, |
| init as c_int, |
| raw_opts.as_mut().map_or(ptr::null_mut(), |o| o) |
| )); |
| } |
| Ok(()) |
| } |
| } |
| |
| impl<'repo> Binding for Submodule<'repo> { |
| type Raw = *mut raw::git_submodule; |
| unsafe fn from_raw(raw: *mut raw::git_submodule) -> Submodule<'repo> { |
| Submodule { |
| raw, |
| _marker: marker::PhantomData, |
| } |
| } |
| fn raw(&self) -> *mut raw::git_submodule { |
| self.raw |
| } |
| } |
| |
| impl<'repo> Drop for Submodule<'repo> { |
| fn drop(&mut self) { |
| unsafe { raw::git_submodule_free(self.raw) } |
| } |
| } |
| |
| /// Options to update a submodule. |
| pub struct SubmoduleUpdateOptions<'cb> { |
| checkout_builder: CheckoutBuilder<'cb>, |
| fetch_opts: FetchOptions<'cb>, |
| allow_fetch: bool, |
| } |
| |
| impl<'cb> SubmoduleUpdateOptions<'cb> { |
| /// Return default options. |
| pub fn new() -> Self { |
| SubmoduleUpdateOptions { |
| checkout_builder: CheckoutBuilder::new(), |
| fetch_opts: FetchOptions::new(), |
| allow_fetch: true, |
| } |
| } |
| |
| unsafe fn raw(&mut self) -> raw::git_submodule_update_options { |
| let mut checkout_opts: raw::git_checkout_options = mem::zeroed(); |
| let init_res = |
| raw::git_checkout_init_options(&mut checkout_opts, raw::GIT_CHECKOUT_OPTIONS_VERSION); |
| assert_eq!(0, init_res); |
| self.checkout_builder.configure(&mut checkout_opts); |
| let opts = raw::git_submodule_update_options { |
| version: raw::GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, |
| checkout_opts, |
| fetch_opts: self.fetch_opts.raw(), |
| allow_fetch: self.allow_fetch as c_int, |
| }; |
| opts |
| } |
| |
| /// Set checkout options. |
| pub fn checkout(&mut self, opts: CheckoutBuilder<'cb>) -> &mut Self { |
| self.checkout_builder = opts; |
| self |
| } |
| |
| /// Set fetch options and allow fetching. |
| pub fn fetch(&mut self, opts: FetchOptions<'cb>) -> &mut Self { |
| self.fetch_opts = opts; |
| self.allow_fetch = true; |
| self |
| } |
| |
| /// Allow or disallow fetching. |
| pub fn allow_fetch(&mut self, b: bool) -> &mut Self { |
| self.allow_fetch = b; |
| self |
| } |
| } |
| |
| impl<'cb> Default for SubmoduleUpdateOptions<'cb> { |
| fn default() -> Self { |
| Self::new() |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use std::fs; |
| use std::path::Path; |
| use tempfile::TempDir; |
| use url::Url; |
| |
| use crate::Repository; |
| use crate::SubmoduleUpdateOptions; |
| |
| #[test] |
| fn smoke() { |
| let td = TempDir::new().unwrap(); |
| let repo = Repository::init(td.path()).unwrap(); |
| let mut s1 = repo |
| .submodule("/path/to/nowhere", Path::new("foo"), true) |
| .unwrap(); |
| s1.init(false).unwrap(); |
| s1.sync().unwrap(); |
| |
| let s2 = repo |
| .submodule("/path/to/nowhere", Path::new("bar"), true) |
| .unwrap(); |
| drop((s1, s2)); |
| |
| let mut submodules = repo.submodules().unwrap(); |
| assert_eq!(submodules.len(), 2); |
| let mut s = submodules.remove(0); |
| assert_eq!(s.name(), Some("bar")); |
| assert_eq!(s.url(), Some("/path/to/nowhere")); |
| assert_eq!(s.branch(), None); |
| assert!(s.head_id().is_none()); |
| assert!(s.index_id().is_none()); |
| assert!(s.workdir_id().is_none()); |
| |
| repo.find_submodule("bar").unwrap(); |
| s.open().unwrap(); |
| assert!(s.path() == Path::new("bar")); |
| s.reload(true).unwrap(); |
| } |
| |
| #[test] |
| fn add_a_submodule() { |
| let (_td, repo1) = crate::test::repo_init(); |
| let (td, repo2) = crate::test::repo_init(); |
| |
| let url = Url::from_file_path(&repo1.workdir().unwrap()).unwrap(); |
| let mut s = repo2 |
| .submodule(&url.to_string(), Path::new("bar"), true) |
| .unwrap(); |
| t!(fs::remove_dir_all(td.path().join("bar"))); |
| t!(Repository::clone(&url.to_string(), td.path().join("bar"))); |
| t!(s.add_to_index(false)); |
| t!(s.add_finalize()); |
| } |
| |
| #[test] |
| fn update_submodule() { |
| // ----------------------------------- |
| // Same as `add_a_submodule()` |
| let (_td, repo1) = crate::test::repo_init(); |
| let (td, repo2) = crate::test::repo_init(); |
| |
| let url = Url::from_file_path(&repo1.workdir().unwrap()).unwrap(); |
| let mut s = repo2 |
| .submodule(&url.to_string(), Path::new("bar"), true) |
| .unwrap(); |
| t!(fs::remove_dir_all(td.path().join("bar"))); |
| t!(Repository::clone(&url.to_string(), td.path().join("bar"))); |
| t!(s.add_to_index(false)); |
| t!(s.add_finalize()); |
| // ----------------------------------- |
| |
| // Attempt to update submodule |
| let submodules = t!(repo1.submodules()); |
| for mut submodule in submodules { |
| let mut submodule_options = SubmoduleUpdateOptions::new(); |
| let init = true; |
| let opts = Some(&mut submodule_options); |
| |
| t!(submodule.update(init, opts)); |
| } |
| } |
| |
| #[test] |
| fn clone_submodule() { |
| // ----------------------------------- |
| // Same as `add_a_submodule()` |
| let (_td, repo1) = crate::test::repo_init(); |
| let (_td, repo2) = crate::test::repo_init(); |
| let (_td, parent) = crate::test::repo_init(); |
| |
| let url1 = Url::from_file_path(&repo1.workdir().unwrap()).unwrap(); |
| let url3 = Url::from_file_path(&repo2.workdir().unwrap()).unwrap(); |
| let mut s1 = parent |
| .submodule(&url1.to_string(), Path::new("bar"), true) |
| .unwrap(); |
| let mut s2 = parent |
| .submodule(&url3.to_string(), Path::new("bar2"), true) |
| .unwrap(); |
| // ----------------------------------- |
| |
| t!(s1.clone(Some(&mut SubmoduleUpdateOptions::default()))); |
| t!(s2.clone(None)); |
| } |
| } |