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