Skip to content

Commit bd47fb5

Browse files
authored
Merge pull request #2153 from cruessler/add-blame-file-on-repository
Add `Repository::blame_file()`
2 parents 752d6dc + 3c54e5b commit bd47fb5

File tree

9 files changed

+145
-8
lines changed

9 files changed

+145
-8
lines changed

gix-blame/src/file/function.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::{types::BlamePathEntry, BlameEntry, Error, Options, Outcome, Statisti
1515
/// Produce a list of consecutive [`BlameEntry`] instances to indicate in which commits the ranges of the file
1616
/// at `suspect:<file_path>` originated in.
1717
///
18-
/// ## Paramters
18+
/// ## Parameters
1919
///
2020
/// * `odb`
2121
/// - Access to database objects, also for used for diffing.
@@ -55,13 +55,13 @@ use crate::{types::BlamePathEntry, BlameEntry, Error, Options, Outcome, Statisti
5555
///
5656
/// The algorithm in `libgit2` works by going through parents and keeping a linked list of blame
5757
/// suspects. It can be visualized as follows:
58-
//
59-
// <---------------------------------------->
60-
// <---------------><----------------------->
61-
// <---><----------><----------------------->
62-
// <---><----------><-------><-----><------->
63-
// <---><---><-----><-------><-----><------->
64-
// <---><---><-----><-------><-----><-><-><->
58+
///
59+
/// <---------------------------------------->
60+
/// <---------------><----------------------->
61+
/// <---><----------><----------------------->
62+
/// <---><----------><-------><-----><------->
63+
/// <---><---><-----><-------><-----><------->
64+
/// <---><---><-----><-------><-----><-><-><->
6565
pub fn file(
6666
odb: impl gix_object::Find + gix_object::FindHeader,
6767
suspect: ObjectId,

gix/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ extras = [
6767
"interrupt",
6868
"status",
6969
"dirwalk",
70+
"blame"
7071
]
7172

7273
## A collection of features that need a larger MSRV, and thus are disabled by default.

gix/src/repository/blame.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use gix_hash::ObjectId;
2+
use gix_ref::bstr::BStr;
3+
4+
use crate::{repository::blame_file, Repository};
5+
6+
impl Repository {
7+
/// Produce a list of consecutive [`gix_blame::BlameEntry`] instances. Each `BlameEntry`
8+
/// corresponds to a hunk of consecutive lines of the file at `suspect:<file_path>` that got
9+
/// introduced by a specific commit.
10+
///
11+
/// For details, see the documentation of [`gix_blame::file()`].
12+
pub fn blame_file(
13+
&self,
14+
file_path: &BStr,
15+
suspect: impl Into<ObjectId>,
16+
options: gix_blame::Options,
17+
) -> Result<gix_blame::Outcome, blame_file::Error> {
18+
let cache: Option<gix_commitgraph::Graph> = self.commit_graph_if_enabled()?;
19+
let mut resource_cache = self.diff_resource_cache_for_tree_diff()?;
20+
21+
let outcome = gix_blame::file(
22+
&self.objects,
23+
suspect.into(),
24+
cache,
25+
&mut resource_cache,
26+
file_path,
27+
options,
28+
)?;
29+
30+
Ok(outcome)
31+
}
32+
}

gix/src/repository/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ pub enum Kind {
1919

2020
#[cfg(any(feature = "attributes", feature = "excludes"))]
2121
pub mod attributes;
22+
#[cfg(feature = "blame")]
23+
mod blame;
2224
mod cache;
2325
#[cfg(feature = "worktree-mutation")]
2426
mod checkout;
@@ -61,6 +63,22 @@ mod submodule;
6163
mod thread_safe;
6264
mod worktree;
6365

66+
///
67+
#[cfg(feature = "blame")]
68+
pub mod blame_file {
69+
/// The error returned by [Repository::blame_file()](crate::Repository::blame_file()).
70+
#[derive(Debug, thiserror::Error)]
71+
#[allow(missing_docs)]
72+
pub enum Error {
73+
#[error(transparent)]
74+
CommitGraphIfEnabled(#[from] super::commit_graph_if_enabled::Error),
75+
#[error(transparent)]
76+
DiffResourceCache(#[from] super::diff_resource_cache::Error),
77+
#[error(transparent)]
78+
Blame(#[from] gix_blame::Error),
79+
}
80+
}
81+
6482
///
6583
#[cfg(feature = "blob-diff")]
6684
pub mod diff_tree_to_tree {
74.5 KB
Binary file not shown.

gix/tests/fixtures/make_blame_repo.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env bash
2+
set -eu -o pipefail
3+
4+
git init -q
5+
6+
echo "line 1" >> simple.txt
7+
git add simple.txt
8+
git commit -q -m c1
9+
10+
echo "line 2" >> simple.txt
11+
git add simple.txt
12+
git commit -q -m c2
13+
14+
echo "line 3" >> simple.txt
15+
git add simple.txt
16+
git commit -q -m c3
17+
18+
echo "line 4" >> simple.txt
19+
git add simple.txt
20+
git commit -q -m c4

gix/tests/gix/repository/blame.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use gix::bstr::BString;
2+
use std::num::NonZero;
3+
4+
#[test]
5+
fn simple() -> crate::Result {
6+
let repo = crate::named_repo("make_blame_repo.sh")?;
7+
8+
let suspect = repo.head_id()?;
9+
let outcome = repo.blame_file("simple.txt".into(), suspect, Default::default())?;
10+
11+
assert_eq!(outcome.entries.len(), 4);
12+
13+
Ok(())
14+
}
15+
16+
#[test]
17+
fn with_options() -> crate::Result {
18+
let repo = crate::named_repo("make_blame_repo.sh")?;
19+
20+
let options = gix::blame::Options {
21+
range: gix::blame::BlameRanges::from_range(1..=2),
22+
..Default::default()
23+
};
24+
25+
let suspect = repo.head_id()?;
26+
let outcome = repo.blame_file("simple.txt".into(), suspect, options)?;
27+
28+
assert_eq!(outcome.entries.len(), 2);
29+
30+
let entries_with_lines: Vec<_> = outcome.entries_with_lines().collect();
31+
32+
assert!(matches!(
33+
entries_with_lines.as_slice(),
34+
&[
35+
(
36+
gix::blame::BlameEntry {
37+
start_in_blamed_file: 0,
38+
start_in_source_file: 0,
39+
source_file_name: None,
40+
..
41+
},
42+
_,
43+
),
44+
(
45+
gix::blame::BlameEntry {
46+
start_in_blamed_file: 1,
47+
start_in_source_file: 1,
48+
source_file_name: None,
49+
..
50+
},
51+
_,
52+
)
53+
]
54+
));
55+
56+
assert_eq!(entries_with_lines[0].0.len, NonZero::new(1).unwrap());
57+
assert_eq!(entries_with_lines[1].0.len, NonZero::new(1).unwrap());
58+
59+
assert_eq!(entries_with_lines[0].1, vec![BString::new("line 1\n".into())]);
60+
assert_eq!(entries_with_lines[1].1, vec![BString::new("line 2\n".into())]);
61+
62+
Ok(())
63+
}

gix/tests/gix/repository/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use gix::Repository;
22

3+
#[cfg(feature = "blame")]
4+
mod blame;
35
mod config;
46
#[cfg(feature = "excludes")]
57
mod excludes;

justfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ check:
133133
cargo check -p gix --no-default-features --features credentials --tests
134134
cargo check -p gix --no-default-features --features index --tests
135135
cargo check -p gix --no-default-features --features interrupt --tests
136+
cargo check -p gix --no-default-features --features blame --tests
136137
cargo check -p gix --no-default-features
137138
cargo check -p gix-odb --features serde
138139
cargo check --no-default-features --features max-control

0 commit comments

Comments
 (0)