1 use std::ffi::CString;
2 use std::marker;
3 use std::mem;
4 use std::ptr;
5 
6 use libc::{c_int, c_uint};
7 
8 use crate::util::Binding;
9 use crate::{raw, Buf, Error, Repository};
10 
11 /// The result of a `describe` operation on either an `Describe` or a
12 /// `Repository`.
13 pub struct Describe<'repo> {
14     raw: *mut raw::git_describe_result,
15     _marker: marker::PhantomData<&'repo Repository>,
16 }
17 
18 /// Options which indicate how a `Describe` is created.
19 pub struct DescribeOptions {
20     raw: raw::git_describe_options,
21     pattern: CString,
22 }
23 
24 /// Options which can be used to customize how a description is formatted.
25 pub struct DescribeFormatOptions {
26     raw: raw::git_describe_format_options,
27     dirty_suffix: CString,
28 }
29 
30 impl<'repo> Describe<'repo> {
31     /// Prints this describe result, returning the result as a string.
format(&self, opts: Option<&DescribeFormatOptions>) -> Result<String, Error>32     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 
42 impl<'repo> Binding for Describe<'repo> {
43     type Raw = *mut raw::git_describe_result;
44 
from_raw(raw: *mut raw::git_describe_result) -> Describe<'repo>45     unsafe fn from_raw(raw: *mut raw::git_describe_result) -> Describe<'repo> {
46         Describe {
47             raw,
48             _marker: marker::PhantomData,
49         }
50     }
raw(&self) -> *mut raw::git_describe_result51     fn raw(&self) -> *mut raw::git_describe_result {
52         self.raw
53     }
54 }
55 
56 impl<'repo> Drop for Describe<'repo> {
drop(&mut self)57     fn drop(&mut self) {
58         unsafe { raw::git_describe_result_free(self.raw) }
59     }
60 }
61 
62 impl Default for DescribeFormatOptions {
default() -> Self63     fn default() -> Self {
64         Self::new()
65     }
66 }
67 
68 impl DescribeFormatOptions {
69     /// Creates a new blank set of formatting options for a description.
new() -> DescribeFormatOptions70     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     /// Sets the size of the abbreviated commit id to use.
81     ///
82     /// The value is the lower bound for the length of the abbreviated string,
83     /// and the default is 7.
abbreviated_size(&mut self, size: u32) -> &mut Self84     pub fn abbreviated_size(&mut self, size: u32) -> &mut Self {
85         self.raw.abbreviated_size = size as c_uint;
86         self
87     }
88 
89     /// Sets whether or not the long format is used even when a shorter name
90     /// could be used.
always_use_long_format(&mut self, long: bool) -> &mut Self91     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     /// If the workdir is dirty and this is set, this string will be appended to
97     /// the description string.
dirty_suffix(&mut self, suffix: &str) -> &mut Self98     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 
105 impl Default for DescribeOptions {
default() -> Self106     fn default() -> Self {
107         Self::new()
108     }
109 }
110 
111 impl DescribeOptions {
112     /// Creates a new blank set of formatting options for a description.
new() -> DescribeOptions113     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)]
max_candidates_tags(&mut self, max: u32) -> &mut Self124     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     /// Sets the reference lookup strategy
130     ///
131     /// This behaves like the `--tags` option to git-describe.
describe_tags(&mut self) -> &mut Self132     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     /// Sets the reference lookup strategy
138     ///
139     /// This behaves like the `--all` option to git-describe.
describe_all(&mut self) -> &mut Self140     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     /// Indicates when calculating the distance from the matching tag or
146     /// reference whether to only walk down the first-parent ancestry.
only_follow_first_parent(&mut self, follow: bool) -> &mut Self147     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     /// If no matching tag or reference is found whether a describe option would
153     /// normally fail. This option indicates, however, that it will instead fall
154     /// back to showing the full id of the commit.
show_commit_oid_as_fallback(&mut self, show: bool) -> &mut Self155     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)]
pattern(&mut self, pattern: &str) -> &mut Self161     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 
168 impl Binding for DescribeOptions {
169     type Raw = *mut raw::git_describe_options;
170 
from_raw(_raw: *mut raw::git_describe_options) -> DescribeOptions171     unsafe fn from_raw(_raw: *mut raw::git_describe_options) -> DescribeOptions {
172         panic!("unimplemened")
173     }
raw(&self) -> *mut raw::git_describe_options174     fn raw(&self) -> *mut raw::git_describe_options {
175         &self.raw as *const _ as *mut _
176     }
177 }
178 
179 #[cfg(test)]
180 mod tests {
181     use crate::DescribeOptions;
182 
183     #[test]
smoke()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 }
202