Skip to content

Commit 48ef17e

Browse files
committed
Merge branch 'fix-1096'
2 parents 7825637 + 5d78ab3 commit 48ef17e

File tree

14 files changed

+142
-54
lines changed

14 files changed

+142
-54
lines changed

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

+11-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ pub struct Options {
44
pub format: OutputFormat,
55
pub explain: bool,
66
pub cat_file: bool,
7+
pub tree_mode: TreeMode,
8+
}
9+
10+
pub enum TreeMode {
11+
Raw,
12+
Pretty,
713
}
814

915
pub(crate) mod function {
@@ -13,6 +19,7 @@ pub(crate) mod function {
1319
use gix::revision::Spec;
1420

1521
use super::Options;
22+
use crate::repository::revision::resolve::TreeMode;
1623
use crate::{repository::revision, OutputFormat};
1724

1825
pub fn resolve(
@@ -23,6 +30,7 @@ pub(crate) mod function {
2330
format,
2431
explain,
2532
cat_file,
33+
tree_mode,
2634
}: Options,
2735
) -> anyhow::Result<()> {
2836
repo.object_cache_size_if_unset(1024 * 1024);
@@ -36,7 +44,7 @@ pub(crate) mod function {
3644
let spec = gix::path::os_str_into_bstr(&spec)?;
3745
let spec = repo.rev_parse(spec)?;
3846
if cat_file {
39-
return display_object(spec, out);
47+
return display_object(spec, tree_mode, out);
4048
}
4149
writeln!(out, "{spec}", spec = spec.detach())?;
4250
}
@@ -63,11 +71,11 @@ pub(crate) mod function {
6371
Ok(())
6472
}
6573

66-
fn display_object(spec: Spec<'_>, mut out: impl std::io::Write) -> anyhow::Result<()> {
74+
fn display_object(spec: Spec<'_>, tree_mode: TreeMode, mut out: impl std::io::Write) -> anyhow::Result<()> {
6775
let id = spec.single().context("rev-spec must resolve to a single object")?;
6876
let object = id.object()?;
6977
match object.kind {
70-
gix::object::Kind::Tree => {
78+
gix::object::Kind::Tree if matches!(tree_mode, TreeMode::Pretty) => {
7179
for entry in object.into_tree().iter() {
7280
writeln!(out, "{}", entry?)?;
7381
}

Diff for: gix-object/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ serde = ["dep:serde", "bstr/serde", "smallvec/serde", "gix-hash/serde", "gix-act
2525
## details information about the error location will be collected.
2626
## Use it in applications which expect broken or invalid objects or for debugging purposes. Incorrectly formatted objects aren't at all
2727
## common otherwise.
28-
verbose-object-parsing-errors = []
28+
verbose-object-parsing-errors = ["winnow/std"]
2929

3030
[dependencies]
3131
gix-features = { version = "^0.36.0", path = "../gix-features", features = ["rustsha1", "progress"] }

Diff for: gix-object/benches/decode_objects.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ fn parse_tag(c: &mut Criterion) {
1919
}
2020

2121
fn parse_tree(c: &mut Criterion) {
22-
c.bench_function("TreeRef(sig)", |b| {
22+
c.bench_function("TreeRef()", |b| {
2323
b.iter(|| black_box(gix_object::TreeRef::from_bytes(TREE)).unwrap())
2424
});
25-
c.bench_function("TreeRefIter(sig)", |b| {
25+
c.bench_function("TreeRefIter()", |b| {
2626
b.iter(|| black_box(gix_object::TreeRefIter::from_bytes(TREE).count()))
2727
});
2828
}

Diff for: gix-object/src/lib.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,12 @@ pub mod decode {
290290
self.inner.fmt(f)
291291
}
292292
}
293+
294+
impl std::error::Error for Error {
295+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
296+
self.inner.cause().map(|v| v as &(dyn std::error::Error + 'static))
297+
}
298+
}
293299
}
294300

295301
///
@@ -318,14 +324,15 @@ pub mod decode {
318324
}
319325

320326
impl std::fmt::Display for Error {
321-
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
322-
Ok(())
327+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328+
f.write_str("object parsing failed")
323329
}
324330
}
331+
332+
impl std::error::Error for Error {}
325333
}
326334
pub(crate) use _decode::empty_error;
327335
pub use _decode::{Error, ParseError};
328-
impl std::error::Error for Error {}
329336

330337
/// Returned by [`loose_header()`]
331338
#[derive(Debug, thiserror::Error)]

Diff for: gix-object/src/tree/ref_iter.rs

+18-30
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@ impl<'a> Iterator for TreeRefIter<'a> {
6767
Some(Ok(entry))
6868
}
6969
None => {
70+
let failing = self.data;
7071
self.data = &[];
71-
let empty = &[] as &[u8];
7272
#[allow(clippy::unit_arg)]
7373
Some(Err(crate::decode::Error::with_err(
74-
winnow::error::ErrMode::from_error_kind(&empty, winnow::error::ErrorKind::Verify),
74+
winnow::error::ErrMode::from_error_kind(&failing, winnow::error::ErrorKind::Verify),
7575
)))
7676
}
7777
}
@@ -116,17 +116,9 @@ mod decode {
116116
use std::convert::TryFrom;
117117

118118
use bstr::ByteSlice;
119-
use winnow::{
120-
combinator::{eof, repeat, terminated},
121-
error::ParserError,
122-
prelude::*,
123-
stream::AsChar,
124-
token::{take, take_while},
125-
};
119+
use winnow::{error::ParserError, prelude::*};
126120

127-
use crate::{parse::SPACE, tree, tree::EntryRef, TreeRef};
128-
129-
const NULL: &[u8] = b"\0";
121+
use crate::{tree, tree::EntryRef, TreeRef};
130122

131123
pub fn fast_entry(i: &[u8]) -> Option<(&[u8], EntryRef<'_>)> {
132124
let mut mode = 0u32;
@@ -157,24 +149,20 @@ mod decode {
157149
))
158150
}
159151

160-
pub fn entry<'a, E: ParserError<&'a [u8]>>(i: &mut &'a [u8]) -> PResult<EntryRef<'a>, E> {
161-
(
162-
terminated(take_while(5..=6, AsChar::is_dec_digit), SPACE)
163-
.verify_map(|mode| tree::EntryMode::try_from(mode).ok()),
164-
terminated(take_while(1.., |b| b != NULL[0]), NULL),
165-
take(20u8), // TODO(SHA256): make this compatible with other hash lengths
166-
)
167-
.map(|(mode, filename, oid): (_, &[u8], _)| EntryRef {
168-
mode,
169-
filename: filename.as_bstr(),
170-
oid: gix_hash::oid::try_from_bytes(oid).expect("we counted exactly 20 bytes"),
171-
})
172-
.parse_next(i)
173-
}
174-
175152
pub fn tree<'a, E: ParserError<&'a [u8]>>(i: &mut &'a [u8]) -> PResult<TreeRef<'a>, E> {
176-
terminated(repeat(0.., entry), eof)
177-
.map(|entries| TreeRef { entries })
178-
.parse_next(i)
153+
let mut out = Vec::new();
154+
let mut i = &**i;
155+
while !i.is_empty() {
156+
let Some((rest, entry)) = fast_entry(i) else {
157+
#[allow(clippy::unit_arg)]
158+
return Err(winnow::error::ErrMode::from_error_kind(
159+
&i,
160+
winnow::error::ErrorKind::Verify,
161+
));
162+
};
163+
i = rest;
164+
out.push(entry);
165+
}
166+
Ok(TreeRef { entries: out })
179167
}
180168
}

Diff for: gix-object/tests/commit/mod.rs

+24
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
use crate::fixture_name;
2+
use gix_object::{CommitRef, CommitRefIter};
3+
14
const SIGNATURE: & [u8; 487] = b"-----BEGIN PGP SIGNATURE-----\n\niQEzBAABCAAdFiEEdjYp/sh4j8NRKLX27gKdHl60AwAFAl7q2DsACgkQ7gKdHl60\nAwDvewgAkL5UjEztzeVXlzceom0uCrAkCw9wSGLTmYcMKW3JwEaTRgQ4FX+sDuFT\nLZ8DoPu3UHUP0QnKrUwHulTTlKcOAvsczHbVPIKtXCxo6QpUfhsJQwz/J29kiE4L\nsOd+lqKGnn4oati/de2xwqNGi081fO5KILX75z6KfsAe7Qz7R3jxRF4uzHI033O+\nJc2Y827XeaELxW40SmzoLanWgEcdreXf3PstXEWW77CAu0ozXmvYj56vTviVybxx\nG7bc8lwc+SSKVe2VVB+CCfVbs0i541gmghUpZfMhUgaqttcCH8ysrUJDhne1BLG8\nCrOJIWTwAeEDtomV1p76qrMeqr1GFg==\n=qlSN\n-----END PGP SIGNATURE-----";
25

36
const LONG_MESSAGE: &str = "Merge tag 'thermal-v5.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thermal/linux
@@ -159,6 +162,27 @@ mod method {
159162
}
160163
}
161164

165+
#[test]
166+
fn invalid() {
167+
let fixture = fixture_name("commit", "unsigned.txt");
168+
let partial_commit = &fixture[..fixture.len() / 2];
169+
assert_eq!(
170+
CommitRef::from_bytes(partial_commit).unwrap_err().to_string(),
171+
if cfg!(feature = "verbose-object-parsing-errors") {
172+
"expected `<timestamp>`, `<name> <<email>> <timestamp> <+|-><HHMM>`, `author <signature>`"
173+
} else {
174+
"object parsing failed"
175+
}
176+
);
177+
assert_eq!(
178+
CommitRefIter::from_bytes(partial_commit)
179+
.take_while(Result::is_ok)
180+
.count(),
181+
1,
182+
"we can decode some fields before failing"
183+
);
184+
}
185+
162186
mod from_bytes;
163187
mod iter;
164188
mod message;

Diff for: gix-object/tests/fixtures/tree/special-1.tree

172 Bytes
Binary file not shown.

Diff for: gix-object/tests/fixtures/tree/special-2.tree

659 Bytes
Binary file not shown.

Diff for: gix-object/tests/fixtures/tree/special-3.tree

172 Bytes
Binary file not shown.

Diff for: gix-object/tests/fixtures/tree/special-4.tree

659 Bytes
Binary file not shown.

Diff for: gix-object/tests/tag/mod.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
use crate::fixture_name;
12
use gix_date::time::Sign;
2-
use gix_object::{bstr::ByteSlice, Kind, TagRef};
3+
use gix_object::{bstr::ByteSlice, Kind, TagRef, TagRefIter};
34

45
mod method {
56
use gix_object::TagRef;
@@ -115,6 +116,25 @@ KLMHist5yj0sw1E4hDTyQa0=
115116
}
116117
}
117118

119+
#[test]
120+
fn invalid() {
121+
let fixture = fixture_name("tag", "whitespace.txt");
122+
let partial_tag = &fixture[..fixture.len() / 2];
123+
assert_eq!(
124+
TagRef::from_bytes(partial_tag).unwrap_err().to_string(),
125+
if cfg!(feature = "verbose-object-parsing-errors") {
126+
""
127+
} else {
128+
"object parsing failed"
129+
}
130+
);
131+
assert_eq!(
132+
TagRefIter::from_bytes(partial_tag).take_while(Result::is_ok).count(),
133+
4,
134+
"we can decode some fields before failing"
135+
);
136+
}
137+
118138
mod from_bytes {
119139
use gix_object::{bstr::ByteSlice, Kind, TagRef};
120140

Diff for: gix-object/tests/tree/mod.rs

+36-14
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ mod iter {
5656
}
5757

5858
mod from_bytes {
59-
use gix_object::{bstr::ByteSlice, tree, tree::EntryRef, TreeRef};
59+
use gix_object::{bstr::ByteSlice, tree, tree::EntryRef, TreeRef, TreeRefIter};
6060

6161
use crate::{fixture_name, hex_to_id};
6262

@@ -108,24 +108,46 @@ mod from_bytes {
108108
}
109109

110110
#[test]
111-
fn maybe_special() -> crate::Result {
111+
fn invalid() {
112+
let fixture = fixture_name("tree", "definitely-special.tree");
113+
let partial_tree = &fixture[..fixture.len() / 2];
112114
assert_eq!(
113-
TreeRef::from_bytes(&fixture_name("tree", "maybe-special.tree"))?
114-
.entries
115-
.len(),
116-
160
115+
TreeRef::from_bytes(partial_tree).unwrap_err().to_string(),
116+
if cfg!(feature = "verbose-object-parsing-errors") {
117+
""
118+
} else {
119+
"object parsing failed"
120+
}
121+
);
122+
assert_eq!(
123+
TreeRefIter::from_bytes(partial_tree).take_while(Result::is_ok).count(),
124+
9,
125+
"we can decode about half of it before failing"
117126
);
118-
Ok(())
119127
}
120128

121129
#[test]
122-
fn definitely_special() -> crate::Result {
123-
assert_eq!(
124-
TreeRef::from_bytes(&fixture_name("tree", "definitely-special.tree"))?
125-
.entries
126-
.len(),
127-
19
128-
);
130+
fn special_trees() -> crate::Result {
131+
for (name, expected_entry_count) in [
132+
("maybe-special", 160),
133+
("definitely-special", 19),
134+
("special-1", 5),
135+
("special-2", 18),
136+
("special-3", 5),
137+
("special-4", 18),
138+
] {
139+
let fixture = fixture_name("tree", &format!("{name}.tree"));
140+
assert_eq!(
141+
TreeRef::from_bytes(&fixture)?.entries.len(),
142+
expected_entry_count,
143+
"{name}"
144+
);
145+
assert_eq!(
146+
TreeRefIter::from_bytes(&fixture).map(Result::unwrap).count(),
147+
expected_entry_count,
148+
"{name}"
149+
);
150+
}
129151
Ok(())
130152
}
131153
}

Diff for: src/plumbing/main.rs

+7
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,7 @@ pub fn main() -> Result<()> {
925925
specs,
926926
explain,
927927
cat_file,
928+
tree_mode,
928929
} => prepare_and_run(
929930
"revision-parse",
930931
trace,
@@ -941,6 +942,12 @@ pub fn main() -> Result<()> {
941942
format,
942943
explain,
943944
cat_file,
945+
tree_mode: match tree_mode.unwrap_or_default() {
946+
revision::resolve::TreeMode::Raw => core::repository::revision::resolve::TreeMode::Raw,
947+
revision::resolve::TreeMode::Pretty => {
948+
core::repository::revision::resolve::TreeMode::Pretty
949+
}
950+
},
944951
},
945952
)
946953
},

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

+12
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,16 @@ pub mod commitgraph {
595595
}
596596

597597
pub mod revision {
598+
pub mod resolve {
599+
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
600+
pub enum TreeMode {
601+
/// Show the raw bytes - only useful for piping into files for use with tooling.
602+
Raw,
603+
/// Display a tree in human-readable form.
604+
#[default]
605+
Pretty,
606+
}
607+
}
598608
#[derive(Debug, clap::Subcommand)]
599609
#[clap(visible_alias = "rev", visible_alias = "r")]
600610
pub enum Subcommands {
@@ -625,6 +635,8 @@ pub mod revision {
625635
/// Show the first resulting object similar to how `git cat-file` would, but don't show the resolved spec.
626636
#[clap(short = 'c', long, conflicts_with = "explain")]
627637
cat_file: bool,
638+
#[clap(short = 't', long)]
639+
tree_mode: Option<resolve::TreeMode>,
628640
/// rev-specs like `@`, `@~1` or `HEAD^2`.
629641
#[clap(required = true)]
630642
specs: Vec<std::ffi::OsString>,

0 commit comments

Comments
 (0)