|
| 1 | +use gix::bstr::ByteSlice; |
| 2 | +use gix::config::tree; |
| 3 | +use std::ffi::OsStr; |
| 4 | + |
| 5 | +pub fn blame_file( |
| 6 | + mut repo: gix::Repository, |
| 7 | + file: &OsStr, |
| 8 | + out: impl std::io::Write, |
| 9 | + err: Option<&mut dyn std::io::Write>, |
| 10 | +) -> anyhow::Result<()> { |
| 11 | + { |
| 12 | + let mut config = repo.config_snapshot_mut(); |
| 13 | + if config.string(&tree::Core::DELTA_BASE_CACHE_LIMIT).is_none() { |
| 14 | + config.set_value(&tree::Core::DELTA_BASE_CACHE_LIMIT, "100m")?; |
| 15 | + } |
| 16 | + } |
| 17 | + let index = repo.index_or_empty()?; |
| 18 | + repo.object_cache_size_if_unset(repo.compute_object_cache_size_for_tree_diffs(&index)); |
| 19 | + |
| 20 | + let file = gix::path::os_str_into_bstr(file)?; |
| 21 | + let specs = repo.pathspec( |
| 22 | + false, |
| 23 | + [file], |
| 24 | + true, |
| 25 | + &index, |
| 26 | + gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping.adjust_for_bare(repo.is_bare()), |
| 27 | + )?; |
| 28 | + // TODO: there should be a way to normalize paths without going through patterns, at least in this case maybe? |
| 29 | + // `Search` actually sorts patterns by excluding or not, all that can lead to strange results. |
| 30 | + let file = specs |
| 31 | + .search() |
| 32 | + .patterns() |
| 33 | + .map(|p| p.path().to_owned()) |
| 34 | + .next() |
| 35 | + .expect("exactly one pattern"); |
| 36 | + |
| 37 | + let suspect = repo.head()?.peel_to_commit_in_place()?; |
| 38 | + let traverse = |
| 39 | + gix::traverse::commit::topo::Builder::from_iters(&repo.objects, [suspect.id], None::<Vec<gix::ObjectId>>) |
| 40 | + .with_commit_graph(repo.commit_graph_if_enabled()?) |
| 41 | + .build()?; |
| 42 | + let mut resource_cache = repo.diff_resource_cache_for_tree_diff()?; |
| 43 | + let outcome = gix::blame::file(&repo.objects, traverse, &mut resource_cache, file.as_bstr())?; |
| 44 | + let statistics = outcome.statistics; |
| 45 | + write_blame_entries(out, outcome)?; |
| 46 | + |
| 47 | + if let Some(err) = err { |
| 48 | + writeln!(err, "{statistics:#?}")?; |
| 49 | + } |
| 50 | + Ok(()) |
| 51 | +} |
| 52 | + |
| 53 | +fn write_blame_entries(mut out: impl std::io::Write, outcome: gix::blame::Outcome) -> Result<(), std::io::Error> { |
| 54 | + for (entry, lines_in_hunk) in outcome.entries_with_lines() { |
| 55 | + for ((actual_lno, source_lno), line) in entry |
| 56 | + .range_in_blamed_file() |
| 57 | + .zip(entry.range_in_source_file()) |
| 58 | + .zip(lines_in_hunk) |
| 59 | + { |
| 60 | + write!( |
| 61 | + out, |
| 62 | + "{short_id} {line_no} {src_line_no} {line}", |
| 63 | + line_no = actual_lno + 1, |
| 64 | + src_line_no = source_lno + 1, |
| 65 | + short_id = entry.commit_id.to_hex_with_len(8), |
| 66 | + )?; |
| 67 | + } |
| 68 | + } |
| 69 | + |
| 70 | + Ok(()) |
| 71 | +} |
0 commit comments