1 use libc;
2 use std::ffi::CString;
3 use std::marker;
4 use std::mem;
5 use std::ops::Range;
6 use std::ptr;
7 use std::slice;
8 use std::str;
9 
10 use crate::string_array::StringArray;
11 use crate::util::Binding;
12 use crate::{raw, Buf, Direction, Error, FetchPrune, Oid, ProxyOptions, Refspec};
13 use crate::{AutotagOption, Progress, RemoteCallbacks, Repository};
14 
15 /// A structure representing a [remote][1] of a git repository.
16 ///
17 /// [1]: http://git-scm.com/book/en/Git-Basics-Working-with-Remotes
18 ///
19 /// The lifetime is the lifetime of the repository that it is attached to. The
20 /// remote is used to manage fetches and pushes as well as refspecs.
21 pub struct Remote<'repo> {
22     raw: *mut raw::git_remote,
23     _marker: marker::PhantomData<&'repo Repository>,
24 }
25 
26 /// An iterator over the refspecs that a remote contains.
27 pub struct Refspecs<'remote> {
28     range: Range<usize>,
29     remote: &'remote Remote<'remote>,
30 }
31 
32 /// Description of a reference advertised bya remote server, given out on calls
33 /// to `list`.
34 pub struct RemoteHead<'remote> {
35     raw: *const raw::git_remote_head,
36     _marker: marker::PhantomData<&'remote str>,
37 }
38 
39 /// Options which can be specified to various fetch operations.
40 pub struct FetchOptions<'cb> {
41     callbacks: Option<RemoteCallbacks<'cb>>,
42     proxy: Option<ProxyOptions<'cb>>,
43     prune: FetchPrune,
44     update_fetchhead: bool,
45     download_tags: AutotagOption,
46 }
47 
48 /// Options to control the behavior of a git push.
49 pub struct PushOptions<'cb> {
50     callbacks: Option<RemoteCallbacks<'cb>>,
51     proxy: Option<ProxyOptions<'cb>>,
52     pb_parallelism: u32,
53 }
54 
55 /// Holds callbacks for a connection to a `Remote`. Disconnects when dropped
56 pub struct RemoteConnection<'repo, 'connection, 'cb> {
57     _callbacks: Box<RemoteCallbacks<'cb>>,
58     _proxy: ProxyOptions<'cb>,
59     remote: &'connection mut Remote<'repo>,
60 }
61 
remote_into_raw(remote: Remote<'_>) -> *mut raw::git_remote62 pub fn remote_into_raw(remote: Remote<'_>) -> *mut raw::git_remote {
63     let ret = remote.raw;
64     mem::forget(remote);
65     return ret;
66 }
67 
68 impl<'repo> Remote<'repo> {
69     /// Ensure the remote name is well-formed.
is_valid_name(remote_name: &str) -> bool70     pub fn is_valid_name(remote_name: &str) -> bool {
71         crate::init();
72         let remote_name = CString::new(remote_name).unwrap();
73         unsafe { raw::git_remote_is_valid_name(remote_name.as_ptr()) == 1 }
74     }
75 
76     /// Get the remote's name.
77     ///
78     /// Returns `None` if this remote has not yet been named or if the name is
79     /// not valid utf-8
name(&self) -> Option<&str>80     pub fn name(&self) -> Option<&str> {
81         self.name_bytes().and_then(|s| str::from_utf8(s).ok())
82     }
83 
84     /// Get the remote's name, in bytes.
85     ///
86     /// Returns `None` if this remote has not yet been named
name_bytes(&self) -> Option<&[u8]>87     pub fn name_bytes(&self) -> Option<&[u8]> {
88         unsafe { crate::opt_bytes(self, raw::git_remote_name(&*self.raw)) }
89     }
90 
91     /// Get the remote's url.
92     ///
93     /// Returns `None` if the url is not valid utf-8
url(&self) -> Option<&str>94     pub fn url(&self) -> Option<&str> {
95         str::from_utf8(self.url_bytes()).ok()
96     }
97 
98     /// Get the remote's url as a byte array.
url_bytes(&self) -> &[u8]99     pub fn url_bytes(&self) -> &[u8] {
100         unsafe { crate::opt_bytes(self, raw::git_remote_url(&*self.raw)).unwrap() }
101     }
102 
103     /// Get the remote's pushurl.
104     ///
105     /// Returns `None` if the pushurl is not valid utf-8
pushurl(&self) -> Option<&str>106     pub fn pushurl(&self) -> Option<&str> {
107         self.pushurl_bytes().and_then(|s| str::from_utf8(s).ok())
108     }
109 
110     /// Get the remote's pushurl as a byte array.
pushurl_bytes(&self) -> Option<&[u8]>111     pub fn pushurl_bytes(&self) -> Option<&[u8]> {
112         unsafe { crate::opt_bytes(self, raw::git_remote_pushurl(&*self.raw)) }
113     }
114 
115     /// Get the remote's default branch.
116     ///
117     /// The remote (or more exactly its transport) must have connected to the
118     /// remote repository. This default branch is available as soon as the
119     /// connection to the remote is initiated and it remains available after
120     /// disconnecting.
default_branch(&self) -> Result<Buf, Error>121     pub fn default_branch(&self) -> Result<Buf, Error> {
122         unsafe {
123             let buf = Buf::new();
124             try_call!(raw::git_remote_default_branch(buf.raw(), self.raw));
125             Ok(buf)
126         }
127     }
128 
129     /// Open a connection to a remote.
connect(&mut self, dir: Direction) -> Result<(), Error>130     pub fn connect(&mut self, dir: Direction) -> Result<(), Error> {
131         // TODO: can callbacks be exposed safely?
132         unsafe {
133             try_call!(raw::git_remote_connect(
134                 self.raw,
135                 dir,
136                 ptr::null(),
137                 ptr::null(),
138                 ptr::null()
139             ));
140         }
141         Ok(())
142     }
143 
144     /// Open a connection to a remote with callbacks and proxy settings
145     ///
146     /// Returns a `RemoteConnection` that will disconnect once dropped
connect_auth<'connection, 'cb>( &'connection mut self, dir: Direction, cb: Option<RemoteCallbacks<'cb>>, proxy_options: Option<ProxyOptions<'cb>>, ) -> Result<RemoteConnection<'repo, 'connection, 'cb>, Error>147     pub fn connect_auth<'connection, 'cb>(
148         &'connection mut self,
149         dir: Direction,
150         cb: Option<RemoteCallbacks<'cb>>,
151         proxy_options: Option<ProxyOptions<'cb>>,
152     ) -> Result<RemoteConnection<'repo, 'connection, 'cb>, Error> {
153         let cb = Box::new(cb.unwrap_or_else(RemoteCallbacks::new));
154         let proxy_options = proxy_options.unwrap_or_else(ProxyOptions::new);
155         unsafe {
156             try_call!(raw::git_remote_connect(
157                 self.raw,
158                 dir,
159                 &cb.raw(),
160                 &proxy_options.raw(),
161                 ptr::null()
162             ));
163         }
164 
165         Ok(RemoteConnection {
166             _callbacks: cb,
167             _proxy: proxy_options,
168             remote: self,
169         })
170     }
171 
172     /// Check whether the remote is connected
connected(&mut self) -> bool173     pub fn connected(&mut self) -> bool {
174         unsafe { raw::git_remote_connected(self.raw) == 1 }
175     }
176 
177     /// Disconnect from the remote
disconnect(&mut self) -> Result<(), Error>178     pub fn disconnect(&mut self) -> Result<(), Error> {
179         unsafe {
180             try_call!(raw::git_remote_disconnect(self.raw));
181         }
182         Ok(())
183     }
184 
185     /// Download and index the packfile
186     ///
187     /// Connect to the remote if it hasn't been done yet, negotiate with the
188     /// remote git which objects are missing, download and index the packfile.
189     ///
190     /// The .idx file will be created and both it and the packfile with be
191     /// renamed to their final name.
192     ///
193     /// The `specs` argument is a list of refspecs to use for this negotiation
194     /// and download. Use an empty array to use the base refspecs.
download<Str: AsRef<str> + crate::IntoCString + Clone>( &mut self, specs: &[Str], opts: Option<&mut FetchOptions<'_>>, ) -> Result<(), Error>195     pub fn download<Str: AsRef<str> + crate::IntoCString + Clone>(
196         &mut self,
197         specs: &[Str],
198         opts: Option<&mut FetchOptions<'_>>,
199     ) -> Result<(), Error> {
200         let (_a, _b, arr) = crate::util::iter2cstrs(specs.iter())?;
201         let raw = opts.map(|o| o.raw());
202         unsafe {
203             try_call!(raw::git_remote_download(self.raw, &arr, raw.as_ref()));
204         }
205         Ok(())
206     }
207 
208     /// Get the number of refspecs for a remote
refspecs(&self) -> Refspecs<'_>209     pub fn refspecs(&self) -> Refspecs<'_> {
210         let cnt = unsafe { raw::git_remote_refspec_count(&*self.raw) as usize };
211         Refspecs {
212             range: 0..cnt,
213             remote: self,
214         }
215     }
216 
217     /// Get the `nth` refspec from this remote.
218     ///
219     /// The `refspecs` iterator can be used to iterate over all refspecs.
get_refspec(&self, i: usize) -> Option<Refspec<'repo>>220     pub fn get_refspec(&self, i: usize) -> Option<Refspec<'repo>> {
221         unsafe {
222             let ptr = raw::git_remote_get_refspec(&*self.raw, i as libc::size_t);
223             Binding::from_raw_opt(ptr)
224         }
225     }
226 
227     /// Download new data and update tips
228     ///
229     /// Convenience function to connect to a remote, download the data,
230     /// disconnect and update the remote-tracking branches.
231     ///
232     /// # Examples
233     ///
234     /// Example of functionality similar to `git fetch origin/master`:
235     ///
236     /// ```no_run
237     /// fn fetch_origin_master(repo: git2::Repository) -> Result<(), git2::Error> {
238     ///     repo.find_remote("origin")?.fetch(&["master"], None, None)
239     /// }
240     ///
241     /// let repo = git2::Repository::discover("rust").unwrap();
242     /// fetch_origin_master(repo).unwrap();
243     /// ```
fetch<Str: AsRef<str> + crate::IntoCString + Clone>( &mut self, refspecs: &[Str], opts: Option<&mut FetchOptions<'_>>, reflog_msg: Option<&str>, ) -> Result<(), Error>244     pub fn fetch<Str: AsRef<str> + crate::IntoCString + Clone>(
245         &mut self,
246         refspecs: &[Str],
247         opts: Option<&mut FetchOptions<'_>>,
248         reflog_msg: Option<&str>,
249     ) -> Result<(), Error> {
250         let (_a, _b, arr) = crate::util::iter2cstrs(refspecs.iter())?;
251         let msg = crate::opt_cstr(reflog_msg)?;
252         let raw = opts.map(|o| o.raw());
253         unsafe {
254             try_call!(raw::git_remote_fetch(self.raw, &arr, raw.as_ref(), msg));
255         }
256         Ok(())
257     }
258 
259     /// Update the tips to the new state
update_tips( &mut self, callbacks: Option<&mut RemoteCallbacks<'_>>, update_fetchhead: bool, download_tags: AutotagOption, msg: Option<&str>, ) -> Result<(), Error>260     pub fn update_tips(
261         &mut self,
262         callbacks: Option<&mut RemoteCallbacks<'_>>,
263         update_fetchhead: bool,
264         download_tags: AutotagOption,
265         msg: Option<&str>,
266     ) -> Result<(), Error> {
267         let msg = crate::opt_cstr(msg)?;
268         let cbs = callbacks.map(|cb| cb.raw());
269         unsafe {
270             try_call!(raw::git_remote_update_tips(
271                 self.raw,
272                 cbs.as_ref(),
273                 update_fetchhead,
274                 download_tags,
275                 msg
276             ));
277         }
278         Ok(())
279     }
280 
281     /// Perform a push
282     ///
283     /// Perform all the steps for a push. If no refspecs are passed then the
284     /// configured refspecs will be used.
285     ///
286     /// Note that you'll likely want to use `RemoteCallbacks` and set
287     /// `push_update_reference` to test whether all the references were pushed
288     /// successfully.
push<Str: AsRef<str> + crate::IntoCString + Clone>( &mut self, refspecs: &[Str], opts: Option<&mut PushOptions<'_>>, ) -> Result<(), Error>289     pub fn push<Str: AsRef<str> + crate::IntoCString + Clone>(
290         &mut self,
291         refspecs: &[Str],
292         opts: Option<&mut PushOptions<'_>>,
293     ) -> Result<(), Error> {
294         let (_a, _b, arr) = crate::util::iter2cstrs(refspecs.iter())?;
295         let raw = opts.map(|o| o.raw());
296         unsafe {
297             try_call!(raw::git_remote_push(self.raw, &arr, raw.as_ref()));
298         }
299         Ok(())
300     }
301 
302     /// Get the statistics structure that is filled in by the fetch operation.
stats(&self) -> Progress<'_>303     pub fn stats(&self) -> Progress<'_> {
304         unsafe { Binding::from_raw(raw::git_remote_stats(self.raw)) }
305     }
306 
307     /// Get the remote repository's reference advertisement list.
308     ///
309     /// Get the list of references with which the server responds to a new
310     /// connection.
311     ///
312     /// The remote (or more exactly its transport) must have connected to the
313     /// remote repository. This list is available as soon as the connection to
314     /// the remote is initiated and it remains available after disconnecting.
list(&self) -> Result<&[RemoteHead<'_>], Error>315     pub fn list(&self) -> Result<&[RemoteHead<'_>], Error> {
316         let mut size = 0;
317         let mut base = ptr::null_mut();
318         unsafe {
319             try_call!(raw::git_remote_ls(&mut base, &mut size, self.raw));
320             assert_eq!(
321                 mem::size_of::<RemoteHead<'_>>(),
322                 mem::size_of::<*const raw::git_remote_head>()
323             );
324             let slice = slice::from_raw_parts(base as *const _, size as usize);
325             Ok(mem::transmute::<
326                 &[*const raw::git_remote_head],
327                 &[RemoteHead<'_>],
328             >(slice))
329         }
330     }
331 
332     /// Get the remote's list of fetch refspecs
fetch_refspecs(&self) -> Result<StringArray, Error>333     pub fn fetch_refspecs(&self) -> Result<StringArray, Error> {
334         unsafe {
335             let mut raw: raw::git_strarray = mem::zeroed();
336             try_call!(raw::git_remote_get_fetch_refspecs(&mut raw, self.raw));
337             Ok(StringArray::from_raw(raw))
338         }
339     }
340 
341     /// Get the remote's list of push refspecs
push_refspecs(&self) -> Result<StringArray, Error>342     pub fn push_refspecs(&self) -> Result<StringArray, Error> {
343         unsafe {
344             let mut raw: raw::git_strarray = mem::zeroed();
345             try_call!(raw::git_remote_get_push_refspecs(&mut raw, self.raw));
346             Ok(StringArray::from_raw(raw))
347         }
348     }
349 }
350 
351 impl<'repo> Clone for Remote<'repo> {
clone(&self) -> Remote<'repo>352     fn clone(&self) -> Remote<'repo> {
353         let mut ret = ptr::null_mut();
354         let rc = unsafe { call!(raw::git_remote_dup(&mut ret, self.raw)) };
355         assert_eq!(rc, 0);
356         Remote {
357             raw: ret,
358             _marker: marker::PhantomData,
359         }
360     }
361 }
362 
363 impl<'repo> Binding for Remote<'repo> {
364     type Raw = *mut raw::git_remote;
365 
from_raw(raw: *mut raw::git_remote) -> Remote<'repo>366     unsafe fn from_raw(raw: *mut raw::git_remote) -> Remote<'repo> {
367         Remote {
368             raw: raw,
369             _marker: marker::PhantomData,
370         }
371     }
raw(&self) -> *mut raw::git_remote372     fn raw(&self) -> *mut raw::git_remote {
373         self.raw
374     }
375 }
376 
377 impl<'repo> Drop for Remote<'repo> {
drop(&mut self)378     fn drop(&mut self) {
379         unsafe { raw::git_remote_free(self.raw) }
380     }
381 }
382 
383 impl<'repo> Iterator for Refspecs<'repo> {
384     type Item = Refspec<'repo>;
next(&mut self) -> Option<Refspec<'repo>>385     fn next(&mut self) -> Option<Refspec<'repo>> {
386         self.range.next().and_then(|i| self.remote.get_refspec(i))
387     }
size_hint(&self) -> (usize, Option<usize>)388     fn size_hint(&self) -> (usize, Option<usize>) {
389         self.range.size_hint()
390     }
391 }
392 impl<'repo> DoubleEndedIterator for Refspecs<'repo> {
next_back(&mut self) -> Option<Refspec<'repo>>393     fn next_back(&mut self) -> Option<Refspec<'repo>> {
394         self.range
395             .next_back()
396             .and_then(|i| self.remote.get_refspec(i))
397     }
398 }
399 impl<'repo> ExactSizeIterator for Refspecs<'repo> {}
400 
401 #[allow(missing_docs)] // not documented in libgit2 :(
402 impl<'remote> RemoteHead<'remote> {
403     /// Flag if this is available locally.
is_local(&self) -> bool404     pub fn is_local(&self) -> bool {
405         unsafe { (*self.raw).local != 0 }
406     }
407 
oid(&self) -> Oid408     pub fn oid(&self) -> Oid {
409         unsafe { Binding::from_raw(&(*self.raw).oid as *const _) }
410     }
loid(&self) -> Oid411     pub fn loid(&self) -> Oid {
412         unsafe { Binding::from_raw(&(*self.raw).loid as *const _) }
413     }
414 
name(&self) -> &str415     pub fn name(&self) -> &str {
416         let b = unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() };
417         str::from_utf8(b).unwrap()
418     }
419 
symref_target(&self) -> Option<&str>420     pub fn symref_target(&self) -> Option<&str> {
421         let b = unsafe { crate::opt_bytes(self, (*self.raw).symref_target) };
422         b.map(|b| str::from_utf8(b).unwrap())
423     }
424 }
425 
426 impl<'cb> Default for FetchOptions<'cb> {
default() -> Self427     fn default() -> Self {
428         Self::new()
429     }
430 }
431 
432 impl<'cb> FetchOptions<'cb> {
433     /// Creates a new blank set of fetch options
new() -> FetchOptions<'cb>434     pub fn new() -> FetchOptions<'cb> {
435         FetchOptions {
436             callbacks: None,
437             proxy: None,
438             prune: FetchPrune::Unspecified,
439             update_fetchhead: true,
440             download_tags: AutotagOption::Unspecified,
441         }
442     }
443 
444     /// Set the callbacks to use for the fetch operation.
remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self445     pub fn remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self {
446         self.callbacks = Some(cbs);
447         self
448     }
449 
450     /// Set the proxy options to use for the fetch operation.
proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self451     pub fn proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self {
452         self.proxy = Some(opts);
453         self
454     }
455 
456     /// Set whether to perform a prune after the fetch.
prune(&mut self, prune: FetchPrune) -> &mut Self457     pub fn prune(&mut self, prune: FetchPrune) -> &mut Self {
458         self.prune = prune;
459         self
460     }
461 
462     /// Set whether to write the results to FETCH_HEAD.
463     ///
464     /// Defaults to `true`.
update_fetchhead(&mut self, update: bool) -> &mut Self465     pub fn update_fetchhead(&mut self, update: bool) -> &mut Self {
466         self.update_fetchhead = update;
467         self
468     }
469 
470     /// Set how to behave regarding tags on the remote, such as auto-downloading
471     /// tags for objects we're downloading or downloading all of them.
472     ///
473     /// The default is to auto-follow tags.
download_tags(&mut self, opt: AutotagOption) -> &mut Self474     pub fn download_tags(&mut self, opt: AutotagOption) -> &mut Self {
475         self.download_tags = opt;
476         self
477     }
478 }
479 
480 impl<'cb> Binding for FetchOptions<'cb> {
481     type Raw = raw::git_fetch_options;
482 
from_raw(_raw: raw::git_fetch_options) -> FetchOptions<'cb>483     unsafe fn from_raw(_raw: raw::git_fetch_options) -> FetchOptions<'cb> {
484         panic!("unimplemented");
485     }
raw(&self) -> raw::git_fetch_options486     fn raw(&self) -> raw::git_fetch_options {
487         raw::git_fetch_options {
488             version: 1,
489             callbacks: self
490                 .callbacks
491                 .as_ref()
492                 .map(|m| m.raw())
493                 .unwrap_or_else(|| RemoteCallbacks::new().raw()),
494             proxy_opts: self
495                 .proxy
496                 .as_ref()
497                 .map(|m| m.raw())
498                 .unwrap_or_else(|| ProxyOptions::new().raw()),
499             prune: crate::call::convert(&self.prune),
500             update_fetchhead: crate::call::convert(&self.update_fetchhead),
501             download_tags: crate::call::convert(&self.download_tags),
502             // TODO: expose this as a builder option
503             custom_headers: raw::git_strarray {
504                 count: 0,
505                 strings: ptr::null_mut(),
506             },
507         }
508     }
509 }
510 
511 impl<'cb> Default for PushOptions<'cb> {
default() -> Self512     fn default() -> Self {
513         Self::new()
514     }
515 }
516 
517 impl<'cb> PushOptions<'cb> {
518     /// Creates a new blank set of push options
new() -> PushOptions<'cb>519     pub fn new() -> PushOptions<'cb> {
520         PushOptions {
521             callbacks: None,
522             proxy: None,
523             pb_parallelism: 1,
524         }
525     }
526 
527     /// Set the callbacks to use for the fetch operation.
remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self528     pub fn remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self {
529         self.callbacks = Some(cbs);
530         self
531     }
532 
533     /// Set the proxy options to use for the fetch operation.
proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self534     pub fn proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self {
535         self.proxy = Some(opts);
536         self
537     }
538 
539     /// If the transport being used to push to the remote requires the creation
540     /// of a pack file, this controls the number of worker threads used by the
541     /// packbuilder when creating that pack file to be sent to the remote.
542     ///
543     /// if set to 0 the packbuilder will auto-detect the number of threads to
544     /// create, and the default value is 1.
packbuilder_parallelism(&mut self, parallel: u32) -> &mut Self545     pub fn packbuilder_parallelism(&mut self, parallel: u32) -> &mut Self {
546         self.pb_parallelism = parallel;
547         self
548     }
549 }
550 
551 impl<'cb> Binding for PushOptions<'cb> {
552     type Raw = raw::git_push_options;
553 
from_raw(_raw: raw::git_push_options) -> PushOptions<'cb>554     unsafe fn from_raw(_raw: raw::git_push_options) -> PushOptions<'cb> {
555         panic!("unimplemented");
556     }
raw(&self) -> raw::git_push_options557     fn raw(&self) -> raw::git_push_options {
558         raw::git_push_options {
559             version: 1,
560             callbacks: self
561                 .callbacks
562                 .as_ref()
563                 .map(|m| m.raw())
564                 .unwrap_or_else(|| RemoteCallbacks::new().raw()),
565             proxy_opts: self
566                 .proxy
567                 .as_ref()
568                 .map(|m| m.raw())
569                 .unwrap_or_else(|| ProxyOptions::new().raw()),
570             pb_parallelism: self.pb_parallelism as libc::c_uint,
571             // TODO: expose this as a builder option
572             custom_headers: raw::git_strarray {
573                 count: 0,
574                 strings: ptr::null_mut(),
575             },
576         }
577     }
578 }
579 
580 impl<'repo, 'connection, 'cb> RemoteConnection<'repo, 'connection, 'cb> {
581     /// Check whether the remote is (still) connected
connected(&mut self) -> bool582     pub fn connected(&mut self) -> bool {
583         self.remote.connected()
584     }
585 
586     /// Get the remote repository's reference advertisement list.
587     ///
588     /// This list is available as soon as the connection to
589     /// the remote is initiated and it remains available after disconnecting.
list(&self) -> Result<&[RemoteHead<'_>], Error>590     pub fn list(&self) -> Result<&[RemoteHead<'_>], Error> {
591         self.remote.list()
592     }
593 
594     /// Get the remote's default branch.
595     ///
596     /// This default branch is available as soon as the connection to the remote
597     /// is initiated and it remains available after disconnecting.
default_branch(&self) -> Result<Buf, Error>598     pub fn default_branch(&self) -> Result<Buf, Error> {
599         self.remote.default_branch()
600     }
601 }
602 
603 impl<'repo, 'connection, 'cb> Drop for RemoteConnection<'repo, 'connection, 'cb> {
drop(&mut self)604     fn drop(&mut self) {
605         drop(self.remote.disconnect());
606     }
607 }
608 
609 #[cfg(test)]
610 mod tests {
611     use crate::{AutotagOption, PushOptions};
612     use crate::{Direction, FetchOptions, Remote, RemoteCallbacks, Repository};
613     use std::cell::Cell;
614     use tempfile::TempDir;
615 
616     #[test]
smoke()617     fn smoke() {
618         let (td, repo) = crate::test::repo_init();
619         t!(repo.remote("origin", "/path/to/nowhere"));
620         drop(repo);
621 
622         let repo = t!(Repository::init(td.path()));
623         let origin = t!(repo.find_remote("origin"));
624         assert_eq!(origin.name(), Some("origin"));
625         assert_eq!(origin.url(), Some("/path/to/nowhere"));
626         assert_eq!(origin.pushurl(), None);
627 
628         t!(repo.remote_set_url("origin", "/path/to/elsewhere"));
629         t!(repo.remote_set_pushurl("origin", Some("/path/to/elsewhere")));
630 
631         let stats = origin.stats();
632         assert_eq!(stats.total_objects(), 0);
633     }
634 
635     #[test]
create_remote()636     fn create_remote() {
637         let td = TempDir::new().unwrap();
638         let remote = td.path().join("remote");
639         Repository::init_bare(&remote).unwrap();
640 
641         let (_td, repo) = crate::test::repo_init();
642         let url = if cfg!(unix) {
643             format!("file://{}", remote.display())
644         } else {
645             format!(
646                 "file:///{}",
647                 remote.display().to_string().replace("\\", "/")
648             )
649         };
650 
651         let mut origin = repo.remote("origin", &url).unwrap();
652         assert_eq!(origin.name(), Some("origin"));
653         assert_eq!(origin.url(), Some(&url[..]));
654         assert_eq!(origin.pushurl(), None);
655 
656         {
657             let mut specs = origin.refspecs();
658             let spec = specs.next().unwrap();
659             assert!(specs.next().is_none());
660             assert_eq!(spec.str(), Some("+refs/heads/*:refs/remotes/origin/*"));
661             assert_eq!(spec.dst(), Some("refs/remotes/origin/*"));
662             assert_eq!(spec.src(), Some("refs/heads/*"));
663             assert!(spec.is_force());
664         }
665         assert!(origin.refspecs().next_back().is_some());
666         {
667             let remotes = repo.remotes().unwrap();
668             assert_eq!(remotes.len(), 1);
669             assert_eq!(remotes.get(0), Some("origin"));
670             assert_eq!(remotes.iter().count(), 1);
671             assert_eq!(remotes.iter().next().unwrap(), Some("origin"));
672         }
673 
674         origin.connect(Direction::Push).unwrap();
675         assert!(origin.connected());
676         origin.disconnect().unwrap();
677 
678         origin.connect(Direction::Fetch).unwrap();
679         assert!(origin.connected());
680         origin.download(&[] as &[&str], None).unwrap();
681         origin.disconnect().unwrap();
682 
683         {
684             let mut connection = origin.connect_auth(Direction::Push, None, None).unwrap();
685             assert!(connection.connected());
686         }
687         assert!(!origin.connected());
688 
689         {
690             let mut connection = origin.connect_auth(Direction::Fetch, None, None).unwrap();
691             assert!(connection.connected());
692         }
693         assert!(!origin.connected());
694 
695         origin.fetch(&[] as &[&str], None, None).unwrap();
696         origin.fetch(&[] as &[&str], None, Some("foo")).unwrap();
697         origin
698             .update_tips(None, true, AutotagOption::Unspecified, None)
699             .unwrap();
700         origin
701             .update_tips(None, true, AutotagOption::All, Some("foo"))
702             .unwrap();
703 
704         t!(repo.remote_add_fetch("origin", "foo"));
705         t!(repo.remote_add_fetch("origin", "bar"));
706     }
707 
708     #[test]
709     fn rename_remote() {
710         let (_td, repo) = crate::test::repo_init();
711         repo.remote("origin", "foo").unwrap();
712         drop(repo.remote_rename("origin", "foo"));
713         drop(repo.remote_delete("foo"));
714     }
715 
716     #[test]
717     fn create_remote_anonymous() {
718         let td = TempDir::new().unwrap();
719         let repo = Repository::init(td.path()).unwrap();
720 
721         let origin = repo.remote_anonymous("/path/to/nowhere").unwrap();
722         assert_eq!(origin.name(), None);
723         drop(origin.clone());
724     }
725 
726     #[test]
727     fn is_valid() {
728         assert!(Remote::is_valid_name("foobar"));
729         assert!(!Remote::is_valid_name("\x01"));
730     }
731 
732     #[test]
733     fn transfer_cb() {
734         let (td, _repo) = crate::test::repo_init();
735         let td2 = TempDir::new().unwrap();
736         let url = crate::test::path2url(&td.path());
737 
738         let repo = Repository::init(td2.path()).unwrap();
739         let progress_hit = Cell::new(false);
740         {
741             let mut callbacks = RemoteCallbacks::new();
742             let mut origin = repo.remote("origin", &url).unwrap();
743 
744             callbacks.transfer_progress(|_progress| {
745                 progress_hit.set(true);
746                 true
747             });
748             origin
749                 .fetch(
750                     &[] as &[&str],
751                     Some(FetchOptions::new().remote_callbacks(callbacks)),
752                     None,
753                 )
754                 .unwrap();
755 
756             let list = t!(origin.list());
757             assert_eq!(list.len(), 2);
758             assert_eq!(list[0].name(), "HEAD");
759             assert!(!list[0].is_local());
760             assert_eq!(list[1].name(), "refs/heads/master");
761             assert!(!list[1].is_local());
762         }
763         assert!(progress_hit.get());
764     }
765 
766     /// This test is meant to assure that the callbacks provided to connect will not cause
767     /// segfaults
768     #[test]
769     fn connect_list() {
770         let (td, _repo) = crate::test::repo_init();
771         let td2 = TempDir::new().unwrap();
772         let url = crate::test::path2url(&td.path());
773 
774         let repo = Repository::init(td2.path()).unwrap();
775         let mut callbacks = RemoteCallbacks::new();
776         callbacks.sideband_progress(|_progress| {
777             // no-op
778             true
779         });
780 
781         let mut origin = repo.remote("origin", &url).unwrap();
782 
783         {
784             let mut connection = origin
785                 .connect_auth(Direction::Fetch, Some(callbacks), None)
786                 .unwrap();
787             assert!(connection.connected());
788 
789             let list = t!(connection.list());
790             assert_eq!(list.len(), 2);
791             assert_eq!(list[0].name(), "HEAD");
792             assert!(!list[0].is_local());
793             assert_eq!(list[1].name(), "refs/heads/master");
794             assert!(!list[1].is_local());
795         }
796         assert!(!origin.connected());
797     }
798 
799     #[test]
800     fn push() {
801         let (_td, repo) = crate::test::repo_init();
802         let td2 = TempDir::new().unwrap();
803         let td3 = TempDir::new().unwrap();
804         let url = crate::test::path2url(&td2.path());
805 
806         Repository::init_bare(td2.path()).unwrap();
807         // git push
808         let mut remote = repo.remote("origin", &url).unwrap();
809         let mut updated = false;
810         {
811             let mut callbacks = RemoteCallbacks::new();
812             callbacks.push_update_reference(|refname, status| {
813                 updated = true;
814                 assert_eq!(refname, "refs/heads/master");
815                 assert_eq!(status, None);
816                 Ok(())
817             });
818             let mut options = PushOptions::new();
819             options.remote_callbacks(callbacks);
820             remote
821                 .push(&["refs/heads/master"], Some(&mut options))
822                 .unwrap();
823         }
824         assert!(updated);
825 
826         let repo = Repository::clone(&url, td3.path()).unwrap();
827         let commit = repo.head().unwrap().target().unwrap();
828         let commit = repo.find_commit(commit).unwrap();
829         assert_eq!(commit.message(), Some("initial"));
830     }
831 }
832