Skip to content

Commit ba5bc6f

Browse files
cruesslerByron
andcommitted
feat: add gix blame to the CLI
That way it's possible to see the `blame` result of any file in the repository. Co-authored-by: Sebastian Thiel <[email protected]>
1 parent bee2ec1 commit ba5bc6f

File tree

4 files changed

+70
-0
lines changed

4 files changed

+70
-0
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use std::{ffi::OsStr, path::PathBuf, str::Lines};
2+
3+
use gix::bstr::BStr;
4+
5+
pub fn blame_file(mut repo: gix::Repository, file: &OsStr, out: impl std::io::Write) -> anyhow::Result<()> {
6+
repo.object_cache_size_if_unset(repo.compute_object_cache_size_for_tree_diffs(&**repo.index_or_empty()?));
7+
8+
let suspect = repo.head()?.peel_to_commit_in_place()?;
9+
let traverse: Vec<_> = gix::traverse::commit::Simple::new(Some(suspect.id), &repo.objects).collect();
10+
let mut resource_cache = repo.diff_resource_cache_for_tree_diff()?;
11+
12+
let work_dir: PathBuf = repo.work_dir().expect("TODO").into();
13+
let file_path: &BStr = gix::path::os_str_into_bstr(file)?;
14+
15+
let blame_entries = gix::blame::blame_file(
16+
&repo.objects,
17+
traverse,
18+
&mut resource_cache,
19+
suspect.id,
20+
work_dir.clone(),
21+
file_path,
22+
)
23+
.expect("TODO");
24+
25+
let absolute_path = work_dir.join(file);
26+
let file_content = std::fs::read_to_string(absolute_path).expect("TODO");
27+
let lines = file_content.lines();
28+
29+
write_blame_entries(out, lines, blame_entries)?;
30+
31+
Ok(())
32+
}
33+
34+
fn write_blame_entries(
35+
mut out: impl std::io::Write,
36+
mut lines: Lines<'_>,
37+
blame_entries: Vec<gix::blame::BlameEntry>,
38+
) -> Result<(), std::io::Error> {
39+
for blame_entry in blame_entries {
40+
for line_number in blame_entry.range_in_blamed_file {
41+
let line = lines.next().unwrap();
42+
43+
writeln!(
44+
out,
45+
"{} {} {}",
46+
blame_entry.commit_id.to_hex_with_len(8),
47+
// `line_number` is 0-based, but we want to show 1-based line numbers (as `git`
48+
// does).
49+
line_number + 1,
50+
line
51+
)?;
52+
}
53+
}
54+
55+
Ok(())
56+
}

gitoxide-core/src/repository/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub enum PathsOrPatterns {
2121
pub mod archive;
2222
pub mod cat;
2323
pub use cat::function::cat;
24+
pub mod blame;
2425
pub mod commit;
2526
pub mod config;
2627
mod credential;

src/plumbing/main.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,6 +1483,15 @@ pub fn main() -> Result<()> {
14831483
},
14841484
),
14851485
},
1486+
Subcommands::Blame { file } => prepare_and_run(
1487+
"blame",
1488+
trace,
1489+
verbose,
1490+
progress,
1491+
progress_keep_open,
1492+
None,
1493+
move |_progress, out, _err| core::repository::blame::blame_file(repository(Mode::Lenient)?, &file, out),
1494+
),
14861495
Subcommands::Completions { shell, out_dir } => {
14871496
let mut app = Args::command();
14881497

src/plumbing/options/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ pub enum Subcommands {
150150
/// Subcommands that need no git repository to run.
151151
#[clap(subcommand)]
152152
Free(free::Subcommands),
153+
/// Blame lines in a file
154+
Blame {
155+
file: std::ffi::OsString,
156+
},
153157
/// Generate shell completions to stdout or a directory.
154158
#[clap(visible_alias = "generate-completions", visible_alias = "shell-completions")]
155159
Completions {

0 commit comments

Comments
 (0)