]> git.proxmox.com Git - rustc.git/blame - vendor/git2/src/remote.rs
New upstream version 1.75.0+dfsg1
[rustc.git] / vendor / git2 / src / remote.rs
CommitLineData
0a29b90c
FG
1use libc;
2use raw::git_strarray;
781aab86 3use std::iter::FusedIterator;
0a29b90c
FG
4use std::marker;
5use std::mem;
6use std::ops::Range;
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, 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>>,
ed00b5ec 44 depth: i32,
0a29b90c
FG
45 proxy: Option<ProxyOptions<'cb>>,
46 prune: FetchPrune,
47 update_fetchhead: bool,
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}
63
64/// Holds callbacks for a connection to a `Remote`. Disconnects when dropped
65pub struct RemoteConnection<'repo, 'connection, 'cb> {
66 _callbacks: Box<RemoteCallbacks<'cb>>,
67 _proxy: ProxyOptions<'cb>,
68 remote: &'connection mut Remote<'repo>,
69}
70
71/// Remote redirection settings; whether redirects to another host are
72/// permitted.
73///
74/// By default, git will follow a redirect on the initial request
75/// (`/info/refs`), but not subsequent requests.
76pub enum RemoteRedirect {
77 /// Do not follow any off-site redirects at any stage of the fetch or push.
78 None,
79 /// Allow off-site redirects only upon the initial request. This is the
80 /// default.
81 Initial,
82 /// Allow redirects at any stage in the fetch or push.
83 All,
84}
85
86pub fn remote_into_raw(remote: Remote<'_>) -> *mut raw::git_remote {
87 let ret = remote.raw;
88 mem::forget(remote);
89 ret
90}
91
92impl<'repo> Remote<'repo> {
93 /// Ensure the remote name is well-formed.
94 pub fn is_valid_name(remote_name: &str) -> bool {
95 crate::init();
96 let remote_name = CString::new(remote_name).unwrap();
97 let mut valid: libc::c_int = 0;
98 unsafe {
99 call::c_try(raw::git_remote_name_is_valid(
100 &mut valid,
101 remote_name.as_ptr(),
102 ))
103 .unwrap();
104 }
105 valid == 1
106 }
107
108 /// Create a detached remote
109 ///
110 /// Create a remote with the given URL in-memory. You can use this
111 /// when you have a URL instead of a remote's name.
112 /// Contrasted with an anonymous remote, a detached remote will not
113 /// consider any repo configuration values.
114 pub fn create_detached<S: Into<Vec<u8>>>(url: S) -> Result<Remote<'repo>, Error> {
115 crate::init();
116 let mut ret = ptr::null_mut();
117 let url = CString::new(url)?;
118 unsafe {
119 try_call!(raw::git_remote_create_detached(&mut ret, url));
120 Ok(Binding::from_raw(ret))
121 }
122 }
123
124 /// Get the remote's name.
125 ///
126 /// Returns `None` if this remote has not yet been named or if the name is
127 /// not valid utf-8
128 pub fn name(&self) -> Option<&str> {
129 self.name_bytes().and_then(|s| str::from_utf8(s).ok())
130 }
131
132 /// Get the remote's name, in bytes.
133 ///
134 /// Returns `None` if this remote has not yet been named
135 pub fn name_bytes(&self) -> Option<&[u8]> {
136 unsafe { crate::opt_bytes(self, raw::git_remote_name(&*self.raw)) }
137 }
138
139 /// Get the remote's URL.
140 ///
141 /// Returns `None` if the URL is not valid utf-8
142 pub fn url(&self) -> Option<&str> {
143 str::from_utf8(self.url_bytes()).ok()
144 }
145
146 /// Get the remote's URL as a byte array.
147 pub fn url_bytes(&self) -> &[u8] {
148 unsafe { crate::opt_bytes(self, raw::git_remote_url(&*self.raw)).unwrap() }
149 }
150
151 /// Get the remote's pushurl.
152 ///
153 /// Returns `None` if the pushurl is not valid utf-8
154 pub fn pushurl(&self) -> Option<&str> {
155 self.pushurl_bytes().and_then(|s| str::from_utf8(s).ok())
156 }
157
158 /// Get the remote's pushurl as a byte array.
159 pub fn pushurl_bytes(&self) -> Option<&[u8]> {
160 unsafe { crate::opt_bytes(self, raw::git_remote_pushurl(&*self.raw)) }
161 }
162
163 /// Get the remote's default branch.
164 ///
165 /// The remote (or more exactly its transport) must have connected to the
166 /// remote repository. This default branch is available as soon as the
167 /// connection to the remote is initiated and it remains available after
168 /// disconnecting.
169 pub fn default_branch(&self) -> Result<Buf, Error> {
170 unsafe {
171 let buf = Buf::new();
172 try_call!(raw::git_remote_default_branch(buf.raw(), self.raw));
173 Ok(buf)
174 }
175 }
176
177 /// Open a connection to a remote.
178 pub fn connect(&mut self, dir: Direction) -> Result<(), Error> {
179 // TODO: can callbacks be exposed safely?
180 unsafe {
181 try_call!(raw::git_remote_connect(
182 self.raw,
183 dir,
184 ptr::null(),
185 ptr::null(),
186 ptr::null()
187 ));
188 }
189 Ok(())
190 }
191
192 /// Open a connection to a remote with callbacks and proxy settings
193 ///
194 /// Returns a `RemoteConnection` that will disconnect once dropped
195 pub fn connect_auth<'connection, 'cb>(
196 &'connection mut self,
197 dir: Direction,
198 cb: Option<RemoteCallbacks<'cb>>,
199 proxy_options: Option<ProxyOptions<'cb>>,
200 ) -> Result<RemoteConnection<'repo, 'connection, 'cb>, Error> {
201 let cb = Box::new(cb.unwrap_or_else(RemoteCallbacks::new));
202 let proxy_options = proxy_options.unwrap_or_else(ProxyOptions::new);
203 unsafe {
204 try_call!(raw::git_remote_connect(
205 self.raw,
206 dir,
207 &cb.raw(),
208 &proxy_options.raw(),
209 ptr::null()
210 ));
211 }
212
213 Ok(RemoteConnection {
214 _callbacks: cb,
215 _proxy: proxy_options,
216 remote: self,
217 })
218 }
219
220 /// Check whether the remote is connected
221 pub fn connected(&mut self) -> bool {
222 unsafe { raw::git_remote_connected(self.raw) == 1 }
223 }
224
225 /// Disconnect from the remote
226 pub fn disconnect(&mut self) -> Result<(), Error> {
227 unsafe {
228 try_call!(raw::git_remote_disconnect(self.raw));
229 }
230 Ok(())
231 }
232
233 /// Download and index the packfile
234 ///
235 /// Connect to the remote if it hasn't been done yet, negotiate with the
236 /// remote git which objects are missing, download and index the packfile.
237 ///
238 /// The .idx file will be created and both it and the packfile with be
239 /// renamed to their final name.
240 ///
241 /// The `specs` argument is a list of refspecs to use for this negotiation
242 /// and download. Use an empty array to use the base refspecs.
243 pub fn download<Str: AsRef<str> + crate::IntoCString + Clone>(
244 &mut self,
245 specs: &[Str],
246 opts: Option<&mut FetchOptions<'_>>,
247 ) -> Result<(), Error> {
248 let (_a, _b, arr) = crate::util::iter2cstrs(specs.iter())?;
249 let raw = opts.map(|o| o.raw());
250 unsafe {
251 try_call!(raw::git_remote_download(self.raw, &arr, raw.as_ref()));
252 }
253 Ok(())
254 }
255
256 /// Cancel the operation
257 ///
258 /// At certain points in its operation, the network code checks whether the
259 /// operation has been canceled and if so stops the operation.
260 pub fn stop(&mut self) -> Result<(), Error> {
261 unsafe {
262 try_call!(raw::git_remote_stop(self.raw));
263 }
264 Ok(())
265 }
266
267 /// Get the number of refspecs for a remote
268 pub fn refspecs(&self) -> Refspecs<'_> {
269 let cnt = unsafe { raw::git_remote_refspec_count(&*self.raw) as usize };
270 Refspecs {
271 range: 0..cnt,
272 remote: self,
273 }
274 }
275
276 /// Get the `nth` refspec from this remote.
277 ///
278 /// The `refspecs` iterator can be used to iterate over all refspecs.
279 pub fn get_refspec(&self, i: usize) -> Option<Refspec<'repo>> {
280 unsafe {
281 let ptr = raw::git_remote_get_refspec(&*self.raw, i as libc::size_t);
282 Binding::from_raw_opt(ptr)
283 }
284 }
285
286 /// Download new data and update tips
287 ///
288 /// Convenience function to connect to a remote, download the data,
289 /// disconnect and update the remote-tracking branches.
290 ///
291 /// # Examples
292 ///
293 /// Example of functionality similar to `git fetch origin/main`:
294 ///
295 /// ```no_run
296 /// fn fetch_origin_main(repo: git2::Repository) -> Result<(), git2::Error> {
297 /// repo.find_remote("origin")?.fetch(&["main"], None, None)
298 /// }
299 ///
300 /// let repo = git2::Repository::discover("rust").unwrap();
301 /// fetch_origin_main(repo).unwrap();
302 /// ```
303 pub fn fetch<Str: AsRef<str> + crate::IntoCString + Clone>(
304 &mut self,
305 refspecs: &[Str],
306 opts: Option<&mut FetchOptions<'_>>,
307 reflog_msg: Option<&str>,
308 ) -> Result<(), Error> {
309 let (_a, _b, arr) = crate::util::iter2cstrs(refspecs.iter())?;
310 let msg = crate::opt_cstr(reflog_msg)?;
311 let raw = opts.map(|o| o.raw());
312 unsafe {
313 try_call!(raw::git_remote_fetch(self.raw, &arr, raw.as_ref(), msg));
314 }
315 Ok(())
316 }
317
318 /// Update the tips to the new state
319 pub fn update_tips(
320 &mut self,
321 callbacks: Option<&mut RemoteCallbacks<'_>>,
322 update_fetchhead: bool,
323 download_tags: AutotagOption,
324 msg: Option<&str>,
325 ) -> Result<(), Error> {
326 let msg = crate::opt_cstr(msg)?;
327 let cbs = callbacks.map(|cb| cb.raw());
328 unsafe {
329 try_call!(raw::git_remote_update_tips(
330 self.raw,
331 cbs.as_ref(),
332 update_fetchhead,
333 download_tags,
334 msg
335 ));
336 }
337 Ok(())
338 }
339
340 /// Perform a push
341 ///
342 /// Perform all the steps for a push. If no refspecs are passed then the
343 /// configured refspecs will be used.
344 ///
345 /// Note that you'll likely want to use `RemoteCallbacks` and set
346 /// `push_update_reference` to test whether all the references were pushed
347 /// successfully.
348 pub fn push<Str: AsRef<str> + crate::IntoCString + Clone>(
349 &mut self,
350 refspecs: &[Str],
351 opts: Option<&mut PushOptions<'_>>,
352 ) -> Result<(), Error> {
353 let (_a, _b, arr) = crate::util::iter2cstrs(refspecs.iter())?;
354 let raw = opts.map(|o| o.raw());
355 unsafe {
356 try_call!(raw::git_remote_push(self.raw, &arr, raw.as_ref()));
357 }
358 Ok(())
359 }
360
361 /// Get the statistics structure that is filled in by the fetch operation.
362 pub fn stats(&self) -> Progress<'_> {
363 unsafe { Binding::from_raw(raw::git_remote_stats(self.raw)) }
364 }
365
366 /// Get the remote repository's reference advertisement list.
367 ///
368 /// Get the list of references with which the server responds to a new
369 /// connection.
370 ///
371 /// The remote (or more exactly its transport) must have connected to the
372 /// remote repository. This list is available as soon as the connection to
373 /// the remote is initiated and it remains available after disconnecting.
374 pub fn list(&self) -> Result<&[RemoteHead<'_>], Error> {
375 let mut size = 0;
376 let mut base = ptr::null_mut();
377 unsafe {
378 try_call!(raw::git_remote_ls(&mut base, &mut size, self.raw));
379 assert_eq!(
380 mem::size_of::<RemoteHead<'_>>(),
381 mem::size_of::<*const raw::git_remote_head>()
382 );
383 let slice = slice::from_raw_parts(base as *const _, size as usize);
384 Ok(mem::transmute::<
385 &[*const raw::git_remote_head],
386 &[RemoteHead<'_>],
387 >(slice))
388 }
389 }
390
391 /// Prune tracking refs that are no longer present on remote
392 pub fn prune(&mut self, callbacks: Option<RemoteCallbacks<'_>>) -> Result<(), Error> {
393 let cbs = Box::new(callbacks.unwrap_or_else(RemoteCallbacks::new));
394 unsafe {
395 try_call!(raw::git_remote_prune(self.raw, &cbs.raw()));
396 }
397 Ok(())
398 }
399
400 /// Get the remote's list of fetch refspecs
401 pub fn fetch_refspecs(&self) -> Result<StringArray, Error> {
402 unsafe {
403 let mut raw: raw::git_strarray = mem::zeroed();
404 try_call!(raw::git_remote_get_fetch_refspecs(&mut raw, self.raw));
405 Ok(StringArray::from_raw(raw))
406 }
407 }
408
409 /// Get the remote's list of push refspecs
410 pub fn push_refspecs(&self) -> Result<StringArray, Error> {
411 unsafe {
412 let mut raw: raw::git_strarray = mem::zeroed();
413 try_call!(raw::git_remote_get_push_refspecs(&mut raw, self.raw));
414 Ok(StringArray::from_raw(raw))
415 }
416 }
417}
418
419impl<'repo> Clone for Remote<'repo> {
420 fn clone(&self) -> Remote<'repo> {
421 let mut ret = ptr::null_mut();
422 let rc = unsafe { call!(raw::git_remote_dup(&mut ret, self.raw)) };
423 assert_eq!(rc, 0);
424 Remote {
425 raw: ret,
426 _marker: marker::PhantomData,
427 }
428 }
429}
430
431impl<'repo> Binding for Remote<'repo> {
432 type Raw = *mut raw::git_remote;
433
434 unsafe fn from_raw(raw: *mut raw::git_remote) -> Remote<'repo> {
435 Remote {
436 raw,
437 _marker: marker::PhantomData,
438 }
439 }
440 fn raw(&self) -> *mut raw::git_remote {
441 self.raw
442 }
443}
444
445impl<'repo> Drop for Remote<'repo> {
446 fn drop(&mut self) {
447 unsafe { raw::git_remote_free(self.raw) }
448 }
449}
450
451impl<'repo> Iterator for Refspecs<'repo> {
452 type Item = Refspec<'repo>;
453 fn next(&mut self) -> Option<Refspec<'repo>> {
454 self.range.next().and_then(|i| self.remote.get_refspec(i))
455 }
456 fn size_hint(&self) -> (usize, Option<usize>) {
457 self.range.size_hint()
458 }
459}
460impl<'repo> DoubleEndedIterator for Refspecs<'repo> {
461 fn next_back(&mut self) -> Option<Refspec<'repo>> {
462 self.range
463 .next_back()
464 .and_then(|i| self.remote.get_refspec(i))
465 }
466}
781aab86 467impl<'repo> FusedIterator for Refspecs<'repo> {}
0a29b90c
FG
468impl<'repo> ExactSizeIterator for Refspecs<'repo> {}
469
470#[allow(missing_docs)] // not documented in libgit2 :(
471impl<'remote> RemoteHead<'remote> {
472 /// Flag if this is available locally.
473 pub fn is_local(&self) -> bool {
474 unsafe { (*self.raw).local != 0 }
475 }
476
477 pub fn oid(&self) -> Oid {
478 unsafe { Binding::from_raw(&(*self.raw).oid as *const _) }
479 }
480 pub fn loid(&self) -> Oid {
481 unsafe { Binding::from_raw(&(*self.raw).loid as *const _) }
482 }
483
484 pub fn name(&self) -> &str {
485 let b = unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() };
486 str::from_utf8(b).unwrap()
487 }
488
489 pub fn symref_target(&self) -> Option<&str> {
490 let b = unsafe { crate::opt_bytes(self, (*self.raw).symref_target) };
491 b.map(|b| str::from_utf8(b).unwrap())
492 }
493}
494
495impl<'cb> Default for FetchOptions<'cb> {
496 fn default() -> Self {
497 Self::new()
498 }
499}
500
501impl<'cb> FetchOptions<'cb> {
502 /// Creates a new blank set of fetch options
503 pub fn new() -> FetchOptions<'cb> {
504 FetchOptions {
505 callbacks: None,
506 proxy: None,
507 prune: FetchPrune::Unspecified,
508 update_fetchhead: true,
509 download_tags: AutotagOption::Unspecified,
510 follow_redirects: RemoteRedirect::Initial,
511 custom_headers: Vec::new(),
512 custom_headers_ptrs: Vec::new(),
ed00b5ec 513 depth: 0, // Not limited depth
0a29b90c
FG
514 }
515 }
516
517 /// Set the callbacks to use for the fetch operation.
518 pub fn remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self {
519 self.callbacks = Some(cbs);
520 self
521 }
522
523 /// Set the proxy options to use for the fetch operation.
524 pub fn proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self {
525 self.proxy = Some(opts);
526 self
527 }
528
529 /// Set whether to perform a prune after the fetch.
530 pub fn prune(&mut self, prune: FetchPrune) -> &mut Self {
531 self.prune = prune;
532 self
533 }
534
535 /// Set whether to write the results to FETCH_HEAD.
536 ///
537 /// Defaults to `true`.
538 pub fn update_fetchhead(&mut self, update: bool) -> &mut Self {
539 self.update_fetchhead = update;
540 self
541 }
542
ed00b5ec
FG
543 /// Set fetch depth, a value less or equal to 0 is interpreted as pull
544 /// everything (effectively the same as not declaring a limit depth).
545
546 // FIXME(blyxyas): We currently don't have a test for shallow functions
547 // because libgit2 doesn't support local shallow clones.
548 // https://github.com/rust-lang/git2-rs/pull/979#issuecomment-1716299900
549 pub fn depth(&mut self, depth: i32) -> &mut Self {
550 self.depth = depth.max(0);
551 self
552 }
553
0a29b90c
FG
554 /// Set how to behave regarding tags on the remote, such as auto-downloading
555 /// tags for objects we're downloading or downloading all of them.
556 ///
557 /// The default is to auto-follow tags.
558 pub fn download_tags(&mut self, opt: AutotagOption) -> &mut Self {
559 self.download_tags = opt;
560 self
561 }
562
563 /// Set remote redirection settings; whether redirects to another host are
564 /// permitted.
565 ///
566 /// By default, git will follow a redirect on the initial request
567 /// (`/info/refs`), but not subsequent requests.
568 pub fn follow_redirects(&mut self, redirect: RemoteRedirect) -> &mut Self {
569 self.follow_redirects = redirect;
570 self
571 }
572
573 /// Set extra headers for this fetch operation.
574 pub fn custom_headers(&mut self, custom_headers: &[&str]) -> &mut Self {
575 self.custom_headers = custom_headers
576 .iter()
577 .map(|&s| CString::new(s).unwrap())
578 .collect();
579 self.custom_headers_ptrs = self.custom_headers.iter().map(|s| s.as_ptr()).collect();
580 self
581 }
582}
583
584impl<'cb> Binding for FetchOptions<'cb> {
585 type Raw = raw::git_fetch_options;
586
587 unsafe fn from_raw(_raw: raw::git_fetch_options) -> FetchOptions<'cb> {
588 panic!("unimplemented");
589 }
590 fn raw(&self) -> raw::git_fetch_options {
591 raw::git_fetch_options {
592 version: 1,
593 callbacks: self
594 .callbacks
595 .as_ref()
596 .map(|m| m.raw())
597 .unwrap_or_else(|| RemoteCallbacks::new().raw()),
598 proxy_opts: self
599 .proxy
600 .as_ref()
601 .map(|m| m.raw())
602 .unwrap_or_else(|| ProxyOptions::new().raw()),
603 prune: crate::call::convert(&self.prune),
604 update_fetchhead: crate::call::convert(&self.update_fetchhead),
605 download_tags: crate::call::convert(&self.download_tags),
ed00b5ec 606 depth: self.depth,
0a29b90c
FG
607 follow_redirects: self.follow_redirects.raw(),
608 custom_headers: git_strarray {
609 count: self.custom_headers_ptrs.len(),
610 strings: self.custom_headers_ptrs.as_ptr() as *mut _,
611 },
612 }
613 }
614}
615
616impl<'cb> Default for PushOptions<'cb> {
617 fn default() -> Self {
618 Self::new()
619 }
620}
621
622impl<'cb> PushOptions<'cb> {
623 /// Creates a new blank set of push options
624 pub fn new() -> PushOptions<'cb> {
625 PushOptions {
626 callbacks: None,
627 proxy: None,
628 pb_parallelism: 1,
629 follow_redirects: RemoteRedirect::Initial,
630 custom_headers: Vec::new(),
631 custom_headers_ptrs: Vec::new(),
632 }
633 }
634
635 /// Set the callbacks to use for the push operation.
636 pub fn remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self {
637 self.callbacks = Some(cbs);
638 self
639 }
640
641 /// Set the proxy options to use for the push operation.
642 pub fn proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self {
643 self.proxy = Some(opts);
644 self
645 }
646
647 /// If the transport being used to push to the remote requires the creation
648 /// of a pack file, this controls the number of worker threads used by the
649 /// packbuilder when creating that pack file to be sent to the remote.
650 ///
651 /// if set to 0 the packbuilder will auto-detect the number of threads to
652 /// create, and the default value is 1.
653 pub fn packbuilder_parallelism(&mut self, parallel: u32) -> &mut Self {
654 self.pb_parallelism = parallel;
655 self
656 }
657
658 /// Set remote redirection settings; whether redirects to another host are
659 /// permitted.
660 ///
661 /// By default, git will follow a redirect on the initial request
662 /// (`/info/refs`), but not subsequent requests.
663 pub fn follow_redirects(&mut self, redirect: RemoteRedirect) -> &mut Self {
664 self.follow_redirects = redirect;
665 self
666 }
667
668 /// Set extra headers for this push operation.
669 pub fn custom_headers(&mut self, custom_headers: &[&str]) -> &mut Self {
670 self.custom_headers = custom_headers
671 .iter()
672 .map(|&s| CString::new(s).unwrap())
673 .collect();
674 self.custom_headers_ptrs = self.custom_headers.iter().map(|s| s.as_ptr()).collect();
675 self
676 }
677}
678
679impl<'cb> Binding for PushOptions<'cb> {
680 type Raw = raw::git_push_options;
681
682 unsafe fn from_raw(_raw: raw::git_push_options) -> PushOptions<'cb> {
683 panic!("unimplemented");
684 }
685 fn raw(&self) -> raw::git_push_options {
686 raw::git_push_options {
687 version: 1,
688 callbacks: self
689 .callbacks
690 .as_ref()
691 .map(|m| m.raw())
692 .unwrap_or_else(|| RemoteCallbacks::new().raw()),
693 proxy_opts: self
694 .proxy
695 .as_ref()
696 .map(|m| m.raw())
697 .unwrap_or_else(|| ProxyOptions::new().raw()),
698 pb_parallelism: self.pb_parallelism as libc::c_uint,
699 follow_redirects: self.follow_redirects.raw(),
700 custom_headers: git_strarray {
701 count: self.custom_headers_ptrs.len(),
702 strings: self.custom_headers_ptrs.as_ptr() as *mut _,
703 },
704 }
705 }
706}
707
708impl<'repo, 'connection, 'cb> RemoteConnection<'repo, 'connection, 'cb> {
709 /// Check whether the remote is (still) connected
710 pub fn connected(&mut self) -> bool {
711 self.remote.connected()
712 }
713
714 /// Get the remote repository's reference advertisement list.
715 ///
716 /// This list is available as soon as the connection to
717 /// the remote is initiated and it remains available after disconnecting.
718 pub fn list(&self) -> Result<&[RemoteHead<'_>], Error> {
719 self.remote.list()
720 }
721
722 /// Get the remote's default branch.
723 ///
724 /// This default branch is available as soon as the connection to the remote
725 /// is initiated and it remains available after disconnecting.
726 pub fn default_branch(&self) -> Result<Buf, Error> {
727 self.remote.default_branch()
728 }
729
730 /// access remote bound to this connection
731 pub fn remote(&mut self) -> &mut Remote<'repo> {
732 self.remote
733 }
734}
735
736impl<'repo, 'connection, 'cb> Drop for RemoteConnection<'repo, 'connection, 'cb> {
737 fn drop(&mut self) {
738 drop(self.remote.disconnect());
739 }
740}
741
742impl Default for RemoteRedirect {
743 fn default() -> Self {
744 RemoteRedirect::Initial
745 }
746}
747
748impl RemoteRedirect {
749 fn raw(&self) -> raw::git_remote_redirect_t {
750 match self {
751 RemoteRedirect::None => raw::GIT_REMOTE_REDIRECT_NONE,
752 RemoteRedirect::Initial => raw::GIT_REMOTE_REDIRECT_INITIAL,
753 RemoteRedirect::All => raw::GIT_REMOTE_REDIRECT_ALL,
754 }
755 }
756}
757
758#[cfg(test)]
759mod tests {
760 use crate::{AutotagOption, PushOptions};
761 use crate::{Direction, FetchOptions, Remote, RemoteCallbacks, Repository};
762 use std::cell::Cell;
763 use tempfile::TempDir;
764
765 #[test]
766 fn smoke() {
767 let (td, repo) = crate::test::repo_init();
768 t!(repo.remote("origin", "/path/to/nowhere"));
769 drop(repo);
770
771 let repo = t!(Repository::init(td.path()));
772 let mut origin = t!(repo.find_remote("origin"));
773 assert_eq!(origin.name(), Some("origin"));
774 assert_eq!(origin.url(), Some("/path/to/nowhere"));
775 assert_eq!(origin.pushurl(), None);
776
777 t!(repo.remote_set_url("origin", "/path/to/elsewhere"));
778 t!(repo.remote_set_pushurl("origin", Some("/path/to/elsewhere")));
779
780 let stats = origin.stats();
781 assert_eq!(stats.total_objects(), 0);
782
783 t!(origin.stop());
784 }
785
786 #[test]
787 fn create_remote() {
788 let td = TempDir::new().unwrap();
789 let remote = td.path().join("remote");
790 Repository::init_bare(&remote).unwrap();
791
792 let (_td, repo) = crate::test::repo_init();
793 let url = if cfg!(unix) {
794 format!("file://{}", remote.display())
795 } else {
796 format!(
797 "file:///{}",
798 remote.display().to_string().replace("\\", "/")
799 )
800 };
801
802 let mut origin = repo.remote("origin", &url).unwrap();
803 assert_eq!(origin.name(), Some("origin"));
804 assert_eq!(origin.url(), Some(&url[..]));
805 assert_eq!(origin.pushurl(), None);
806
807 {
808 let mut specs = origin.refspecs();
809 let spec = specs.next().unwrap();
810 assert!(specs.next().is_none());
811 assert_eq!(spec.str(), Some("+refs/heads/*:refs/remotes/origin/*"));
812 assert_eq!(spec.dst(), Some("refs/remotes/origin/*"));
813 assert_eq!(spec.src(), Some("refs/heads/*"));
814 assert!(spec.is_force());
815 }
816 assert!(origin.refspecs().next_back().is_some());
817 {
818 let remotes = repo.remotes().unwrap();
819 assert_eq!(remotes.len(), 1);
820 assert_eq!(remotes.get(0), Some("origin"));
821 assert_eq!(remotes.iter().count(), 1);
822 assert_eq!(remotes.iter().next().unwrap(), Some("origin"));
823 }
824
825 origin.connect(Direction::Push).unwrap();
826 assert!(origin.connected());
827 origin.disconnect().unwrap();
828
829 origin.connect(Direction::Fetch).unwrap();
830 assert!(origin.connected());
831 origin.download(&[] as &[&str], None).unwrap();
832 origin.disconnect().unwrap();
833
834 {
835 let mut connection = origin.connect_auth(Direction::Push, None, None).unwrap();
836 assert!(connection.connected());
837 }
838 assert!(!origin.connected());
839
840 {
841 let mut connection = origin.connect_auth(Direction::Fetch, None, None).unwrap();
842 assert!(connection.connected());
843 }
844 assert!(!origin.connected());
845
846 origin.fetch(&[] as &[&str], None, None).unwrap();
847 origin.fetch(&[] as &[&str], None, Some("foo")).unwrap();
848 origin
849 .update_tips(None, true, AutotagOption::Unspecified, None)
850 .unwrap();
851 origin
852 .update_tips(None, true, AutotagOption::All, Some("foo"))
853 .unwrap();
854
855 t!(repo.remote_add_fetch("origin", "foo"));
856 t!(repo.remote_add_fetch("origin", "bar"));
857 }
858
859 #[test]
860 fn rename_remote() {
861 let (_td, repo) = crate::test::repo_init();
862 repo.remote("origin", "foo").unwrap();
863 drop(repo.remote_rename("origin", "foo"));
864 drop(repo.remote_delete("foo"));
865 }
866
867 #[test]
868 fn create_remote_anonymous() {
869 let td = TempDir::new().unwrap();
870 let repo = Repository::init(td.path()).unwrap();
871
872 let origin = repo.remote_anonymous("/path/to/nowhere").unwrap();
873 assert_eq!(origin.name(), None);
874 drop(origin.clone());
875 }
876
877 #[test]
878 fn is_valid_name() {
879 assert!(Remote::is_valid_name("foobar"));
880 assert!(!Remote::is_valid_name("\x01"));
881 }
882
883 #[test]
884 #[should_panic]
885 fn is_valid_name_for_invalid_remote() {
886 Remote::is_valid_name("ab\012");
887 }
888
889 #[test]
890 fn transfer_cb() {
891 let (td, _repo) = crate::test::repo_init();
892 let td2 = TempDir::new().unwrap();
893 let url = crate::test::path2url(&td.path());
894
895 let repo = Repository::init(td2.path()).unwrap();
896 let progress_hit = Cell::new(false);
897 {
898 let mut callbacks = RemoteCallbacks::new();
899 let mut origin = repo.remote("origin", &url).unwrap();
900
901 callbacks.transfer_progress(|_progress| {
902 progress_hit.set(true);
903 true
904 });
905 origin
906 .fetch(
907 &[] as &[&str],
908 Some(FetchOptions::new().remote_callbacks(callbacks)),
909 None,
910 )
911 .unwrap();
912
913 let list = t!(origin.list());
914 assert_eq!(list.len(), 2);
915 assert_eq!(list[0].name(), "HEAD");
916 assert!(!list[0].is_local());
917 assert_eq!(list[1].name(), "refs/heads/main");
918 assert!(!list[1].is_local());
919 }
920 assert!(progress_hit.get());
921 }
922
923 /// This test is meant to assure that the callbacks provided to connect will not cause
924 /// segfaults
925 #[test]
926 fn connect_list() {
927 let (td, _repo) = crate::test::repo_init();
928 let td2 = TempDir::new().unwrap();
929 let url = crate::test::path2url(&td.path());
930
931 let repo = Repository::init(td2.path()).unwrap();
932 let mut callbacks = RemoteCallbacks::new();
933 callbacks.sideband_progress(|_progress| {
934 // no-op
935 true
936 });
937
938 let mut origin = repo.remote("origin", &url).unwrap();
939
940 {
941 let mut connection = origin
942 .connect_auth(Direction::Fetch, Some(callbacks), None)
943 .unwrap();
944 assert!(connection.connected());
945
946 let list = t!(connection.list());
947 assert_eq!(list.len(), 2);
948 assert_eq!(list[0].name(), "HEAD");
949 assert!(!list[0].is_local());
950 assert_eq!(list[1].name(), "refs/heads/main");
951 assert!(!list[1].is_local());
952 }
953 assert!(!origin.connected());
954 }
955
956 #[test]
957 fn push() {
958 let (_td, repo) = crate::test::repo_init();
959 let td2 = TempDir::new().unwrap();
960 let td3 = TempDir::new().unwrap();
961 let url = crate::test::path2url(&td2.path());
962
963 let mut opts = crate::RepositoryInitOptions::new();
964 opts.bare(true);
965 opts.initial_head("main");
966 Repository::init_opts(td2.path(), &opts).unwrap();
967 // git push
968 let mut remote = repo.remote("origin", &url).unwrap();
969 let mut updated = false;
970 {
971 let mut callbacks = RemoteCallbacks::new();
972 callbacks.push_update_reference(|refname, status| {
973 updated = true;
974 assert_eq!(refname, "refs/heads/main");
975 assert_eq!(status, None);
976 Ok(())
977 });
978 let mut options = PushOptions::new();
979 options.remote_callbacks(callbacks);
980 remote
981 .push(&["refs/heads/main"], Some(&mut options))
982 .unwrap();
983 }
984 assert!(updated);
985
986 let repo = Repository::clone(&url, td3.path()).unwrap();
987 let commit = repo.head().unwrap().target().unwrap();
988 let commit = repo.find_commit(commit).unwrap();
989 assert_eq!(commit.message(), Some("initial\n\nbody"));
990 }
991
992 #[test]
993 fn prune() {
994 let (td, remote_repo) = crate::test::repo_init();
995 let oid = remote_repo.head().unwrap().target().unwrap();
996 let commit = remote_repo.find_commit(oid).unwrap();
997 remote_repo.branch("stale", &commit, true).unwrap();
998
999 let td2 = TempDir::new().unwrap();
1000 let url = crate::test::path2url(&td.path());
1001 let repo = Repository::clone(&url, &td2).unwrap();
1002
1003 fn assert_branch_count(repo: &Repository, count: usize) {
1004 assert_eq!(
1005 repo.branches(Some(crate::BranchType::Remote))
1006 .unwrap()
1007 .filter(|b| b.as_ref().unwrap().0.name().unwrap() == Some("origin/stale"))
1008 .count(),
1009 count,
1010 );
1011 }
1012
1013 assert_branch_count(&repo, 1);
1014
1015 // delete `stale` branch on remote repo
1016 let mut stale_branch = remote_repo
1017 .find_branch("stale", crate::BranchType::Local)
1018 .unwrap();
1019 stale_branch.delete().unwrap();
1020
1021 // prune
1022 let mut remote = repo.find_remote("origin").unwrap();
1023 remote.connect(Direction::Push).unwrap();
1024 let mut callbacks = RemoteCallbacks::new();
1025 callbacks.update_tips(|refname, _a, b| {
1026 assert_eq!(refname, "refs/remotes/origin/stale");
1027 assert!(b.is_zero());
1028 true
1029 });
1030 remote.prune(Some(callbacks)).unwrap();
1031 assert_branch_count(&repo, 0);
1032 }
1033
1034 #[test]
1035 fn push_negotiation() {
1036 let (_td, repo) = crate::test::repo_init();
1037 let oid = repo.head().unwrap().target().unwrap();
1038
1039 let td2 = TempDir::new().unwrap();
1040 let url = crate::test::path2url(td2.path());
1041 let mut opts = crate::RepositoryInitOptions::new();
1042 opts.bare(true);
1043 opts.initial_head("main");
1044 let remote_repo = Repository::init_opts(td2.path(), &opts).unwrap();
1045
1046 // reject pushing a branch
1047 let mut remote = repo.remote("origin", &url).unwrap();
1048 let mut updated = false;
1049 {
1050 let mut callbacks = RemoteCallbacks::new();
1051 callbacks.push_negotiation(|updates| {
1052 assert!(!updated);
1053 updated = true;
1054 assert_eq!(updates.len(), 1);
1055 let u = &updates[0];
1056 assert_eq!(u.src_refname().unwrap(), "refs/heads/main");
1057 assert!(u.src().is_zero());
1058 assert_eq!(u.dst_refname().unwrap(), "refs/heads/main");
1059 assert_eq!(u.dst(), oid);
1060 Err(crate::Error::from_str("rejected"))
1061 });
1062 let mut options = PushOptions::new();
1063 options.remote_callbacks(callbacks);
1064 assert!(remote
1065 .push(&["refs/heads/main"], Some(&mut options))
1066 .is_err());
1067 }
1068 assert!(updated);
1069 assert_eq!(remote_repo.branches(None).unwrap().count(), 0);
1070
1071 // push 3 branches
1072 let commit = repo.find_commit(oid).unwrap();
1073 repo.branch("new1", &commit, true).unwrap();
1074 repo.branch("new2", &commit, true).unwrap();
1075 let mut flag = 0;
1076 updated = false;
1077 {
1078 let mut callbacks = RemoteCallbacks::new();
1079 callbacks.push_negotiation(|updates| {
1080 assert!(!updated);
1081 updated = true;
1082 assert_eq!(updates.len(), 3);
1083 for u in updates {
1084 assert!(u.src().is_zero());
1085 assert_eq!(u.dst(), oid);
1086 let src_name = u.src_refname().unwrap();
1087 let dst_name = u.dst_refname().unwrap();
1088 match src_name {
1089 "refs/heads/main" => {
1090 assert_eq!(dst_name, src_name);
1091 flag |= 1;
1092 }
1093 "refs/heads/new1" => {
1094 assert_eq!(dst_name, "refs/heads/dev1");
1095 flag |= 2;
1096 }
1097 "refs/heads/new2" => {
1098 assert_eq!(dst_name, "refs/heads/dev2");
1099 flag |= 4;
1100 }
1101 _ => panic!("unexpected refname: {}", src_name),
1102 }
1103 }
1104 Ok(())
1105 });
1106 let mut options = PushOptions::new();
1107 options.remote_callbacks(callbacks);
1108 remote
1109 .push(
1110 &[
1111 "refs/heads/main",
1112 "refs/heads/new1:refs/heads/dev1",
1113 "refs/heads/new2:refs/heads/dev2",
1114 ],
1115 Some(&mut options),
1116 )
1117 .unwrap();
1118 }
1119 assert!(updated);
1120 assert_eq!(flag, 7);
1121 assert_eq!(remote_repo.branches(None).unwrap().count(), 3);
1122 }
1123}