[go: up one dir, main page]

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() }
151    }
152
153    /// Get the remote's pushurl.
154    ///
155    /// Returns `None` if the pushurl is not valid utf-8
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    pub fn pushurl_bytes(&self) -> Option<&[u8]> {
162        unsafe { crate::opt_bytes(self, raw::git_remote_pushurl(&*self.raw)) }
163    }
164
165    /// Get the remote's default branch.
166    ///
167    /// The remote (or more exactly its transport) must have connected to the
168    /// remote repository. This default branch is available as soon as the
169    /// connection to the remote is initiated and it remains available after
170    /// disconnecting.
171    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    /// Open a connection to a remote.
180    pub fn connect(&mut self, dir: Direction) -> Result<(), Error> {
181        // TODO: can callbacks be exposed safely?
182        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    /// Open a connection to a remote with callbacks and proxy settings
195    ///
196    /// Returns a `RemoteConnection` that will disconnect once dropped
197    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    /// Check whether the remote is connected
223    pub fn connected(&mut self) -> bool {
224        unsafe { raw::git_remote_connected(self.raw) == 1 }
225    }
226
227    /// Disconnect from the remote
228    pub fn disconnect(&mut self) -> Result<(), Error> {
229        unsafe {
230            try_call!(raw::git_remote_disconnect(self.raw));
231        }
232        Ok(())
233    }
234
235    /// Download and index the packfile
236    ///
237    /// Connect to the remote if it hasn't been done yet, negotiate with the
238    /// remote git which objects are missing, download and index the packfile.
239    ///
240    /// The .idx file will be created and both it and the packfile with be
241    /// renamed to their final name.
242    ///
243    /// The `specs` argument is a list of refspecs to use for this negotiation
244    /// and download. Use an empty array to use the base refspecs.
245    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    /// Cancel the operation
259    ///
260    /// At certain points in its operation, the network code checks whether the
261    /// operation has been canceled and if so stops the operation.
262    pub fn stop(&mut self) -> Result<(), Error> {
263        unsafe {
264            try_call!(raw::git_remote_stop(self.raw));
265        }
266        Ok(())
267    }
268
269    /// Get the number of refspecs for a remote
270    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    /// Get the `nth` refspec from this remote.
279    ///
280    /// The `refspecs` iterator can be used to iterate over all refspecs.
281    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    /// Download new data and update tips
289    ///
290    /// Convenience function to connect to a remote, download the data,
291    /// disconnect and update the remote-tracking branches.
292    ///
293    /// # Examples
294    ///
295    /// Example of functionality similar to `git fetch origin main`:
296    ///
297    /// ```no_run
298    /// fn fetch_origin_main(repo: git2::Repository) -> Result<(), git2::Error> {
299    ///     repo.find_remote("origin")?.fetch(&["main"], None, None)
300    /// }
301    ///
302    /// let repo = git2::Repository::discover("rust").unwrap();
303    /// fetch_origin_main(repo).unwrap();
304    /// ```
305    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    /// Update the tips to the new state
321    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    /// Perform a push
343    ///
344    /// Perform all the steps for a push. If no refspecs are passed then the
345    /// configured refspecs will be used.
346    ///
347    /// Note that you'll likely want to use `RemoteCallbacks` and set
348    /// `push_update_reference` to test whether all the references were pushed
349    /// successfully.
350    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    /// Get the statistics structure that is filled in by the fetch operation.
364    pub fn stats(&self) -> Progress<'_> {
365        unsafe { Binding::from_raw(raw::git_remote_stats(self.raw)) }
366    }
367
368    /// Get the remote repository's reference advertisement list.
369    ///
370    /// Get the list of references with which the server responds to a new
371    /// connection.
372    ///
373    /// The remote (or more exactly its transport) must have connected to the
374    /// remote repository. This list is available as soon as the connection to
375    /// the remote is initiated and it remains available after disconnecting.
376    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    /// Prune tracking refs that are no longer present on remote
394    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    /// Get the remote's list of fetch refspecs
403    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    /// Get the remote's list of push refspecs
412    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)] // not documented in libgit2 :(
473impl<'remote> RemoteHead<'remote> {
474    /// Flag if this is available locally.
475    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    /// Creates a new blank set of fetch options
505    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, // Not limited depth
516        }
517    }
518
519    /// Set the callbacks to use for the fetch operation.
520    pub fn remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self {
521        self.callbacks = Some(cbs);
522        self
523    }
524
525    /// Set the proxy options to use for the fetch operation.
526    pub fn proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self {
527        self.proxy = Some(opts);
528        self
529    }
530
531    /// Set whether to perform a prune after the fetch.
532    pub fn prune(&mut self, prune: FetchPrune) -> &mut Self {
533        self.prune = prune;
534        self
535    }
536
537    /// Set whether to write the results to FETCH_HEAD.
538    ///
539    /// Defaults to `true`.
540    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    /// Set whether to report unchanged tips in the update_tips callback.
547    ///
548    /// Defaults to `false`.
549    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    /// Set fetch depth, a value less or equal to 0 is interpreted as pull
556    /// everything (effectively the same as not declaring a limit depth).
557
558    // FIXME(blyxyas): We currently don't have a test for shallow functions
559    // because libgit2 doesn't support local shallow clones.
560    // https://github.com/rust-lang/git2-rs/pull/979#issuecomment-1716299900
561    pub fn depth(&mut self, depth: i32) -> &mut Self {
562        self.depth = depth.max(0);
563        self
564    }
565
566    /// Set how to behave regarding tags on the remote, such as auto-downloading
567    /// tags for objects we're downloading or downloading all of them.
568    ///
569    /// The default is to auto-follow tags.
570    pub fn download_tags(&mut self, opt: AutotagOption) -> &mut Self {
571        self.download_tags = opt;
572        self
573    }
574
575    /// Set remote redirection settings; whether redirects to another host are
576    /// permitted.
577    ///
578    /// By default, git will follow a redirect on the initial request
579    /// (`/info/refs`), but not subsequent requests.
580    pub fn follow_redirects(&mut self, redirect: RemoteRedirect) -> &mut Self {
581        self.follow_redirects = redirect;
582        self
583    }
584
585    /// Set extra headers for this fetch operation.
586    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` is an incorrectly named option which contains both
617            // the `UPDATE_FETCHHEAD` and `REPORT_UNCHANGED` flags.
618            // See https://github.com/libgit2/libgit2/pull/6806
619            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    /// Creates a new blank set of push options
639    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    /// Set the callbacks to use for the push operation.
653    pub fn remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self {
654        self.callbacks = Some(cbs);
655        self
656    }
657
658    /// Set the proxy options to use for the push operation.
659    pub fn proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self {
660        self.proxy = Some(opts);
661        self
662    }
663
664    /// If the transport being used to push to the remote requires the creation
665    /// of a pack file, this controls the number of worker threads used by the
666    /// packbuilder when creating that pack file to be sent to the remote.
667    ///
668    /// if set to 0 the packbuilder will auto-detect the number of threads to
669    /// create, and the default value is 1.
670    pub fn packbuilder_parallelism(&mut self, parallel: u32) -> &mut Self {
671        self.pb_parallelism = parallel;
672        self
673    }
674
675    /// Set remote redirection settings; whether redirects to another host are
676    /// permitted.
677    ///
678    /// By default, git will follow a redirect on the initial request
679    /// (`/info/refs`), but not subsequent requests.
680    pub fn follow_redirects(&mut self, redirect: RemoteRedirect) -> &mut Self {
681        self.follow_redirects = redirect;
682        self
683    }
684
685    /// Set extra headers for this push operation.
686    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    /// Set "push options" to deliver to the remote.
696    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    /// Check whether the remote is (still) connected
745    pub fn connected(&mut self) -> bool {
746        self.remote.connected()
747    }
748
749    /// Get the remote repository's reference advertisement list.
750    ///
751    /// This list is available as soon as the connection to
752    /// the remote is initiated and it remains available after disconnecting.
753    pub fn list(&self) -> Result<&[RemoteHead<'_>], Error> {
754        self.remote.list()
755    }
756
757    /// Get the remote's default branch.
758    ///
759    /// This default branch is available as soon as the connection to the remote
760    /// is initiated and it remains available after disconnecting.
761    pub fn default_branch(&self) -> Result<Buf, Error> {
762        self.remote.default_branch()
763    }
764
765    /// access remote bound to this connection
766    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    /// This test is meant to assure that the callbacks provided to connect will not cause
969    /// segfaults
970    #[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            // no-op
980            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        // git push
1013        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        // delete `stale` branch on remote repo
1061        let mut stale_branch = remote_repo
1062            .find_branch("stale", crate::BranchType::Local)
1063            .unwrap();
1064        stale_branch.delete().unwrap();
1065
1066        // prune
1067        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        // reject pushing a branch
1092        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        // push 3 branches
1117        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}