1use std::cmp::Ordering;
2use std::ffi::CString;
3use std::marker;
4use std::mem;
5use std::ptr;
6use std::str;
7
8use crate::object::CastOrPanic;
9use crate::util::{c_cmp_to_ordering, Binding};
10use crate::{
11 call, raw, Blob, Commit, Error, Object, ObjectType, Oid, ReferenceFormat, ReferenceType,
12 Repository, Tag, Tree,
13};
14
15const GIT_REFNAME_MAX: usize = 1024;
18
19struct RefdbMarker<'repo>(#[allow(dead_code)] &'repo Repository);
26
27pub struct Reference<'repo> {
31 raw: *mut raw::git_reference,
32 _marker: marker::PhantomData<RefdbMarker<'repo>>,
33}
34
35pub struct References<'repo> {
37 raw: *mut raw::git_reference_iterator,
38 _marker: marker::PhantomData<RefdbMarker<'repo>>,
39}
40
41pub struct ReferenceNames<'repo, 'references> {
43 inner: &'references mut References<'repo>,
44}
45
46impl<'repo> Reference<'repo> {
47 pub fn is_valid_name(refname: &str) -> bool {
69 crate::init();
70 let refname = match CString::new(refname) {
71 Ok(s) => s,
72 Err(_) => return false,
73 };
74 let mut valid: libc::c_int = 0;
75 unsafe {
76 call::c_try(raw::git_reference_name_is_valid(
77 &mut valid,
78 refname.as_ptr(),
79 ))
80 .unwrap();
81 }
82 valid == 1
83 }
84
85 pub fn normalize_name(refname: &str, flags: ReferenceFormat) -> Result<String, Error> {
158 crate::init();
159 let mut dst = [0u8; GIT_REFNAME_MAX];
160 let refname = CString::new(refname)?;
161 unsafe {
162 try_call!(raw::git_reference_normalize_name(
163 dst.as_mut_ptr() as *mut libc::c_char,
164 dst.len() as libc::size_t,
165 refname,
166 flags.bits()
167 ));
168 let s = &dst[..dst.iter().position(|&a| a == 0).unwrap()];
169 Ok(str::from_utf8(s).unwrap().to_owned())
170 }
171 }
172
173 pub fn raw(&self) -> *mut raw::git_reference {
175 self.raw
176 }
177
178 pub fn delete(&mut self) -> Result<(), Error> {
186 unsafe {
187 try_call!(raw::git_reference_delete(self.raw));
188 }
189 Ok(())
190 }
191
192 pub fn is_branch(&self) -> bool {
194 unsafe { raw::git_reference_is_branch(&*self.raw) == 1 }
195 }
196
197 pub fn is_note(&self) -> bool {
199 unsafe { raw::git_reference_is_note(&*self.raw) == 1 }
200 }
201
202 pub fn is_remote(&self) -> bool {
204 unsafe { raw::git_reference_is_remote(&*self.raw) == 1 }
205 }
206
207 pub fn is_tag(&self) -> bool {
209 unsafe { raw::git_reference_is_tag(&*self.raw) == 1 }
210 }
211
212 pub fn kind(&self) -> Option<ReferenceType> {
216 ReferenceType::from_raw(unsafe { raw::git_reference_type(&*self.raw) })
217 }
218
219 pub fn name(&self) -> Result<&str, Error> {
221 str::from_utf8(self.name_bytes()).map_err(|e| e.into())
222 }
223
224 pub fn name_bytes(&self) -> &[u8] {
226 unsafe { crate::opt_bytes(self, raw::git_reference_name(&*self.raw)).unwrap() }
227 }
228
229 pub fn shorthand(&self) -> Result<&str, Error> {
234 str::from_utf8(self.shorthand_bytes()).map_err(|e| e.into())
235 }
236
237 pub fn shorthand_bytes(&self) -> &[u8] {
239 unsafe { crate::opt_bytes(self, raw::git_reference_shorthand(&*self.raw)).unwrap() }
240 }
241
242 pub fn target(&self) -> Option<Oid> {
247 unsafe { Binding::from_raw_opt(raw::git_reference_target(&*self.raw)) }
248 }
249
250 pub fn target_peel(&self) -> Option<Oid> {
255 unsafe { Binding::from_raw_opt(raw::git_reference_target_peel(&*self.raw)) }
256 }
257
258 pub fn symbolic_target(&self) -> Result<Option<&str>, Error> {
262 match self.symbolic_target_bytes() {
263 Some(stb) => str::from_utf8(stb).map(|s| Some(s)).map_err(|e| e.into()),
264 None => Ok(None),
265 }
266 }
267
268 pub fn symbolic_target_bytes(&self) -> Option<&[u8]> {
272 unsafe { crate::opt_bytes(self, raw::git_reference_symbolic_target(&*self.raw)) }
273 }
274
275 pub fn resolve(&self) -> Result<Reference<'repo>, Error> {
283 let mut raw = ptr::null_mut();
284 unsafe {
285 try_call!(raw::git_reference_resolve(&mut raw, &*self.raw));
286 Ok(Binding::from_raw(raw))
287 }
288 }
289
290 pub fn peel(&self, kind: ObjectType) -> Result<Object<'repo>, Error> {
295 let mut raw = ptr::null_mut();
296 unsafe {
297 try_call!(raw::git_reference_peel(&mut raw, self.raw, kind));
298 Ok(Binding::from_raw(raw))
299 }
300 }
301
302 pub fn peel_to_blob(&self) -> Result<Blob<'repo>, Error> {
307 Ok(self.peel(ObjectType::Blob)?.cast_or_panic(ObjectType::Blob))
308 }
309
310 pub fn peel_to_commit(&self) -> Result<Commit<'repo>, Error> {
315 Ok(self
316 .peel(ObjectType::Commit)?
317 .cast_or_panic(ObjectType::Commit))
318 }
319
320 pub fn peel_to_tree(&self) -> Result<Tree<'repo>, Error> {
325 Ok(self.peel(ObjectType::Tree)?.cast_or_panic(ObjectType::Tree))
326 }
327
328 pub fn peel_to_tag(&self) -> Result<Tag<'repo>, Error> {
333 Ok(self.peel(ObjectType::Tag)?.cast_or_panic(ObjectType::Tag))
334 }
335
336 pub fn rename(
343 &mut self,
344 new_name: &str,
345 force: bool,
346 msg: &str,
347 ) -> Result<Reference<'repo>, Error> {
348 let mut raw = ptr::null_mut();
349 let new_name = CString::new(new_name)?;
350 let msg = CString::new(msg)?;
351 unsafe {
352 try_call!(raw::git_reference_rename(
353 &mut raw, self.raw, new_name, force, msg
354 ));
355 Ok(Binding::from_raw(raw))
356 }
357 }
358
359 pub fn set_target(&mut self, id: Oid, reflog_msg: &str) -> Result<Reference<'repo>, Error> {
366 let mut raw = ptr::null_mut();
367 let msg = CString::new(reflog_msg)?;
368 unsafe {
369 try_call!(raw::git_reference_set_target(
370 &mut raw,
371 self.raw,
372 id.raw(),
373 msg
374 ));
375 Ok(Binding::from_raw(raw))
376 }
377 }
378
379 pub fn symbolic_set_target(
393 &mut self,
394 target: &str,
395 reflog_msg: &str,
396 ) -> Result<Reference<'repo>, Error> {
397 let mut raw = ptr::null_mut();
398 let target = CString::new(target)?;
399 let msg = CString::new(reflog_msg)?;
400 unsafe {
401 try_call!(raw::git_reference_symbolic_set_target(
402 &mut raw, self.raw, target, msg
403 ));
404 Ok(Binding::from_raw(raw))
405 }
406 }
407}
408
409impl<'repo> PartialOrd for Reference<'repo> {
410 fn partial_cmp(&self, other: &Reference<'repo>) -> Option<Ordering> {
411 Some(self.cmp(other))
412 }
413}
414
415impl<'repo> Ord for Reference<'repo> {
416 fn cmp(&self, other: &Reference<'repo>) -> Ordering {
417 c_cmp_to_ordering(unsafe { raw::git_reference_cmp(&*self.raw, &*other.raw) })
418 }
419}
420
421impl<'repo> PartialEq for Reference<'repo> {
422 fn eq(&self, other: &Reference<'repo>) -> bool {
423 self.cmp(other) == Ordering::Equal
424 }
425}
426
427impl<'repo> Eq for Reference<'repo> {}
428
429impl<'repo> Clone for Reference<'repo> {
430 fn clone(&self) -> Reference<'repo> {
431 let mut raw = ptr::null_mut();
432 unsafe {
433 let rc = raw::git_reference_dup(&mut raw, self.raw);
434 assert_eq!(rc, 0);
435 Binding::from_raw(raw)
436 }
437 }
438}
439
440impl<'repo> Binding for Reference<'repo> {
441 type Raw = *mut raw::git_reference;
442 unsafe fn from_raw(raw: *mut raw::git_reference) -> Reference<'repo> {
443 Reference {
444 raw,
445 _marker: marker::PhantomData,
446 }
447 }
448 fn raw(&self) -> *mut raw::git_reference {
449 self.raw
450 }
451}
452
453impl<'repo> Drop for Reference<'repo> {
454 fn drop(&mut self) {
455 unsafe { raw::git_reference_free(self.raw) }
456 }
457}
458
459impl<'repo> References<'repo> {
460 pub fn names<'a>(&'a mut self) -> ReferenceNames<'repo, 'a> {
468 ReferenceNames { inner: self }
469 }
470}
471
472impl<'repo> Binding for References<'repo> {
473 type Raw = *mut raw::git_reference_iterator;
474 unsafe fn from_raw(raw: *mut raw::git_reference_iterator) -> References<'repo> {
475 References {
476 raw,
477 _marker: marker::PhantomData,
478 }
479 }
480 fn raw(&self) -> *mut raw::git_reference_iterator {
481 self.raw
482 }
483}
484
485impl<'repo> Iterator for References<'repo> {
486 type Item = Result<Reference<'repo>, Error>;
487 fn next(&mut self) -> Option<Result<Reference<'repo>, Error>> {
488 let mut out = ptr::null_mut();
489 unsafe {
490 try_call_iter!(raw::git_reference_next(&mut out, self.raw));
491 Some(Ok(Binding::from_raw(out)))
492 }
493 }
494}
495
496impl<'repo> Drop for References<'repo> {
497 fn drop(&mut self) {
498 unsafe { raw::git_reference_iterator_free(self.raw) }
499 }
500}
501
502impl<'repo, 'references> Iterator for ReferenceNames<'repo, 'references> {
503 type Item = Result<&'references str, Error>;
504 fn next(&mut self) -> Option<Result<&'references str, Error>> {
505 let mut out = ptr::null();
506 unsafe {
507 try_call_iter!(raw::git_reference_next_name(&mut out, self.inner.raw));
508 let bytes = crate::opt_bytes(self, out).unwrap();
509 let s = match str::from_utf8(bytes) {
510 Ok(s) => s,
511 Err(e) => return Some(Err(e.into())),
512 };
513 Some(Ok(mem::transmute::<&str, &'references str>(s)))
514 }
515 }
516}
517
518#[cfg(test)]
519mod tests {
520 use crate::{ObjectType, Reference, ReferenceType};
521
522 #[test]
523 fn is_valid_name() {
524 assert!(Reference::is_valid_name("refs/foo"));
525 assert!(!Reference::is_valid_name("foo"));
526 assert!(Reference::is_valid_name("FOO_BAR"));
527
528 assert!(!Reference::is_valid_name("foo"));
529 assert!(!Reference::is_valid_name("_FOO_BAR"));
530
531 assert!(!Reference::is_valid_name("ab\012"));
533 }
534
535 #[test]
536 fn smoke() {
537 let (_td, repo) = crate::test::repo_init();
538 let mut head = repo.head().unwrap();
539 assert!(head.is_branch());
540 assert!(!head.is_remote());
541 assert!(!head.is_tag());
542 assert!(!head.is_note());
543
544 assert_eq!(head.kind().unwrap(), ReferenceType::Direct);
547
548 assert!(head == repo.head().unwrap());
549 assert_eq!(head.name(), Ok("refs/heads/main"));
550
551 assert!(head == repo.find_reference("refs/heads/main").unwrap());
552 assert_eq!(
553 repo.refname_to_id("refs/heads/main").unwrap(),
554 head.target().unwrap()
555 );
556
557 assert!(head
558 .symbolic_target()
559 .expect("Should be okay even if None")
560 .is_none());
561 assert!(head.target_peel().is_none());
562
563 assert_eq!(head.shorthand(), Ok("main"));
564 assert!(head.resolve().unwrap() == head);
565
566 {
569 let cloned = head.clone();
570 assert_eq!(head.kind(), cloned.kind());
571 assert_eq!(head.target(), cloned.target());
572 }
573
574 let mut tag1 = repo
575 .reference("refs/tags/tag1", head.target().unwrap(), false, "test")
576 .unwrap();
577 assert!(tag1.is_tag());
578 assert_eq!(tag1.kind().unwrap(), ReferenceType::Direct);
579
580 let peeled_commit = tag1.peel(ObjectType::Commit).unwrap();
581 assert_eq!(ObjectType::Commit, peeled_commit.kind().unwrap());
582 assert_eq!(tag1.target().unwrap(), peeled_commit.id());
583
584 {
587 let cloned = tag1.clone();
588 assert_eq!(tag1.kind(), cloned.kind());
589 assert_eq!(tag1.target(), cloned.target());
590 }
591
592 tag1.delete().unwrap();
593
594 let mut sym1 = repo
595 .reference_symbolic("refs/tags/tag1", "refs/heads/main", false, "test")
596 .unwrap();
597 assert_eq!(sym1.kind().unwrap(), ReferenceType::Symbolic);
598 let mut sym2 = repo
599 .reference_symbolic("refs/tags/tag2", "refs/heads/main", false, "test")
600 .unwrap()
601 .symbolic_set_target("refs/tags/tag1", "test")
602 .unwrap();
603 assert_eq!(sym2.kind().unwrap(), ReferenceType::Symbolic);
604 assert_eq!(sym2.symbolic_target().unwrap(), Some("refs/tags/tag1"));
605
606 {
609 let cloned = sym1.clone();
610 assert_eq!(sym1.kind(), cloned.kind());
611 assert_eq!(sym1.target(), cloned.target());
612
613 let cloned = sym2.clone();
614 assert_eq!(sym2.kind(), cloned.kind());
615 assert_eq!(sym2.target(), cloned.target());
616 }
617
618 sym2.delete().unwrap();
619 sym1.delete().unwrap();
620
621 {
622 assert!(repo.references().unwrap().count() == 1);
623 assert!(repo.references().unwrap().next().unwrap().unwrap() == head);
624 let mut names = repo.references().unwrap();
625 let mut names = names.names();
626 assert_eq!(names.next().unwrap().unwrap(), "refs/heads/main");
627 assert!(names.next().is_none());
628 assert!(repo.references_glob("foo").unwrap().count() == 0);
629 assert!(repo.references_glob("refs/heads/*").unwrap().count() == 1);
630 }
631
632 let mut head = head.rename("refs/foo", true, "test").unwrap();
633 head.delete().unwrap();
634 }
635}