Skip to content

Commit

Permalink
Merge pull request GitoxideLabs#1453 from cruessler/gix-blame
Browse files Browse the repository at this point in the history
Explore gix APIs, experiment with gix-blame API
  • Loading branch information
Byron authored Dec 25, 2024
2 parents 7659a65 + e951e7d commit 6ed9976
Show file tree
Hide file tree
Showing 19 changed files with 3,178 additions and 28 deletions.
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ is usable to some extent.
* [gix-shallow](https://github.com/GitoxideLabs/gitoxide/blob/main/crate-status.md#gix-shallow)
* `gitoxide-core`
* **very early** _(possibly without any documentation and many rough edges)_
* [gix-blame](https://github.com/GitoxideLabs/gitoxide/blob/main/crate-status.md#gix-blame)
* **idea** _(just a name placeholder)_
* [gix-note](https://github.com/GitoxideLabs/gitoxide/blob/main/crate-status.md#gix-note)
* [gix-fetchhead](https://github.com/GitoxideLabs/gitoxide/blob/main/crate-status.md#gix-fetchhead)
Expand Down
21 changes: 20 additions & 1 deletion crate-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ The top-level crate that acts as hub to all functionality provided by the `gix-*
* [x] safe with cycles and recursive configurations
* [x] multi-line with comments and quotes
* **promisor**
* It's vague, but these seems to be like index files allowing to fetch objects from a server on demand.
* It's vague, but these seem to be like index files allowing to fetch objects from a server on demand.
* [x] API documentation
* [ ] Some examples

Expand Down Expand Up @@ -361,6 +361,25 @@ Check out the [performance discussion][gix-diff-performance] as well.
* [x] API documentation
* [ ] Examples

### gix-blame

* [x] commit-annotations for a single file
- [ ] progress
- [ ] interruptibility
- [ ] streaming
- [ ] support for worktree changes (creates virtual commit on top of `HEAD`)
- [ ] shallow-history support
- [ ] rename tracking (track different paths through history)
- [ ] commits to ignore
- [ ] pass all blame-cornercases (from Git)
* **Performance-Improvements**
* Without the following the performance isn't competitive with Git.
1. Implement custom graph walk which won't run down parents that don't have the path in question.
2. Implement access of trees from commit-graph and fill that information into the traversal info by default.
3. commit-graph with bloom filter, used to quickly check if a commit has a path.
* [x] API documentation
* [ ] Examples

### gix-traverse

Check out the [performance discussion][gix-traverse-performance] as well.
Expand Down
2 changes: 1 addition & 1 deletion gitoxide-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ serde = ["gix/serde", "dep:serde_json", "dep:serde", "bytesize/serde"]

[dependencies]
# deselect everything else (like "performance") as this should be controllable by the parent application.
gix = { version = "^0.69.1", path = "../gix", default-features = false, features = ["merge", "blob-diff", "revision", "mailmap", "excludes", "attributes", "worktree-mutation", "credentials", "interrupt", "status", "dirwalk"] }
gix = { version = "^0.69.1", path = "../gix", default-features = false, features = ["merge", "blob-diff", "blame", "revision", "mailmap", "excludes", "attributes", "worktree-mutation", "credentials", "interrupt", "status", "dirwalk"] }
gix-pack-for-configuration-only = { package = "gix-pack", version = "^0.56.0", path = "../gix-pack", default-features = false, features = ["pack-cache-lru-dynamic", "pack-cache-lru-static", "generate", "streaming-input"] }
gix-transport-configuration-only = { package = "gix-transport", version = "^0.44.0", path = "../gix-transport", default-features = false }
gix-archive-for-configuration-only = { package = "gix-archive", version = "^0.18.0", path = "../gix-archive", optional = true, features = ["tar", "tar_gz"] }
Expand Down
71 changes: 71 additions & 0 deletions gitoxide-core/src/repository/blame.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use gix::bstr::ByteSlice;
use gix::config::tree;
use std::ffi::OsStr;

pub fn blame_file(
mut repo: gix::Repository,
file: &OsStr,
out: impl std::io::Write,
err: Option<&mut dyn std::io::Write>,
) -> anyhow::Result<()> {
{
let mut config = repo.config_snapshot_mut();
if config.string(&tree::Core::DELTA_BASE_CACHE_LIMIT).is_none() {
config.set_value(&tree::Core::DELTA_BASE_CACHE_LIMIT, "100m")?;
}
}
let index = repo.index_or_empty()?;
repo.object_cache_size_if_unset(repo.compute_object_cache_size_for_tree_diffs(&index));

let file = gix::path::os_str_into_bstr(file)?;
let specs = repo.pathspec(
false,
[file],
true,
&index,
gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping.adjust_for_bare(repo.is_bare()),
)?;
// TODO: there should be a way to normalize paths without going through patterns, at least in this case maybe?
// `Search` actually sorts patterns by excluding or not, all that can lead to strange results.
let file = specs
.search()
.patterns()
.map(|p| p.path().to_owned())
.next()
.expect("exactly one pattern");

let suspect = repo.head()?.peel_to_commit_in_place()?;
let traverse =
gix::traverse::commit::topo::Builder::from_iters(&repo.objects, [suspect.id], None::<Vec<gix::ObjectId>>)
.with_commit_graph(repo.commit_graph_if_enabled()?)
.build()?;
let mut resource_cache = repo.diff_resource_cache_for_tree_diff()?;
let outcome = gix::blame::file(&repo.objects, traverse, &mut resource_cache, file.as_bstr())?;
let statistics = outcome.statistics;
write_blame_entries(out, outcome)?;

if let Some(err) = err {
writeln!(err, "{statistics:#?}")?;
}
Ok(())
}

fn write_blame_entries(mut out: impl std::io::Write, outcome: gix::blame::Outcome) -> Result<(), std::io::Error> {
for (entry, lines_in_hunk) in outcome.entries_with_lines() {
for ((actual_lno, source_lno), line) in entry
.range_in_blamed_file()
.zip(entry.range_in_source_file())
.zip(lines_in_hunk)
{
write!(
out,
"{short_id} {line_no} {src_line_no} {line}",
line_no = actual_lno + 1,
src_line_no = source_lno + 1,
short_id = entry.commit_id.to_hex_with_len(8),
)?;
}
}

Ok(())
}
1 change: 1 addition & 0 deletions gitoxide-core/src/repository/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub enum PathsOrPatterns {
pub mod archive;
pub mod cat;
pub use cat::function::cat;
pub mod blame;
pub mod commit;
pub mod config;
mod credential;
Expand Down
15 changes: 14 additions & 1 deletion gix-blame/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ name = "gix-blame"
version = "0.0.0"
repository = "https://github.com/GitoxideLabs/gitoxide"
license = "MIT OR Apache-2.0"
description = "A crate of the gitoxide project dedicated implementing a 'blame' algorithm"
description = "A crate of the gitoxide project dedicated to implementing a 'blame' algorithm"
authors = ["Christoph Rüßler <[email protected]>", "Sebastian Thiel <[email protected]>"]
edition = "2021"
rust-version = "1.65"
Expand All @@ -14,6 +14,19 @@ rust-version = "1.65"
doctest = false

[dependencies]
gix-trace = { version = "^0.1.11", path = "../gix-trace" }
gix-diff = { version = "^0.49.0", path = "../gix-diff", default-features = false, features = ["blob"] }
gix-object = { version = "^0.46.0", path = "../gix-object" }
gix-hash = { version = "^0.15.0", path = "../gix-hash" }
gix-worktree = { version = "^0.38.0", path = "../gix-worktree", default-features = false, features = ["attributes"] }
gix-traverse = { version = "^0.43.0", path = "../gix-traverse" }

thiserror = "2.0.0"

[dev-dependencies]
gix-ref = { version = "^0.49.0", path = "../gix-ref" }
gix-filter = { version = "^0.16.0", path = "../gix-filter" }
gix-fs = { version = "^0.12.0", path = "../gix-fs" }
gix-index = { version = "^0.37.0", path = "../gix-index" }
gix-odb = { version = "^0.66.0", path = "../gix-odb" }
gix-testtools = { path = "../tests/tools" }
30 changes: 30 additions & 0 deletions gix-blame/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use gix_object::bstr::BString;

/// The error returned by [file()](crate::file()).
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error("No commit was given")]
EmptyTraversal,
#[error(transparent)]
BlobDiffSetResource(#[from] gix_diff::blob::platform::set_resource::Error),
#[error(transparent)]
BlobDiffPrepare(#[from] gix_diff::blob::platform::prepare_diff::Error),
#[error("The file to blame at '{file_path}' wasn't found in the first commit at {commit_id}")]
FileMissing {
/// The file-path to the object to blame.
file_path: BString,
/// The commit whose tree didn't contain `file_path`.
commit_id: gix_hash::ObjectId,
},
#[error("Couldn't find commit or tree in the object database")]
FindObject(#[from] gix_object::find::Error),
#[error("Could not find existing blob or commit")]
FindExistingObject(#[from] gix_object::find::existing_object::Error),
#[error("Could not find existing iterator over a tree")]
FindExistingIter(#[from] gix_object::find::existing_iter::Error),
#[error("Failed to obtain the next commit in the commit-graph traversal")]
Traverse(#[source] Box<dyn std::error::Error + Send + Sync>),
#[error(transparent)]
DiffTree(#[from] gix_diff::tree::Error),
}
Loading

0 comments on commit 6ed9976

Please sign in to comment.