Skip to content

Commit cf51a4d

Browse files
committed
feat: gix rev parse --format to provide different versions of the same 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
1 parent 77686db commit cf51a4d

File tree

4 files changed

+107
-10
lines changed

4 files changed

+107
-10
lines changed

Diff for: crate-status.md

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ The top-level crate that acts as hub to all functionality provided by the `gix-*
4343
* [x] support for `GIT_CEILING_DIRECTORIES` environment variable
4444
* [ ] handle other non-discovery modes and provide control over environment variable usage required in applications
4545
* [x] rev-parse
46+
- [ ] handle relative paths as relative to working directory
4647
* [x] rev-walk
4748
* [x] include tips
4849
* [ ] exclude commits

Diff for: gitoxide-core/src/repository/revision/resolve.rs

+71-7
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,32 @@ pub struct Options {
55
pub explain: bool,
66
pub cat_file: bool,
77
pub tree_mode: TreeMode,
8+
pub blob_format: BlobFormat,
89
}
910

1011
pub enum TreeMode {
1112
Raw,
1213
Pretty,
1314
}
1415

16+
#[derive(Copy, Clone)]
17+
pub enum BlobFormat {
18+
Git,
19+
Worktree,
20+
Diff,
21+
DiffOrGit,
22+
}
23+
1524
pub(crate) mod function {
1625
use std::ffi::OsString;
1726

18-
use anyhow::Context;
27+
use anyhow::{anyhow, Context};
28+
use gix::diff::blob::ResourceKind;
29+
use gix::filter::plumbing::driver::apply::Delay;
1930
use gix::revision::Spec;
2031

2132
use super::Options;
33+
use crate::repository::revision::resolve::BlobFormat;
2234
use crate::{
2335
repository::{revision, revision::resolve::TreeMode},
2436
OutputFormat,
@@ -33,9 +45,26 @@ pub(crate) mod function {
3345
explain,
3446
cat_file,
3547
tree_mode,
48+
blob_format,
3649
}: Options,
3750
) -> anyhow::Result<()> {
3851
repo.object_cache_size_if_unset(1024 * 1024);
52+
let mut cache = (!matches!(blob_format, BlobFormat::Git))
53+
.then(|| {
54+
repo.diff_resource_cache(
55+
match blob_format {
56+
BlobFormat::Git => {
57+
unreachable!("checked before")
58+
}
59+
BlobFormat::Worktree | BlobFormat::Diff => {
60+
gix::diff::blob::pipeline::Mode::ToWorktreeAndBinaryToText
61+
}
62+
BlobFormat::DiffOrGit => gix::diff::blob::pipeline::Mode::ToGitUnlessBinaryToTextIsPresent,
63+
},
64+
Default::default(),
65+
)
66+
})
67+
.transpose()?;
3968

4069
match format {
4170
OutputFormat::Human => {
@@ -46,7 +75,7 @@ pub(crate) mod function {
4675
let spec = gix::path::os_str_into_bstr(&spec)?;
4776
let spec = repo.rev_parse(spec)?;
4877
if cat_file {
49-
return display_object(spec, tree_mode, out);
78+
return display_object(&repo, spec, tree_mode, cache.as_mut().map(|c| (blob_format, c)), out);
5079
}
5180
writeln!(out, "{spec}", spec = spec.detach())?;
5281
}
@@ -73,16 +102,51 @@ pub(crate) mod function {
73102
Ok(())
74103
}
75104

76-
fn display_object(spec: Spec<'_>, tree_mode: TreeMode, mut out: impl std::io::Write) -> anyhow::Result<()> {
105+
fn display_object(
106+
repo: &gix::Repository,
107+
spec: Spec<'_>,
108+
tree_mode: TreeMode,
109+
cache: Option<(BlobFormat, &mut gix::diff::blob::Platform)>,
110+
mut out: impl std::io::Write,
111+
) -> anyhow::Result<()> {
77112
let id = spec.single().context("rev-spec must resolve to a single object")?;
78-
let object = id.object()?;
79-
match object.kind {
113+
let header = id.header()?;
114+
match header.kind() {
80115
gix::object::Kind::Tree if matches!(tree_mode, TreeMode::Pretty) => {
81-
for entry in object.into_tree().iter() {
116+
for entry in id.object()?.into_tree().iter() {
82117
writeln!(out, "{}", entry?)?;
83118
}
84119
}
85-
_ => out.write_all(&object.data)?,
120+
gix::object::Kind::Blob if cache.is_some() && spec.path_and_mode().is_some() => {
121+
let (path, mode) = spec.path_and_mode().expect("is present");
122+
let is_dir = Some(mode.is_tree());
123+
match cache.expect("is some") {
124+
(BlobFormat::Git, _) => unreachable!("no need for a cache when querying object db"),
125+
(BlobFormat::Worktree, cache) => {
126+
let platform = cache.attr_stack.at_entry(path, is_dir, &repo.objects)?;
127+
let object = id.object()?;
128+
let mut converted = cache.filter.worktree_filter.convert_to_worktree(
129+
&object.data,
130+
path,
131+
&mut |_path, attrs| {
132+
let _ = platform.matching_attributes(attrs);
133+
},
134+
Delay::Forbid,
135+
)?;
136+
std::io::copy(&mut converted, &mut out)?;
137+
}
138+
(BlobFormat::Diff | BlobFormat::DiffOrGit, cache) => {
139+
cache.set_resource(id.detach(), mode.kind(), path, ResourceKind::OldOrSource, &repo.objects)?;
140+
let resource = cache.resource(ResourceKind::OldOrSource).expect("just set");
141+
let data = resource
142+
.data
143+
.as_slice()
144+
.ok_or_else(|| anyhow!("Binary data at {} cannot be diffed", path))?;
145+
out.write_all(data)?;
146+
}
147+
}
148+
}
149+
_ => out.write_all(&id.object()?.data)?,
86150
}
87151
Ok(())
88152
}

Diff for: src/plumbing/main.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,7 @@ pub fn main() -> Result<()> {
926926
explain,
927927
cat_file,
928928
tree_mode,
929+
blob_format,
929930
} => prepare_and_run(
930931
"revision-parse",
931932
trace,
@@ -942,12 +943,26 @@ pub fn main() -> Result<()> {
942943
format,
943944
explain,
944945
cat_file,
945-
tree_mode: match tree_mode.unwrap_or_default() {
946+
tree_mode: match tree_mode {
946947
revision::resolve::TreeMode::Raw => core::repository::revision::resolve::TreeMode::Raw,
947948
revision::resolve::TreeMode::Pretty => {
948949
core::repository::revision::resolve::TreeMode::Pretty
949950
}
950951
},
952+
blob_format: match blob_format {
953+
revision::resolve::BlobFormat::Git => {
954+
core::repository::revision::resolve::BlobFormat::Git
955+
}
956+
revision::resolve::BlobFormat::Worktree => {
957+
core::repository::revision::resolve::BlobFormat::Worktree
958+
}
959+
revision::resolve::BlobFormat::Diff => {
960+
core::repository::revision::resolve::BlobFormat::Diff
961+
}
962+
revision::resolve::BlobFormat::DiffOrGit => {
963+
core::repository::revision::resolve::BlobFormat::DiffOrGit
964+
}
965+
},
951966
},
952967
)
953968
},

Diff for: src/plumbing/options/mod.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,19 @@ pub mod revision {
614614
#[default]
615615
Pretty,
616616
}
617+
618+
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
619+
pub enum BlobFormat {
620+
/// The version stored in the Git Object Database.
621+
#[default]
622+
Git,
623+
/// The version that would be checked out into the worktree, including filters.
624+
Worktree,
625+
/// The version that would be diffed (Worktree + Text-Conversion)
626+
Diff,
627+
/// The version that would be diffed if there is a text-conversion, or the one stored in Git otherwise.
628+
DiffOrGit,
629+
}
617630
}
618631
#[derive(Debug, clap::Subcommand)]
619632
#[clap(visible_alias = "rev", visible_alias = "r")]
@@ -645,8 +658,12 @@ pub mod revision {
645658
/// Show the first resulting object similar to how `git cat-file` would, but don't show the resolved spec.
646659
#[clap(short = 'c', long, conflicts_with = "explain")]
647660
cat_file: bool,
648-
#[clap(short = 't', long)]
649-
tree_mode: Option<resolve::TreeMode>,
661+
/// How to display blobs.
662+
#[clap(short = 'b', long, default_value = "git")]
663+
blob_format: resolve::BlobFormat,
664+
/// How to display trees as obtained with `@:dirname` or `@^{tree}`.
665+
#[clap(short = 't', long, default_value = "pretty")]
666+
tree_mode: resolve::TreeMode,
650667
/// rev-specs like `@`, `@~1` or `HEAD^2`.
651668
#[clap(required = true)]
652669
specs: Vec<std::ffi::OsString>,

0 commit comments

Comments
 (0)