git2/reference.rs
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
15// Not in the public header files (yet?), but a hard limit used by libgit2
16// internally
17const GIT_REFNAME_MAX: usize = 1024;
18
19/// This is used to logically indicate that a [`raw::git_reference`] or
20/// [`raw::git_reference_iterator`] holds a reference to [`raw::git_refdb`].
21/// It is not necessary to have a wrapper like this in the
22/// [`marker::PhantomData`], since all that matters is that it is tied to the
23/// lifetime of the [`Repository`], but this helps distinguish the actual
24/// references involved.
25struct Refdb<'repo>(#[allow(dead_code)] &'repo Repository);
26
27/// A structure to represent a git [reference][1].
28///
29/// [1]: http://git-scm.com/book/en/Git-Internals-Git-References
30pub struct Reference<'repo> {
31 raw: *mut raw::git_reference,
32 _marker: marker::PhantomData<Refdb<'repo>>,
33}
34
35/// An iterator over the references in a repository.
36pub struct References<'repo> {
37 raw: *mut raw::git_reference_iterator,
38 _marker: marker::PhantomData<Refdb<'repo>>,
39}
40
41/// An iterator over the names of references in a repository.
42pub struct ReferenceNames<'repo, 'references> {
43 inner: &'references mut References<'repo>,
44}
45
46impl<'repo> Reference<'repo> {
47 /// Ensure the reference name is well-formed.
48 ///
49 /// Validation is performed as if [`ReferenceFormat::ALLOW_ONELEVEL`]
50 /// was given to [`Reference::normalize_name`]. No normalization is
51 /// performed, however.
52 ///
53 /// ```rust
54 /// use git2::Reference;
55 ///
56 /// assert!(Reference::is_valid_name("HEAD"));
57 /// assert!(Reference::is_valid_name("refs/heads/main"));
58 ///
59 /// // But:
60 /// assert!(!Reference::is_valid_name("main"));
61 /// assert!(!Reference::is_valid_name("refs/heads/*"));
62 /// assert!(!Reference::is_valid_name("foo//bar"));
63 /// ```
64 ///
65 /// [`ReferenceFormat::ALLOW_ONELEVEL`]:
66 /// struct.ReferenceFormat#associatedconstant.ALLOW_ONELEVEL
67 /// [`Reference::normalize_name`]: struct.Reference#method.normalize_name
68 pub fn is_valid_name(refname: &str) -> bool {
69 crate::init();
70 let refname = CString::new(refname).unwrap();
71 let mut valid: libc::c_int = 0;
72 unsafe {
73 call::c_try(raw::git_reference_name_is_valid(
74 &mut valid,
75 refname.as_ptr(),
76 ))
77 .unwrap();
78 }
79 valid == 1
80 }
81
82 /// Normalize reference name and check validity.
83 ///
84 /// This will normalize the reference name by collapsing runs of adjacent
85 /// slashes between name components into a single slash. It also validates
86 /// the name according to the following rules:
87 ///
88 /// 1. If [`ReferenceFormat::ALLOW_ONELEVEL`] is given, the name may
89 /// contain only capital letters and underscores, and must begin and end
90 /// with a letter. (e.g. "HEAD", "ORIG_HEAD").
91 /// 2. The flag [`ReferenceFormat::REFSPEC_SHORTHAND`] has an effect
92 /// only when combined with [`ReferenceFormat::ALLOW_ONELEVEL`]. If
93 /// it is given, "shorthand" branch names (i.e. those not prefixed by
94 /// `refs/`, but consisting of a single word without `/` separators)
95 /// become valid. For example, "main" would be accepted.
96 /// 3. If [`ReferenceFormat::REFSPEC_PATTERN`] is given, the name may
97 /// contain a single `*` in place of a full pathname component (e.g.
98 /// `foo/*/bar`, `foo/bar*`).
99 /// 4. Names prefixed with "refs/" can be almost anything. You must avoid
100 /// the characters '~', '^', ':', '\\', '?', '[', and '*', and the
101 /// sequences ".." and "@{" which have special meaning to revparse.
102 ///
103 /// If the reference passes validation, it is returned in normalized form,
104 /// otherwise an [`Error`] with [`ErrorCode::InvalidSpec`] is returned.
105 ///
106 /// ```rust
107 /// use git2::{Reference, ReferenceFormat};
108 ///
109 /// assert_eq!(
110 /// Reference::normalize_name(
111 /// "foo//bar",
112 /// ReferenceFormat::NORMAL
113 /// )
114 /// .unwrap(),
115 /// "foo/bar".to_owned()
116 /// );
117 ///
118 /// assert_eq!(
119 /// Reference::normalize_name(
120 /// "HEAD",
121 /// ReferenceFormat::ALLOW_ONELEVEL
122 /// )
123 /// .unwrap(),
124 /// "HEAD".to_owned()
125 /// );
126 ///
127 /// assert_eq!(
128 /// Reference::normalize_name(
129 /// "refs/heads/*",
130 /// ReferenceFormat::REFSPEC_PATTERN
131 /// )
132 /// .unwrap(),
133 /// "refs/heads/*".to_owned()
134 /// );
135 ///
136 /// assert_eq!(
137 /// Reference::normalize_name(
138 /// "main",
139 /// ReferenceFormat::ALLOW_ONELEVEL | ReferenceFormat::REFSPEC_SHORTHAND
140 /// )
141 /// .unwrap(),
142 /// "main".to_owned()
143 /// );
144 /// ```
145 ///
146 /// [`ReferenceFormat::ALLOW_ONELEVEL`]:
147 /// struct.ReferenceFormat#associatedconstant.ALLOW_ONELEVEL
148 /// [`ReferenceFormat::REFSPEC_SHORTHAND`]:
149 /// struct.ReferenceFormat#associatedconstant.REFSPEC_SHORTHAND
150 /// [`ReferenceFormat::REFSPEC_PATTERN`]:
151 /// struct.ReferenceFormat#associatedconstant.REFSPEC_PATTERN
152 /// [`Error`]: struct.Error
153 /// [`ErrorCode::InvalidSpec`]: enum.ErrorCode#variant.InvalidSpec
154 pub fn normalize_name(refname: &str, flags: ReferenceFormat) -> Result<String, Error> {
155 crate::init();
156 let mut dst = [0u8; GIT_REFNAME_MAX];
157 let refname = CString::new(refname)?;
158 unsafe {
159 try_call!(raw::git_reference_normalize_name(
160 dst.as_mut_ptr() as *mut libc::c_char,
161 dst.len() as libc::size_t,
162 refname,
163 flags.bits()
164 ));
165 let s = &dst[..dst.iter().position(|&a| a == 0).unwrap()];
166 Ok(str::from_utf8(s).unwrap().to_owned())
167 }
168 }
169
170 /// Get access to the underlying raw pointer.
171 pub fn raw(&self) -> *mut raw::git_reference {
172 self.raw
173 }
174
175 /// Delete an existing reference.
176 ///
177 /// This method works for both direct and symbolic references. The reference
178 /// will be immediately removed on disk.
179 ///
180 /// This function will return an error if the reference has changed from the
181 /// time it was looked up.
182 pub fn delete(&mut self) -> Result<(), Error> {
183 unsafe {
184 try_call!(raw::git_reference_delete(self.raw));
185 }
186 Ok(())
187 }
188
189 /// Check if a reference is a local branch.
190 pub fn is_branch(&self) -> bool {
191 unsafe { raw::git_reference_is_branch(&*self.raw) == 1 }
192 }
193
194 /// Check if a reference is a note.
195 pub fn is_note(&self) -> bool {
196 unsafe { raw::git_reference_is_note(&*self.raw) == 1 }
197 }
198
199 /// Check if a reference is a remote tracking branch
200 pub fn is_remote(&self) -> bool {
201 unsafe { raw::git_reference_is_remote(&*self.raw) == 1 }
202 }
203
204 /// Check if a reference is a tag
205 pub fn is_tag(&self) -> bool {
206 unsafe { raw::git_reference_is_tag(&*self.raw) == 1 }
207 }
208
209 /// Get the reference type of a reference.
210 ///
211 /// If the type is unknown, then `None` is returned.
212 pub fn kind(&self) -> Option<ReferenceType> {
213 ReferenceType::from_raw(unsafe { raw::git_reference_type(&*self.raw) })
214 }
215
216 /// Get the full name of a reference.
217 ///
218 /// Returns `None` if the name is not valid utf-8.
219 pub fn name(&self) -> Option<&str> {
220 str::from_utf8(self.name_bytes()).ok()
221 }
222
223 /// Get the full name of a reference.
224 pub fn name_bytes(&self) -> &[u8] {
225 unsafe { crate::opt_bytes(self, raw::git_reference_name(&*self.raw)).unwrap() }
226 }
227
228 /// Get the full shorthand of a reference.
229 ///
230 /// This will transform the reference name into a name "human-readable"
231 /// version. If no shortname is appropriate, it will return the full name.
232 ///
233 /// Returns `None` if the shorthand is not valid utf-8.
234 pub fn shorthand(&self) -> Option<&str> {
235 str::from_utf8(self.shorthand_bytes()).ok()
236 }
237
238 /// Get the full shorthand of a reference.
239 pub fn shorthand_bytes(&self) -> &[u8] {
240 unsafe { crate::opt_bytes(self, raw::git_reference_shorthand(&*self.raw)).unwrap() }
241 }
242
243 /// Get the OID pointed to by a direct reference.
244 ///
245 /// Only available if the reference is direct (i.e. an object id reference,
246 /// not a symbolic one).
247 pub fn target(&self) -> Option<Oid> {
248 unsafe { Binding::from_raw_opt(raw::git_reference_target(&*self.raw)) }
249 }
250
251 /// Return the peeled OID target of this reference.
252 ///
253 /// This peeled OID only applies to direct references that point to a hard
254 /// Tag object: it is the result of peeling such Tag.
255 pub fn target_peel(&self) -> Option<Oid> {
256 unsafe { Binding::from_raw_opt(raw::git_reference_target_peel(&*self.raw)) }
257 }
258
259 /// Get full name to the reference pointed to by a symbolic reference.
260 ///
261 /// May return `None` if the reference is either not symbolic or not a
262 /// valid utf-8 string.
263 pub fn symbolic_target(&self) -> Option<&str> {
264 self.symbolic_target_bytes()
265 .and_then(|s| str::from_utf8(s).ok())
266 }
267
268 /// Get full name to the reference pointed to by a symbolic reference.
269 ///
270 /// Only available if the reference is symbolic.
271 pub fn symbolic_target_bytes(&self) -> Option<&[u8]> {
272 unsafe { crate::opt_bytes(self, raw::git_reference_symbolic_target(&*self.raw)) }
273 }
274
275 /// Resolve a symbolic reference to a direct reference.
276 ///
277 /// This method iteratively peels a symbolic reference until it resolves to
278 /// a direct reference to an OID.
279 ///
280 /// If a direct reference is passed as an argument, a copy of that
281 /// reference is returned.
282 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 /// Peel a reference to an object
291 ///
292 /// This method recursively peels the reference until it reaches
293 /// an object of the specified type.
294 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 /// Peel a reference to a blob
303 ///
304 /// This method recursively peels the reference until it reaches
305 /// a blob.
306 pub fn peel_to_blob(&self) -> Result<Blob<'repo>, Error> {
307 Ok(self.peel(ObjectType::Blob)?.cast_or_panic(ObjectType::Blob))
308 }
309
310 /// Peel a reference to a commit
311 ///
312 /// This method recursively peels the reference until it reaches
313 /// a commit.
314 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 /// Peel a reference to a tree
321 ///
322 /// This method recursively peels the reference until it reaches
323 /// a tree.
324 pub fn peel_to_tree(&self) -> Result<Tree<'repo>, Error> {
325 Ok(self.peel(ObjectType::Tree)?.cast_or_panic(ObjectType::Tree))
326 }
327
328 /// Peel a reference to a tag
329 ///
330 /// This method recursively peels the reference until it reaches
331 /// a tag.
332 pub fn peel_to_tag(&self) -> Result<Tag<'repo>, Error> {
333 Ok(self.peel(ObjectType::Tag)?.cast_or_panic(ObjectType::Tag))
334 }
335
336 /// Rename an existing reference.
337 ///
338 /// This method works for both direct and symbolic references.
339 ///
340 /// If the force flag is not enabled, and there's already a reference with
341 /// the given name, the renaming will fail.
342 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 /// Conditionally create a new reference with the same name as the given
360 /// reference but a different OID target. The reference must be a direct
361 /// reference, otherwise this will fail.
362 ///
363 /// The new reference will be written to disk, overwriting the given
364 /// reference.
365 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 /// Create a new reference with the same name as the given reference but a
380 /// different symbolic target. The reference must be a symbolic reference,
381 /// otherwise this will fail.
382 ///
383 /// The new reference will be written to disk, overwriting the given
384 /// reference.
385 ///
386 /// The target name will be checked for validity. See
387 /// [`Repository::reference_symbolic`] for rules about valid names.
388 ///
389 /// The message for the reflog will be ignored if the reference does not
390 /// belong in the standard set (HEAD, branches and remote-tracking
391 /// branches) and it does not have a reflog.
392 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> Binding for Reference<'repo> {
430 type Raw = *mut raw::git_reference;
431 unsafe fn from_raw(raw: *mut raw::git_reference) -> Reference<'repo> {
432 Reference {
433 raw,
434 _marker: marker::PhantomData,
435 }
436 }
437 fn raw(&self) -> *mut raw::git_reference {
438 self.raw
439 }
440}
441
442impl<'repo> Drop for Reference<'repo> {
443 fn drop(&mut self) {
444 unsafe { raw::git_reference_free(self.raw) }
445 }
446}
447
448impl<'repo> References<'repo> {
449 /// Consumes a `References` iterator to create an iterator over just the
450 /// name of some references.
451 ///
452 /// This is more efficient if only the names are desired of references as
453 /// the references themselves don't have to be allocated and deallocated.
454 ///
455 /// The returned iterator will yield strings as opposed to a `Reference`.
456 pub fn names<'a>(&'a mut self) -> ReferenceNames<'repo, 'a> {
457 ReferenceNames { inner: self }
458 }
459}
460
461impl<'repo> Binding for References<'repo> {
462 type Raw = *mut raw::git_reference_iterator;
463 unsafe fn from_raw(raw: *mut raw::git_reference_iterator) -> References<'repo> {
464 References {
465 raw,
466 _marker: marker::PhantomData,
467 }
468 }
469 fn raw(&self) -> *mut raw::git_reference_iterator {
470 self.raw
471 }
472}
473
474impl<'repo> Iterator for References<'repo> {
475 type Item = Result<Reference<'repo>, Error>;
476 fn next(&mut self) -> Option<Result<Reference<'repo>, Error>> {
477 let mut out = ptr::null_mut();
478 unsafe {
479 try_call_iter!(raw::git_reference_next(&mut out, self.raw));
480 Some(Ok(Binding::from_raw(out)))
481 }
482 }
483}
484
485impl<'repo> Drop for References<'repo> {
486 fn drop(&mut self) {
487 unsafe { raw::git_reference_iterator_free(self.raw) }
488 }
489}
490
491impl<'repo, 'references> Iterator for ReferenceNames<'repo, 'references> {
492 type Item = Result<&'references str, Error>;
493 fn next(&mut self) -> Option<Result<&'references str, Error>> {
494 let mut out = ptr::null();
495 unsafe {
496 try_call_iter!(raw::git_reference_next_name(&mut out, self.inner.raw));
497 let bytes = crate::opt_bytes(self, out).unwrap();
498 let s = str::from_utf8(bytes).unwrap();
499 Some(Ok(mem::transmute::<&str, &'references str>(s)))
500 }
501 }
502}
503
504#[cfg(test)]
505mod tests {
506 use crate::{ObjectType, Reference, ReferenceType};
507
508 #[test]
509 fn is_valid_name() {
510 assert!(Reference::is_valid_name("refs/foo"));
511 assert!(!Reference::is_valid_name("foo"));
512 assert!(Reference::is_valid_name("FOO_BAR"));
513
514 assert!(!Reference::is_valid_name("foo"));
515 assert!(!Reference::is_valid_name("_FOO_BAR"));
516 }
517
518 #[test]
519 #[should_panic]
520 fn is_valid_name_for_invalid_ref() {
521 Reference::is_valid_name("ab\012");
522 }
523
524 #[test]
525 fn smoke() {
526 let (_td, repo) = crate::test::repo_init();
527 let mut head = repo.head().unwrap();
528 assert!(head.is_branch());
529 assert!(!head.is_remote());
530 assert!(!head.is_tag());
531 assert!(!head.is_note());
532
533 // HEAD is a symbolic reference but git_repository_head resolves it
534 // so it is a GIT_REFERENCE_DIRECT.
535 assert_eq!(head.kind().unwrap(), ReferenceType::Direct);
536
537 assert!(head == repo.head().unwrap());
538 assert_eq!(head.name(), Some("refs/heads/main"));
539
540 assert!(head == repo.find_reference("refs/heads/main").unwrap());
541 assert_eq!(
542 repo.refname_to_id("refs/heads/main").unwrap(),
543 head.target().unwrap()
544 );
545
546 assert!(head.symbolic_target().is_none());
547 assert!(head.target_peel().is_none());
548
549 assert_eq!(head.shorthand(), Some("main"));
550 assert!(head.resolve().unwrap() == head);
551
552 let mut tag1 = repo
553 .reference("refs/tags/tag1", head.target().unwrap(), false, "test")
554 .unwrap();
555 assert!(tag1.is_tag());
556 assert_eq!(tag1.kind().unwrap(), ReferenceType::Direct);
557
558 let peeled_commit = tag1.peel(ObjectType::Commit).unwrap();
559 assert_eq!(ObjectType::Commit, peeled_commit.kind().unwrap());
560 assert_eq!(tag1.target().unwrap(), peeled_commit.id());
561
562 tag1.delete().unwrap();
563
564 let mut sym1 = repo
565 .reference_symbolic("refs/tags/tag1", "refs/heads/main", false, "test")
566 .unwrap();
567 assert_eq!(sym1.kind().unwrap(), ReferenceType::Symbolic);
568 let mut sym2 = repo
569 .reference_symbolic("refs/tags/tag2", "refs/heads/main", false, "test")
570 .unwrap()
571 .symbolic_set_target("refs/tags/tag1", "test")
572 .unwrap();
573 assert_eq!(sym2.kind().unwrap(), ReferenceType::Symbolic);
574 assert_eq!(sym2.symbolic_target().unwrap(), "refs/tags/tag1");
575 sym2.delete().unwrap();
576 sym1.delete().unwrap();
577
578 {
579 assert!(repo.references().unwrap().count() == 1);
580 assert!(repo.references().unwrap().next().unwrap().unwrap() == head);
581 let mut names = repo.references().unwrap();
582 let mut names = names.names();
583 assert_eq!(names.next().unwrap().unwrap(), "refs/heads/main");
584 assert!(names.next().is_none());
585 assert!(repo.references_glob("foo").unwrap().count() == 0);
586 assert!(repo.references_glob("refs/heads/*").unwrap().count() == 1);
587 }
588
589 let mut head = head.rename("refs/foo", true, "test").unwrap();
590 head.delete().unwrap();
591 }
592}