1 use std::cmp::Ordering;
2 use std::ffi::CString;
3 use std::marker;
4 use std::mem;
5 use std::ptr;
6 use std::str;
7 
8 use crate::object::CastOrPanic;
9 use crate::util::{c_cmp_to_ordering, Binding};
10 use crate::{
11     raw, Blob, Commit, Error, Object, ObjectType, Oid, ReferenceType, Repository, Tag, Tree,
12 };
13 
14 struct Refdb<'repo>(&'repo Repository);
15 
16 /// A structure to represent a git [reference][1].
17 ///
18 /// [1]: http://git-scm.com/book/en/Git-Internals-Git-References
19 pub struct Reference<'repo> {
20     raw: *mut raw::git_reference,
21     _marker: marker::PhantomData<Refdb<'repo>>,
22 }
23 
24 /// An iterator over the references in a repository.
25 pub struct References<'repo> {
26     raw: *mut raw::git_reference_iterator,
27     _marker: marker::PhantomData<Refdb<'repo>>,
28 }
29 
30 /// An iterator over the names of references in a repository.
31 pub struct ReferenceNames<'repo, 'references> {
32     inner: &'references mut References<'repo>,
33 }
34 
35 impl<'repo> Reference<'repo> {
36     /// Ensure the reference name is well-formed.
is_valid_name(refname: &str) -> bool37     pub fn is_valid_name(refname: &str) -> bool {
38         crate::init();
39         let refname = CString::new(refname).unwrap();
40         unsafe { raw::git_reference_is_valid_name(refname.as_ptr()) == 1 }
41     }
42 
43     /// Get access to the underlying raw pointer.
raw(&self) -> *mut raw::git_reference44     pub fn raw(&self) -> *mut raw::git_reference {
45         self.raw
46     }
47 
48     /// Delete an existing reference.
49     ///
50     /// This method works for both direct and symbolic references. The reference
51     /// will be immediately removed on disk.
52     ///
53     /// This function will return an error if the reference has changed from the
54     /// time it was looked up.
delete(&mut self) -> Result<(), Error>55     pub fn delete(&mut self) -> Result<(), Error> {
56         unsafe {
57             try_call!(raw::git_reference_delete(self.raw));
58         }
59         Ok(())
60     }
61 
62     /// Check if a reference is a local branch.
is_branch(&self) -> bool63     pub fn is_branch(&self) -> bool {
64         unsafe { raw::git_reference_is_branch(&*self.raw) == 1 }
65     }
66 
67     /// Check if a reference is a note.
is_note(&self) -> bool68     pub fn is_note(&self) -> bool {
69         unsafe { raw::git_reference_is_note(&*self.raw) == 1 }
70     }
71 
72     /// Check if a reference is a remote tracking branch
is_remote(&self) -> bool73     pub fn is_remote(&self) -> bool {
74         unsafe { raw::git_reference_is_remote(&*self.raw) == 1 }
75     }
76 
77     /// Check if a reference is a tag
is_tag(&self) -> bool78     pub fn is_tag(&self) -> bool {
79         unsafe { raw::git_reference_is_tag(&*self.raw) == 1 }
80     }
81 
82     /// Get the reference type of a reference.
83     ///
84     /// If the type is unknown, then `None` is returned.
kind(&self) -> Option<ReferenceType>85     pub fn kind(&self) -> Option<ReferenceType> {
86         ReferenceType::from_raw(unsafe { raw::git_reference_type(&*self.raw) })
87     }
88 
89     /// Get the full name of a reference.
90     ///
91     /// Returns `None` if the name is not valid utf-8.
name(&self) -> Option<&str>92     pub fn name(&self) -> Option<&str> {
93         str::from_utf8(self.name_bytes()).ok()
94     }
95 
96     /// Get the full name of a reference.
name_bytes(&self) -> &[u8]97     pub fn name_bytes(&self) -> &[u8] {
98         unsafe { crate::opt_bytes(self, raw::git_reference_name(&*self.raw)).unwrap() }
99     }
100 
101     /// Get the full shorthand of a reference.
102     ///
103     /// This will transform the reference name into a name "human-readable"
104     /// version. If no shortname is appropriate, it will return the full name.
105     ///
106     /// Returns `None` if the shorthand is not valid utf-8.
shorthand(&self) -> Option<&str>107     pub fn shorthand(&self) -> Option<&str> {
108         str::from_utf8(self.shorthand_bytes()).ok()
109     }
110 
111     /// Get the full shorthand of a reference.
shorthand_bytes(&self) -> &[u8]112     pub fn shorthand_bytes(&self) -> &[u8] {
113         unsafe { crate::opt_bytes(self, raw::git_reference_shorthand(&*self.raw)).unwrap() }
114     }
115 
116     /// Get the OID pointed to by a direct reference.
117     ///
118     /// Only available if the reference is direct (i.e. an object id reference,
119     /// not a symbolic one).
target(&self) -> Option<Oid>120     pub fn target(&self) -> Option<Oid> {
121         unsafe { Binding::from_raw_opt(raw::git_reference_target(&*self.raw)) }
122     }
123 
124     /// Return the peeled OID target of this reference.
125     ///
126     /// This peeled OID only applies to direct references that point to a hard
127     /// Tag object: it is the result of peeling such Tag.
target_peel(&self) -> Option<Oid>128     pub fn target_peel(&self) -> Option<Oid> {
129         unsafe { Binding::from_raw_opt(raw::git_reference_target_peel(&*self.raw)) }
130     }
131 
132     /// Get full name to the reference pointed to by a symbolic reference.
133     ///
134     /// May return `None` if the reference is either not symbolic or not a
135     /// valid utf-8 string.
symbolic_target(&self) -> Option<&str>136     pub fn symbolic_target(&self) -> Option<&str> {
137         self.symbolic_target_bytes()
138             .and_then(|s| str::from_utf8(s).ok())
139     }
140 
141     /// Get full name to the reference pointed to by a symbolic reference.
142     ///
143     /// Only available if the reference is symbolic.
symbolic_target_bytes(&self) -> Option<&[u8]>144     pub fn symbolic_target_bytes(&self) -> Option<&[u8]> {
145         unsafe { crate::opt_bytes(self, raw::git_reference_symbolic_target(&*self.raw)) }
146     }
147 
148     /// Resolve a symbolic reference to a direct reference.
149     ///
150     /// This method iteratively peels a symbolic reference until it resolves to
151     /// a direct reference to an OID.
152     ///
153     /// If a direct reference is passed as an argument, a copy of that
154     /// reference is returned.
resolve(&self) -> Result<Reference<'repo>, Error>155     pub fn resolve(&self) -> Result<Reference<'repo>, Error> {
156         let mut raw = ptr::null_mut();
157         unsafe {
158             try_call!(raw::git_reference_resolve(&mut raw, &*self.raw));
159             Ok(Binding::from_raw(raw))
160         }
161     }
162 
163     /// Peel a reference to an object
164     ///
165     /// This method recursively peels the reference until it reaches
166     /// an object of the specified type.
peel(&self, kind: ObjectType) -> Result<Object<'repo>, Error>167     pub fn peel(&self, kind: ObjectType) -> Result<Object<'repo>, Error> {
168         let mut raw = ptr::null_mut();
169         unsafe {
170             try_call!(raw::git_reference_peel(&mut raw, self.raw, kind));
171             Ok(Binding::from_raw(raw))
172         }
173     }
174 
175     /// Peel a reference to a blob
176     ///
177     /// This method recursively peels the reference until it reaches
178     /// a blob.
peel_to_blob(&self) -> Result<Blob<'repo>, Error>179     pub fn peel_to_blob(&self) -> Result<Blob<'repo>, Error> {
180         Ok(self.peel(ObjectType::Blob)?.cast_or_panic(ObjectType::Blob))
181     }
182 
183     /// Peel a reference to a commit
184     ///
185     /// This method recursively peels the reference until it reaches
186     /// a commit.
peel_to_commit(&self) -> Result<Commit<'repo>, Error>187     pub fn peel_to_commit(&self) -> Result<Commit<'repo>, Error> {
188         Ok(self
189             .peel(ObjectType::Commit)?
190             .cast_or_panic(ObjectType::Commit))
191     }
192 
193     /// Peel a reference to a tree
194     ///
195     /// This method recursively peels the reference until it reaches
196     /// a tree.
peel_to_tree(&self) -> Result<Tree<'repo>, Error>197     pub fn peel_to_tree(&self) -> Result<Tree<'repo>, Error> {
198         Ok(self.peel(ObjectType::Tree)?.cast_or_panic(ObjectType::Tree))
199     }
200 
201     /// Peel a reference to a tag
202     ///
203     /// This method recursively peels the reference until it reaches
204     /// a tag.
peel_to_tag(&self) -> Result<Tag<'repo>, Error>205     pub fn peel_to_tag(&self) -> Result<Tag<'repo>, Error> {
206         Ok(self.peel(ObjectType::Tag)?.cast_or_panic(ObjectType::Tag))
207     }
208 
209     /// Rename an existing reference.
210     ///
211     /// This method works for both direct and symbolic references.
212     ///
213     /// If the force flag is not enabled, and there's already a reference with
214     /// the given name, the renaming will fail.
rename( &mut self, new_name: &str, force: bool, msg: &str, ) -> Result<Reference<'repo>, Error>215     pub fn rename(
216         &mut self,
217         new_name: &str,
218         force: bool,
219         msg: &str,
220     ) -> Result<Reference<'repo>, Error> {
221         let mut raw = ptr::null_mut();
222         let new_name = CString::new(new_name)?;
223         let msg = CString::new(msg)?;
224         unsafe {
225             try_call!(raw::git_reference_rename(
226                 &mut raw, self.raw, new_name, force, msg
227             ));
228             Ok(Binding::from_raw(raw))
229         }
230     }
231 
232     /// Conditionally create a new reference with the same name as the given
233     /// reference but a different OID target. The reference must be a direct
234     /// reference, otherwise this will fail.
235     ///
236     /// The new reference will be written to disk, overwriting the given
237     /// reference.
set_target(&mut self, id: Oid, reflog_msg: &str) -> Result<Reference<'repo>, Error>238     pub fn set_target(&mut self, id: Oid, reflog_msg: &str) -> Result<Reference<'repo>, Error> {
239         let mut raw = ptr::null_mut();
240         let msg = CString::new(reflog_msg)?;
241         unsafe {
242             try_call!(raw::git_reference_set_target(
243                 &mut raw,
244                 self.raw,
245                 id.raw(),
246                 msg
247             ));
248             Ok(Binding::from_raw(raw))
249         }
250     }
251 }
252 
253 impl<'repo> PartialOrd for Reference<'repo> {
partial_cmp(&self, other: &Reference<'repo>) -> Option<Ordering>254     fn partial_cmp(&self, other: &Reference<'repo>) -> Option<Ordering> {
255         Some(self.cmp(other))
256     }
257 }
258 
259 impl<'repo> Ord for Reference<'repo> {
cmp(&self, other: &Reference<'repo>) -> Ordering260     fn cmp(&self, other: &Reference<'repo>) -> Ordering {
261         c_cmp_to_ordering(unsafe { raw::git_reference_cmp(&*self.raw, &*other.raw) })
262     }
263 }
264 
265 impl<'repo> PartialEq for Reference<'repo> {
eq(&self, other: &Reference<'repo>) -> bool266     fn eq(&self, other: &Reference<'repo>) -> bool {
267         self.cmp(other) == Ordering::Equal
268     }
269 }
270 
271 impl<'repo> Eq for Reference<'repo> {}
272 
273 impl<'repo> Binding for Reference<'repo> {
274     type Raw = *mut raw::git_reference;
from_raw(raw: *mut raw::git_reference) -> Reference<'repo>275     unsafe fn from_raw(raw: *mut raw::git_reference) -> Reference<'repo> {
276         Reference {
277             raw: raw,
278             _marker: marker::PhantomData,
279         }
280     }
raw(&self) -> *mut raw::git_reference281     fn raw(&self) -> *mut raw::git_reference {
282         self.raw
283     }
284 }
285 
286 impl<'repo> Drop for Reference<'repo> {
drop(&mut self)287     fn drop(&mut self) {
288         unsafe { raw::git_reference_free(self.raw) }
289     }
290 }
291 
292 impl<'repo> References<'repo> {
293     /// Consumes a `References` iterator to create an iterator over just the
294     /// name of some references.
295     ///
296     /// This is more efficient if only the names are desired of references as
297     /// the references themselves don't have to be allocated and deallocated.
298     ///
299     /// The returned iterator will yield strings as opposed to a `Reference`.
names<'a>(&'a mut self) -> ReferenceNames<'repo, 'a>300     pub fn names<'a>(&'a mut self) -> ReferenceNames<'repo, 'a> {
301         ReferenceNames { inner: self }
302     }
303 }
304 
305 impl<'repo> Binding for References<'repo> {
306     type Raw = *mut raw::git_reference_iterator;
from_raw(raw: *mut raw::git_reference_iterator) -> References<'repo>307     unsafe fn from_raw(raw: *mut raw::git_reference_iterator) -> References<'repo> {
308         References {
309             raw: raw,
310             _marker: marker::PhantomData,
311         }
312     }
raw(&self) -> *mut raw::git_reference_iterator313     fn raw(&self) -> *mut raw::git_reference_iterator {
314         self.raw
315     }
316 }
317 
318 impl<'repo> Iterator for References<'repo> {
319     type Item = Result<Reference<'repo>, Error>;
next(&mut self) -> Option<Result<Reference<'repo>, Error>>320     fn next(&mut self) -> Option<Result<Reference<'repo>, Error>> {
321         let mut out = ptr::null_mut();
322         unsafe {
323             try_call_iter!(raw::git_reference_next(&mut out, self.raw));
324             Some(Ok(Binding::from_raw(out)))
325         }
326     }
327 }
328 
329 impl<'repo> Drop for References<'repo> {
drop(&mut self)330     fn drop(&mut self) {
331         unsafe { raw::git_reference_iterator_free(self.raw) }
332     }
333 }
334 
335 impl<'repo, 'references> Iterator for ReferenceNames<'repo, 'references> {
336     type Item = Result<&'references str, Error>;
next(&mut self) -> Option<Result<&'references str, Error>>337     fn next(&mut self) -> Option<Result<&'references str, Error>> {
338         let mut out = ptr::null();
339         unsafe {
340             try_call_iter!(raw::git_reference_next_name(&mut out, self.inner.raw));
341             let bytes = crate::opt_bytes(self, out).unwrap();
342             let s = str::from_utf8(bytes).unwrap();
343             Some(Ok(mem::transmute::<&str, &'references str>(s)))
344         }
345     }
346 }
347 
348 #[cfg(test)]
349 mod tests {
350     use crate::{ObjectType, Reference, ReferenceType};
351 
352     #[test]
smoke()353     fn smoke() {
354         assert!(Reference::is_valid_name("refs/foo"));
355         assert!(!Reference::is_valid_name("foo"));
356     }
357 
358     #[test]
smoke2()359     fn smoke2() {
360         let (_td, repo) = crate::test::repo_init();
361         let mut head = repo.head().unwrap();
362         assert!(head.is_branch());
363         assert!(!head.is_remote());
364         assert!(!head.is_tag());
365         assert!(!head.is_note());
366 
367         // HEAD is a symbolic reference but git_repository_head resolves it
368         // so it is a GIT_REFERENCE_DIRECT.
369         assert_eq!(head.kind().unwrap(), ReferenceType::Direct);
370 
371         assert!(head == repo.head().unwrap());
372         assert_eq!(head.name(), Some("refs/heads/master"));
373 
374         assert!(head == repo.find_reference("refs/heads/master").unwrap());
375         assert_eq!(
376             repo.refname_to_id("refs/heads/master").unwrap(),
377             head.target().unwrap()
378         );
379 
380         assert!(head.symbolic_target().is_none());
381         assert!(head.target_peel().is_none());
382 
383         assert_eq!(head.shorthand(), Some("master"));
384         assert!(head.resolve().unwrap() == head);
385 
386         let mut tag1 = repo
387             .reference("refs/tags/tag1", head.target().unwrap(), false, "test")
388             .unwrap();
389         assert!(tag1.is_tag());
390         assert_eq!(tag1.kind().unwrap(), ReferenceType::Direct);
391 
392         let peeled_commit = tag1.peel(ObjectType::Commit).unwrap();
393         assert_eq!(ObjectType::Commit, peeled_commit.kind().unwrap());
394         assert_eq!(tag1.target().unwrap(), peeled_commit.id());
395 
396         tag1.delete().unwrap();
397 
398         let mut sym1 = repo
399             .reference_symbolic("refs/tags/tag1", "refs/heads/master", false, "test")
400             .unwrap();
401         assert_eq!(sym1.kind().unwrap(), ReferenceType::Symbolic);
402         sym1.delete().unwrap();
403 
404         {
405             assert!(repo.references().unwrap().count() == 1);
406             assert!(repo.references().unwrap().next().unwrap().unwrap() == head);
407             let mut names = repo.references().unwrap();
408             let mut names = names.names();
409             assert_eq!(names.next().unwrap().unwrap(), "refs/heads/master");
410             assert!(names.next().is_none());
411             assert!(repo.references_glob("foo").unwrap().count() == 0);
412             assert!(repo.references_glob("refs/heads/*").unwrap().count() == 1);
413         }
414 
415         let mut head = head.rename("refs/foo", true, "test").unwrap();
416         head.delete().unwrap();
417     }
418 }
419