[go: up one dir, main page]
More Web Proxy on the site http://driver.im/

git2/
remote.rs

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
17/// A structure representing a [remote][1] of a git repository.
18///
19/// [1]: http://git-scm.com/book/en/Git-Basics-Working-with-Remotes
20///
21/// The lifetime is the lifetime of the repository that it is attached to. The
22/// remote is used to manage fetches and pushes as well as refspecs.
23pub struct Remote<'repo> {
24    raw: *mut raw::git_remote,
25    _marker: marker::PhantomData<&'repo Repository>,
26}
27
28/// An iterator over the refspecs that a remote contains.
29pub struct Refspecs<'remote> {
30    range: Range<usize>,
31    remote: &'remote Remote<'remote>,
32}
33
34/// Description of a reference advertised by a remote server, given out on calls
35/// to `list`.
36pub struct RemoteHead<'remote> {
37    raw: *const raw::git_remote_head,
38    _marker: marker::PhantomData<&'remote str>,
39}
40
41/// Options which can be specified to various fetch operations.
42pub 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
54/// Options to control the behavior of a git push.
55pub 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
66/// Holds callbacks for a connection to a `Remote`. Disconnects when dropped
67pub struct RemoteConnection<'repo, 'connection, 'cb> {
68    _callbacks: Box<RemoteCallbacks<'cb>>,
69    _proxy: ProxyOptions<'cb>,
70    remote: &'connection mut Remote<'repo>,
71}
72
73/// Remote redirection settings; whether redirects to another host are
74/// permitted.
75///
76/// By default, git will follow a redirect on the initial request
77/// (`/info/refs`), but not subsequent requests.
78pub enum RemoteRedirect {
79    /// Do not follow any off-site redirects at any stage of the fetch or push.
80    None,
81    /// Allow off-site redirects only upon the initial request. This is the
82    /// default.
83    Initial,
84    /// Allow redirects at any stage in the fetch or push.
85    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    /// Ensure the remote name is well-formed.
96    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    /// Create a detached remote
111    ///
112    /// Create a remote with the given URL in-memory. You can use this
113    /// when you have a URL instead of a remote's name.
114    /// Contrasted with an anonymous remote, a detached remote will not
115    /// consider any repo configuration values.
116    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    /// Get the remote's name.
127    ///
128    /// Returns `None` if this remote has not yet been named or if the name is
129    /// not valid utf-8
130    pub fn name(&self) -> Option<&str> {
131        self.name_bytes().and_then(|s| str::from_utf8(s).ok())
132    }
133
134    /// Get the remote's name, in bytes.
135    ///
136    /// Returns `None` if this remote has not yet been named
137    pub fn name_bytes(&self) -> Option<&[u8]> {
138        unsafe { crate::opt_bytes(self, raw::git_remote_name(&*self.raw)) }
139    }
140
141    /// Get the remote's URL.
142    ///
143    /// Returns `None` if the URL is not valid utf-8
144    pub fn url(&self) -> Option<&str> {
145        str::from_utf8(self.url_bytes()).ok()
146    }
147
148    /// Get the remote's URL as a byte array.
149    pub fn url_bytes(&self) -> &[u8] {
150        unsafe { crate::opt_bytes(self, raw::git_remote_url(&*self.raw)).unwrap_or(&[]) }
151    }
152
153    /// Get the remote's pushurl.
154    ///
155    /// Returns `None` if the pushurl is not valid utf-8 or no special url for pushing is set.
156    pub fn pushurl(&self) -> Option<&str> {
157        self.pushurl_bytes().and_then(|s| str::from_utf8(s).ok())
158    }
159
160    /// Get the remote's pushurl as a byte array.
161    ///
162    /// Returns `None` if no special url for pushing is set.
163    pub fn pushurl_bytes(&self) -> Option<&[u8]> {
164        unsafe { crate::opt_bytes(self, raw::git_remote_pushurl(&*self.raw)) }
165    }
166
167    /// Get the remote's default branch.
168    ///
169    /// The remote (or more exactly its transport) must have connected to the
170    /// remote repository. This default branch is available as soon as the
171    /// connection to the remote is initiated and it remains available after
172    /// disconnecting.
173    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    /// Open a connection to a remote.
182    pub fn connect(&mut self, dir: Direction) -> Result<(), Error> {
183        // TODO: can callbacks be exposed safely?
184        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    /// Open a connection to a remote with callbacks and proxy settings
197    ///
198    /// Returns a `RemoteConnection` that will disconnect once dropped
199    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    /// Check whether the remote is connected
225    pub fn connected(&mut self) -> bool {
226        unsafe { raw::git_remote_connected(self.raw) == 1 }
227    }
228
229    /// Disconnect from the remote
230    pub fn disconnect(&mut self) -> Result<(), Error> {
231        unsafe {
232            try_call!(raw::git_remote_disconnect(self.raw));
233        }
234        Ok(())
235    }
236
237    /// Download and index the packfile
238    ///
239    /// Connect to the remote if it hasn't been done yet, negotiate with the
240    /// remote git which objects are missing, download and index the packfile.
241    ///
242    /// The .idx file will be created and both it and the packfile with be
243    /// renamed to their final name.
244    ///
245    /// The `specs` argument is a list of refspecs to use for this negotiation
246    /// and download. Use an empty array to use the base refspecs.
247    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    /// Cancel the operation
261    ///
262    /// At certain points in its operation, the network code checks whether the
263    /// operation has been canceled and if so stops the operation.
264    pub fn stop(&mut self) -> Result<(), Error> {
265        unsafe {
266            try_call!(raw::git_remote_stop(self.raw));
267        }
268        Ok(())
269    }
270
271    /// Get the number of refspecs for a remote
272    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    /// Get the `nth` refspec from this remote.
281    ///
282    /// The `refspecs` iterator can be used to iterate over all refspecs.
283    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    /// Download new data and update tips
291    ///
292    /// Convenience function to connect to a remote, download the data,
293    /// disconnect and update the remote-tracking branches.
294    ///
295    /// # Examples
296    ///
297    /// Example of functionality similar to `git fetch origin main`:
298    ///
299    /// ```no_run
300    /// fn fetch_origin_main(repo: git2::Repository) -> Result<(), git2::Error> {
301    ///     repo.find_remote("origin")?.fetch(&["main"], None, None)
302    /// }
303    ///
304    /// let repo = git2::Repository::discover("rust").unwrap();
305    /// fetch_origin_main(repo).unwrap();
306    /// ```
307    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    /// Update the tips to the new state
323    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    /// Perform a push
345    ///
346    /// Perform all the steps for a push. If no refspecs are passed then the
347    /// configured refspecs will be used.
348    ///
349    /// Note that you'll likely want to use `RemoteCallbacks` and set
350    /// `push_update_reference` to test whether all the references were pushed
351    /// successfully.
352    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    /// Get the statistics structure that is filled in by the fetch operation.
366    pub fn stats(&self) -> Progress<'_> {
367        unsafe { Binding::from_raw(raw::git_remote_stats(self.raw)) }
368    }
369
370    /// Get the remote repository's reference advertisement list.
371    ///
372    /// Get the list of references with which the server responds to a new
373    /// connection.
374    ///
375    /// The remote (or more exactly its transport) must have connected to the
376    /// remote repository. This list is available as soon as the connection to
377    /// the remote is initiated and it remains available after disconnecting.
378    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    /// Prune tracking refs that are no longer present on remote
396    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    /// Get the remote's list of fetch refspecs
405    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    /// Get the remote's list of push refspecs
414    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)] // not documented in libgit2 :(
475impl<'remote> RemoteHead<'remote> {
476    /// Flag if this is available locally.
477    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    /// Creates a new blank set of fetch options
507    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, // Not limited depth
518        }
519    }
520
521    /// Set the callbacks to use for the fetch operation.
522    pub fn remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self {
523        self.callbacks = Some(cbs);
524        self
525    }
526
527    /// Set the proxy options to use for the fetch operation.
528    pub fn proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self {
529        self.proxy = Some(opts);
530        self
531    }
532
533    /// Set whether to perform a prune after the fetch.
534    pub fn prune(&mut self, prune: FetchPrune) -> &mut Self {
535        self.prune = prune;
536        self
537    }
538
539    /// Set whether to write the results to FETCH_HEAD.
540    ///
541    /// Defaults to `true`.
542    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    /// Set whether to report unchanged tips in the update_tips callback.
549    ///
550    /// Defaults to `false`.
551    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    /// Set fetch depth, a value less or equal to 0 is interpreted as pull
558    /// everything (effectively the same as not declaring a limit depth).
559
560    // FIXME(blyxyas): We currently don't have a test for shallow functions
561    // because libgit2 doesn't support local shallow clones.
562    // https://github.com/rust-lang/git2-rs/pull/979#issuecomment-1716299900
563    pub fn depth(&mut self, depth: i32) -> &mut Self {
564        self.depth = depth.max(0);
565        self
566    }
567
568    /// Set how to behave regarding tags on the remote, such as auto-downloading
569    /// tags for objects we're downloading or downloading all of them.
570    ///
571    /// The default is to auto-follow tags.
572    pub fn download_tags(&mut self, opt: AutotagOption) -> &mut Self {
573        self.download_tags = opt;
574        self
575    }
576
577    /// Set remote redirection settings; whether redirects to another host are
578    /// permitted.
579    ///
580    /// By default, git will follow a redirect on the initial request
581    /// (`/info/refs`), but not subsequent requests.
582    pub fn follow_redirects(&mut self, redirect: RemoteRedirect) -> &mut Self {
583        self.follow_redirects = redirect;
584        self
585    }
586
587    /// Set extra headers for this fetch operation.
588    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` is an incorrectly named option which contains both
619            // the `UPDATE_FETCHHEAD` and `REPORT_UNCHANGED` flags.
620            // See https://github.com/libgit2/libgit2/pull/6806
621            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    /// Creates a new blank set of push options
641    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    /// Set the callbacks to use for the push operation.
655    pub fn remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self {
656        self.callbacks = Some(cbs);
657        self
658    }
659
660    /// Set the proxy options to use for the push operation.
661    pub fn proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self {
662        self.proxy = Some(opts);
663        self
664    }
665
666    /// If the transport being used to push to the remote requires the creation
667    /// of a pack file, this controls the number of worker threads used by the
668    /// packbuilder when creating that pack file to be sent to the remote.
669    ///
670    /// if set to 0 the packbuilder will auto-detect the number of threads to
671    /// create, and the default value is 1.
672    pub fn packbuilder_parallelism(&mut self, parallel: u32) -> &mut Self {
673        self.pb_parallelism = parallel;
674        self
675    }
676
677    /// Set remote redirection settings; whether redirects to another host are
678    /// permitted.
679    ///
680    /// By default, git will follow a redirect on the initial request
681    /// (`/info/refs`), but not subsequent requests.
682    pub fn follow_redirects(&mut self, redirect: RemoteRedirect) -> &mut Self {
683        self.follow_redirects = redirect;
684        self
685    }
686
687    /// Set extra headers for this push operation.
688    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    /// Set "push options" to deliver to the remote.
698    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    /// Check whether the remote is (still) connected
747    pub fn connected(&mut self) -> bool {
748        self.remote.connected()
749    }
750
751    /// Get the remote repository's reference advertisement list.
752    ///
753    /// This list is available as soon as the connection to
754    /// the remote is initiated and it remains available after disconnecting.
755    pub fn list(&self) -> Result<&[RemoteHead<'_>], Error> {
756        self.remote.list()
757    }
758
759    /// Get the remote's default branch.
760    ///
761    /// This default branch is available as soon as the connection to the remote
762    /// is initiated and it remains available after disconnecting.
763    pub fn default_branch(&self) -> Result<Buf, Error> {
764        self.remote.default_branch()
765    }
766
767    /// access remote bound to this connection
768    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    /// This test is meant to assure that the callbacks provided to connect will not cause
971    /// segfaults
972    #[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            // no-op
982            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        // git push
1015        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        // delete `stale` branch on remote repo
1063        let mut stale_branch = remote_repo
1064            .find_branch("stale", crate::BranchType::Local)
1065            .unwrap();
1066        stale_branch.delete().unwrap();
1067
1068        // prune
1069        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        // reject pushing a branch
1094        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        // push 3 branches
1119        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}