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