1use raw::git_strarray;
2use std::iter::FusedIterator;
3use std::marker;
4use std::mem;
5use std::ops::Range;
6use std::os::raw::c_uint;
7use std::ptr;
8use std::slice;
9use std::str;
10use std::{ffi::CString, os::raw::c_char};
11
12use crate::string_array::StringArray;
13use crate::util::Binding;
14use crate::{call, raw, Buf, Direction, Error, FetchPrune, Oid, ProxyOptions, Refspec};
15use crate::{AutotagOption, Progress, RemoteCallbacks, RemoteUpdateFlags, Repository};
16
17pub struct Remote<'repo> {
24 raw: *mut raw::git_remote,
25 _marker: marker::PhantomData<&'repo Repository>,
26}
27
28pub struct Refspecs<'remote> {
30 range: Range<usize>,
31 remote: &'remote Remote<'remote>,
32}
33
34pub struct RemoteHead<'remote> {
37 raw: *const raw::git_remote_head,
38 _marker: marker::PhantomData<&'remote str>,
39}
40
41pub struct FetchOptions<'cb> {
43 callbacks: Option<RemoteCallbacks<'cb>>,
44 depth: i32,
45 proxy: Option<ProxyOptions<'cb>>,
46 prune: FetchPrune,
47 update_flags: RemoteUpdateFlags,
48 download_tags: AutotagOption,
49 follow_redirects: RemoteRedirect,
50 custom_headers: Vec<CString>,
51 custom_headers_ptrs: Vec<*const c_char>,
52}
53
54pub struct PushOptions<'cb> {
56 callbacks: Option<RemoteCallbacks<'cb>>,
57 proxy: Option<ProxyOptions<'cb>>,
58 pb_parallelism: u32,
59 follow_redirects: RemoteRedirect,
60 custom_headers: Vec<CString>,
61 custom_headers_ptrs: Vec<*const c_char>,
62 remote_push_options: Vec<CString>,
63 remote_push_options_ptrs: Vec<*const c_char>,
64}
65
66pub struct RemoteConnection<'repo, 'connection, 'cb> {
68 _callbacks: Box<RemoteCallbacks<'cb>>,
69 _proxy: ProxyOptions<'cb>,
70 remote: &'connection mut Remote<'repo>,
71}
72
73pub enum RemoteRedirect {
79 None,
81 Initial,
84 All,
86}
87
88pub fn remote_into_raw(remote: Remote<'_>) -> *mut raw::git_remote {
89 let ret = remote.raw;
90 mem::forget(remote);
91 ret
92}
93
94impl<'repo> Remote<'repo> {
95 pub fn is_valid_name(remote_name: &str) -> bool {
97 crate::init();
98 let remote_name = CString::new(remote_name).unwrap();
99 let mut valid: libc::c_int = 0;
100 unsafe {
101 call::c_try(raw::git_remote_name_is_valid(
102 &mut valid,
103 remote_name.as_ptr(),
104 ))
105 .unwrap();
106 }
107 valid == 1
108 }
109
110 pub fn create_detached<S: Into<Vec<u8>>>(url: S) -> Result<Remote<'repo>, Error> {
117 crate::init();
118 let mut ret = ptr::null_mut();
119 let url = CString::new(url)?;
120 unsafe {
121 try_call!(raw::git_remote_create_detached(&mut ret, url));
122 Ok(Binding::from_raw(ret))
123 }
124 }
125
126 pub fn name(&self) -> Option<&str> {
131 self.name_bytes().and_then(|s| str::from_utf8(s).ok())
132 }
133
134 pub fn name_bytes(&self) -> Option<&[u8]> {
138 unsafe { crate::opt_bytes(self, raw::git_remote_name(&*self.raw)) }
139 }
140
141 pub fn url(&self) -> Option<&str> {
145 str::from_utf8(self.url_bytes()).ok()
146 }
147
148 pub fn url_bytes(&self) -> &[u8] {
150 unsafe { crate::opt_bytes(self, raw::git_remote_url(&*self.raw)).unwrap() }
151 }
152
153 pub fn pushurl(&self) -> Option<&str> {
157 self.pushurl_bytes().and_then(|s| str::from_utf8(s).ok())
158 }
159
160 pub fn pushurl_bytes(&self) -> Option<&[u8]> {
162 unsafe { crate::opt_bytes(self, raw::git_remote_pushurl(&*self.raw)) }
163 }
164
165 pub fn default_branch(&self) -> Result<Buf, Error> {
172 unsafe {
173 let buf = Buf::new();
174 try_call!(raw::git_remote_default_branch(buf.raw(), self.raw));
175 Ok(buf)
176 }
177 }
178
179 pub fn connect(&mut self, dir: Direction) -> Result<(), Error> {
181 unsafe {
183 try_call!(raw::git_remote_connect(
184 self.raw,
185 dir,
186 ptr::null(),
187 ptr::null(),
188 ptr::null()
189 ));
190 }
191 Ok(())
192 }
193
194 pub fn connect_auth<'connection, 'cb>(
198 &'connection mut self,
199 dir: Direction,
200 cb: Option<RemoteCallbacks<'cb>>,
201 proxy_options: Option<ProxyOptions<'cb>>,
202 ) -> Result<RemoteConnection<'repo, 'connection, 'cb>, Error> {
203 let cb = Box::new(cb.unwrap_or_else(RemoteCallbacks::new));
204 let proxy_options = proxy_options.unwrap_or_else(ProxyOptions::new);
205 unsafe {
206 try_call!(raw::git_remote_connect(
207 self.raw,
208 dir,
209 &cb.raw(),
210 &proxy_options.raw(),
211 ptr::null()
212 ));
213 }
214
215 Ok(RemoteConnection {
216 _callbacks: cb,
217 _proxy: proxy_options,
218 remote: self,
219 })
220 }
221
222 pub fn connected(&mut self) -> bool {
224 unsafe { raw::git_remote_connected(self.raw) == 1 }
225 }
226
227 pub fn disconnect(&mut self) -> Result<(), Error> {
229 unsafe {
230 try_call!(raw::git_remote_disconnect(self.raw));
231 }
232 Ok(())
233 }
234
235 pub fn download<Str: AsRef<str> + crate::IntoCString + Clone>(
246 &mut self,
247 specs: &[Str],
248 opts: Option<&mut FetchOptions<'_>>,
249 ) -> Result<(), Error> {
250 let (_a, _b, arr) = crate::util::iter2cstrs(specs.iter())?;
251 let raw = opts.map(|o| o.raw());
252 unsafe {
253 try_call!(raw::git_remote_download(self.raw, &arr, raw.as_ref()));
254 }
255 Ok(())
256 }
257
258 pub fn stop(&mut self) -> Result<(), Error> {
263 unsafe {
264 try_call!(raw::git_remote_stop(self.raw));
265 }
266 Ok(())
267 }
268
269 pub fn refspecs(&self) -> Refspecs<'_> {
271 let cnt = unsafe { raw::git_remote_refspec_count(&*self.raw) as usize };
272 Refspecs {
273 range: 0..cnt,
274 remote: self,
275 }
276 }
277
278 pub fn get_refspec(&self, i: usize) -> Option<Refspec<'repo>> {
282 unsafe {
283 let ptr = raw::git_remote_get_refspec(&*self.raw, i as libc::size_t);
284 Binding::from_raw_opt(ptr)
285 }
286 }
287
288 pub fn fetch<Str: AsRef<str> + crate::IntoCString + Clone>(
306 &mut self,
307 refspecs: &[Str],
308 opts: Option<&mut FetchOptions<'_>>,
309 reflog_msg: Option<&str>,
310 ) -> Result<(), Error> {
311 let (_a, _b, arr) = crate::util::iter2cstrs(refspecs.iter())?;
312 let msg = crate::opt_cstr(reflog_msg)?;
313 let raw = opts.map(|o| o.raw());
314 unsafe {
315 try_call!(raw::git_remote_fetch(self.raw, &arr, raw.as_ref(), msg));
316 }
317 Ok(())
318 }
319
320 pub fn update_tips(
322 &mut self,
323 callbacks: Option<&mut RemoteCallbacks<'_>>,
324 update_flags: RemoteUpdateFlags,
325 download_tags: AutotagOption,
326 msg: Option<&str>,
327 ) -> Result<(), Error> {
328 let msg = crate::opt_cstr(msg)?;
329 let cbs = callbacks.map(|cb| cb.raw());
330 unsafe {
331 try_call!(raw::git_remote_update_tips(
332 self.raw,
333 cbs.as_ref(),
334 update_flags.bits() as c_uint,
335 download_tags,
336 msg
337 ));
338 }
339 Ok(())
340 }
341
342 pub fn push<Str: AsRef<str> + crate::IntoCString + Clone>(
351 &mut self,
352 refspecs: &[Str],
353 opts: Option<&mut PushOptions<'_>>,
354 ) -> Result<(), Error> {
355 let (_a, _b, arr) = crate::util::iter2cstrs(refspecs.iter())?;
356 let raw = opts.map(|o| o.raw());
357 unsafe {
358 try_call!(raw::git_remote_push(self.raw, &arr, raw.as_ref()));
359 }
360 Ok(())
361 }
362
363 pub fn stats(&self) -> Progress<'_> {
365 unsafe { Binding::from_raw(raw::git_remote_stats(self.raw)) }
366 }
367
368 pub fn list(&self) -> Result<&[RemoteHead<'_>], Error> {
377 let mut size = 0;
378 let mut base = ptr::null_mut();
379 unsafe {
380 try_call!(raw::git_remote_ls(&mut base, &mut size, self.raw));
381 assert_eq!(
382 mem::size_of::<RemoteHead<'_>>(),
383 mem::size_of::<*const raw::git_remote_head>()
384 );
385 let slice = slice::from_raw_parts(base as *const _, size as usize);
386 Ok(mem::transmute::<
387 &[*const raw::git_remote_head],
388 &[RemoteHead<'_>],
389 >(slice))
390 }
391 }
392
393 pub fn prune(&mut self, callbacks: Option<RemoteCallbacks<'_>>) -> Result<(), Error> {
395 let cbs = Box::new(callbacks.unwrap_or_else(RemoteCallbacks::new));
396 unsafe {
397 try_call!(raw::git_remote_prune(self.raw, &cbs.raw()));
398 }
399 Ok(())
400 }
401
402 pub fn fetch_refspecs(&self) -> Result<StringArray, Error> {
404 unsafe {
405 let mut raw: raw::git_strarray = mem::zeroed();
406 try_call!(raw::git_remote_get_fetch_refspecs(&mut raw, self.raw));
407 Ok(StringArray::from_raw(raw))
408 }
409 }
410
411 pub fn push_refspecs(&self) -> Result<StringArray, Error> {
413 unsafe {
414 let mut raw: raw::git_strarray = mem::zeroed();
415 try_call!(raw::git_remote_get_push_refspecs(&mut raw, self.raw));
416 Ok(StringArray::from_raw(raw))
417 }
418 }
419}
420
421impl<'repo> Clone for Remote<'repo> {
422 fn clone(&self) -> Remote<'repo> {
423 let mut ret = ptr::null_mut();
424 let rc = unsafe { call!(raw::git_remote_dup(&mut ret, self.raw)) };
425 assert_eq!(rc, 0);
426 Remote {
427 raw: ret,
428 _marker: marker::PhantomData,
429 }
430 }
431}
432
433impl<'repo> Binding for Remote<'repo> {
434 type Raw = *mut raw::git_remote;
435
436 unsafe fn from_raw(raw: *mut raw::git_remote) -> Remote<'repo> {
437 Remote {
438 raw,
439 _marker: marker::PhantomData,
440 }
441 }
442 fn raw(&self) -> *mut raw::git_remote {
443 self.raw
444 }
445}
446
447impl<'repo> Drop for Remote<'repo> {
448 fn drop(&mut self) {
449 unsafe { raw::git_remote_free(self.raw) }
450 }
451}
452
453impl<'repo> Iterator for Refspecs<'repo> {
454 type Item = Refspec<'repo>;
455 fn next(&mut self) -> Option<Refspec<'repo>> {
456 self.range.next().and_then(|i| self.remote.get_refspec(i))
457 }
458 fn size_hint(&self) -> (usize, Option<usize>) {
459 self.range.size_hint()
460 }
461}
462impl<'repo> DoubleEndedIterator for Refspecs<'repo> {
463 fn next_back(&mut self) -> Option<Refspec<'repo>> {
464 self.range
465 .next_back()
466 .and_then(|i| self.remote.get_refspec(i))
467 }
468}
469impl<'repo> FusedIterator for Refspecs<'repo> {}
470impl<'repo> ExactSizeIterator for Refspecs<'repo> {}
471
472#[allow(missing_docs)] impl<'remote> RemoteHead<'remote> {
474 pub fn is_local(&self) -> bool {
476 unsafe { (*self.raw).local != 0 }
477 }
478
479 pub fn oid(&self) -> Oid {
480 unsafe { Binding::from_raw(&(*self.raw).oid as *const _) }
481 }
482 pub fn loid(&self) -> Oid {
483 unsafe { Binding::from_raw(&(*self.raw).loid as *const _) }
484 }
485
486 pub fn name(&self) -> &str {
487 let b = unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() };
488 str::from_utf8(b).unwrap()
489 }
490
491 pub fn symref_target(&self) -> Option<&str> {
492 let b = unsafe { crate::opt_bytes(self, (*self.raw).symref_target) };
493 b.map(|b| str::from_utf8(b).unwrap())
494 }
495}
496
497impl<'cb> Default for FetchOptions<'cb> {
498 fn default() -> Self {
499 Self::new()
500 }
501}
502
503impl<'cb> FetchOptions<'cb> {
504 pub fn new() -> FetchOptions<'cb> {
506 FetchOptions {
507 callbacks: None,
508 proxy: None,
509 prune: FetchPrune::Unspecified,
510 update_flags: RemoteUpdateFlags::UPDATE_FETCHHEAD,
511 download_tags: AutotagOption::Unspecified,
512 follow_redirects: RemoteRedirect::Initial,
513 custom_headers: Vec::new(),
514 custom_headers_ptrs: Vec::new(),
515 depth: 0, }
517 }
518
519 pub fn remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self {
521 self.callbacks = Some(cbs);
522 self
523 }
524
525 pub fn proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self {
527 self.proxy = Some(opts);
528 self
529 }
530
531 pub fn prune(&mut self, prune: FetchPrune) -> &mut Self {
533 self.prune = prune;
534 self
535 }
536
537 pub fn update_fetchhead(&mut self, update: bool) -> &mut Self {
541 self.update_flags
542 .set(RemoteUpdateFlags::UPDATE_FETCHHEAD, update);
543 self
544 }
545
546 pub fn report_unchanged(&mut self, update: bool) -> &mut Self {
550 self.update_flags
551 .set(RemoteUpdateFlags::REPORT_UNCHANGED, update);
552 self
553 }
554
555 pub fn depth(&mut self, depth: i32) -> &mut Self {
562 self.depth = depth.max(0);
563 self
564 }
565
566 pub fn download_tags(&mut self, opt: AutotagOption) -> &mut Self {
571 self.download_tags = opt;
572 self
573 }
574
575 pub fn follow_redirects(&mut self, redirect: RemoteRedirect) -> &mut Self {
581 self.follow_redirects = redirect;
582 self
583 }
584
585 pub fn custom_headers(&mut self, custom_headers: &[&str]) -> &mut Self {
587 self.custom_headers = custom_headers
588 .iter()
589 .map(|&s| CString::new(s).unwrap())
590 .collect();
591 self.custom_headers_ptrs = self.custom_headers.iter().map(|s| s.as_ptr()).collect();
592 self
593 }
594}
595
596impl<'cb> Binding for FetchOptions<'cb> {
597 type Raw = raw::git_fetch_options;
598
599 unsafe fn from_raw(_raw: raw::git_fetch_options) -> FetchOptions<'cb> {
600 panic!("unimplemented");
601 }
602 fn raw(&self) -> raw::git_fetch_options {
603 raw::git_fetch_options {
604 version: 1,
605 callbacks: self
606 .callbacks
607 .as_ref()
608 .map(|m| m.raw())
609 .unwrap_or_else(|| RemoteCallbacks::new().raw()),
610 proxy_opts: self
611 .proxy
612 .as_ref()
613 .map(|m| m.raw())
614 .unwrap_or_else(|| ProxyOptions::new().raw()),
615 prune: crate::call::convert(&self.prune),
616 update_fetchhead: self.update_flags.bits() as c_uint,
620 download_tags: crate::call::convert(&self.download_tags),
621 depth: self.depth,
622 follow_redirects: self.follow_redirects.raw(),
623 custom_headers: git_strarray {
624 count: self.custom_headers_ptrs.len(),
625 strings: self.custom_headers_ptrs.as_ptr() as *mut _,
626 },
627 }
628 }
629}
630
631impl<'cb> Default for PushOptions<'cb> {
632 fn default() -> Self {
633 Self::new()
634 }
635}
636
637impl<'cb> PushOptions<'cb> {
638 pub fn new() -> PushOptions<'cb> {
640 PushOptions {
641 callbacks: None,
642 proxy: None,
643 pb_parallelism: 1,
644 follow_redirects: RemoteRedirect::Initial,
645 custom_headers: Vec::new(),
646 custom_headers_ptrs: Vec::new(),
647 remote_push_options: Vec::new(),
648 remote_push_options_ptrs: Vec::new(),
649 }
650 }
651
652 pub fn remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self {
654 self.callbacks = Some(cbs);
655 self
656 }
657
658 pub fn proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self {
660 self.proxy = Some(opts);
661 self
662 }
663
664 pub fn packbuilder_parallelism(&mut self, parallel: u32) -> &mut Self {
671 self.pb_parallelism = parallel;
672 self
673 }
674
675 pub fn follow_redirects(&mut self, redirect: RemoteRedirect) -> &mut Self {
681 self.follow_redirects = redirect;
682 self
683 }
684
685 pub fn custom_headers(&mut self, custom_headers: &[&str]) -> &mut Self {
687 self.custom_headers = custom_headers
688 .iter()
689 .map(|&s| CString::new(s).unwrap())
690 .collect();
691 self.custom_headers_ptrs = self.custom_headers.iter().map(|s| s.as_ptr()).collect();
692 self
693 }
694
695 pub fn remote_push_options(&mut self, remote_push_options: &[&str]) -> &mut Self {
697 self.remote_push_options = remote_push_options
698 .iter()
699 .map(|&s| CString::new(s).unwrap())
700 .collect();
701 self.remote_push_options_ptrs = self
702 .remote_push_options
703 .iter()
704 .map(|s| s.as_ptr())
705 .collect();
706 self
707 }
708}
709
710impl<'cb> Binding for PushOptions<'cb> {
711 type Raw = raw::git_push_options;
712
713 unsafe fn from_raw(_raw: raw::git_push_options) -> PushOptions<'cb> {
714 panic!("unimplemented");
715 }
716 fn raw(&self) -> raw::git_push_options {
717 raw::git_push_options {
718 version: 1,
719 callbacks: self
720 .callbacks
721 .as_ref()
722 .map(|m| m.raw())
723 .unwrap_or_else(|| RemoteCallbacks::new().raw()),
724 proxy_opts: self
725 .proxy
726 .as_ref()
727 .map(|m| m.raw())
728 .unwrap_or_else(|| ProxyOptions::new().raw()),
729 pb_parallelism: self.pb_parallelism as libc::c_uint,
730 follow_redirects: self.follow_redirects.raw(),
731 custom_headers: git_strarray {
732 count: self.custom_headers_ptrs.len(),
733 strings: self.custom_headers_ptrs.as_ptr() as *mut _,
734 },
735 remote_push_options: git_strarray {
736 count: self.remote_push_options.len(),
737 strings: self.remote_push_options_ptrs.as_ptr() as *mut _,
738 },
739 }
740 }
741}
742
743impl<'repo, 'connection, 'cb> RemoteConnection<'repo, 'connection, 'cb> {
744 pub fn connected(&mut self) -> bool {
746 self.remote.connected()
747 }
748
749 pub fn list(&self) -> Result<&[RemoteHead<'_>], Error> {
754 self.remote.list()
755 }
756
757 pub fn default_branch(&self) -> Result<Buf, Error> {
762 self.remote.default_branch()
763 }
764
765 pub fn remote(&mut self) -> &mut Remote<'repo> {
767 self.remote
768 }
769}
770
771impl<'repo, 'connection, 'cb> Drop for RemoteConnection<'repo, 'connection, 'cb> {
772 fn drop(&mut self) {
773 drop(self.remote.disconnect());
774 }
775}
776
777impl Default for RemoteRedirect {
778 fn default() -> Self {
779 RemoteRedirect::Initial
780 }
781}
782
783impl RemoteRedirect {
784 fn raw(&self) -> raw::git_remote_redirect_t {
785 match self {
786 RemoteRedirect::None => raw::GIT_REMOTE_REDIRECT_NONE,
787 RemoteRedirect::Initial => raw::GIT_REMOTE_REDIRECT_INITIAL,
788 RemoteRedirect::All => raw::GIT_REMOTE_REDIRECT_ALL,
789 }
790 }
791}
792
793#[cfg(test)]
794mod tests {
795 use crate::{AutotagOption, PushOptions, RemoteUpdateFlags};
796 use crate::{Direction, FetchOptions, Remote, RemoteCallbacks, Repository};
797 use std::cell::Cell;
798 use tempfile::TempDir;
799
800 #[test]
801 fn smoke() {
802 let (td, repo) = crate::test::repo_init();
803 t!(repo.remote("origin", "/path/to/nowhere"));
804 drop(repo);
805
806 let repo = t!(Repository::init(td.path()));
807 let mut origin = t!(repo.find_remote("origin"));
808 assert_eq!(origin.name(), Some("origin"));
809 assert_eq!(origin.url(), Some("/path/to/nowhere"));
810 assert_eq!(origin.pushurl(), None);
811
812 t!(repo.remote_set_url("origin", "/path/to/elsewhere"));
813 t!(repo.remote_set_pushurl("origin", Some("/path/to/elsewhere")));
814
815 let stats = origin.stats();
816 assert_eq!(stats.total_objects(), 0);
817
818 t!(origin.stop());
819 }
820
821 #[test]
822 fn create_remote() {
823 let td = TempDir::new().unwrap();
824 let remote = td.path().join("remote");
825 Repository::init_bare(&remote).unwrap();
826
827 let (_td, repo) = crate::test::repo_init();
828 let url = if cfg!(unix) {
829 format!("file://{}", remote.display())
830 } else {
831 format!(
832 "file:///{}",
833 remote.display().to_string().replace("\\", "/")
834 )
835 };
836
837 let mut origin = repo.remote("origin", &url).unwrap();
838 assert_eq!(origin.name(), Some("origin"));
839 assert_eq!(origin.url(), Some(&url[..]));
840 assert_eq!(origin.pushurl(), None);
841
842 {
843 let mut specs = origin.refspecs();
844 let spec = specs.next().unwrap();
845 assert!(specs.next().is_none());
846 assert_eq!(spec.str(), Some("+refs/heads/*:refs/remotes/origin/*"));
847 assert_eq!(spec.dst(), Some("refs/remotes/origin/*"));
848 assert_eq!(spec.src(), Some("refs/heads/*"));
849 assert!(spec.is_force());
850 }
851 assert!(origin.refspecs().next_back().is_some());
852 {
853 let remotes = repo.remotes().unwrap();
854 assert_eq!(remotes.len(), 1);
855 assert_eq!(remotes.get(0), Some("origin"));
856 assert_eq!(remotes.iter().count(), 1);
857 assert_eq!(remotes.iter().next().unwrap(), Some("origin"));
858 }
859
860 origin.connect(Direction::Push).unwrap();
861 assert!(origin.connected());
862 origin.disconnect().unwrap();
863
864 origin.connect(Direction::Fetch).unwrap();
865 assert!(origin.connected());
866 origin.download(&[] as &[&str], None).unwrap();
867 origin.disconnect().unwrap();
868
869 {
870 let mut connection = origin.connect_auth(Direction::Push, None, None).unwrap();
871 assert!(connection.connected());
872 }
873 assert!(!origin.connected());
874
875 {
876 let mut connection = origin.connect_auth(Direction::Fetch, None, None).unwrap();
877 assert!(connection.connected());
878 }
879 assert!(!origin.connected());
880
881 origin.fetch(&[] as &[&str], None, None).unwrap();
882 origin.fetch(&[] as &[&str], None, Some("foo")).unwrap();
883 origin
884 .update_tips(
885 None,
886 RemoteUpdateFlags::UPDATE_FETCHHEAD,
887 AutotagOption::Unspecified,
888 None,
889 )
890 .unwrap();
891 origin
892 .update_tips(
893 None,
894 RemoteUpdateFlags::UPDATE_FETCHHEAD,
895 AutotagOption::All,
896 Some("foo"),
897 )
898 .unwrap();
899
900 t!(repo.remote_add_fetch("origin", "foo"));
901 t!(repo.remote_add_fetch("origin", "bar"));
902 }
903
904 #[test]
905 fn rename_remote() {
906 let (_td, repo) = crate::test::repo_init();
907 repo.remote("origin", "foo").unwrap();
908 drop(repo.remote_rename("origin", "foo"));
909 drop(repo.remote_delete("foo"));
910 }
911
912 #[test]
913 fn create_remote_anonymous() {
914 let td = TempDir::new().unwrap();
915 let repo = Repository::init(td.path()).unwrap();
916
917 let origin = repo.remote_anonymous("/path/to/nowhere").unwrap();
918 assert_eq!(origin.name(), None);
919 drop(origin.clone());
920 }
921
922 #[test]
923 fn is_valid_name() {
924 assert!(Remote::is_valid_name("foobar"));
925 assert!(!Remote::is_valid_name("\x01"));
926 }
927
928 #[test]
929 #[should_panic]
930 fn is_valid_name_for_invalid_remote() {
931 Remote::is_valid_name("ab\012");
932 }
933
934 #[test]
935 fn transfer_cb() {
936 let (td, _repo) = crate::test::repo_init();
937 let td2 = TempDir::new().unwrap();
938 let url = crate::test::path2url(&td.path());
939
940 let repo = Repository::init(td2.path()).unwrap();
941 let progress_hit = Cell::new(false);
942 {
943 let mut callbacks = RemoteCallbacks::new();
944 let mut origin = repo.remote("origin", &url).unwrap();
945
946 callbacks.transfer_progress(|_progress| {
947 progress_hit.set(true);
948 true
949 });
950 origin
951 .fetch(
952 &[] as &[&str],
953 Some(FetchOptions::new().remote_callbacks(callbacks)),
954 None,
955 )
956 .unwrap();
957
958 let list = t!(origin.list());
959 assert_eq!(list.len(), 2);
960 assert_eq!(list[0].name(), "HEAD");
961 assert!(!list[0].is_local());
962 assert_eq!(list[1].name(), "refs/heads/main");
963 assert!(!list[1].is_local());
964 }
965 assert!(progress_hit.get());
966 }
967
968 #[test]
971 fn connect_list() {
972 let (td, _repo) = crate::test::repo_init();
973 let td2 = TempDir::new().unwrap();
974 let url = crate::test::path2url(&td.path());
975
976 let repo = Repository::init(td2.path()).unwrap();
977 let mut callbacks = RemoteCallbacks::new();
978 callbacks.sideband_progress(|_progress| {
979 true
981 });
982
983 let mut origin = repo.remote("origin", &url).unwrap();
984
985 {
986 let mut connection = origin
987 .connect_auth(Direction::Fetch, Some(callbacks), None)
988 .unwrap();
989 assert!(connection.connected());
990
991 let list = t!(connection.list());
992 assert_eq!(list.len(), 2);
993 assert_eq!(list[0].name(), "HEAD");
994 assert!(!list[0].is_local());
995 assert_eq!(list[1].name(), "refs/heads/main");
996 assert!(!list[1].is_local());
997 }
998 assert!(!origin.connected());
999 }
1000
1001 #[test]
1002 fn push() {
1003 let (_td, repo) = crate::test::repo_init();
1004 let td2 = TempDir::new().unwrap();
1005 let td3 = TempDir::new().unwrap();
1006 let url = crate::test::path2url(&td2.path());
1007
1008 let mut opts = crate::RepositoryInitOptions::new();
1009 opts.bare(true);
1010 opts.initial_head("main");
1011 Repository::init_opts(td2.path(), &opts).unwrap();
1012 let mut remote = repo.remote("origin", &url).unwrap();
1014 let mut updated = false;
1015 {
1016 let mut callbacks = RemoteCallbacks::new();
1017 callbacks.push_update_reference(|refname, status| {
1018 updated = true;
1019 assert_eq!(refname, "refs/heads/main");
1020 assert_eq!(status, None);
1021 Ok(())
1022 });
1023 let mut options = PushOptions::new();
1024 options.remote_callbacks(callbacks);
1025 remote
1026 .push(&["refs/heads/main"], Some(&mut options))
1027 .unwrap();
1028 }
1029 assert!(updated);
1030
1031 let repo = Repository::clone(&url, td3.path()).unwrap();
1032 let commit = repo.head().unwrap().target().unwrap();
1033 let commit = repo.find_commit(commit).unwrap();
1034 assert_eq!(commit.message(), Some("initial\n\nbody"));
1035 }
1036
1037 #[test]
1038 fn prune() {
1039 let (td, remote_repo) = crate::test::repo_init();
1040 let oid = remote_repo.head().unwrap().target().unwrap();
1041 let commit = remote_repo.find_commit(oid).unwrap();
1042 remote_repo.branch("stale", &commit, true).unwrap();
1043
1044 let td2 = TempDir::new().unwrap();
1045 let url = crate::test::path2url(&td.path());
1046 let repo = Repository::clone(&url, &td2).unwrap();
1047
1048 fn assert_branch_count(repo: &Repository, count: usize) {
1049 assert_eq!(
1050 repo.branches(Some(crate::BranchType::Remote))
1051 .unwrap()
1052 .filter(|b| b.as_ref().unwrap().0.name().unwrap() == Some("origin/stale"))
1053 .count(),
1054 count,
1055 );
1056 }
1057
1058 assert_branch_count(&repo, 1);
1059
1060 let mut stale_branch = remote_repo
1062 .find_branch("stale", crate::BranchType::Local)
1063 .unwrap();
1064 stale_branch.delete().unwrap();
1065
1066 let mut remote = repo.find_remote("origin").unwrap();
1068 remote.connect(Direction::Push).unwrap();
1069 let mut callbacks = RemoteCallbacks::new();
1070 callbacks.update_tips(|refname, _a, b| {
1071 assert_eq!(refname, "refs/remotes/origin/stale");
1072 assert!(b.is_zero());
1073 true
1074 });
1075 remote.prune(Some(callbacks)).unwrap();
1076 assert_branch_count(&repo, 0);
1077 }
1078
1079 #[test]
1080 fn push_negotiation() {
1081 let (_td, repo) = crate::test::repo_init();
1082 let oid = repo.head().unwrap().target().unwrap();
1083
1084 let td2 = TempDir::new().unwrap();
1085 let url = crate::test::path2url(td2.path());
1086 let mut opts = crate::RepositoryInitOptions::new();
1087 opts.bare(true);
1088 opts.initial_head("main");
1089 let remote_repo = Repository::init_opts(td2.path(), &opts).unwrap();
1090
1091 let mut remote = repo.remote("origin", &url).unwrap();
1093 let mut updated = false;
1094 {
1095 let mut callbacks = RemoteCallbacks::new();
1096 callbacks.push_negotiation(|updates| {
1097 assert!(!updated);
1098 updated = true;
1099 assert_eq!(updates.len(), 1);
1100 let u = &updates[0];
1101 assert_eq!(u.src_refname().unwrap(), "refs/heads/main");
1102 assert!(u.src().is_zero());
1103 assert_eq!(u.dst_refname().unwrap(), "refs/heads/main");
1104 assert_eq!(u.dst(), oid);
1105 Err(crate::Error::from_str("rejected"))
1106 });
1107 let mut options = PushOptions::new();
1108 options.remote_callbacks(callbacks);
1109 assert!(remote
1110 .push(&["refs/heads/main"], Some(&mut options))
1111 .is_err());
1112 }
1113 assert!(updated);
1114 assert_eq!(remote_repo.branches(None).unwrap().count(), 0);
1115
1116 let commit = repo.find_commit(oid).unwrap();
1118 repo.branch("new1", &commit, true).unwrap();
1119 repo.branch("new2", &commit, true).unwrap();
1120 let mut flag = 0;
1121 updated = false;
1122 {
1123 let mut callbacks = RemoteCallbacks::new();
1124 callbacks.push_negotiation(|updates| {
1125 assert!(!updated);
1126 updated = true;
1127 assert_eq!(updates.len(), 3);
1128 for u in updates {
1129 assert!(u.src().is_zero());
1130 assert_eq!(u.dst(), oid);
1131 let src_name = u.src_refname().unwrap();
1132 let dst_name = u.dst_refname().unwrap();
1133 match src_name {
1134 "refs/heads/main" => {
1135 assert_eq!(dst_name, src_name);
1136 flag |= 1;
1137 }
1138 "refs/heads/new1" => {
1139 assert_eq!(dst_name, "refs/heads/dev1");
1140 flag |= 2;
1141 }
1142 "refs/heads/new2" => {
1143 assert_eq!(dst_name, "refs/heads/dev2");
1144 flag |= 4;
1145 }
1146 _ => panic!("unexpected refname: {}", src_name),
1147 }
1148 }
1149 Ok(())
1150 });
1151 let mut options = PushOptions::new();
1152 options.remote_callbacks(callbacks);
1153 remote
1154 .push(
1155 &[
1156 "refs/heads/main",
1157 "refs/heads/new1:refs/heads/dev1",
1158 "refs/heads/new2:refs/heads/dev2",
1159 ],
1160 Some(&mut options),
1161 )
1162 .unwrap();
1163 }
1164 assert!(updated);
1165 assert_eq!(flag, 7);
1166 assert_eq!(remote_repo.branches(None).unwrap().count(), 3);
1167 }
1168}