]>
Commit | Line | Data |
---|---|---|
0a29b90c | 1 | use libc::size_t; |
781aab86 | 2 | use std::iter::FusedIterator; |
0a29b90c FG |
3 | use std::marker; |
4 | use std::ops::Range; | |
5 | use std::str; | |
6 | ||
7 | use crate::util::Binding; | |
8 | use crate::{raw, signature, Error, Oid, Signature}; | |
9 | ||
10 | /// A reference log of a git repository. | |
11 | pub struct Reflog { | |
12 | raw: *mut raw::git_reflog, | |
13 | } | |
14 | ||
15 | /// An entry inside the reflog of a repository | |
16 | pub struct ReflogEntry<'reflog> { | |
17 | raw: *const raw::git_reflog_entry, | |
18 | _marker: marker::PhantomData<&'reflog Reflog>, | |
19 | } | |
20 | ||
21 | /// An iterator over the entries inside of a reflog. | |
22 | pub struct ReflogIter<'reflog> { | |
23 | range: Range<usize>, | |
24 | reflog: &'reflog Reflog, | |
25 | } | |
26 | ||
27 | impl Reflog { | |
28 | /// Add a new entry to the in-memory reflog. | |
29 | pub fn append( | |
30 | &mut self, | |
31 | new_oid: Oid, | |
32 | committer: &Signature<'_>, | |
33 | msg: Option<&str>, | |
34 | ) -> Result<(), Error> { | |
35 | let msg = crate::opt_cstr(msg)?; | |
36 | unsafe { | |
37 | try_call!(raw::git_reflog_append( | |
38 | self.raw, | |
39 | new_oid.raw(), | |
40 | committer.raw(), | |
41 | msg | |
42 | )); | |
43 | } | |
44 | Ok(()) | |
45 | } | |
46 | ||
47 | /// Remove an entry from the reflog by its index | |
48 | /// | |
49 | /// To ensure there's no gap in the log history, set rewrite_previous_entry | |
50 | /// param value to `true`. When deleting entry n, member old_oid of entry | |
51 | /// n-1 (if any) will be updated with the value of member new_oid of entry | |
52 | /// n+1. | |
53 | pub fn remove(&mut self, i: usize, rewrite_previous_entry: bool) -> Result<(), Error> { | |
54 | unsafe { | |
55 | try_call!(raw::git_reflog_drop( | |
56 | self.raw, | |
57 | i as size_t, | |
58 | rewrite_previous_entry | |
59 | )); | |
60 | } | |
61 | Ok(()) | |
62 | } | |
63 | ||
64 | /// Lookup an entry by its index | |
65 | /// | |
66 | /// Requesting the reflog entry with an index of 0 (zero) will return the | |
67 | /// most recently created entry. | |
68 | pub fn get(&self, i: usize) -> Option<ReflogEntry<'_>> { | |
69 | unsafe { | |
70 | let ptr = raw::git_reflog_entry_byindex(self.raw, i as size_t); | |
71 | Binding::from_raw_opt(ptr) | |
72 | } | |
73 | } | |
74 | ||
75 | /// Get the number of log entries in a reflog | |
76 | pub fn len(&self) -> usize { | |
77 | unsafe { raw::git_reflog_entrycount(self.raw) as usize } | |
78 | } | |
79 | ||
80 | /// Return `true ` is there is no log entry in a reflog | |
81 | pub fn is_empty(&self) -> bool { | |
82 | self.len() == 0 | |
83 | } | |
84 | ||
85 | /// Get an iterator to all entries inside of this reflog | |
86 | pub fn iter(&self) -> ReflogIter<'_> { | |
87 | ReflogIter { | |
88 | range: 0..self.len(), | |
89 | reflog: self, | |
90 | } | |
91 | } | |
92 | ||
93 | /// Write an existing in-memory reflog object back to disk using an atomic | |
94 | /// file lock. | |
95 | pub fn write(&mut self) -> Result<(), Error> { | |
96 | unsafe { | |
97 | try_call!(raw::git_reflog_write(self.raw)); | |
98 | } | |
99 | Ok(()) | |
100 | } | |
101 | } | |
102 | ||
103 | impl Binding for Reflog { | |
104 | type Raw = *mut raw::git_reflog; | |
105 | ||
106 | unsafe fn from_raw(raw: *mut raw::git_reflog) -> Reflog { | |
107 | Reflog { raw } | |
108 | } | |
109 | fn raw(&self) -> *mut raw::git_reflog { | |
110 | self.raw | |
111 | } | |
112 | } | |
113 | ||
114 | impl Drop for Reflog { | |
115 | fn drop(&mut self) { | |
116 | unsafe { raw::git_reflog_free(self.raw) } | |
117 | } | |
118 | } | |
119 | ||
120 | impl<'reflog> ReflogEntry<'reflog> { | |
121 | /// Get the committer of this entry | |
122 | pub fn committer(&self) -> Signature<'_> { | |
123 | unsafe { | |
124 | let ptr = raw::git_reflog_entry_committer(self.raw); | |
125 | signature::from_raw_const(self, ptr) | |
126 | } | |
127 | } | |
128 | ||
129 | /// Get the new oid | |
130 | pub fn id_new(&self) -> Oid { | |
131 | unsafe { Binding::from_raw(raw::git_reflog_entry_id_new(self.raw)) } | |
132 | } | |
133 | ||
134 | /// Get the old oid | |
135 | pub fn id_old(&self) -> Oid { | |
136 | unsafe { Binding::from_raw(raw::git_reflog_entry_id_old(self.raw)) } | |
137 | } | |
138 | ||
139 | /// Get the log message, returning `None` on invalid UTF-8. | |
140 | pub fn message(&self) -> Option<&str> { | |
141 | self.message_bytes().and_then(|s| str::from_utf8(s).ok()) | |
142 | } | |
143 | ||
144 | /// Get the log message as a byte array. | |
145 | pub fn message_bytes(&self) -> Option<&[u8]> { | |
146 | unsafe { crate::opt_bytes(self, raw::git_reflog_entry_message(self.raw)) } | |
147 | } | |
148 | } | |
149 | ||
150 | impl<'reflog> Binding for ReflogEntry<'reflog> { | |
151 | type Raw = *const raw::git_reflog_entry; | |
152 | ||
153 | unsafe fn from_raw(raw: *const raw::git_reflog_entry) -> ReflogEntry<'reflog> { | |
154 | ReflogEntry { | |
155 | raw, | |
156 | _marker: marker::PhantomData, | |
157 | } | |
158 | } | |
159 | fn raw(&self) -> *const raw::git_reflog_entry { | |
160 | self.raw | |
161 | } | |
162 | } | |
163 | ||
164 | impl<'reflog> Iterator for ReflogIter<'reflog> { | |
165 | type Item = ReflogEntry<'reflog>; | |
166 | fn next(&mut self) -> Option<ReflogEntry<'reflog>> { | |
167 | self.range.next().and_then(|i| self.reflog.get(i)) | |
168 | } | |
169 | fn size_hint(&self) -> (usize, Option<usize>) { | |
170 | self.range.size_hint() | |
171 | } | |
172 | } | |
173 | impl<'reflog> DoubleEndedIterator for ReflogIter<'reflog> { | |
174 | fn next_back(&mut self) -> Option<ReflogEntry<'reflog>> { | |
175 | self.range.next_back().and_then(|i| self.reflog.get(i)) | |
176 | } | |
177 | } | |
781aab86 | 178 | impl<'reflog> FusedIterator for ReflogIter<'reflog> {} |
0a29b90c FG |
179 | impl<'reflog> ExactSizeIterator for ReflogIter<'reflog> {} |
180 | ||
181 | #[cfg(test)] | |
182 | mod tests { | |
183 | #[test] | |
184 | fn smoke() { | |
185 | let (_td, repo) = crate::test::repo_init(); | |
186 | let mut reflog = repo.reflog("HEAD").unwrap(); | |
187 | assert_eq!(reflog.iter().len(), 1); | |
188 | reflog.write().unwrap(); | |
189 | ||
190 | let entry = reflog.iter().next().unwrap(); | |
191 | assert!(entry.message().is_some()); | |
192 | ||
193 | repo.reflog_rename("HEAD", "refs/heads/foo").unwrap(); | |
194 | repo.reflog_delete("refs/heads/foo").unwrap(); | |
195 | } | |
196 | } |