diff --git a/crate-status.md b/crate-status.md index 406d594f110..fa247e189fa 100644 --- a/crate-status.md +++ b/crate-status.md @@ -43,6 +43,7 @@ The top-level crate that acts as hub to all functionality provided by the `gix-* * [x] support for `GIT_CEILING_DIRECTORIES` environment variable * [ ] handle other non-discovery modes and provide control over environment variable usage required in applications * [x] rev-parse + - [ ] handle relative paths as relative to working directory * [x] rev-walk * [x] include tips * [ ] exclude commits diff --git a/gitoxide-core/src/repository/revision/resolve.rs b/gitoxide-core/src/repository/revision/resolve.rs index 9b2bda61f44..b0844c81a20 100644 --- a/gitoxide-core/src/repository/revision/resolve.rs +++ b/gitoxide-core/src/repository/revision/resolve.rs @@ -5,6 +5,7 @@ pub struct Options { pub explain: bool, pub cat_file: bool, pub tree_mode: TreeMode, + pub blob_format: BlobFormat, } pub enum TreeMode { @@ -12,13 +13,24 @@ pub enum TreeMode { Pretty, } +#[derive(Copy, Clone)] +pub enum BlobFormat { + Git, + Worktree, + Diff, + DiffOrGit, +} + pub(crate) mod function { use std::ffi::OsString; - use anyhow::Context; + use anyhow::{anyhow, Context}; + use gix::diff::blob::ResourceKind; + use gix::filter::plumbing::driver::apply::Delay; use gix::revision::Spec; use super::Options; + use crate::repository::revision::resolve::BlobFormat; use crate::{ repository::{revision, revision::resolve::TreeMode}, OutputFormat, @@ -33,9 +45,26 @@ pub(crate) mod function { explain, cat_file, tree_mode, + blob_format, }: Options, ) -> anyhow::Result<()> { repo.object_cache_size_if_unset(1024 * 1024); + let mut cache = (!matches!(blob_format, BlobFormat::Git)) + .then(|| { + repo.diff_resource_cache( + match blob_format { + BlobFormat::Git => { + unreachable!("checked before") + } + BlobFormat::Worktree | BlobFormat::Diff => { + gix::diff::blob::pipeline::Mode::ToWorktreeAndBinaryToText + } + BlobFormat::DiffOrGit => gix::diff::blob::pipeline::Mode::ToGitUnlessBinaryToTextIsPresent, + }, + Default::default(), + ) + }) + .transpose()?; match format { OutputFormat::Human => { @@ -46,7 +75,7 @@ pub(crate) mod function { let spec = gix::path::os_str_into_bstr(&spec)?; let spec = repo.rev_parse(spec)?; if cat_file { - return display_object(spec, tree_mode, out); + return display_object(&repo, spec, tree_mode, cache.as_mut().map(|c| (blob_format, c)), out); } writeln!(out, "{spec}", spec = spec.detach())?; } @@ -73,16 +102,51 @@ pub(crate) mod function { Ok(()) } - fn display_object(spec: Spec<'_>, tree_mode: TreeMode, mut out: impl std::io::Write) -> anyhow::Result<()> { + fn display_object( + repo: &gix::Repository, + spec: Spec<'_>, + tree_mode: TreeMode, + cache: Option<(BlobFormat, &mut gix::diff::blob::Platform)>, + mut out: impl std::io::Write, + ) -> anyhow::Result<()> { let id = spec.single().context("rev-spec must resolve to a single object")?; - let object = id.object()?; - match object.kind { + let header = id.header()?; + match header.kind() { gix::object::Kind::Tree if matches!(tree_mode, TreeMode::Pretty) => { - for entry in object.into_tree().iter() { + for entry in id.object()?.into_tree().iter() { writeln!(out, "{}", entry?)?; } } - _ => out.write_all(&object.data)?, + gix::object::Kind::Blob if cache.is_some() && spec.path_and_mode().is_some() => { + let (path, mode) = spec.path_and_mode().expect("is present"); + let is_dir = Some(mode.is_tree()); + match cache.expect("is some") { + (BlobFormat::Git, _) => unreachable!("no need for a cache when querying object db"), + (BlobFormat::Worktree, cache) => { + let platform = cache.attr_stack.at_entry(path, is_dir, &repo.objects)?; + let object = id.object()?; + let mut converted = cache.filter.worktree_filter.convert_to_worktree( + &object.data, + path, + &mut |_path, attrs| { + let _ = platform.matching_attributes(attrs); + }, + Delay::Forbid, + )?; + std::io::copy(&mut converted, &mut out)?; + } + (BlobFormat::Diff | BlobFormat::DiffOrGit, cache) => { + cache.set_resource(id.detach(), mode.kind(), path, ResourceKind::OldOrSource, &repo.objects)?; + let resource = cache.resource(ResourceKind::OldOrSource).expect("just set"); + let data = resource + .data + .as_slice() + .ok_or_else(|| anyhow!("Binary data at {} cannot be diffed", path))?; + out.write_all(data)?; + } + } + } + _ => out.write_all(&id.object()?.data)?, } Ok(()) } diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 77b41a5dd37..6b49746b624 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -926,6 +926,7 @@ pub fn main() -> Result<()> { explain, cat_file, tree_mode, + blob_format, } => prepare_and_run( "revision-parse", trace, @@ -942,12 +943,26 @@ pub fn main() -> Result<()> { format, explain, cat_file, - tree_mode: match tree_mode.unwrap_or_default() { + tree_mode: match tree_mode { revision::resolve::TreeMode::Raw => core::repository::revision::resolve::TreeMode::Raw, revision::resolve::TreeMode::Pretty => { core::repository::revision::resolve::TreeMode::Pretty } }, + blob_format: match blob_format { + revision::resolve::BlobFormat::Git => { + core::repository::revision::resolve::BlobFormat::Git + } + revision::resolve::BlobFormat::Worktree => { + core::repository::revision::resolve::BlobFormat::Worktree + } + revision::resolve::BlobFormat::Diff => { + core::repository::revision::resolve::BlobFormat::Diff + } + revision::resolve::BlobFormat::DiffOrGit => { + core::repository::revision::resolve::BlobFormat::DiffOrGit + } + }, }, ) }, diff --git a/src/plumbing/options/mod.rs b/src/plumbing/options/mod.rs index a5a14989057..60d3c02c2b1 100644 --- a/src/plumbing/options/mod.rs +++ b/src/plumbing/options/mod.rs @@ -614,6 +614,19 @@ pub mod revision { #[default] Pretty, } + + #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] + pub enum BlobFormat { + /// The version stored in the Git Object Database. + #[default] + Git, + /// The version that would be checked out into the worktree, including filters. + Worktree, + /// The version that would be diffed (Worktree + Text-Conversion) + Diff, + /// The version that would be diffed if there is a text-conversion, or the one stored in Git otherwise. + DiffOrGit, + } } #[derive(Debug, clap::Subcommand)] #[clap(visible_alias = "rev", visible_alias = "r")] @@ -645,8 +658,12 @@ pub mod revision { /// Show the first resulting object similar to how `git cat-file` would, but don't show the resolved spec. #[clap(short = 'c', long, conflicts_with = "explain")] cat_file: bool, - #[clap(short = 't', long)] - tree_mode: Option, + /// How to display blobs. + #[clap(short = 'b', long, default_value = "git")] + blob_format: resolve::BlobFormat, + /// How to display trees as obtained with `@:dirname` or `@^{tree}`. + #[clap(short = 't', long, default_value = "pretty")] + tree_mode: resolve::TreeMode, /// rev-specs like `@`, `@~1` or `HEAD^2`. #[clap(required = true)] specs: Vec,