Skip to content

Commit

Permalink
feat: gix rev parse --format to provide different versions of the s…
Browse files Browse the repository at this point in the history
…ame content.

This only applies to blobs, but allows to obtain different versions of the same blob
like:

* what's stored in Git
* what would be checked out to the worktree
* what would be diffed
  • Loading branch information
Byron committed Dec 5, 2023
1 parent 77686db commit cf51a4d
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 10 deletions.
1 change: 1 addition & 0 deletions crate-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
78 changes: 71 additions & 7 deletions gitoxide-core/src/repository/revision/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,32 @@ pub struct Options {
pub explain: bool,
pub cat_file: bool,
pub tree_mode: TreeMode,
pub blob_format: BlobFormat,
}

pub enum TreeMode {
Raw,
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,
Expand All @@ -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 => {
Expand All @@ -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())?;
}
Expand All @@ -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(())
}
Expand Down
17 changes: 16 additions & 1 deletion src/plumbing/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,7 @@ pub fn main() -> Result<()> {
explain,
cat_file,
tree_mode,
blob_format,
} => prepare_and_run(
"revision-parse",
trace,
Expand All @@ -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
}
},
},
)
},
Expand Down
21 changes: 19 additions & 2 deletions src/plumbing/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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<resolve::TreeMode>,
/// 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<std::ffi::OsString>,
Expand Down

0 comments on commit cf51a4d

Please sign in to comment.