Skip to content

Commit 5fdc9df

Browse files
committed
Merge branch 'gix-status'
2 parents d2ba97c + cf51a4d commit 5fdc9df

File tree

17 files changed

+247
-23
lines changed

17 files changed

+247
-23
lines changed

crate-status.md

Lines changed: 1 addition & 0 deletions
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

gitoxide-core/src/repository/revision/resolve.rs

Lines changed: 71 additions & 7 deletions
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
}

gix-diff/src/blob/pipeline.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,11 @@ impl Pipeline {
377377
.try_header(id)
378378
.map_err(gix_object::find::existing_object::Error::Find)?
379379
.ok_or_else(|| gix_object::find::existing_object::Error::NotFound { oid: id.to_owned() })?;
380-
if is_binary.is_none() && self.options.large_file_threshold_bytes > 0 {
381-
is_binary = Some(header.size > self.options.large_file_threshold_bytes);
380+
if is_binary.is_none()
381+
&& self.options.large_file_threshold_bytes > 0
382+
&& header.size > self.options.large_file_threshold_bytes
383+
{
384+
is_binary = Some(true);
382385
};
383386
let data = if is_binary == Some(true) {
384387
Data::Binary { size: header.size }
@@ -425,7 +428,18 @@ impl Pipeline {
425428
})?;
426429
match cmd_and_file {
427430
Some((cmd, mut tmp_file)) => {
428-
tmp_file.write_all(out).map_err(|err| {
431+
match res {
432+
ToWorktreeOutcome::Unchanged(buf) | ToWorktreeOutcome::Buffer(buf) => {
433+
tmp_file.write_all(buf)
434+
}
435+
ToWorktreeOutcome::Process(MaybeDelayed::Immediate(mut stream)) => {
436+
std::io::copy(&mut stream, &mut tmp_file).map(|_| ())
437+
}
438+
ToWorktreeOutcome::Process(MaybeDelayed::Delayed(_)) => {
439+
unreachable!("we prohibit this")
440+
}
441+
}
442+
.map_err(|err| {
429443
convert_to_diffable::Error::CreateTempfile {
430444
source: err,
431445
rela_path: rela_path.to_owned(),

gix-diff/tests/blob/pipeline.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,61 @@ pub(crate) mod convert_to_diffable {
9393
Ok(())
9494
}
9595

96+
#[test]
97+
fn binary_below_large_file_threshold() -> crate::Result {
98+
let tmp = gix_testtools::tempfile::TempDir::new()?;
99+
let mut filter = gix_diff::blob::Pipeline::new(
100+
WorktreeRoots {
101+
old_root: None,
102+
new_root: Some(tmp.path().to_owned()),
103+
},
104+
gix_filter::Pipeline::default(),
105+
vec![],
106+
gix_diff::blob::pipeline::Options {
107+
large_file_threshold_bytes: 5,
108+
..default_options()
109+
},
110+
);
111+
112+
let does_not_matter = gix_hash::Kind::Sha1.null();
113+
let mut buf = Vec::new();
114+
let a_name = "a";
115+
let large_content = "a\0b";
116+
std::fs::write(tmp.path().join(a_name), large_content.as_bytes())?;
117+
let out = filter.convert_to_diffable(
118+
&does_not_matter,
119+
EntryKind::BlobExecutable,
120+
a_name.into(),
121+
ResourceKind::NewOrDestination,
122+
&mut |_, _| {},
123+
&gix_object::find::Never,
124+
pipeline::Mode::default(),
125+
&mut buf,
126+
)?;
127+
assert!(out.driver_index.is_none(), "there was no driver");
128+
assert_eq!(out.data, Some(pipeline::Data::Binary { size: 3 }), "detected in buffer");
129+
assert_eq!(buf.len(), 0, "it should avoid querying that data in the first place");
130+
131+
let mut db = ObjectDb::default();
132+
let id = db.insert(large_content);
133+
let out = filter.convert_to_diffable(
134+
&id,
135+
EntryKind::Blob,
136+
a_name.into(),
137+
ResourceKind::OldOrSource,
138+
&mut |_, _| {},
139+
&db,
140+
pipeline::Mode::default(),
141+
&mut buf,
142+
)?;
143+
144+
assert!(out.driver_index.is_none(), "there was no driver");
145+
assert_eq!(out.data, Some(pipeline::Data::Binary { size: 3 }));
146+
assert_eq!(buf.len(), 0, "it should avoid querying that data in the first place");
147+
148+
Ok(())
149+
}
150+
96151
#[test]
97152
fn above_large_file_threshold() -> crate::Result {
98153
let tmp = gix_testtools::tempfile::TempDir::new()?;

gix-index/src/entry/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ mod write;
1818

1919
use bitflags::bitflags;
2020

21-
// TODO: we essentially treat this as an enum withj the only exception being
22-
// that `FILE_EXECUTABLE.contains(FILE)` works might want to turn this into an
23-
// enum proper
21+
// TODO: we essentially treat this as an enum with the only exception being
22+
// that `FILE_EXECUTABLE.contains(FILE)` works might want to turn this into an
23+
// enum proper
2424
bitflags! {
2525
/// The kind of file of an entry.
2626
#[derive(Copy, Clone, Debug, PartialEq, Eq)]

gix-index/src/entry/mode.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ impl Mode {
1111
*self == Self::DIR | Self::SYMLINK
1212
}
1313

14+
/// Convert this instance to a tree's entry mode, or return `None` if for some
15+
/// and unexpected reason the bitflags don't resemble any known entry-mode.
16+
pub fn to_tree_entry_mode(&self) -> Option<gix_object::tree::EntryMode> {
17+
gix_object::tree::EntryMode::try_from(self.bits()).ok()
18+
}
19+
1420
/// Compares this mode to the file system version ([`std::fs::symlink_metadata`])
1521
/// and returns the change needed to update this mode to match the file.
1622
///

gix/src/ext/rev_spec.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ impl RevSpecExt for gix_revision::Spec {
1212
fn attach(self, repo: &crate::Repository) -> crate::revision::Spec<'_> {
1313
crate::revision::Spec {
1414
inner: self,
15+
path: None,
1516
first_ref: None,
1617
second_ref: None,
1718
repo,

gix/src/revision/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub use gix_revision as plumbing;
77

88
///
99
pub mod walk;
10+
use crate::bstr::BString;
1011
pub use walk::iter::Walk;
1112

1213
///
@@ -22,6 +23,8 @@ pub mod spec;
2223
#[cfg(feature = "revision")]
2324
pub struct Spec<'repo> {
2425
pub(crate) inner: gix_revision::Spec,
26+
/// The path we encountered in the revspec, like `@:<path>` or `@..@~1:<path>`.
27+
pub(crate) path: Option<(BString, gix_object::tree::EntryMode)>,
2528
/// The first name of a reference as seen while parsing a `RevSpec`, for completeness.
2629
pub(crate) first_ref: Option<gix_ref::Reference>,
2730
/// The second name of a reference as seen while parsing a `RevSpec`, for completeness.

gix/src/revision/spec/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::bstr::BStr;
12
use crate::{ext::ReferenceExt, revision::Spec, Id, Reference};
23

34
///
@@ -37,6 +38,7 @@ impl<'repo> Spec<'repo> {
3738
pub fn from_id(id: Id<'repo>) -> Self {
3839
Spec {
3940
inner: gix_revision::Spec::Include(id.inner),
41+
path: None,
4042
repo: id.repo,
4143
first_ref: None,
4244
second_ref: None,
@@ -62,6 +64,13 @@ impl<'repo> Spec<'repo> {
6264
)
6365
}
6466

67+
/// Return the path encountered in specs like `@:<path>` or `:<path>`, along with the kind of object it represents.
68+
///
69+
/// Note that there can only be one as paths always terminates further revspec parsing.
70+
pub fn path_and_mode(&self) -> Option<(&BStr, gix_object::tree::EntryMode)> {
71+
self.path.as_ref().map(|(p, mode)| (p.as_ref(), *mode))
72+
}
73+
6574
/// Return the name of the first reference we encountered while resolving the rev-spec, or `None` if a short hash
6675
/// was used. For example, `@` might yield `Some(HEAD)`, but `abcd` yields `None`.
6776
pub fn first_reference(&self) -> Option<&gix_ref::Reference> {

gix/src/revision/spec/parse/delegate/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ impl<'repo> Delegate<'repo> {
1717
Delegate {
1818
refs: Default::default(),
1919
objs: Default::default(),
20+
paths: Default::default(),
2021
ambiguous_objects: Default::default(),
2122
idx: 0,
2223
kind: None,
@@ -100,6 +101,7 @@ impl<'repo> Delegate<'repo> {
100101

101102
let range = zero_or_one_objects_or_ambiguity_err(self.objs, self.prefix, self.err, self.repo)?;
102103
Ok(crate::revision::Spec {
104+
path: self.paths[0].take().or(self.paths[1].take()),
103105
first_ref: self.refs[0].take(),
104106
second_ref: self.refs[1].take(),
105107
inner: kind_to_spec(self.kind, range)?,

gix/src/revision/spec/parse/delegate/navigate.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ impl<'repo> delegate::Navigate for Delegate<'repo> {
121121
let lookup_path = |obj: &ObjectId| {
122122
let tree_id = peel(repo, obj, gix_object::Kind::Tree)?;
123123
if path.is_empty() {
124-
return Ok(tree_id);
124+
return Ok((tree_id, gix_object::tree::EntryKind::Tree.into()));
125125
}
126126
let mut tree = repo.find_object(tree_id)?.into_tree();
127127
let entry =
@@ -131,11 +131,17 @@ impl<'repo> delegate::Navigate for Delegate<'repo> {
131131
object: obj.attach(repo).shorten_or_id(),
132132
tree: tree_id.attach(repo).shorten_or_id(),
133133
})?;
134-
Ok(entry.object_id())
134+
Ok((entry.object_id(), entry.mode()))
135135
};
136136
for obj in objs.iter() {
137137
match lookup_path(obj) {
138-
Ok(replace) => replacements.push((*obj, replace)),
138+
Ok((replace, mode)) => {
139+
if !path.is_empty() {
140+
// Technically this is letting the last one win, but so be it.
141+
self.paths[self.idx] = Some((path.to_owned(), mode));
142+
}
143+
replacements.push((*obj, replace))
144+
}
139145
Err(err) => errors.push((*obj, err)),
140146
}
141147
}
@@ -306,6 +312,14 @@ impl<'repo> delegate::Navigate for Delegate<'repo> {
306312
self.objs[self.idx]
307313
.get_or_insert_with(HashSet::default)
308314
.insert(entry.id);
315+
316+
self.paths[self.idx] = Some((
317+
path.to_owned(),
318+
entry
319+
.mode
320+
.to_tree_entry_mode()
321+
.unwrap_or(gix_object::tree::EntryKind::Blob.into()),
322+
));
309323
Some(())
310324
}
311325
None => {

gix/src/revision/spec/parse/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use gix_revision::spec::parse;
77
use crate::{bstr::BStr, revision::Spec, Repository};
88

99
mod types;
10+
use crate::bstr::BString;
1011
pub use types::{Error, ObjectKindHint, Options, RefsHint};
1112

1213
///
@@ -45,6 +46,9 @@ impl<'repo> Spec<'repo> {
4546
struct Delegate<'repo> {
4647
refs: [Option<gix_ref::Reference>; 2],
4748
objs: [Option<HashSet<ObjectId>>; 2],
49+
/// Path specified like `@:<path>` or `:<path>` for later use when looking up specs.
50+
/// Note that it terminates spec parsing, so it's either `0` or `1`, never both.
51+
paths: [Option<(BString, gix_object::tree::EntryMode)>; 2],
4852
/// The originally encountered ambiguous objects for potential later use in errors.
4953
ambiguous_objects: [Option<HashSet<ObjectId>>; 2],
5054
idx: usize,

0 commit comments

Comments
 (0)