]> git.proxmox.com Git - cargo.git/blob - vendor/git2/src/reference.rs
New upstream version 0.52.0
[cargo.git] / vendor / git2 / src / reference.rs
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, ReferenceFormat, ReferenceType, Repository,
12 Tag, Tree,
13 };
14
15 // Not in the public header files (yet?), but a hard limit used by libgit2
16 // internally
17 const GIT_REFNAME_MAX: usize = 1024;
18
19 struct Refdb<'repo>(&'repo Repository);
20
21 /// A structure to represent a git [reference][1].
22 ///
23 /// [1]: http://git-scm.com/book/en/Git-Internals-Git-References
24 pub struct Reference<'repo> {
25 raw: *mut raw::git_reference,
26 _marker: marker::PhantomData<Refdb<'repo>>,
27 }
28
29 /// An iterator over the references in a repository.
30 pub struct References<'repo> {
31 raw: *mut raw::git_reference_iterator,
32 _marker: marker::PhantomData<Refdb<'repo>>,
33 }
34
35 /// An iterator over the names of references in a repository.
36 pub struct ReferenceNames<'repo, 'references> {
37 inner: &'references mut References<'repo>,
38 }
39
40 impl<'repo> Reference<'repo> {
41 /// Ensure the reference name is well-formed.
42 ///
43 /// Validation is performed as if [`ReferenceFormat::ALLOW_ONELEVEL`]
44 /// was given to [`Reference::normalize_name`]. No normalization is
45 /// performed, however.
46 ///
47 /// ```rust
48 /// use git2::Reference;
49 ///
50 /// assert!(Reference::is_valid_name("HEAD"));
51 /// assert!(Reference::is_valid_name("refs/heads/main"));
52 ///
53 /// // But:
54 /// assert!(!Reference::is_valid_name("main"));
55 /// assert!(!Reference::is_valid_name("refs/heads/*"));
56 /// assert!(!Reference::is_valid_name("foo//bar"));
57 /// ```
58 ///
59 /// [`ReferenceFormat::ALLOW_ONELEVEL`]:
60 /// struct.ReferenceFormat#associatedconstant.ALLOW_ONELEVEL
61 /// [`Reference::normalize_name`]: struct.Reference#method.normalize_name
62 pub fn is_valid_name(refname: &str) -> bool {
63 crate::init();
64 let refname = CString::new(refname).unwrap();
65 unsafe { raw::git_reference_is_valid_name(refname.as_ptr()) == 1 }
66 }
67
68 /// Normalize reference name and check validity.
69 ///
70 /// This will normalize the reference name by collapsing runs of adjacent
71 /// slashes between name components into a single slash. It also validates
72 /// the name according to the following rules:
73 ///
74 /// 1. If [`ReferenceFormat::ALLOW_ONELEVEL`] is given, the name may
75 /// contain only capital letters and underscores, and must begin and end
76 /// with a letter. (e.g. "HEAD", "ORIG_HEAD").
77 /// 2. The flag [`ReferenceFormat::REFSPEC_SHORTHAND`] has an effect
78 /// only when combined with [`ReferenceFormat::ALLOW_ONELEVEL`]. If
79 /// it is given, "shorthand" branch names (i.e. those not prefixed by
80 /// `refs/`, but consisting of a single word without `/` separators)
81 /// become valid. For example, "main" would be accepted.
82 /// 3. If [`ReferenceFormat::REFSPEC_PATTERN`] is given, the name may
83 /// contain a single `*` in place of a full pathname component (e.g.
84 /// `foo/*/bar`, `foo/bar*`).
85 /// 4. Names prefixed with "refs/" can be almost anything. You must avoid
86 /// the characters '~', '^', ':', '\\', '?', '[', and '*', and the
87 /// sequences ".." and "@{" which have special meaning to revparse.
88 ///
89 /// If the reference passes validation, it is returned in normalized form,
90 /// otherwise an [`Error`] with [`ErrorCode::InvalidSpec`] is returned.
91 ///
92 /// ```rust
93 /// use git2::{Reference, ReferenceFormat};
94 ///
95 /// assert_eq!(
96 /// Reference::normalize_name(
97 /// "foo//bar",
98 /// ReferenceFormat::NORMAL
99 /// )
100 /// .unwrap(),
101 /// "foo/bar".to_owned()
102 /// );
103 ///
104 /// assert_eq!(
105 /// Reference::normalize_name(
106 /// "HEAD",
107 /// ReferenceFormat::ALLOW_ONELEVEL
108 /// )
109 /// .unwrap(),
110 /// "HEAD".to_owned()
111 /// );
112 ///
113 /// assert_eq!(
114 /// Reference::normalize_name(
115 /// "refs/heads/*",
116 /// ReferenceFormat::REFSPEC_PATTERN
117 /// )
118 /// .unwrap(),
119 /// "refs/heads/*".to_owned()
120 /// );
121 ///
122 /// assert_eq!(
123 /// Reference::normalize_name(
124 /// "main",
125 /// ReferenceFormat::ALLOW_ONELEVEL | ReferenceFormat::REFSPEC_SHORTHAND
126 /// )
127 /// .unwrap(),
128 /// "main".to_owned()
129 /// );
130 /// ```
131 ///
132 /// [`ReferenceFormat::ALLOW_ONELEVEL`]:
133 /// struct.ReferenceFormat#associatedconstant.ALLOW_ONELEVEL
134 /// [`ReferenceFormat::REFSPEC_SHORTHAND`]:
135 /// struct.ReferenceFormat#associatedconstant.REFSPEC_SHORTHAND
136 /// [`ReferenceFormat::REFSPEC_PATTERN`]:
137 /// struct.ReferenceFormat#associatedconstant.REFSPEC_PATTERN
138 /// [`Error`]: struct.Error
139 /// [`ErrorCode::InvalidSpec`]: enum.ErrorCode#variant.InvalidSpec
140 pub fn normalize_name(refname: &str, flags: ReferenceFormat) -> Result<String, Error> {
141 crate::init();
142 let mut dst = [0u8; GIT_REFNAME_MAX];
143 let refname = CString::new(refname)?;
144 unsafe {
145 try_call!(raw::git_reference_normalize_name(
146 dst.as_mut_ptr() as *mut libc::c_char,
147 dst.len() as libc::size_t,
148 refname,
149 flags.bits()
150 ));
151 let s = &dst[..dst.iter().position(|&a| a == 0).unwrap()];
152 Ok(str::from_utf8(s).unwrap().to_owned())
153 }
154 }
155
156 /// Get access to the underlying raw pointer.
157 pub fn raw(&self) -> *mut raw::git_reference {
158 self.raw
159 }
160
161 /// Delete an existing reference.
162 ///
163 /// This method works for both direct and symbolic references. The reference
164 /// will be immediately removed on disk.
165 ///
166 /// This function will return an error if the reference has changed from the
167 /// time it was looked up.
168 pub fn delete(&mut self) -> Result<(), Error> {
169 unsafe {
170 try_call!(raw::git_reference_delete(self.raw));
171 }
172 Ok(())
173 }
174
175 /// Check if a reference is a local branch.
176 pub fn is_branch(&self) -> bool {
177 unsafe { raw::git_reference_is_branch(&*self.raw) == 1 }
178 }
179
180 /// Check if a reference is a note.
181 pub fn is_note(&self) -> bool {
182 unsafe { raw::git_reference_is_note(&*self.raw) == 1 }
183 }
184
185 /// Check if a reference is a remote tracking branch
186 pub fn is_remote(&self) -> bool {
187 unsafe { raw::git_reference_is_remote(&*self.raw) == 1 }
188 }
189
190 /// Check if a reference is a tag
191 pub fn is_tag(&self) -> bool {
192 unsafe { raw::git_reference_is_tag(&*self.raw) == 1 }
193 }
194
195 /// Get the reference type of a reference.
196 ///
197 /// If the type is unknown, then `None` is returned.
198 pub fn kind(&self) -> Option<ReferenceType> {
199 ReferenceType::from_raw(unsafe { raw::git_reference_type(&*self.raw) })
200 }
201
202 /// Get the full name of a reference.
203 ///
204 /// Returns `None` if the name is not valid utf-8.
205 pub fn name(&self) -> Option<&str> {
206 str::from_utf8(self.name_bytes()).ok()
207 }
208
209 /// Get the full name of a reference.
210 pub fn name_bytes(&self) -> &[u8] {
211 unsafe { crate::opt_bytes(self, raw::git_reference_name(&*self.raw)).unwrap() }
212 }
213
214 /// Get the full shorthand of a reference.
215 ///
216 /// This will transform the reference name into a name "human-readable"
217 /// version. If no shortname is appropriate, it will return the full name.
218 ///
219 /// Returns `None` if the shorthand is not valid utf-8.
220 pub fn shorthand(&self) -> Option<&str> {
221 str::from_utf8(self.shorthand_bytes()).ok()
222 }
223
224 /// Get the full shorthand of a reference.
225 pub fn shorthand_bytes(&self) -> &[u8] {
226 unsafe { crate::opt_bytes(self, raw::git_reference_shorthand(&*self.raw)).unwrap() }
227 }
228
229 /// Get the OID pointed to by a direct reference.
230 ///
231 /// Only available if the reference is direct (i.e. an object id reference,
232 /// not a symbolic one).
233 pub fn target(&self) -> Option<Oid> {
234 unsafe { Binding::from_raw_opt(raw::git_reference_target(&*self.raw)) }
235 }
236
237 /// Return the peeled OID target of this reference.
238 ///
239 /// This peeled OID only applies to direct references that point to a hard
240 /// Tag object: it is the result of peeling such Tag.
241 pub fn target_peel(&self) -> Option<Oid> {
242 unsafe { Binding::from_raw_opt(raw::git_reference_target_peel(&*self.raw)) }
243 }
244
245 /// Get full name to the reference pointed to by a symbolic reference.
246 ///
247 /// May return `None` if the reference is either not symbolic or not a
248 /// valid utf-8 string.
249 pub fn symbolic_target(&self) -> Option<&str> {
250 self.symbolic_target_bytes()
251 .and_then(|s| str::from_utf8(s).ok())
252 }
253
254 /// Get full name to the reference pointed to by a symbolic reference.
255 ///
256 /// Only available if the reference is symbolic.
257 pub fn symbolic_target_bytes(&self) -> Option<&[u8]> {
258 unsafe { crate::opt_bytes(self, raw::git_reference_symbolic_target(&*self.raw)) }
259 }
260
261 /// Resolve a symbolic reference to a direct reference.
262 ///
263 /// This method iteratively peels a symbolic reference until it resolves to
264 /// a direct reference to an OID.
265 ///
266 /// If a direct reference is passed as an argument, a copy of that
267 /// reference is returned.
268 pub fn resolve(&self) -> Result<Reference<'repo>, Error> {
269 let mut raw = ptr::null_mut();
270 unsafe {
271 try_call!(raw::git_reference_resolve(&mut raw, &*self.raw));
272 Ok(Binding::from_raw(raw))
273 }
274 }
275
276 /// Peel a reference to an object
277 ///
278 /// This method recursively peels the reference until it reaches
279 /// an object of the specified type.
280 pub fn peel(&self, kind: ObjectType) -> Result<Object<'repo>, Error> {
281 let mut raw = ptr::null_mut();
282 unsafe {
283 try_call!(raw::git_reference_peel(&mut raw, self.raw, kind));
284 Ok(Binding::from_raw(raw))
285 }
286 }
287
288 /// Peel a reference to a blob
289 ///
290 /// This method recursively peels the reference until it reaches
291 /// a blob.
292 pub fn peel_to_blob(&self) -> Result<Blob<'repo>, Error> {
293 Ok(self.peel(ObjectType::Blob)?.cast_or_panic(ObjectType::Blob))
294 }
295
296 /// Peel a reference to a commit
297 ///
298 /// This method recursively peels the reference until it reaches
299 /// a commit.
300 pub fn peel_to_commit(&self) -> Result<Commit<'repo>, Error> {
301 Ok(self
302 .peel(ObjectType::Commit)?
303 .cast_or_panic(ObjectType::Commit))
304 }
305
306 /// Peel a reference to a tree
307 ///
308 /// This method recursively peels the reference until it reaches
309 /// a tree.
310 pub fn peel_to_tree(&self) -> Result<Tree<'repo>, Error> {
311 Ok(self.peel(ObjectType::Tree)?.cast_or_panic(ObjectType::Tree))
312 }
313
314 /// Peel a reference to a tag
315 ///
316 /// This method recursively peels the reference until it reaches
317 /// a tag.
318 pub fn peel_to_tag(&self) -> Result<Tag<'repo>, Error> {
319 Ok(self.peel(ObjectType::Tag)?.cast_or_panic(ObjectType::Tag))
320 }
321
322 /// Rename an existing reference.
323 ///
324 /// This method works for both direct and symbolic references.
325 ///
326 /// If the force flag is not enabled, and there's already a reference with
327 /// the given name, the renaming will fail.
328 pub fn rename(
329 &mut self,
330 new_name: &str,
331 force: bool,
332 msg: &str,
333 ) -> Result<Reference<'repo>, Error> {
334 let mut raw = ptr::null_mut();
335 let new_name = CString::new(new_name)?;
336 let msg = CString::new(msg)?;
337 unsafe {
338 try_call!(raw::git_reference_rename(
339 &mut raw, self.raw, new_name, force, msg
340 ));
341 Ok(Binding::from_raw(raw))
342 }
343 }
344
345 /// Conditionally create a new reference with the same name as the given
346 /// reference but a different OID target. The reference must be a direct
347 /// reference, otherwise this will fail.
348 ///
349 /// The new reference will be written to disk, overwriting the given
350 /// reference.
351 pub fn set_target(&mut self, id: Oid, reflog_msg: &str) -> Result<Reference<'repo>, Error> {
352 let mut raw = ptr::null_mut();
353 let msg = CString::new(reflog_msg)?;
354 unsafe {
355 try_call!(raw::git_reference_set_target(
356 &mut raw,
357 self.raw,
358 id.raw(),
359 msg
360 ));
361 Ok(Binding::from_raw(raw))
362 }
363 }
364 }
365
366 impl<'repo> PartialOrd for Reference<'repo> {
367 fn partial_cmp(&self, other: &Reference<'repo>) -> Option<Ordering> {
368 Some(self.cmp(other))
369 }
370 }
371
372 impl<'repo> Ord for Reference<'repo> {
373 fn cmp(&self, other: &Reference<'repo>) -> Ordering {
374 c_cmp_to_ordering(unsafe { raw::git_reference_cmp(&*self.raw, &*other.raw) })
375 }
376 }
377
378 impl<'repo> PartialEq for Reference<'repo> {
379 fn eq(&self, other: &Reference<'repo>) -> bool {
380 self.cmp(other) == Ordering::Equal
381 }
382 }
383
384 impl<'repo> Eq for Reference<'repo> {}
385
386 impl<'repo> Binding for Reference<'repo> {
387 type Raw = *mut raw::git_reference;
388 unsafe fn from_raw(raw: *mut raw::git_reference) -> Reference<'repo> {
389 Reference {
390 raw: raw,
391 _marker: marker::PhantomData,
392 }
393 }
394 fn raw(&self) -> *mut raw::git_reference {
395 self.raw
396 }
397 }
398
399 impl<'repo> Drop for Reference<'repo> {
400 fn drop(&mut self) {
401 unsafe { raw::git_reference_free(self.raw) }
402 }
403 }
404
405 impl<'repo> References<'repo> {
406 /// Consumes a `References` iterator to create an iterator over just the
407 /// name of some references.
408 ///
409 /// This is more efficient if only the names are desired of references as
410 /// the references themselves don't have to be allocated and deallocated.
411 ///
412 /// The returned iterator will yield strings as opposed to a `Reference`.
413 pub fn names<'a>(&'a mut self) -> ReferenceNames<'repo, 'a> {
414 ReferenceNames { inner: self }
415 }
416 }
417
418 impl<'repo> Binding for References<'repo> {
419 type Raw = *mut raw::git_reference_iterator;
420 unsafe fn from_raw(raw: *mut raw::git_reference_iterator) -> References<'repo> {
421 References {
422 raw: raw,
423 _marker: marker::PhantomData,
424 }
425 }
426 fn raw(&self) -> *mut raw::git_reference_iterator {
427 self.raw
428 }
429 }
430
431 impl<'repo> Iterator for References<'repo> {
432 type Item = Result<Reference<'repo>, Error>;
433 fn next(&mut self) -> Option<Result<Reference<'repo>, Error>> {
434 let mut out = ptr::null_mut();
435 unsafe {
436 try_call_iter!(raw::git_reference_next(&mut out, self.raw));
437 Some(Ok(Binding::from_raw(out)))
438 }
439 }
440 }
441
442 impl<'repo> Drop for References<'repo> {
443 fn drop(&mut self) {
444 unsafe { raw::git_reference_iterator_free(self.raw) }
445 }
446 }
447
448 impl<'repo, 'references> Iterator for ReferenceNames<'repo, 'references> {
449 type Item = Result<&'references str, Error>;
450 fn next(&mut self) -> Option<Result<&'references str, Error>> {
451 let mut out = ptr::null();
452 unsafe {
453 try_call_iter!(raw::git_reference_next_name(&mut out, self.inner.raw));
454 let bytes = crate::opt_bytes(self, out).unwrap();
455 let s = str::from_utf8(bytes).unwrap();
456 Some(Ok(mem::transmute::<&str, &'references str>(s)))
457 }
458 }
459 }
460
461 #[cfg(test)]
462 mod tests {
463 use crate::{ObjectType, Reference, ReferenceType};
464
465 #[test]
466 fn smoke() {
467 assert!(Reference::is_valid_name("refs/foo"));
468 assert!(!Reference::is_valid_name("foo"));
469 }
470
471 #[test]
472 fn smoke2() {
473 let (_td, repo) = crate::test::repo_init();
474 let mut head = repo.head().unwrap();
475 assert!(head.is_branch());
476 assert!(!head.is_remote());
477 assert!(!head.is_tag());
478 assert!(!head.is_note());
479
480 // HEAD is a symbolic reference but git_repository_head resolves it
481 // so it is a GIT_REFERENCE_DIRECT.
482 assert_eq!(head.kind().unwrap(), ReferenceType::Direct);
483
484 assert!(head == repo.head().unwrap());
485 assert_eq!(head.name(), Some("refs/heads/main"));
486
487 assert!(head == repo.find_reference("refs/heads/main").unwrap());
488 assert_eq!(
489 repo.refname_to_id("refs/heads/main").unwrap(),
490 head.target().unwrap()
491 );
492
493 assert!(head.symbolic_target().is_none());
494 assert!(head.target_peel().is_none());
495
496 assert_eq!(head.shorthand(), Some("main"));
497 assert!(head.resolve().unwrap() == head);
498
499 let mut tag1 = repo
500 .reference("refs/tags/tag1", head.target().unwrap(), false, "test")
501 .unwrap();
502 assert!(tag1.is_tag());
503 assert_eq!(tag1.kind().unwrap(), ReferenceType::Direct);
504
505 let peeled_commit = tag1.peel(ObjectType::Commit).unwrap();
506 assert_eq!(ObjectType::Commit, peeled_commit.kind().unwrap());
507 assert_eq!(tag1.target().unwrap(), peeled_commit.id());
508
509 tag1.delete().unwrap();
510
511 let mut sym1 = repo
512 .reference_symbolic("refs/tags/tag1", "refs/heads/main", false, "test")
513 .unwrap();
514 assert_eq!(sym1.kind().unwrap(), ReferenceType::Symbolic);
515 sym1.delete().unwrap();
516
517 {
518 assert!(repo.references().unwrap().count() == 1);
519 assert!(repo.references().unwrap().next().unwrap().unwrap() == head);
520 let mut names = repo.references().unwrap();
521 let mut names = names.names();
522 assert_eq!(names.next().unwrap().unwrap(), "refs/heads/main");
523 assert!(names.next().is_none());
524 assert!(repo.references_glob("foo").unwrap().count() == 0);
525 assert!(repo.references_glob("refs/heads/*").unwrap().count() == 1);
526 }
527
528 let mut head = head.rename("refs/foo", true, "test").unwrap();
529 head.delete().unwrap();
530 }
531 }