use std::ffi::CString; use std::marker; use std::mem; use std::ptr; use libc::{c_int, c_uint}; use crate::util::Binding; use crate::{raw, Buf, Error, Repository}; /// The result of a `describe` operation on either an `Describe` or a /// `Repository`. pub struct Describe<'repo> { raw: *mut raw::git_describe_result, _marker: marker::PhantomData<&'repo Repository>, } /// Options which indicate how a `Describe` is created. pub struct DescribeOptions { raw: raw::git_describe_options, pattern: CString, } /// Options which can be used to customize how a description is formatted. pub struct DescribeFormatOptions { raw: raw::git_describe_format_options, dirty_suffix: CString, } impl<'repo> Describe<'repo> { /// Prints this describe result, returning the result as a string. pub fn format(&self, opts: Option<&DescribeFormatOptions>) -> Result { let buf = Buf::new(); let opts = opts.map(|o| &o.raw as *const _).unwrap_or(ptr::null()); unsafe { try_call!(raw::git_describe_format(buf.raw(), self.raw, opts)); } Ok(String::from_utf8(buf.to_vec()).unwrap()) } } impl<'repo> Binding for Describe<'repo> { type Raw = *mut raw::git_describe_result; unsafe fn from_raw(raw: *mut raw::git_describe_result) -> Describe<'repo> { Describe { raw, _marker: marker::PhantomData, } } fn raw(&self) -> *mut raw::git_describe_result { self.raw } } impl<'repo> Drop for Describe<'repo> { fn drop(&mut self) { unsafe { raw::git_describe_result_free(self.raw) } } } impl Default for DescribeFormatOptions { fn default() -> Self { Self::new() } } impl DescribeFormatOptions { /// Creates a new blank set of formatting options for a description. pub fn new() -> DescribeFormatOptions { let mut opts = DescribeFormatOptions { raw: unsafe { mem::zeroed() }, dirty_suffix: CString::new(Vec::new()).unwrap(), }; opts.raw.version = 1; opts.raw.abbreviated_size = 7; opts } /// Sets the size of the abbreviated commit id to use. /// /// The value is the lower bound for the length of the abbreviated string, /// and the default is 7. pub fn abbreviated_size(&mut self, size: u32) -> &mut Self { self.raw.abbreviated_size = size as c_uint; self } /// Sets whether or not the long format is used even when a shorter name /// could be used. pub fn always_use_long_format(&mut self, long: bool) -> &mut Self { self.raw.always_use_long_format = long as c_int; self } /// If the workdir is dirty and this is set, this string will be appended to /// the description string. pub fn dirty_suffix(&mut self, suffix: &str) -> &mut Self { self.dirty_suffix = CString::new(suffix).unwrap(); self.raw.dirty_suffix = self.dirty_suffix.as_ptr(); self } } impl Default for DescribeOptions { fn default() -> Self { Self::new() } } impl DescribeOptions { /// Creates a new blank set of formatting options for a description. pub fn new() -> DescribeOptions { let mut opts = DescribeOptions { raw: unsafe { mem::zeroed() }, pattern: CString::new(Vec::new()).unwrap(), }; opts.raw.version = 1; opts.raw.max_candidates_tags = 10; opts } #[allow(missing_docs)] pub fn max_candidates_tags(&mut self, max: u32) -> &mut Self { self.raw.max_candidates_tags = max as c_uint; self } /// Sets the reference lookup strategy /// /// This behaves like the `--tags` option to git-describe. pub fn describe_tags(&mut self) -> &mut Self { self.raw.describe_strategy = raw::GIT_DESCRIBE_TAGS as c_uint; self } /// Sets the reference lookup strategy /// /// This behaves like the `--all` option to git-describe. pub fn describe_all(&mut self) -> &mut Self { self.raw.describe_strategy = raw::GIT_DESCRIBE_ALL as c_uint; self } /// Indicates when calculating the distance from the matching tag or /// reference whether to only walk down the first-parent ancestry. pub fn only_follow_first_parent(&mut self, follow: bool) -> &mut Self { self.raw.only_follow_first_parent = follow as c_int; self } /// If no matching tag or reference is found whether a describe option would /// normally fail. This option indicates, however, that it will instead fall /// back to showing the full id of the commit. pub fn show_commit_oid_as_fallback(&mut self, show: bool) -> &mut Self { self.raw.show_commit_oid_as_fallback = show as c_int; self } #[allow(missing_docs)] pub fn pattern(&mut self, pattern: &str) -> &mut Self { self.pattern = CString::new(pattern).unwrap(); self.raw.pattern = self.pattern.as_ptr(); self } } impl Binding for DescribeOptions { type Raw = *mut raw::git_describe_options; unsafe fn from_raw(_raw: *mut raw::git_describe_options) -> DescribeOptions { panic!("unimplemened") } fn raw(&self) -> *mut raw::git_describe_options { &self.raw as *const _ as *mut _ } } #[cfg(test)] mod tests { use crate::DescribeOptions; #[test] fn smoke() { let (_td, repo) = crate::test::repo_init(); let head = t!(repo.head()).target().unwrap(); let d = t!(repo.describe(DescribeOptions::new().show_commit_oid_as_fallback(true))); let id = head.to_string(); assert_eq!(t!(d.format(None)), &id[..7]); let obj = t!(repo.find_object(head, None)); let sig = t!(repo.signature()); t!(repo.tag("foo", &obj, &sig, "message", true)); let d = t!(repo.describe(&DescribeOptions::new())); assert_eq!(t!(d.format(None)), "foo"); let d = t!(obj.describe(&DescribeOptions::new())); assert_eq!(t!(d.format(None)), "foo"); } }