summaryrefslogtreecommitdiffstats
path: root/vendor/git2/src/blame.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
commit10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch)
treebdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/git2/src/blame.rs
parentReleasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff)
downloadrustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz
rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/git2/src/blame.rs')
-rw-r--r--vendor/git2/src/blame.rs350
1 files changed, 350 insertions, 0 deletions
diff --git a/vendor/git2/src/blame.rs b/vendor/git2/src/blame.rs
new file mode 100644
index 000000000..496efa923
--- /dev/null
+++ b/vendor/git2/src/blame.rs
@@ -0,0 +1,350 @@
+use crate::util::{self, Binding};
+use crate::{raw, signature, Oid, Repository, Signature};
+use std::marker;
+use std::mem;
+use std::ops::Range;
+use std::path::Path;
+
+/// Opaque structure to hold blame results.
+pub struct Blame<'repo> {
+ raw: *mut raw::git_blame,
+ _marker: marker::PhantomData<&'repo Repository>,
+}
+
+/// Structure that represents a blame hunk.
+pub struct BlameHunk<'blame> {
+ raw: *mut raw::git_blame_hunk,
+ _marker: marker::PhantomData<&'blame raw::git_blame>,
+}
+
+/// Blame options
+pub struct BlameOptions {
+ raw: raw::git_blame_options,
+}
+
+/// An iterator over the hunks in a blame.
+pub struct BlameIter<'blame> {
+ range: Range<usize>,
+ blame: &'blame Blame<'blame>,
+}
+
+impl<'repo> Blame<'repo> {
+ /// Gets the number of hunks that exist in the blame structure.
+ pub fn len(&self) -> usize {
+ unsafe { raw::git_blame_get_hunk_count(self.raw) as usize }
+ }
+
+ /// Return `true` is there is no hunk in the blame structure.
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ /// Gets the blame hunk at the given index.
+ pub fn get_index(&self, index: usize) -> Option<BlameHunk<'_>> {
+ unsafe {
+ let ptr = raw::git_blame_get_hunk_byindex(self.raw(), index as u32);
+ if ptr.is_null() {
+ None
+ } else {
+ Some(BlameHunk::from_raw_const(ptr))
+ }
+ }
+ }
+
+ /// Gets the hunk that relates to the given line number in the newest
+ /// commit.
+ pub fn get_line(&self, lineno: usize) -> Option<BlameHunk<'_>> {
+ unsafe {
+ let ptr = raw::git_blame_get_hunk_byline(self.raw(), lineno);
+ if ptr.is_null() {
+ None
+ } else {
+ Some(BlameHunk::from_raw_const(ptr))
+ }
+ }
+ }
+
+ /// Returns an iterator over the hunks in this blame.
+ pub fn iter(&self) -> BlameIter<'_> {
+ BlameIter {
+ range: 0..self.len(),
+ blame: self,
+ }
+ }
+}
+
+impl<'blame> BlameHunk<'blame> {
+ unsafe fn from_raw_const(raw: *const raw::git_blame_hunk) -> BlameHunk<'blame> {
+ BlameHunk {
+ raw: raw as *mut raw::git_blame_hunk,
+ _marker: marker::PhantomData,
+ }
+ }
+
+ /// Returns OID of the commit where this line was last changed
+ pub fn final_commit_id(&self) -> Oid {
+ unsafe { Oid::from_raw(&(*self.raw).final_commit_id) }
+ }
+
+ /// Returns signature of the commit.
+ pub fn final_signature(&self) -> Signature<'_> {
+ unsafe { signature::from_raw_const(self, (*self.raw).final_signature) }
+ }
+
+ /// Returns line number where this hunk begins.
+ ///
+ /// Note that the start line is counting from 1.
+ pub fn final_start_line(&self) -> usize {
+ unsafe { (*self.raw).final_start_line_number }
+ }
+
+ /// Returns the OID of the commit where this hunk was found.
+ ///
+ /// This will usually be the same as `final_commit_id`,
+ /// except when `BlameOptions::track_copies_any_commit_copies` has been
+ /// turned on
+ pub fn orig_commit_id(&self) -> Oid {
+ unsafe { Oid::from_raw(&(*self.raw).orig_commit_id) }
+ }
+
+ /// Returns signature of the commit.
+ pub fn orig_signature(&self) -> Signature<'_> {
+ unsafe { signature::from_raw_const(self, (*self.raw).orig_signature) }
+ }
+
+ /// Returns line number where this hunk begins.
+ ///
+ /// Note that the start line is counting from 1.
+ pub fn orig_start_line(&self) -> usize {
+ unsafe { (*self.raw).orig_start_line_number }
+ }
+
+ /// Returns path to the file where this hunk originated.
+ ///
+ /// Note: `None` could be returned for non-unicode paths on Windows.
+ pub fn path(&self) -> Option<&Path> {
+ unsafe {
+ if let Some(bytes) = crate::opt_bytes(self, (*self.raw).orig_path) {
+ Some(util::bytes2path(bytes))
+ } else {
+ None
+ }
+ }
+ }
+
+ /// Tests whether this hunk has been tracked to a boundary commit
+ /// (the root, or the commit specified in git_blame_options.oldest_commit).
+ pub fn is_boundary(&self) -> bool {
+ unsafe { (*self.raw).boundary == 1 }
+ }
+
+ /// Returns number of lines in this hunk.
+ pub fn lines_in_hunk(&self) -> usize {
+ unsafe { (*self.raw).lines_in_hunk as usize }
+ }
+}
+
+impl Default for BlameOptions {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl BlameOptions {
+ /// Initialize options
+ pub fn new() -> BlameOptions {
+ unsafe {
+ let mut raw: raw::git_blame_options = mem::zeroed();
+ assert_eq!(
+ raw::git_blame_init_options(&mut raw, raw::GIT_BLAME_OPTIONS_VERSION),
+ 0
+ );
+
+ Binding::from_raw(&raw as *const _ as *mut _)
+ }
+ }
+
+ fn flag(&mut self, opt: u32, val: bool) -> &mut BlameOptions {
+ if val {
+ self.raw.flags |= opt;
+ } else {
+ self.raw.flags &= !opt;
+ }
+ self
+ }
+
+ /// Track lines that have moved within a file.
+ pub fn track_copies_same_file(&mut self, opt: bool) -> &mut BlameOptions {
+ self.flag(raw::GIT_BLAME_TRACK_COPIES_SAME_FILE, opt)
+ }
+
+ /// Track lines that have moved across files in the same commit.
+ pub fn track_copies_same_commit_moves(&mut self, opt: bool) -> &mut BlameOptions {
+ self.flag(raw::GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES, opt)
+ }
+
+ /// Track lines that have been copied from another file that exists
+ /// in the same commit.
+ pub fn track_copies_same_commit_copies(&mut self, opt: bool) -> &mut BlameOptions {
+ self.flag(raw::GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES, opt)
+ }
+
+ /// Track lines that have been copied from another file that exists
+ /// in any commit.
+ pub fn track_copies_any_commit_copies(&mut self, opt: bool) -> &mut BlameOptions {
+ self.flag(raw::GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES, opt)
+ }
+
+ /// Restrict the search of commits to those reachable following only
+ /// the first parents.
+ pub fn first_parent(&mut self, opt: bool) -> &mut BlameOptions {
+ self.flag(raw::GIT_BLAME_FIRST_PARENT, opt)
+ }
+
+ /// Use mailmap file to map author and committer names and email addresses
+ /// to canonical real names and email addresses. The mailmap will be read
+ /// from the working directory, or HEAD in a bare repository.
+ pub fn use_mailmap(&mut self, opt: bool) -> &mut BlameOptions {
+ self.flag(raw::GIT_BLAME_USE_MAILMAP, opt)
+ }
+
+ /// Ignore whitespace differences.
+ pub fn ignore_whitespace(&mut self, opt: bool) -> &mut BlameOptions {
+ self.flag(raw::GIT_BLAME_IGNORE_WHITESPACE, opt)
+ }
+
+ /// Setter for the id of the newest commit to consider.
+ pub fn newest_commit(&mut self, id: Oid) -> &mut BlameOptions {
+ unsafe {
+ self.raw.newest_commit = *id.raw();
+ }
+ self
+ }
+
+ /// Setter for the id of the oldest commit to consider.
+ pub fn oldest_commit(&mut self, id: Oid) -> &mut BlameOptions {
+ unsafe {
+ self.raw.oldest_commit = *id.raw();
+ }
+ self
+ }
+
+ /// The first line in the file to blame.
+ pub fn min_line(&mut self, lineno: usize) -> &mut BlameOptions {
+ self.raw.min_line = lineno;
+ self
+ }
+
+ /// The last line in the file to blame.
+ pub fn max_line(&mut self, lineno: usize) -> &mut BlameOptions {
+ self.raw.max_line = lineno;
+ self
+ }
+}
+
+impl<'repo> Binding for Blame<'repo> {
+ type Raw = *mut raw::git_blame;
+
+ unsafe fn from_raw(raw: *mut raw::git_blame) -> Blame<'repo> {
+ Blame {
+ raw,
+ _marker: marker::PhantomData,
+ }
+ }
+
+ fn raw(&self) -> *mut raw::git_blame {
+ self.raw
+ }
+}
+
+impl<'repo> Drop for Blame<'repo> {
+ fn drop(&mut self) {
+ unsafe { raw::git_blame_free(self.raw) }
+ }
+}
+
+impl<'blame> Binding for BlameHunk<'blame> {
+ type Raw = *mut raw::git_blame_hunk;
+
+ unsafe fn from_raw(raw: *mut raw::git_blame_hunk) -> BlameHunk<'blame> {
+ BlameHunk {
+ raw,
+ _marker: marker::PhantomData,
+ }
+ }
+
+ fn raw(&self) -> *mut raw::git_blame_hunk {
+ self.raw
+ }
+}
+
+impl Binding for BlameOptions {
+ type Raw = *mut raw::git_blame_options;
+
+ unsafe fn from_raw(opts: *mut raw::git_blame_options) -> BlameOptions {
+ BlameOptions { raw: *opts }
+ }
+
+ fn raw(&self) -> *mut raw::git_blame_options {
+ &self.raw as *const _ as *mut _
+ }
+}
+
+impl<'blame> Iterator for BlameIter<'blame> {
+ type Item = BlameHunk<'blame>;
+ fn next(&mut self) -> Option<BlameHunk<'blame>> {
+ self.range.next().and_then(|i| self.blame.get_index(i))
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.range.size_hint()
+ }
+}
+
+impl<'blame> DoubleEndedIterator for BlameIter<'blame> {
+ fn next_back(&mut self) -> Option<BlameHunk<'blame>> {
+ self.range.next_back().and_then(|i| self.blame.get_index(i))
+ }
+}
+
+impl<'blame> ExactSizeIterator for BlameIter<'blame> {}
+
+#[cfg(test)]
+mod tests {
+ use std::fs::{self, File};
+ use std::path::Path;
+
+ #[test]
+ fn smoke() {
+ let (_td, repo) = crate::test::repo_init();
+ let mut index = repo.index().unwrap();
+
+ let root = repo.workdir().unwrap();
+ fs::create_dir(&root.join("foo")).unwrap();
+ File::create(&root.join("foo/bar")).unwrap();
+ index.add_path(Path::new("foo/bar")).unwrap();
+
+ let id = index.write_tree().unwrap();
+ let tree = repo.find_tree(id).unwrap();
+ let sig = repo.signature().unwrap();
+ let id = repo.refname_to_id("HEAD").unwrap();
+ let parent = repo.find_commit(id).unwrap();
+ let commit = repo
+ .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent])
+ .unwrap();
+
+ let blame = repo.blame_file(Path::new("foo/bar"), None).unwrap();
+
+ assert_eq!(blame.len(), 1);
+ assert_eq!(blame.iter().count(), 1);
+
+ let hunk = blame.get_index(0).unwrap();
+ assert_eq!(hunk.final_commit_id(), commit);
+ assert_eq!(hunk.final_signature().name(), sig.name());
+ assert_eq!(hunk.final_signature().email(), sig.email());
+ assert_eq!(hunk.final_start_line(), 1);
+ assert_eq!(hunk.path(), Some(Path::new("foo/bar")));
+ assert_eq!(hunk.lines_in_hunk(), 0);
+ assert!(!hunk.is_boundary())
+ }
+}