1use crate::util::{self, Binding};
2use crate::{raw, signature, Error, Oid, Repository, Signature};
3use libc::c_char;
4use std::iter::FusedIterator;
5use std::mem;
6use std::ops::Range;
7use std::path::Path;
8use std::{marker, ptr};
9
10pub struct Blame<'repo> {
12 raw: *mut raw::git_blame,
13 _marker: marker::PhantomData<&'repo Repository>,
14}
15
16pub struct BlameHunk<'blame> {
18 raw: *mut raw::git_blame_hunk,
19 _marker: marker::PhantomData<&'blame raw::git_blame>,
20}
21
22pub struct BlameOptions {
24 raw: raw::git_blame_options,
25}
26
27pub struct BlameIter<'blame> {
29 range: Range<usize>,
30 blame: &'blame Blame<'blame>,
31}
32
33impl<'repo> Blame<'repo> {
34 pub fn blame_buffer(&self, buffer: &[u8]) -> Result<Blame<'_>, Error> {
39 let mut raw = ptr::null_mut();
40
41 unsafe {
42 try_call!(raw::git_blame_buffer(
43 &mut raw,
44 self.raw,
45 buffer.as_ptr() as *const c_char,
46 buffer.len()
47 ));
48 Ok(Binding::from_raw(raw))
49 }
50 }
51
52 pub fn len(&self) -> usize {
54 unsafe { raw::git_blame_get_hunk_count(self.raw) as usize }
55 }
56
57 pub fn is_empty(&self) -> bool {
59 self.len() == 0
60 }
61
62 pub fn get_index(&self, index: usize) -> Option<BlameHunk<'_>> {
64 unsafe {
65 let ptr = raw::git_blame_get_hunk_byindex(self.raw(), index as u32);
66 if ptr.is_null() {
67 None
68 } else {
69 Some(BlameHunk::from_raw_const(ptr))
70 }
71 }
72 }
73
74 pub fn get_line(&self, lineno: usize) -> Option<BlameHunk<'_>> {
77 unsafe {
78 let ptr = raw::git_blame_get_hunk_byline(self.raw(), lineno);
79 if ptr.is_null() {
80 None
81 } else {
82 Some(BlameHunk::from_raw_const(ptr))
83 }
84 }
85 }
86
87 pub fn iter(&self) -> BlameIter<'_> {
89 BlameIter {
90 range: 0..self.len(),
91 blame: self,
92 }
93 }
94}
95
96impl<'blame> BlameHunk<'blame> {
97 unsafe fn from_raw_const(raw: *const raw::git_blame_hunk) -> BlameHunk<'blame> {
98 BlameHunk {
99 raw: raw as *mut raw::git_blame_hunk,
100 _marker: marker::PhantomData,
101 }
102 }
103
104 pub fn final_commit_id(&self) -> Oid {
106 unsafe { Oid::from_raw(&(*self.raw).final_commit_id) }
107 }
108
109 pub fn final_signature(&self) -> Signature<'_> {
111 unsafe { signature::from_raw_const(self, (*self.raw).final_signature) }
112 }
113
114 pub fn final_start_line(&self) -> usize {
118 unsafe { (*self.raw).final_start_line_number }
119 }
120
121 pub fn orig_commit_id(&self) -> Oid {
127 unsafe { Oid::from_raw(&(*self.raw).orig_commit_id) }
128 }
129
130 pub fn orig_signature(&self) -> Signature<'_> {
132 unsafe { signature::from_raw_const(self, (*self.raw).orig_signature) }
133 }
134
135 pub fn orig_start_line(&self) -> usize {
139 unsafe { (*self.raw).orig_start_line_number }
140 }
141
142 pub fn path(&self) -> Option<&Path> {
146 unsafe {
147 if let Some(bytes) = crate::opt_bytes(self, (*self.raw).orig_path) {
148 Some(util::bytes2path(bytes))
149 } else {
150 None
151 }
152 }
153 }
154
155 pub fn is_boundary(&self) -> bool {
158 unsafe { (*self.raw).boundary == 1 }
159 }
160
161 pub fn lines_in_hunk(&self) -> usize {
163 unsafe { (*self.raw).lines_in_hunk as usize }
164 }
165}
166
167impl Default for BlameOptions {
168 fn default() -> Self {
169 Self::new()
170 }
171}
172
173impl BlameOptions {
174 pub fn new() -> BlameOptions {
176 unsafe {
177 let mut raw: raw::git_blame_options = mem::zeroed();
178 assert_eq!(
179 raw::git_blame_init_options(&mut raw, raw::GIT_BLAME_OPTIONS_VERSION),
180 0
181 );
182
183 Binding::from_raw(&raw as *const _ as *mut _)
184 }
185 }
186
187 fn flag(&mut self, opt: u32, val: bool) -> &mut BlameOptions {
188 if val {
189 self.raw.flags |= opt;
190 } else {
191 self.raw.flags &= !opt;
192 }
193 self
194 }
195
196 pub fn track_copies_same_file(&mut self, opt: bool) -> &mut BlameOptions {
198 self.flag(raw::GIT_BLAME_TRACK_COPIES_SAME_FILE, opt)
199 }
200
201 pub fn track_copies_same_commit_moves(&mut self, opt: bool) -> &mut BlameOptions {
203 self.flag(raw::GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES, opt)
204 }
205
206 pub fn track_copies_same_commit_copies(&mut self, opt: bool) -> &mut BlameOptions {
209 self.flag(raw::GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES, opt)
210 }
211
212 pub fn track_copies_any_commit_copies(&mut self, opt: bool) -> &mut BlameOptions {
215 self.flag(raw::GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES, opt)
216 }
217
218 pub fn first_parent(&mut self, opt: bool) -> &mut BlameOptions {
221 self.flag(raw::GIT_BLAME_FIRST_PARENT, opt)
222 }
223
224 pub fn use_mailmap(&mut self, opt: bool) -> &mut BlameOptions {
228 self.flag(raw::GIT_BLAME_USE_MAILMAP, opt)
229 }
230
231 pub fn ignore_whitespace(&mut self, opt: bool) -> &mut BlameOptions {
233 self.flag(raw::GIT_BLAME_IGNORE_WHITESPACE, opt)
234 }
235
236 pub fn newest_commit(&mut self, id: Oid) -> &mut BlameOptions {
238 unsafe {
239 self.raw.newest_commit = *id.raw();
240 }
241 self
242 }
243
244 pub fn oldest_commit(&mut self, id: Oid) -> &mut BlameOptions {
246 unsafe {
247 self.raw.oldest_commit = *id.raw();
248 }
249 self
250 }
251
252 pub fn min_line(&mut self, lineno: usize) -> &mut BlameOptions {
254 self.raw.min_line = lineno;
255 self
256 }
257
258 pub fn max_line(&mut self, lineno: usize) -> &mut BlameOptions {
260 self.raw.max_line = lineno;
261 self
262 }
263}
264
265impl<'repo> Binding for Blame<'repo> {
266 type Raw = *mut raw::git_blame;
267
268 unsafe fn from_raw(raw: *mut raw::git_blame) -> Blame<'repo> {
269 Blame {
270 raw,
271 _marker: marker::PhantomData,
272 }
273 }
274
275 fn raw(&self) -> *mut raw::git_blame {
276 self.raw
277 }
278}
279
280impl<'repo> Drop for Blame<'repo> {
281 fn drop(&mut self) {
282 unsafe { raw::git_blame_free(self.raw) }
283 }
284}
285
286impl<'blame> Binding for BlameHunk<'blame> {
287 type Raw = *mut raw::git_blame_hunk;
288
289 unsafe fn from_raw(raw: *mut raw::git_blame_hunk) -> BlameHunk<'blame> {
290 BlameHunk {
291 raw,
292 _marker: marker::PhantomData,
293 }
294 }
295
296 fn raw(&self) -> *mut raw::git_blame_hunk {
297 self.raw
298 }
299}
300
301impl Binding for BlameOptions {
302 type Raw = *mut raw::git_blame_options;
303
304 unsafe fn from_raw(opts: *mut raw::git_blame_options) -> BlameOptions {
305 BlameOptions { raw: *opts }
306 }
307
308 fn raw(&self) -> *mut raw::git_blame_options {
309 &self.raw as *const _ as *mut _
310 }
311}
312
313impl<'blame> Iterator for BlameIter<'blame> {
314 type Item = BlameHunk<'blame>;
315 fn next(&mut self) -> Option<BlameHunk<'blame>> {
316 self.range.next().and_then(|i| self.blame.get_index(i))
317 }
318
319 fn size_hint(&self) -> (usize, Option<usize>) {
320 self.range.size_hint()
321 }
322}
323
324impl<'blame> DoubleEndedIterator for BlameIter<'blame> {
325 fn next_back(&mut self) -> Option<BlameHunk<'blame>> {
326 self.range.next_back().and_then(|i| self.blame.get_index(i))
327 }
328}
329
330impl<'blame> FusedIterator for BlameIter<'blame> {}
331
332impl<'blame> ExactSizeIterator for BlameIter<'blame> {}
333
334#[cfg(test)]
335mod tests {
336 use std::fs::{self, File};
337 use std::path::Path;
338
339 #[test]
340 fn smoke() {
341 let (_td, repo) = crate::test::repo_init();
342 let mut index = repo.index().unwrap();
343
344 let root = repo.workdir().unwrap();
345 fs::create_dir(&root.join("foo")).unwrap();
346 File::create(&root.join("foo/bar")).unwrap();
347 index.add_path(Path::new("foo/bar")).unwrap();
348
349 let id = index.write_tree().unwrap();
350 let tree = repo.find_tree(id).unwrap();
351 let sig = repo.signature().unwrap();
352 let id = repo.refname_to_id("HEAD").unwrap();
353 let parent = repo.find_commit(id).unwrap();
354 let commit = repo
355 .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent])
356 .unwrap();
357
358 let blame = repo.blame_file(Path::new("foo/bar"), None).unwrap();
359
360 assert_eq!(blame.len(), 1);
361 assert_eq!(blame.iter().count(), 1);
362
363 let hunk = blame.get_index(0).unwrap();
364 assert_eq!(hunk.final_commit_id(), commit);
365 assert_eq!(hunk.final_signature().name(), sig.name());
366 assert_eq!(hunk.final_signature().email(), sig.email());
367 assert_eq!(hunk.final_start_line(), 1);
368 assert_eq!(hunk.path(), Some(Path::new("foo/bar")));
369 assert_eq!(hunk.lines_in_hunk(), 0);
370 assert!(!hunk.is_boundary());
371
372 let blame_buffer = blame.blame_buffer("\n".as_bytes()).unwrap();
373 let line = blame_buffer.get_line(1).unwrap();
374
375 assert_eq!(blame_buffer.len(), 2);
376 assert_eq!(blame_buffer.iter().count(), 2);
377 assert!(line.final_commit_id().is_zero());
378 }
379}