1 use libc;
2 use raw::git_strarray;
3 use std::marker;
4 use std::mem;
5 use std::ops::Range;
6 use std::ptr;
7 use std::slice;
8 use std::str;
9 use std::{ffi::CString, os::raw::c_char};
10
11 use crate::string_array::StringArray;
12 use crate::util::Binding;
13 use crate::{raw, Buf, Direction, Error, FetchPrune, Oid, ProxyOptions, Refspec};
14 use crate::{AutotagOption, Progress, RemoteCallbacks, Repository};
15
16 /// A structure representing a [remote][1] of a git repository.
17 ///
18 /// [1]: http://git-scm.com/book/en/Git-Basics-Working-with-Remotes
19 ///
20 /// The lifetime is the lifetime of the repository that it is attached to. The
21 /// remote is used to manage fetches and pushes as well as refspecs.
22 pub struct Remote<'repo> {
23 raw: *mut raw::git_remote,
24 _marker: marker::PhantomData<&'repo Repository>,
25 }
26
27 /// An iterator over the refspecs that a remote contains.
28 pub struct Refspecs<'remote> {
29 range: Range<usize>,
30 remote: &'remote Remote<'remote>,
31 }
32
33 /// Description of a reference advertised by a remote server, given out on calls
34 /// to `list`.
35 pub struct RemoteHead<'remote> {
36 raw: *const raw::git_remote_head,
37 _marker: marker::PhantomData<&'remote str>,
38 }
39
40 /// Options which can be specified to various fetch operations.
41 pub struct FetchOptions<'cb> {
42 callbacks: Option<RemoteCallbacks<'cb>>,
43 proxy: Option<ProxyOptions<'cb>>,
44 prune: FetchPrune,
45 update_fetchhead: bool,
46 download_tags: AutotagOption,
47 custom_headers: Vec<CString>,
48 custom_headers_ptrs: Vec<*const c_char>,
49 }
50
51 /// Options to control the behavior of a git push.
52 pub struct PushOptions<'cb> {
53 callbacks: Option<RemoteCallbacks<'cb>>,
54 proxy: Option<ProxyOptions<'cb>>,
55 pb_parallelism: u32,
56 custom_headers: Vec<CString>,
57 custom_headers_ptrs: Vec<*const c_char>,
58 }
59
60 /// Holds callbacks for a connection to a `Remote`. Disconnects when dropped
61 pub struct RemoteConnection<'repo, 'connection, 'cb> {
62 _callbacks: Box<RemoteCallbacks<'cb>>,
63 _proxy: ProxyOptions<'cb>,
64 remote: &'connection mut Remote<'repo>,
65 }
66
remote_into_raw(remote: Remote<'_>) -> *mut raw::git_remote67 pub fn remote_into_raw(remote: Remote<'_>) -> *mut raw::git_remote {
68 let ret = remote.raw;
69 mem::forget(remote);
70 ret
71 }
72
73 impl<'repo> Remote<'repo> {
74 /// Ensure the remote name is well-formed.
is_valid_name(remote_name: &str) -> bool75 pub fn is_valid_name(remote_name: &str) -> bool {
76 crate::init();
77 let remote_name = CString::new(remote_name).unwrap();
78 unsafe { raw::git_remote_is_valid_name(remote_name.as_ptr()) == 1 }
79 }
80
81 /// Create a detached remote
82 ///
83 /// Create a remote with the given url in-memory. You can use this
84 /// when you have a URL instead of a remote's name.
85 /// Contrasted with an anonymous remote, a detached remote will not
86 /// consider any repo configuration values.
create_detached(url: &str) -> Result<Remote<'_>, Error>87 pub fn create_detached(url: &str) -> Result<Remote<'_>, Error> {
88 crate::init();
89 let mut ret = ptr::null_mut();
90 let url = CString::new(url)?;
91 unsafe {
92 try_call!(raw::git_remote_create_detached(&mut ret, url));
93 Ok(Binding::from_raw(ret))
94 }
95 }
96
97 /// Get the remote's name.
98 ///
99 /// Returns `None` if this remote has not yet been named or if the name is
100 /// not valid utf-8
name(&self) -> Option<&str>101 pub fn name(&self) -> Option<&str> {
102 self.name_bytes().and_then(|s| str::from_utf8(s).ok())
103 }
104
105 /// Get the remote's name, in bytes.
106 ///
107 /// Returns `None` if this remote has not yet been named
name_bytes(&self) -> Option<&[u8]>108 pub fn name_bytes(&self) -> Option<&[u8]> {
109 unsafe { crate::opt_bytes(self, raw::git_remote_name(&*self.raw)) }
110 }
111
112 /// Get the remote's url.
113 ///
114 /// Returns `None` if the url is not valid utf-8
url(&self) -> Option<&str>115 pub fn url(&self) -> Option<&str> {
116 str::from_utf8(self.url_bytes()).ok()
117 }
118
119 /// Get the remote's url as a byte array.
url_bytes(&self) -> &[u8]120 pub fn url_bytes(&self) -> &[u8] {
121 unsafe { crate::opt_bytes(self, raw::git_remote_url(&*self.raw)).unwrap() }
122 }
123
124 /// Get the remote's pushurl.
125 ///
126 /// Returns `None` if the pushurl is not valid utf-8
pushurl(&self) -> Option<&str>127 pub fn pushurl(&self) -> Option<&str> {
128 self.pushurl_bytes().and_then(|s| str::from_utf8(s).ok())
129 }
130
131 /// Get the remote's pushurl as a byte array.
pushurl_bytes(&self) -> Option<&[u8]>132 pub fn pushurl_bytes(&self) -> Option<&[u8]> {
133 unsafe { crate::opt_bytes(self, raw::git_remote_pushurl(&*self.raw)) }
134 }
135
136 /// Get the remote's default branch.
137 ///
138 /// The remote (or more exactly its transport) must have connected to the
139 /// remote repository. This default branch is available as soon as the
140 /// connection to the remote is initiated and it remains available after
141 /// disconnecting.
default_branch(&self) -> Result<Buf, Error>142 pub fn default_branch(&self) -> Result<Buf, Error> {
143 unsafe {
144 let buf = Buf::new();
145 try_call!(raw::git_remote_default_branch(buf.raw(), self.raw));
146 Ok(buf)
147 }
148 }
149
150 /// Open a connection to a remote.
connect(&mut self, dir: Direction) -> Result<(), Error>151 pub fn connect(&mut self, dir: Direction) -> Result<(), Error> {
152 // TODO: can callbacks be exposed safely?
153 unsafe {
154 try_call!(raw::git_remote_connect(
155 self.raw,
156 dir,
157 ptr::null(),
158 ptr::null(),
159 ptr::null()
160 ));
161 }
162 Ok(())
163 }
164
165 /// Open a connection to a remote with callbacks and proxy settings
166 ///
167 /// Returns a `RemoteConnection` that will disconnect once dropped
connect_auth<'connection, 'cb>( &'connection mut self, dir: Direction, cb: Option<RemoteCallbacks<'cb>>, proxy_options: Option<ProxyOptions<'cb>>, ) -> Result<RemoteConnection<'repo, 'connection, 'cb>, Error>168 pub fn connect_auth<'connection, 'cb>(
169 &'connection mut self,
170 dir: Direction,
171 cb: Option<RemoteCallbacks<'cb>>,
172 proxy_options: Option<ProxyOptions<'cb>>,
173 ) -> Result<RemoteConnection<'repo, 'connection, 'cb>, Error> {
174 let cb = Box::new(cb.unwrap_or_else(RemoteCallbacks::new));
175 let proxy_options = proxy_options.unwrap_or_else(ProxyOptions::new);
176 unsafe {
177 try_call!(raw::git_remote_connect(
178 self.raw,
179 dir,
180 &cb.raw(),
181 &proxy_options.raw(),
182 ptr::null()
183 ));
184 }
185
186 Ok(RemoteConnection {
187 _callbacks: cb,
188 _proxy: proxy_options,
189 remote: self,
190 })
191 }
192
193 /// Check whether the remote is connected
connected(&mut self) -> bool194 pub fn connected(&mut self) -> bool {
195 unsafe { raw::git_remote_connected(self.raw) == 1 }
196 }
197
198 /// Disconnect from the remote
disconnect(&mut self) -> Result<(), Error>199 pub fn disconnect(&mut self) -> Result<(), Error> {
200 unsafe {
201 try_call!(raw::git_remote_disconnect(self.raw));
202 }
203 Ok(())
204 }
205
206 /// Download and index the packfile
207 ///
208 /// Connect to the remote if it hasn't been done yet, negotiate with the
209 /// remote git which objects are missing, download and index the packfile.
210 ///
211 /// The .idx file will be created and both it and the packfile with be
212 /// renamed to their final name.
213 ///
214 /// The `specs` argument is a list of refspecs to use for this negotiation
215 /// and download. Use an empty array to use the base refspecs.
download<Str: AsRef<str> + crate::IntoCString + Clone>( &mut self, specs: &[Str], opts: Option<&mut FetchOptions<'_>>, ) -> Result<(), Error>216 pub fn download<Str: AsRef<str> + crate::IntoCString + Clone>(
217 &mut self,
218 specs: &[Str],
219 opts: Option<&mut FetchOptions<'_>>,
220 ) -> Result<(), Error> {
221 let (_a, _b, arr) = crate::util::iter2cstrs(specs.iter())?;
222 let raw = opts.map(|o| o.raw());
223 unsafe {
224 try_call!(raw::git_remote_download(self.raw, &arr, raw.as_ref()));
225 }
226 Ok(())
227 }
228
229 /// Cancel the operation
230 ///
231 /// At certain points in its operation, the network code checks whether the
232 /// operation has been cancelled and if so stops the operation.
stop(&mut self) -> Result<(), Error>233 pub fn stop(&mut self) -> Result<(), Error> {
234 unsafe {
235 try_call!(raw::git_remote_stop(self.raw));
236 }
237 Ok(())
238 }
239
240 /// Get the number of refspecs for a remote
refspecs(&self) -> Refspecs<'_>241 pub fn refspecs(&self) -> Refspecs<'_> {
242 let cnt = unsafe { raw::git_remote_refspec_count(&*self.raw) as usize };
243 Refspecs {
244 range: 0..cnt,
245 remote: self,
246 }
247 }
248
249 /// Get the `nth` refspec from this remote.
250 ///
251 /// The `refspecs` iterator can be used to iterate over all refspecs.
get_refspec(&self, i: usize) -> Option<Refspec<'repo>>252 pub fn get_refspec(&self, i: usize) -> Option<Refspec<'repo>> {
253 unsafe {
254 let ptr = raw::git_remote_get_refspec(&*self.raw, i as libc::size_t);
255 Binding::from_raw_opt(ptr)
256 }
257 }
258
259 /// Download new data and update tips
260 ///
261 /// Convenience function to connect to a remote, download the data,
262 /// disconnect and update the remote-tracking branches.
263 ///
264 /// # Examples
265 ///
266 /// Example of functionality similar to `git fetch origin/main`:
267 ///
268 /// ```no_run
269 /// fn fetch_origin_main(repo: git2::Repository) -> Result<(), git2::Error> {
270 /// repo.find_remote("origin")?.fetch(&["main"], None, None)
271 /// }
272 ///
273 /// let repo = git2::Repository::discover("rust").unwrap();
274 /// fetch_origin_main(repo).unwrap();
275 /// ```
fetch<Str: AsRef<str> + crate::IntoCString + Clone>( &mut self, refspecs: &[Str], opts: Option<&mut FetchOptions<'_>>, reflog_msg: Option<&str>, ) -> Result<(), Error>276 pub fn fetch<Str: AsRef<str> + crate::IntoCString + Clone>(
277 &mut self,
278 refspecs: &[Str],
279 opts: Option<&mut FetchOptions<'_>>,
280 reflog_msg: Option<&str>,
281 ) -> Result<(), Error> {
282 let (_a, _b, arr) = crate::util::iter2cstrs(refspecs.iter())?;
283 let msg = crate::opt_cstr(reflog_msg)?;
284 let raw = opts.map(|o| o.raw());
285 unsafe {
286 try_call!(raw::git_remote_fetch(self.raw, &arr, raw.as_ref(), msg));
287 }
288 Ok(())
289 }
290
291 /// Update the tips to the new state
update_tips( &mut self, callbacks: Option<&mut RemoteCallbacks<'_>>, update_fetchhead: bool, download_tags: AutotagOption, msg: Option<&str>, ) -> Result<(), Error>292 pub fn update_tips(
293 &mut self,
294 callbacks: Option<&mut RemoteCallbacks<'_>>,
295 update_fetchhead: bool,
296 download_tags: AutotagOption,
297 msg: Option<&str>,
298 ) -> Result<(), Error> {
299 let msg = crate::opt_cstr(msg)?;
300 let cbs = callbacks.map(|cb| cb.raw());
301 unsafe {
302 try_call!(raw::git_remote_update_tips(
303 self.raw,
304 cbs.as_ref(),
305 update_fetchhead,
306 download_tags,
307 msg
308 ));
309 }
310 Ok(())
311 }
312
313 /// Perform a push
314 ///
315 /// Perform all the steps for a push. If no refspecs are passed then the
316 /// configured refspecs will be used.
317 ///
318 /// Note that you'll likely want to use `RemoteCallbacks` and set
319 /// `push_update_reference` to test whether all the references were pushed
320 /// successfully.
push<Str: AsRef<str> + crate::IntoCString + Clone>( &mut self, refspecs: &[Str], opts: Option<&mut PushOptions<'_>>, ) -> Result<(), Error>321 pub fn push<Str: AsRef<str> + crate::IntoCString + Clone>(
322 &mut self,
323 refspecs: &[Str],
324 opts: Option<&mut PushOptions<'_>>,
325 ) -> Result<(), Error> {
326 let (_a, _b, arr) = crate::util::iter2cstrs(refspecs.iter())?;
327 let raw = opts.map(|o| o.raw());
328 unsafe {
329 try_call!(raw::git_remote_push(self.raw, &arr, raw.as_ref()));
330 }
331 Ok(())
332 }
333
334 /// Get the statistics structure that is filled in by the fetch operation.
stats(&self) -> Progress<'_>335 pub fn stats(&self) -> Progress<'_> {
336 unsafe { Binding::from_raw(raw::git_remote_stats(self.raw)) }
337 }
338
339 /// Get the remote repository's reference advertisement list.
340 ///
341 /// Get the list of references with which the server responds to a new
342 /// connection.
343 ///
344 /// The remote (or more exactly its transport) must have connected to the
345 /// remote repository. This list is available as soon as the connection to
346 /// the remote is initiated and it remains available after disconnecting.
list(&self) -> Result<&[RemoteHead<'_>], Error>347 pub fn list(&self) -> Result<&[RemoteHead<'_>], Error> {
348 let mut size = 0;
349 let mut base = ptr::null_mut();
350 unsafe {
351 try_call!(raw::git_remote_ls(&mut base, &mut size, self.raw));
352 assert_eq!(
353 mem::size_of::<RemoteHead<'_>>(),
354 mem::size_of::<*const raw::git_remote_head>()
355 );
356 let slice = slice::from_raw_parts(base as *const _, size as usize);
357 Ok(mem::transmute::<
358 &[*const raw::git_remote_head],
359 &[RemoteHead<'_>],
360 >(slice))
361 }
362 }
363
364 /// Prune tracking refs that are no longer present on remote
prune(&mut self, callbacks: Option<RemoteCallbacks<'_>>) -> Result<(), Error>365 pub fn prune(&mut self, callbacks: Option<RemoteCallbacks<'_>>) -> Result<(), Error> {
366 let cbs = Box::new(callbacks.unwrap_or_else(RemoteCallbacks::new));
367 unsafe {
368 try_call!(raw::git_remote_prune(self.raw, &cbs.raw()));
369 }
370 Ok(())
371 }
372
373 /// Get the remote's list of fetch refspecs
fetch_refspecs(&self) -> Result<StringArray, Error>374 pub fn fetch_refspecs(&self) -> Result<StringArray, Error> {
375 unsafe {
376 let mut raw: raw::git_strarray = mem::zeroed();
377 try_call!(raw::git_remote_get_fetch_refspecs(&mut raw, self.raw));
378 Ok(StringArray::from_raw(raw))
379 }
380 }
381
382 /// Get the remote's list of push refspecs
push_refspecs(&self) -> Result<StringArray, Error>383 pub fn push_refspecs(&self) -> Result<StringArray, Error> {
384 unsafe {
385 let mut raw: raw::git_strarray = mem::zeroed();
386 try_call!(raw::git_remote_get_push_refspecs(&mut raw, self.raw));
387 Ok(StringArray::from_raw(raw))
388 }
389 }
390 }
391
392 impl<'repo> Clone for Remote<'repo> {
clone(&self) -> Remote<'repo>393 fn clone(&self) -> Remote<'repo> {
394 let mut ret = ptr::null_mut();
395 let rc = unsafe { call!(raw::git_remote_dup(&mut ret, self.raw)) };
396 assert_eq!(rc, 0);
397 Remote {
398 raw: ret,
399 _marker: marker::PhantomData,
400 }
401 }
402 }
403
404 impl<'repo> Binding for Remote<'repo> {
405 type Raw = *mut raw::git_remote;
406
from_raw(raw: *mut raw::git_remote) -> Remote<'repo>407 unsafe fn from_raw(raw: *mut raw::git_remote) -> Remote<'repo> {
408 Remote {
409 raw,
410 _marker: marker::PhantomData,
411 }
412 }
raw(&self) -> *mut raw::git_remote413 fn raw(&self) -> *mut raw::git_remote {
414 self.raw
415 }
416 }
417
418 impl<'repo> Drop for Remote<'repo> {
drop(&mut self)419 fn drop(&mut self) {
420 unsafe { raw::git_remote_free(self.raw) }
421 }
422 }
423
424 impl<'repo> Iterator for Refspecs<'repo> {
425 type Item = Refspec<'repo>;
next(&mut self) -> Option<Refspec<'repo>>426 fn next(&mut self) -> Option<Refspec<'repo>> {
427 self.range.next().and_then(|i| self.remote.get_refspec(i))
428 }
size_hint(&self) -> (usize, Option<usize>)429 fn size_hint(&self) -> (usize, Option<usize>) {
430 self.range.size_hint()
431 }
432 }
433 impl<'repo> DoubleEndedIterator for Refspecs<'repo> {
next_back(&mut self) -> Option<Refspec<'repo>>434 fn next_back(&mut self) -> Option<Refspec<'repo>> {
435 self.range
436 .next_back()
437 .and_then(|i| self.remote.get_refspec(i))
438 }
439 }
440 impl<'repo> ExactSizeIterator for Refspecs<'repo> {}
441
442 #[allow(missing_docs)] // not documented in libgit2 :(
443 impl<'remote> RemoteHead<'remote> {
444 /// Flag if this is available locally.
is_local(&self) -> bool445 pub fn is_local(&self) -> bool {
446 unsafe { (*self.raw).local != 0 }
447 }
448
oid(&self) -> Oid449 pub fn oid(&self) -> Oid {
450 unsafe { Binding::from_raw(&(*self.raw).oid as *const _) }
451 }
loid(&self) -> Oid452 pub fn loid(&self) -> Oid {
453 unsafe { Binding::from_raw(&(*self.raw).loid as *const _) }
454 }
455
name(&self) -> &str456 pub fn name(&self) -> &str {
457 let b = unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() };
458 str::from_utf8(b).unwrap()
459 }
460
symref_target(&self) -> Option<&str>461 pub fn symref_target(&self) -> Option<&str> {
462 let b = unsafe { crate::opt_bytes(self, (*self.raw).symref_target) };
463 b.map(|b| str::from_utf8(b).unwrap())
464 }
465 }
466
467 impl<'cb> Default for FetchOptions<'cb> {
default() -> Self468 fn default() -> Self {
469 Self::new()
470 }
471 }
472
473 impl<'cb> FetchOptions<'cb> {
474 /// Creates a new blank set of fetch options
new() -> FetchOptions<'cb>475 pub fn new() -> FetchOptions<'cb> {
476 FetchOptions {
477 callbacks: None,
478 proxy: None,
479 prune: FetchPrune::Unspecified,
480 update_fetchhead: true,
481 download_tags: AutotagOption::Unspecified,
482 custom_headers: Vec::new(),
483 custom_headers_ptrs: Vec::new(),
484 }
485 }
486
487 /// Set the callbacks to use for the fetch operation.
remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self488 pub fn remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self {
489 self.callbacks = Some(cbs);
490 self
491 }
492
493 /// Set the proxy options to use for the fetch operation.
proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self494 pub fn proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self {
495 self.proxy = Some(opts);
496 self
497 }
498
499 /// Set whether to perform a prune after the fetch.
prune(&mut self, prune: FetchPrune) -> &mut Self500 pub fn prune(&mut self, prune: FetchPrune) -> &mut Self {
501 self.prune = prune;
502 self
503 }
504
505 /// Set whether to write the results to FETCH_HEAD.
506 ///
507 /// Defaults to `true`.
update_fetchhead(&mut self, update: bool) -> &mut Self508 pub fn update_fetchhead(&mut self, update: bool) -> &mut Self {
509 self.update_fetchhead = update;
510 self
511 }
512
513 /// Set how to behave regarding tags on the remote, such as auto-downloading
514 /// tags for objects we're downloading or downloading all of them.
515 ///
516 /// The default is to auto-follow tags.
download_tags(&mut self, opt: AutotagOption) -> &mut Self517 pub fn download_tags(&mut self, opt: AutotagOption) -> &mut Self {
518 self.download_tags = opt;
519 self
520 }
521
522 /// Set extra headers for this fetch operation.
custom_headers(&mut self, custom_headers: &[&str]) -> &mut Self523 pub fn custom_headers(&mut self, custom_headers: &[&str]) -> &mut Self {
524 self.custom_headers = custom_headers
525 .iter()
526 .map(|&s| CString::new(s).unwrap())
527 .collect();
528 self.custom_headers_ptrs = self.custom_headers.iter().map(|s| s.as_ptr()).collect();
529 self
530 }
531 }
532
533 impl<'cb> Binding for FetchOptions<'cb> {
534 type Raw = raw::git_fetch_options;
535
from_raw(_raw: raw::git_fetch_options) -> FetchOptions<'cb>536 unsafe fn from_raw(_raw: raw::git_fetch_options) -> FetchOptions<'cb> {
537 panic!("unimplemented");
538 }
raw(&self) -> raw::git_fetch_options539 fn raw(&self) -> raw::git_fetch_options {
540 raw::git_fetch_options {
541 version: 1,
542 callbacks: self
543 .callbacks
544 .as_ref()
545 .map(|m| m.raw())
546 .unwrap_or_else(|| RemoteCallbacks::new().raw()),
547 proxy_opts: self
548 .proxy
549 .as_ref()
550 .map(|m| m.raw())
551 .unwrap_or_else(|| ProxyOptions::new().raw()),
552 prune: crate::call::convert(&self.prune),
553 update_fetchhead: crate::call::convert(&self.update_fetchhead),
554 download_tags: crate::call::convert(&self.download_tags),
555 custom_headers: git_strarray {
556 count: self.custom_headers_ptrs.len(),
557 strings: self.custom_headers_ptrs.as_ptr() as *mut _,
558 },
559 }
560 }
561 }
562
563 impl<'cb> Default for PushOptions<'cb> {
default() -> Self564 fn default() -> Self {
565 Self::new()
566 }
567 }
568
569 impl<'cb> PushOptions<'cb> {
570 /// Creates a new blank set of push options
new() -> PushOptions<'cb>571 pub fn new() -> PushOptions<'cb> {
572 PushOptions {
573 callbacks: None,
574 proxy: None,
575 pb_parallelism: 1,
576 custom_headers: Vec::new(),
577 custom_headers_ptrs: Vec::new(),
578 }
579 }
580
581 /// Set the callbacks to use for the fetch operation.
remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self582 pub fn remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self {
583 self.callbacks = Some(cbs);
584 self
585 }
586
587 /// Set the proxy options to use for the fetch operation.
proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self588 pub fn proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self {
589 self.proxy = Some(opts);
590 self
591 }
592
593 /// If the transport being used to push to the remote requires the creation
594 /// of a pack file, this controls the number of worker threads used by the
595 /// packbuilder when creating that pack file to be sent to the remote.
596 ///
597 /// if set to 0 the packbuilder will auto-detect the number of threads to
598 /// create, and the default value is 1.
packbuilder_parallelism(&mut self, parallel: u32) -> &mut Self599 pub fn packbuilder_parallelism(&mut self, parallel: u32) -> &mut Self {
600 self.pb_parallelism = parallel;
601 self
602 }
603
604 /// Set extra headers for this push operation.
custom_headers(&mut self, custom_headers: &[&str]) -> &mut Self605 pub fn custom_headers(&mut self, custom_headers: &[&str]) -> &mut Self {
606 self.custom_headers = custom_headers
607 .iter()
608 .map(|&s| CString::new(s).unwrap())
609 .collect();
610 self.custom_headers_ptrs = self.custom_headers.iter().map(|s| s.as_ptr()).collect();
611 self
612 }
613 }
614
615 impl<'cb> Binding for PushOptions<'cb> {
616 type Raw = raw::git_push_options;
617
from_raw(_raw: raw::git_push_options) -> PushOptions<'cb>618 unsafe fn from_raw(_raw: raw::git_push_options) -> PushOptions<'cb> {
619 panic!("unimplemented");
620 }
raw(&self) -> raw::git_push_options621 fn raw(&self) -> raw::git_push_options {
622 raw::git_push_options {
623 version: 1,
624 callbacks: self
625 .callbacks
626 .as_ref()
627 .map(|m| m.raw())
628 .unwrap_or_else(|| RemoteCallbacks::new().raw()),
629 proxy_opts: self
630 .proxy
631 .as_ref()
632 .map(|m| m.raw())
633 .unwrap_or_else(|| ProxyOptions::new().raw()),
634 pb_parallelism: self.pb_parallelism as libc::c_uint,
635 custom_headers: git_strarray {
636 count: self.custom_headers_ptrs.len(),
637 strings: self.custom_headers_ptrs.as_ptr() as *mut _,
638 },
639 }
640 }
641 }
642
643 impl<'repo, 'connection, 'cb> RemoteConnection<'repo, 'connection, 'cb> {
644 /// Check whether the remote is (still) connected
connected(&mut self) -> bool645 pub fn connected(&mut self) -> bool {
646 self.remote.connected()
647 }
648
649 /// Get the remote repository's reference advertisement list.
650 ///
651 /// This list is available as soon as the connection to
652 /// the remote is initiated and it remains available after disconnecting.
list(&self) -> Result<&[RemoteHead<'_>], Error>653 pub fn list(&self) -> Result<&[RemoteHead<'_>], Error> {
654 self.remote.list()
655 }
656
657 /// Get the remote's default branch.
658 ///
659 /// This default branch is available as soon as the connection to the remote
660 /// is initiated and it remains available after disconnecting.
default_branch(&self) -> Result<Buf, Error>661 pub fn default_branch(&self) -> Result<Buf, Error> {
662 self.remote.default_branch()
663 }
664
665 /// access remote bound to this connection
remote(&mut self) -> &mut Remote<'repo>666 pub fn remote(&mut self) -> &mut Remote<'repo> {
667 self.remote
668 }
669 }
670
671 impl<'repo, 'connection, 'cb> Drop for RemoteConnection<'repo, 'connection, 'cb> {
drop(&mut self)672 fn drop(&mut self) {
673 drop(self.remote.disconnect());
674 }
675 }
676
677 #[cfg(test)]
678 mod tests {
679 use crate::{AutotagOption, PushOptions};
680 use crate::{Direction, FetchOptions, Remote, RemoteCallbacks, Repository};
681 use std::cell::Cell;
682 use tempfile::TempDir;
683
684 #[test]
smoke()685 fn smoke() {
686 let (td, repo) = crate::test::repo_init();
687 t!(repo.remote("origin", "/path/to/nowhere"));
688 drop(repo);
689
690 let repo = t!(Repository::init(td.path()));
691 let mut origin = t!(repo.find_remote("origin"));
692 assert_eq!(origin.name(), Some("origin"));
693 assert_eq!(origin.url(), Some("/path/to/nowhere"));
694 assert_eq!(origin.pushurl(), None);
695
696 t!(repo.remote_set_url("origin", "/path/to/elsewhere"));
697 t!(repo.remote_set_pushurl("origin", Some("/path/to/elsewhere")));
698
699 let stats = origin.stats();
700 assert_eq!(stats.total_objects(), 0);
701
702 t!(origin.stop());
703 }
704
705 #[test]
create_remote()706 fn create_remote() {
707 let td = TempDir::new().unwrap();
708 let remote = td.path().join("remote");
709 Repository::init_bare(&remote).unwrap();
710
711 let (_td, repo) = crate::test::repo_init();
712 let url = if cfg!(unix) {
713 format!("file://{}", remote.display())
714 } else {
715 format!(
716 "file:///{}",
717 remote.display().to_string().replace("\\", "/")
718 )
719 };
720
721 let mut origin = repo.remote("origin", &url).unwrap();
722 assert_eq!(origin.name(), Some("origin"));
723 assert_eq!(origin.url(), Some(&url[..]));
724 assert_eq!(origin.pushurl(), None);
725
726 {
727 let mut specs = origin.refspecs();
728 let spec = specs.next().unwrap();
729 assert!(specs.next().is_none());
730 assert_eq!(spec.str(), Some("+refs/heads/*:refs/remotes/origin/*"));
731 assert_eq!(spec.dst(), Some("refs/remotes/origin/*"));
732 assert_eq!(spec.src(), Some("refs/heads/*"));
733 assert!(spec.is_force());
734 }
735 assert!(origin.refspecs().next_back().is_some());
736 {
737 let remotes = repo.remotes().unwrap();
738 assert_eq!(remotes.len(), 1);
739 assert_eq!(remotes.get(0), Some("origin"));
740 assert_eq!(remotes.iter().count(), 1);
741 assert_eq!(remotes.iter().next().unwrap(), Some("origin"));
742 }
743
744 origin.connect(Direction::Push).unwrap();
745 assert!(origin.connected());
746 origin.disconnect().unwrap();
747
748 origin.connect(Direction::Fetch).unwrap();
749 assert!(origin.connected());
750 origin.download(&[] as &[&str], None).unwrap();
751 origin.disconnect().unwrap();
752
753 {
754 let mut connection = origin.connect_auth(Direction::Push, None, None).unwrap();
755 assert!(connection.connected());
756 }
757 assert!(!origin.connected());
758
759 {
760 let mut connection = origin.connect_auth(Direction::Fetch, None, None).unwrap();
761 assert!(connection.connected());
762 }
763 assert!(!origin.connected());
764
765 origin.fetch(&[] as &[&str], None, None).unwrap();
766 origin.fetch(&[] as &[&str], None, Some("foo")).unwrap();
767 origin
768 .update_tips(None, true, AutotagOption::Unspecified, None)
769 .unwrap();
770 origin
771 .update_tips(None, true, AutotagOption::All, Some("foo"))
772 .unwrap();
773
774 t!(repo.remote_add_fetch("origin", "foo"));
775 t!(repo.remote_add_fetch("origin", "bar"));
776 }
777
778 #[test]
779 fn rename_remote() {
780 let (_td, repo) = crate::test::repo_init();
781 repo.remote("origin", "foo").unwrap();
782 drop(repo.remote_rename("origin", "foo"));
783 drop(repo.remote_delete("foo"));
784 }
785
786 #[test]
787 fn create_remote_anonymous() {
788 let td = TempDir::new().unwrap();
789 let repo = Repository::init(td.path()).unwrap();
790
791 let origin = repo.remote_anonymous("/path/to/nowhere").unwrap();
792 assert_eq!(origin.name(), None);
793 drop(origin.clone());
794 }
795
796 #[test]
797 fn is_valid() {
798 assert!(Remote::is_valid_name("foobar"));
799 assert!(!Remote::is_valid_name("\x01"));
800 }
801
802 #[test]
803 fn transfer_cb() {
804 let (td, _repo) = crate::test::repo_init();
805 let td2 = TempDir::new().unwrap();
806 let url = crate::test::path2url(&td.path());
807
808 let repo = Repository::init(td2.path()).unwrap();
809 let progress_hit = Cell::new(false);
810 {
811 let mut callbacks = RemoteCallbacks::new();
812 let mut origin = repo.remote("origin", &url).unwrap();
813
814 callbacks.transfer_progress(|_progress| {
815 progress_hit.set(true);
816 true
817 });
818 origin
819 .fetch(
820 &[] as &[&str],
821 Some(FetchOptions::new().remote_callbacks(callbacks)),
822 None,
823 )
824 .unwrap();
825
826 let list = t!(origin.list());
827 assert_eq!(list.len(), 2);
828 assert_eq!(list[0].name(), "HEAD");
829 assert!(!list[0].is_local());
830 assert_eq!(list[1].name(), "refs/heads/main");
831 assert!(!list[1].is_local());
832 }
833 assert!(progress_hit.get());
834 }
835
836 /// This test is meant to assure that the callbacks provided to connect will not cause
837 /// segfaults
838 #[test]
839 fn connect_list() {
840 let (td, _repo) = crate::test::repo_init();
841 let td2 = TempDir::new().unwrap();
842 let url = crate::test::path2url(&td.path());
843
844 let repo = Repository::init(td2.path()).unwrap();
845 let mut callbacks = RemoteCallbacks::new();
846 callbacks.sideband_progress(|_progress| {
847 // no-op
848 true
849 });
850
851 let mut origin = repo.remote("origin", &url).unwrap();
852
853 {
854 let mut connection = origin
855 .connect_auth(Direction::Fetch, Some(callbacks), None)
856 .unwrap();
857 assert!(connection.connected());
858
859 let list = t!(connection.list());
860 assert_eq!(list.len(), 2);
861 assert_eq!(list[0].name(), "HEAD");
862 assert!(!list[0].is_local());
863 assert_eq!(list[1].name(), "refs/heads/main");
864 assert!(!list[1].is_local());
865 }
866 assert!(!origin.connected());
867 }
868
869 #[test]
870 fn push() {
871 let (_td, repo) = crate::test::repo_init();
872 let td2 = TempDir::new().unwrap();
873 let td3 = TempDir::new().unwrap();
874 let url = crate::test::path2url(&td2.path());
875
876 let mut opts = crate::RepositoryInitOptions::new();
877 opts.bare(true);
878 opts.initial_head("main");
879 Repository::init_opts(td2.path(), &opts).unwrap();
880 // git push
881 let mut remote = repo.remote("origin", &url).unwrap();
882 let mut updated = false;
883 {
884 let mut callbacks = RemoteCallbacks::new();
885 callbacks.push_update_reference(|refname, status| {
886 updated = true;
887 assert_eq!(refname, "refs/heads/main");
888 assert_eq!(status, None);
889 Ok(())
890 });
891 let mut options = PushOptions::new();
892 options.remote_callbacks(callbacks);
893 remote
894 .push(&["refs/heads/main"], Some(&mut options))
895 .unwrap();
896 }
897 assert!(updated);
898
899 let repo = Repository::clone(&url, td3.path()).unwrap();
900 let commit = repo.head().unwrap().target().unwrap();
901 let commit = repo.find_commit(commit).unwrap();
902 assert_eq!(commit.message(), Some("initial"));
903 }
904
905 #[test]
906 fn prune() {
907 let (td, remote_repo) = crate::test::repo_init();
908 let oid = remote_repo.head().unwrap().target().unwrap();
909 let commit = remote_repo.find_commit(oid).unwrap();
910 remote_repo.branch("stale", &commit, true).unwrap();
911
912 let td2 = TempDir::new().unwrap();
913 let url = crate::test::path2url(&td.path());
914 let repo = Repository::clone(&url, &td2).unwrap();
915
916 fn assert_branch_count(repo: &Repository, count: usize) {
917 assert_eq!(
918 repo.branches(Some(crate::BranchType::Remote))
919 .unwrap()
920 .filter(|b| b.as_ref().unwrap().0.name().unwrap() == Some("origin/stale"))
921 .count(),
922 count,
923 );
924 }
925
926 assert_branch_count(&repo, 1);
927
928 // delete `stale` branch on remote repo
929 let mut stale_branch = remote_repo
930 .find_branch("stale", crate::BranchType::Local)
931 .unwrap();
932 stale_branch.delete().unwrap();
933
934 // prune
935 let mut remote = repo.find_remote("origin").unwrap();
936 remote.connect(Direction::Push).unwrap();
937 let mut callbacks = RemoteCallbacks::new();
938 callbacks.update_tips(|refname, _a, b| {
939 assert_eq!(refname, "refs/remotes/origin/stale");
940 assert!(b.is_zero());
941 true
942 });
943 remote.prune(Some(callbacks)).unwrap();
944 assert_branch_count(&repo, 0);
945 }
946 }
947