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