1use std::ffi::CString;
2use std::marker;
3use std::mem;
4use std::ptr;
5
6use libc::{c_int, c_uint};
7
8use crate::util::Binding;
9use crate::{raw, Buf, Error, Repository};
10
11pub struct Describe<'repo> {
14    raw: *mut raw::git_describe_result,
15    _marker: marker::PhantomData<&'repo Repository>,
16}
17
18pub struct DescribeOptions {
20    raw: raw::git_describe_options,
21    pattern: CString,
22}
23
24pub struct DescribeFormatOptions {
26    raw: raw::git_describe_format_options,
27    dirty_suffix: CString,
28}
29
30impl<'repo> Describe<'repo> {
31    pub fn format(&self, opts: Option<&DescribeFormatOptions>) -> Result<String, Error> {
33        let buf = Buf::new();
34        let opts = opts.map(|o| &o.raw as *const _).unwrap_or(ptr::null());
35        unsafe {
36            try_call!(raw::git_describe_format(buf.raw(), self.raw, opts));
37        }
38        Ok(String::from_utf8(buf.to_vec()).unwrap())
39    }
40}
41
42impl<'repo> Binding for Describe<'repo> {
43    type Raw = *mut raw::git_describe_result;
44
45    unsafe fn from_raw(raw: *mut raw::git_describe_result) -> Describe<'repo> {
46        Describe {
47            raw,
48            _marker: marker::PhantomData,
49        }
50    }
51    fn raw(&self) -> *mut raw::git_describe_result {
52        self.raw
53    }
54}
55
56impl<'repo> Drop for Describe<'repo> {
57    fn drop(&mut self) {
58        unsafe { raw::git_describe_result_free(self.raw) }
59    }
60}
61
62impl Default for DescribeFormatOptions {
63    fn default() -> Self {
64        Self::new()
65    }
66}
67
68impl DescribeFormatOptions {
69    pub fn new() -> DescribeFormatOptions {
71        let mut opts = DescribeFormatOptions {
72            raw: unsafe { mem::zeroed() },
73            dirty_suffix: CString::new(Vec::new()).unwrap(),
74        };
75        opts.raw.version = 1;
76        opts.raw.abbreviated_size = 7;
77        opts
78    }
79
80    pub fn abbreviated_size(&mut self, size: u32) -> &mut Self {
85        self.raw.abbreviated_size = size as c_uint;
86        self
87    }
88
89    pub fn always_use_long_format(&mut self, long: bool) -> &mut Self {
92        self.raw.always_use_long_format = long as c_int;
93        self
94    }
95
96    pub fn dirty_suffix(&mut self, suffix: &str) -> &mut Self {
99        self.dirty_suffix = CString::new(suffix).unwrap();
100        self.raw.dirty_suffix = self.dirty_suffix.as_ptr();
101        self
102    }
103}
104
105impl Default for DescribeOptions {
106    fn default() -> Self {
107        Self::new()
108    }
109}
110
111impl DescribeOptions {
112    pub fn new() -> DescribeOptions {
114        let mut opts = DescribeOptions {
115            raw: unsafe { mem::zeroed() },
116            pattern: CString::new(Vec::new()).unwrap(),
117        };
118        opts.raw.version = 1;
119        opts.raw.max_candidates_tags = 10;
120        opts
121    }
122
123    #[allow(missing_docs)]
124    pub fn max_candidates_tags(&mut self, max: u32) -> &mut Self {
125        self.raw.max_candidates_tags = max as c_uint;
126        self
127    }
128
129    pub fn describe_tags(&mut self) -> &mut Self {
133        self.raw.describe_strategy = raw::GIT_DESCRIBE_TAGS as c_uint;
134        self
135    }
136
137    pub fn describe_all(&mut self) -> &mut Self {
141        self.raw.describe_strategy = raw::GIT_DESCRIBE_ALL as c_uint;
142        self
143    }
144
145    pub fn only_follow_first_parent(&mut self, follow: bool) -> &mut Self {
148        self.raw.only_follow_first_parent = follow as c_int;
149        self
150    }
151
152    pub fn show_commit_oid_as_fallback(&mut self, show: bool) -> &mut Self {
156        self.raw.show_commit_oid_as_fallback = show as c_int;
157        self
158    }
159
160    #[allow(missing_docs)]
161    pub fn pattern(&mut self, pattern: &str) -> &mut Self {
162        self.pattern = CString::new(pattern).unwrap();
163        self.raw.pattern = self.pattern.as_ptr();
164        self
165    }
166}
167
168impl Binding for DescribeOptions {
169    type Raw = *mut raw::git_describe_options;
170
171    unsafe fn from_raw(_raw: *mut raw::git_describe_options) -> DescribeOptions {
172        panic!("unimplemened")
173    }
174    fn raw(&self) -> *mut raw::git_describe_options {
175        &self.raw as *const _ as *mut _
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use crate::DescribeOptions;
182
183    #[test]
184    fn smoke() {
185        let (_td, repo) = crate::test::repo_init();
186        let head = t!(repo.head()).target().unwrap();
187
188        let d = t!(repo.describe(DescribeOptions::new().show_commit_oid_as_fallback(true)));
189        let id = head.to_string();
190        assert_eq!(t!(d.format(None)), &id[..7]);
191
192        let obj = t!(repo.find_object(head, None));
193        let sig = t!(repo.signature());
194        t!(repo.tag("foo", &obj, &sig, "message", true));
195        let d = t!(repo.describe(&DescribeOptions::new()));
196        assert_eq!(t!(d.format(None)), "foo");
197
198        let d = t!(obj.describe(&DescribeOptions::new()));
199        assert_eq!(t!(d.format(None)), "foo");
200    }
201}