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