diff --git a/gitoxide-core/src/index/mod.rs b/gitoxide-core/src/index/mod.rs index df75cf541b6..4b6b50b36ef 100644 --- a/gitoxide-core/src/index/mod.rs +++ b/gitoxide-core/src/index/mod.rs @@ -8,7 +8,7 @@ pub struct Options { pub mod information; fn parse_file(index_path: impl AsRef, object_hash: gix::hash::Kind) -> anyhow::Result { - gix::index::File::at(index_path.as_ref(), object_hash, Default::default()).map_err(Into::into) + gix::index::File::at(index_path.as_ref(), object_hash, false, Default::default()).map_err(Into::into) } pub mod checkout_exclusive { diff --git a/gitoxide-core/src/repository/index/mod.rs b/gitoxide-core/src/repository/index/mod.rs index 17ba656b16f..63b359e331a 100644 --- a/gitoxide-core/src/repository/index/mod.rs +++ b/gitoxide-core/src/repository/index/mod.rs @@ -1,19 +1,23 @@ use std::{ffi::OsString, path::PathBuf}; use anyhow::bail; -use gix::prelude::FindExt; pub fn from_tree( + repo: gix::Repository, mut spec: OsString, index_path: Option, force: bool, - repo: gix::Repository, + skip_hash: bool, ) -> anyhow::Result<()> { spec.push("^{tree}"); let spec = gix::path::os_str_into_bstr(&spec)?; let tree = repo.rev_parse_single(spec)?; - let index = gix::index::State::from_tree(&tree, |oid, buf| repo.objects.find_tree_iter(oid, buf).ok())?; - let options = gix::index::write::Options::default(); + + let mut index = repo.index_from_tree(&tree)?; + let options = gix::index::write::Options { + skip_hash, + ..Default::default() + }; match index_path { Some(index_path) => { @@ -23,11 +27,10 @@ pub fn from_tree( index_path.display() ); } - let mut index = gix::index::File::from_state(index, index_path); + index.set_path(index_path); index.write(options)?; } None => { - let index = gix::index::File::from_state(index, std::path::PathBuf::new()); let mut out = Vec::with_capacity(512 * 1024); index.write_to(&mut out, options)?; } @@ -36,7 +39,12 @@ pub fn from_tree( Ok(()) } -pub fn from_list(entries_file: PathBuf, index_path: Option, force: bool) -> anyhow::Result<()> { +pub fn from_list( + entries_file: PathBuf, + index_path: Option, + force: bool, + skip_hash: bool, +) -> anyhow::Result<()> { use std::io::BufRead; let object_hash = gix::hash::Kind::Sha1; @@ -57,7 +65,10 @@ pub fn from_list(entries_file: PathBuf, index_path: Option, force: bool } index.sort_entries(); - let options = gix::index::write::Options::default(); + let options = gix::index::write::Options { + skip_hash, + ..Default::default() + }; match index_path { Some(index_path) => { if index_path.is_file() && !force { diff --git a/gitoxide-core/src/repository/remote.rs b/gitoxide-core/src/repository/remote.rs index 7a3f44be7a1..ad10bceb6d4 100644 --- a/gitoxide-core/src/repository/remote.rs +++ b/gitoxide-core/src/repository/remote.rs @@ -334,18 +334,5 @@ pub(crate) fn by_name_or_url<'repo>( repo: &'repo gix::Repository, name_or_url: Option<&str>, ) -> anyhow::Result> { - use anyhow::Context; - Ok(match name_or_url { - Some(name) => { - if name.contains('/') || name.contains('.') { - repo.remote_at(gix::url::parse(name.into())?)? - } else { - repo.find_remote(name)? - } - } - None => repo - .head()? - .into_remote(gix::remote::Direction::Fetch) - .context("Cannot find a remote for unborn branch")??, - }) + repo.find_fetch_remote(name_or_url.map(Into::into)).map_err(Into::into) } diff --git a/gix-index/src/access/mod.rs b/gix-index/src/access/mod.rs index 26990f495e1..3aaa1ca5ca0 100644 --- a/gix-index/src/access/mod.rs +++ b/gix-index/src/access/mod.rs @@ -344,7 +344,7 @@ mod tests { let file = PathBuf::from("tests") .join("fixtures") .join(Path::new("loose_index").join("conflicting-file.git-index")); - let file = crate::File::at(file, gix_hash::Kind::Sha1, Default::default()).expect("valid file"); + let file = crate::File::at(file, gix_hash::Kind::Sha1, false, Default::default()).expect("valid file"); assert_eq!( file.entries().len(), 3, diff --git a/gix-index/src/decode/mod.rs b/gix-index/src/decode/mod.rs index 70a65a880b0..12c8c53e404 100644 --- a/gix-index/src/decode/mod.rs +++ b/gix-index/src/decode/mod.rs @@ -54,7 +54,7 @@ pub struct Options { impl State { /// Decode an index state from `data` and store `timestamp` in the resulting instance for pass-through, assuming `object_hash` - /// to be used through the file. + /// to be used through the file. Also return the stored hash over all bytes in `data` or `None` if none was written due to `index.skipHash`. pub fn from_bytes( data: &[u8], timestamp: FileTime, @@ -64,7 +64,7 @@ impl State { min_extension_block_in_bytes_for_threading, expected_checksum, }: Options, - ) -> Result<(Self, gix_hash::ObjectId), Error> { + ) -> Result<(Self, Option), Error> { let _span = gix_features::trace::detail!("gix_index::State::from_bytes()"); let (version, num_entries, post_header_data) = header::decode(data, object_hash)?; let start_of_extensions = extension::end_of_index_entry::decode(data, object_hash); @@ -214,10 +214,11 @@ impl State { } let checksum = gix_hash::ObjectId::from(data); - if let Some(expected_checksum) = expected_checksum { - if checksum != expected_checksum { + let checksum = (!checksum.is_null()).then_some(checksum); + if let Some((expected_checksum, actual_checksum)) = expected_checksum.zip(checksum) { + if actual_checksum != expected_checksum { return Err(Error::ChecksumMismatch { - actual_checksum: checksum, + actual_checksum, expected_checksum, }); } diff --git a/gix-index/src/extension/link.rs b/gix-index/src/extension/link.rs index 20ce9cb217c..5fed2f960a4 100644 --- a/gix-index/src/extension/link.rs +++ b/gix-index/src/extension/link.rs @@ -72,6 +72,7 @@ impl Link { self, split_index: &mut crate::File, object_hash: gix_hash::Kind, + skip_hash: bool, options: crate::decode::Options, ) -> Result<(), crate::file::init::Error> { let shared_index_path = split_index @@ -82,6 +83,7 @@ impl Link { let mut shared_index = crate::File::at( &shared_index_path, object_hash, + skip_hash, crate::decode::Options { expected_checksum: self.shared_index_checksum.into(), ..options diff --git a/gix-index/src/file/init.rs b/gix-index/src/file/init.rs index a61c7cfff60..68c6c14a356 100644 --- a/gix-index/src/file/init.rs +++ b/gix-index/src/file/init.rs @@ -26,16 +26,18 @@ pub use error::Error; /// Initialization impl File { /// Try to open the index file at `path` with `options`, assuming `object_hash` is used throughout the file, or create a new - /// index that merely exists in memory and is empty. + /// index that merely exists in memory and is empty. `skip_hash` will increase the performance by a factor of 2, at the cost of + /// possibly not detecting corruption. /// /// Note that the `path` will not be written if it doesn't exist. pub fn at_or_default( path: impl Into, object_hash: gix_hash::Kind, + skip_hash: bool, options: decode::Options, ) -> Result { let path = path.into(); - Ok(match Self::at(&path, object_hash, options) { + Ok(match Self::at(&path, object_hash, skip_hash, options) { Ok(f) => f, Err(Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => { File::from_state(State::new(object_hash), path) @@ -44,26 +46,60 @@ impl File { }) } - /// Open an index file at `path` with `options`, assuming `object_hash` is used throughout the file. - pub fn at(path: impl Into, object_hash: gix_hash::Kind, options: decode::Options) -> Result { + /// Open an index file at `path` with `options`, assuming `object_hash` is used throughout the file. If `skip_hash` is `true`, + /// we will not get or compare the checksum of the index at all, which generally increases performance of this method by a factor + /// of 2 or more. + /// + /// Note that the verification of the file hash depends on `options`, and even then it's performed after the file was read and not + /// before it is read. That way, invalid files would see a more descriptive error message as we try to parse them. + pub fn at( + path: impl Into, + object_hash: gix_hash::Kind, + skip_hash: bool, + options: decode::Options, + ) -> Result { let _span = gix_features::trace::detail!("gix_index::File::at()"); let path = path.into(); let (data, mtime) = { - // SAFETY: we have to take the risk of somebody changing the file underneath. Git never writes into the same file. let file = std::fs::File::open(&path)?; + // SAFETY: we have to take the risk of somebody changing the file underneath. Git never writes into the same file. #[allow(unsafe_code)] let data = unsafe { Mmap::map(&file)? }; + + if !skip_hash { + // Note that even though it's trivial to offload this into a thread, which is worth it for all but the smallest + // index files, we choose more safety here just like git does and don't even try to decode the index if the hashes + // don't match. + // Thanks to `skip_hash`, we can get performance and it's under caller control, at the cost of some safety. + let expected = gix_hash::ObjectId::from(&data[data.len() - object_hash.len_in_bytes()..]); + if !expected.is_null() { + let _span = gix_features::trace::detail!("gix::open_index::hash_index", path = ?path); + let meta = file.metadata()?; + let num_bytes_to_hash = meta.len() - object_hash.len_in_bytes() as u64; + let actual_hash = gix_features::hash::bytes( + &file, + num_bytes_to_hash as usize, + object_hash, + &mut gix_features::progress::Discard, + &Default::default(), + )?; + + if actual_hash != expected { + return Err(Error::Decode(decode::Error::ChecksumMismatch { + actual_checksum: actual_hash, + expected_checksum: expected, + })); + } + } + } + (data, filetime::FileTime::from_last_modification_time(&file.metadata()?)) }; let (state, checksum) = State::from_bytes(&data, mtime, object_hash, options)?; - let mut file = File { - state, - path, - checksum: Some(checksum), - }; + let mut file = File { state, path, checksum }; if let Some(mut link) = file.link.take() { - link.dissolve_into(&mut file, object_hash, options)?; + link.dissolve_into(&mut file, object_hash, skip_hash, options)?; } Ok(file) @@ -72,7 +108,7 @@ impl File { /// Consume `state` and pretend it was read from `path`, setting our checksum to `null`. /// /// `File` instances created like that should be written to disk to set the correct checksum via `[File::write()]`. - pub fn from_state(state: crate::State, path: impl Into) -> Self { + pub fn from_state(state: State, path: impl Into) -> Self { File { state, path: path.into(), diff --git a/gix-index/src/file/verify.rs b/gix-index/src/file/verify.rs index a9680845c0e..3890acd9524 100644 --- a/gix-index/src/file/verify.rs +++ b/gix-index/src/file/verify.rs @@ -14,8 +14,6 @@ mod error { actual: gix_hash::ObjectId, expected: gix_hash::ObjectId, }, - #[error("Checksum of in-memory index wasn't computed yet")] - NoChecksum, } } pub use error::Error; @@ -24,19 +22,22 @@ impl File { /// Verify the integrity of the index to assure its consistency. pub fn verify_integrity(&self) -> Result<(), Error> { let _span = gix_features::trace::coarse!("gix_index::File::verify_integrity()"); - let checksum = self.checksum.ok_or(Error::NoChecksum)?; - let num_bytes_to_hash = self.path.metadata()?.len() - checksum.as_bytes().len() as u64; - let should_interrupt = AtomicBool::new(false); - let actual = gix_features::hash::bytes_of_file( - &self.path, - num_bytes_to_hash as usize, - checksum.kind(), - &mut gix_features::progress::Discard, - &should_interrupt, - )?; - (actual == checksum).then_some(()).ok_or(Error::ChecksumMismatch { - actual, - expected: checksum, - }) + if let Some(checksum) = self.checksum { + let num_bytes_to_hash = self.path.metadata()?.len() - checksum.as_bytes().len() as u64; + let should_interrupt = AtomicBool::new(false); + let actual = gix_features::hash::bytes_of_file( + &self.path, + num_bytes_to_hash as usize, + checksum.kind(), + &mut gix_features::progress::Discard, + &should_interrupt, + )?; + (actual == checksum).then_some(()).ok_or(Error::ChecksumMismatch { + actual, + expected: checksum, + }) + } else { + Ok(()) + } } } diff --git a/gix-index/src/file/write.rs b/gix-index/src/file/write.rs index 1e8afc07dc5..47a4cde9661 100644 --- a/gix-index/src/file/write.rs +++ b/gix-index/src/file/write.rs @@ -22,23 +22,28 @@ impl File { mut out: impl std::io::Write, options: write::Options, ) -> std::io::Result<(Version, gix_hash::ObjectId)> { - let mut hasher = hash::Write::new(&mut out, self.state.object_hash); - let version = self.state.write_to(&mut hasher, options)?; - - let hash = hasher.hash.digest(); - out.write_all(&hash)?; - Ok((version, gix_hash::ObjectId::from(hash))) + let (version, hash) = if options.skip_hash { + let out: &mut dyn std::io::Write = &mut out; + let version = self.state.write_to(out, options)?; + (version, self.state.object_hash.null()) + } else { + let mut hasher = hash::Write::new(&mut out, self.state.object_hash); + let out: &mut dyn std::io::Write = &mut hasher; + let version = self.state.write_to(out, options)?; + (version, gix_hash::ObjectId::from(hasher.hash.digest())) + }; + out.write_all(hash.as_slice())?; + Ok((version, hash)) } /// Write ourselves to the path we were read from after acquiring a lock, using `options`. /// /// Note that the hash produced will be stored which is why we need to be mutable. pub fn write(&mut self, options: write::Options) -> Result<(), Error> { - let mut lock = std::io::BufWriter::new(gix_lock::File::acquire_to_update_resource( - &self.path, - gix_lock::acquire::Fail::Immediately, - None, - )?); + let mut lock = std::io::BufWriter::with_capacity( + 64 * 1024, + gix_lock::File::acquire_to_update_resource(&self.path, gix_lock::acquire::Fail::Immediately, None)?, + ); let (version, digest) = self.write_to(&mut lock, options)?; match lock.into_inner() { Ok(lock) => lock.commit()?, diff --git a/gix-index/src/write.rs b/gix-index/src/write.rs index 29066a35e9a..2050ed80941 100644 --- a/gix-index/src/write.rs +++ b/gix-index/src/write.rs @@ -48,13 +48,26 @@ impl Extensions { /// Note that default options write either index V2 or V3 depending on the content of the entries. #[derive(Debug, Default, Clone, Copy)] pub struct Options { - /// Configures which extensions to write + /// Configures which extensions to write. pub extensions: Extensions, + /// Set the trailing hash of the produced index to all zeroes to save some time. + /// + /// This value is typically controlled by `index.skipHash` and is respected when the index is written + /// via [`File::write()`](crate::File::write()) and [`File::write_to()`](crate::File::write_to()). + /// Note that + pub skip_hash: bool, } impl State { /// Serialize this instance to `out` with [`options`][Options]. - pub fn write_to(&self, out: impl std::io::Write, Options { extensions }: Options) -> std::io::Result { + pub fn write_to( + &self, + out: impl std::io::Write, + Options { + extensions, + skip_hash: _, + }: Options, + ) -> std::io::Result { let _span = gix_features::trace::detail!("gix_index::State::write()"); let version = self.detect_required_version(); diff --git a/gix-index/tests/fixtures/loose_index/skip_hash.git-index b/gix-index/tests/fixtures/loose_index/skip_hash.git-index new file mode 100644 index 00000000000..8a755bd4d6c Binary files /dev/null and b/gix-index/tests/fixtures/loose_index/skip_hash.git-index differ diff --git a/gix-index/tests/index/file/init.rs b/gix-index/tests/index/file/init.rs index 3695df73d0c..063640497e1 100644 --- a/gix-index/tests/index/file/init.rs +++ b/gix-index/tests/index/file/init.rs @@ -6,6 +6,7 @@ mod at_or_new { gix_index::File::at_or_default( Generated("v4_more_files_IEOT").to_path(), gix_hash::Kind::Sha1, + false, Default::default(), ) .expect("file exists and can be opened"); @@ -16,6 +17,7 @@ mod at_or_new { let index = gix_index::File::at_or_default( "__definitely no file that exists ever__", gix_hash::Kind::Sha1, + false, Default::default(), ) .expect("file is defaulting to a new one"); @@ -46,7 +48,7 @@ mod from_state { let new_index_path = tmp.path().join(fixture.to_name()); assert!(!new_index_path.exists()); - let index = gix_index::File::at(fixture.to_path(), gix_hash::Kind::Sha1, Default::default())?; + let index = gix_index::File::at(fixture.to_path(), gix_hash::Kind::Sha1, false, Default::default())?; let mut index = gix_index::File::from_state(index.into(), new_index_path.clone()); assert!(index.checksum().is_none()); assert_eq!(index.path(), new_index_path); diff --git a/gix-index/tests/index/file/read.rs b/gix-index/tests/index/file/read.rs index 5cbae8b7bc3..ae2b7ace9dc 100644 --- a/gix-index/tests/index/file/read.rs +++ b/gix-index/tests/index/file/read.rs @@ -19,13 +19,14 @@ fn verify(index: gix_index::File) -> gix_index::File { pub(crate) fn loose_file(name: &str) -> gix_index::File { let path = loose_file_path(name); - let file = gix_index::File::at(path, gix_hash::Kind::Sha1, Default::default()).unwrap(); + let file = gix_index::File::at(path, gix_hash::Kind::Sha1, false, Default::default()).unwrap(); verify(file) } pub(crate) fn try_file(name: &str) -> Result { let file = gix_index::File::at( crate::fixture_index_path(name), gix_hash::Kind::Sha1, + false, Default::default(), )?; Ok(verify(file)) @@ -34,7 +35,7 @@ pub(crate) fn file(name: &str) -> gix_index::File { try_file(name).unwrap() } fn file_opt(name: &str, opts: gix_index::decode::Options) -> gix_index::File { - let file = gix_index::File::at(crate::fixture_index_path(name), gix_hash::Kind::Sha1, opts).unwrap(); + let file = gix_index::File::at(crate::fixture_index_path(name), gix_hash::Kind::Sha1, false, opts).unwrap(); verify(file) } @@ -75,6 +76,28 @@ fn v2_empty() { assert!(tree.name.is_empty()); assert!(tree.children.is_empty()); assert_eq!(tree.id, hex_to_id("4b825dc642cb6eb9a060e54bf8d69288fbee4904")); + assert_eq!( + file.checksum(), + Some(hex_to_id("72d53f787d86a932a25a8537cee236d81846a8f1")), + "checksums are read but not validated by default" + ); +} + +#[test] +fn v2_empty_skip_hash() { + let file = loose_file("skip_hash"); + assert_eq!(file.version(), Version::V2); + assert_eq!(file.entries().len(), 0); + let tree = file.tree().unwrap(); + assert_eq!(tree.num_entries.unwrap_or_default(), 0); + assert!(tree.name.is_empty()); + assert!(tree.children.is_empty()); + assert_eq!(tree.id, hex_to_id("4b825dc642cb6eb9a060e54bf8d69288fbee4904")); + assert_eq!( + file.checksum(), + None, + "unset checksums are represented in the type system" + ); } #[test] @@ -118,6 +141,7 @@ fn split_index_without_any_extension() { let file = gix_index::File::at( find_shared_index_for(crate::fixture_index_path("v2_split_index")), gix_hash::Kind::Sha1, + false, Default::default(), ) .unwrap(); @@ -315,8 +339,15 @@ fn split_index_and_regular_index_of_same_content_are_indeed_the_same() { ) .unwrap(); - let split = - verify(gix_index::File::at(base.join("split/.git/index"), gix_hash::Kind::Sha1, Default::default()).unwrap()); + let split = verify( + gix_index::File::at( + base.join("split/.git/index"), + gix_hash::Kind::Sha1, + false, + Default::default(), + ) + .unwrap(), + ); assert!( split.link().is_none(), @@ -327,6 +358,7 @@ fn split_index_and_regular_index_of_same_content_are_indeed_the_same() { gix_index::File::at( base.join("regular/.git/index"), gix_hash::Kind::Sha1, + false, Default::default(), ) .unwrap(), diff --git a/gix-index/tests/index/file/write.rs b/gix-index/tests/index/file/write.rs index 42646042491..b3c01b21de6 100644 --- a/gix-index/tests/index/file/write.rs +++ b/gix-index/tests/index/file/write.rs @@ -37,6 +37,47 @@ fn roundtrips() -> crate::Result { Ok(()) } +#[test] +fn skip_hash() -> crate::Result { + let tmp = gix_testtools::tempfile::TempDir::new()?; + let path = tmp.path().join("index"); + let mut expected = Loose("conflicting-file").open(); + assert!(expected.checksum().is_some()); + + expected.set_path(&path); + expected.write(Options { + extensions: Default::default(), + skip_hash: false, + })?; + + let actual = gix_index::File::at( + &path, + expected.checksum().expect("present").kind(), + false, + Default::default(), + )?; + assert_eq!( + actual.checksum(), + expected.checksum(), + "a hash is written by default and it matches" + ); + + expected.write(Options { + extensions: Default::default(), + skip_hash: true, + })?; + + let actual = gix_index::File::at( + &path, + expected.checksum().expect("present").kind(), + false, + Default::default(), + )?; + assert_eq!(actual.checksum(), None, "no hash is produced in this case"); + + Ok(()) +} + #[test] fn roundtrips_sparse_index() -> crate::Result { // NOTE: I initially tried putting these fixtures into the main roundtrip test above, @@ -253,9 +294,13 @@ fn only_tree_ext() -> Options { end_of_index_entry: false, tree_cache: true, }, + skip_hash: false, } } fn options_with(extensions: write::Extensions) -> Options { - Options { extensions } + Options { + extensions, + skip_hash: false, + } } diff --git a/gix-index/tests/index/mod.rs b/gix-index/tests/index/mod.rs index ae39f453eb9..5b2c2634ed8 100644 --- a/gix-index/tests/index/mod.rs +++ b/gix-index/tests/index/mod.rs @@ -50,7 +50,7 @@ impl Fixture { } pub fn open(&self) -> gix_index::File { - gix_index::File::at(self.to_path(), gix_hash::Kind::Sha1, Default::default()) + gix_index::File::at(self.to_path(), gix_hash::Kind::Sha1, false, Default::default()) .expect("fixtures are always readable") } } diff --git a/gix-status/tests/status/index_as_worktree.rs b/gix-status/tests/status/index_as_worktree.rs index c075f96a0b8..2629c3e1033 100644 --- a/gix-status/tests/status/index_as_worktree.rs +++ b/gix-status/tests/status/index_as_worktree.rs @@ -31,7 +31,8 @@ const TEST_OPTIONS: index::entry::stat::Options = index::entry::stat::Options { fn fixture(name: &str, expected_status: &[(&BStr, Option, bool)]) { let worktree = fixture_path(name); let git_dir = worktree.join(".git"); - let mut index = gix_index::File::at(git_dir.join("index"), gix_hash::Kind::Sha1, Default::default()).unwrap(); + let mut index = + gix_index::File::at(git_dir.join("index"), gix_hash::Kind::Sha1, false, Default::default()).unwrap(); let mut recorder = Recorder::default(); index_as_worktree( &mut index, @@ -139,7 +140,8 @@ fn racy_git() { let worktree = dir.path(); let git_dir = worktree.join(".git"); let fs = gix_fs::Capabilities::probe(&git_dir); - let mut index = gix_index::File::at(git_dir.join("index"), gix_hash::Kind::Sha1, Default::default()).unwrap(); + let mut index = + gix_index::File::at(git_dir.join("index"), gix_hash::Kind::Sha1, false, Default::default()).unwrap(); #[derive(Clone)] struct CountCalls(Arc, FastEq); diff --git a/gix-worktree-state/tests/state/checkout.rs b/gix-worktree-state/tests/state/checkout.rs index 4c2e1f87b5d..9fa590667f0 100644 --- a/gix-worktree-state/tests/state/checkout.rs +++ b/gix-worktree-state/tests/state/checkout.rs @@ -492,7 +492,7 @@ fn checkout_index_in_tmp_dir_opts( ) -> crate::Result<(PathBuf, TempDir, gix_index::File, gix_worktree_state::checkout::Outcome)> { let source_tree = fixture_path(name); let git_dir = source_tree.join(".git"); - let mut index = gix_index::File::at(git_dir.join("index"), gix_hash::Kind::Sha1, Default::default())?; + let mut index = gix_index::File::at(git_dir.join("index"), gix_hash::Kind::Sha1, false, Default::default())?; let odb = gix_odb::at(git_dir.join("objects"))?.into_inner().into_arc()?; let destination = gix_testtools::tempfile::tempdir_in(std::env::current_dir()?)?; prep_dest(destination.path()).expect("preparation must succeed"); diff --git a/gix-worktree/tests/worktree/stack/ignore.rs b/gix-worktree/tests/worktree/stack/ignore.rs index f20092b76b0..9173cb1f5f1 100644 --- a/gix-worktree/tests/worktree/stack/ignore.rs +++ b/gix-worktree/tests/worktree/stack/ignore.rs @@ -89,7 +89,7 @@ fn check_against_baseline() -> crate::Result { // Due to the way our setup differs from gits dynamic stack (which involves trying to read files from disk // by path) we can only test one case baseline, so we require multiple platforms (or filesystems) to run this. let case = probe_case()?; - let mut index = gix_index::File::at(git_dir.join("index"), gix_hash::Kind::Sha1, Default::default())?; + let mut index = gix_index::File::at(git_dir.join("index"), gix_hash::Kind::Sha1, false, Default::default())?; let odb = gix_odb::at(git_dir.join("objects"))?; let state = gix_worktree::stack::State::for_add( Default::default(), diff --git a/gix/src/config/tree/sections/index.rs b/gix/src/config/tree/sections/index.rs index 08f7ec1bd41..026f35b6daf 100644 --- a/gix/src/config/tree/sections/index.rs +++ b/gix/src/config/tree/sections/index.rs @@ -7,6 +7,9 @@ impl Index { /// The `index.threads` key. pub const THREADS: IndexThreads = IndexThreads::new_with_validate("threads", &config::Tree::INDEX, validate::IndexThreads); + /// The `index.skipHash` key. + pub const SKIP_HASH: keys::Boolean = keys::Boolean::new_boolean("skipHash", &config::Tree::INDEX) + .with_deviation("also used to skip the hash when reading, even if a hash exists in the index file"); } /// The `index.threads` key. @@ -47,7 +50,7 @@ impl Section for Index { } fn keys(&self) -> &[&dyn Key] { - &[&Self::THREADS] + &[&Self::THREADS, &Self::SKIP_HASH] } } diff --git a/gix/src/head/mod.rs b/gix/src/head/mod.rs index 094e78a8642..399b872ba95 100644 --- a/gix/src/head/mod.rs +++ b/gix/src/head/mod.rs @@ -101,8 +101,10 @@ mod remote { /// This is equivalent to calling [`Reference::remote(…)`][crate::Reference::remote()] and /// [`Repository::remote_default_name()`][crate::Repository::remote_default_name()] in order. /// - /// Combine it with [`find_default_remote()`][crate::Repository::find_default_remote()] as fallback to handle detached heads, - /// i.e. obtain a remote even in case of detached heads. + /// Combine it with [`Repository::find_default_remote()`][crate::Repository::find_default_remote()] as fallback to + /// handle detached heads, i.e. obtain a remote even in case of detached heads, + /// or call [`Repository::find_fetch_remote(…)`](crate::Repository::find_fetch_remote()) for the highest-level way of finding + /// the right remote, just like `git fetch` does. pub fn into_remote( self, direction: remote::Direction, diff --git a/gix/src/lib.rs b/gix/src/lib.rs index 2215695fb47..e68037e9780 100644 --- a/gix/src/lib.rs +++ b/gix/src/lib.rs @@ -71,6 +71,7 @@ )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![deny(missing_docs, rust_2018_idioms, unsafe_code)] +#![allow(clippy::result_large_err)] // Re-exports to make this a potential one-stop shop crate avoiding people from having to reference various crates themselves. // This also means that their major version changes affect our major version, but that's alright as we directly expose their diff --git a/gix/src/remote/errors.rs b/gix/src/remote/errors.rs index 20060cedf5e..34ed8246b24 100644 --- a/gix/src/remote/errors.rs +++ b/gix/src/remote/errors.rs @@ -2,7 +2,7 @@ pub mod find { use crate::{bstr::BString, config, remote}; - /// The error returned by [`Repository::find_remote(…)`][crate::Repository::find_remote()]. + /// The error returned by [`Repository::find_remote(…)`](crate::Repository::find_remote()). #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { @@ -30,7 +30,7 @@ pub mod find { pub mod existing { use crate::bstr::BString; - /// The error returned by [`Repository::find_remote(…)`][crate::Repository::find_remote()]. + /// The error returned by [`Repository::find_remote(…)`](crate::Repository::find_remote()). #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { @@ -42,4 +42,23 @@ pub mod find { NotFound { name: BString }, } } + + /// + pub mod for_fetch { + /// The error returned by [`Repository::find_fetch_remote(…)`](crate::Repository::find_fetch_remote()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + FindExisting(#[from] super::existing::Error), + #[error(transparent)] + FindExistingReferences(#[from] crate::reference::find::existing::Error), + #[error("Could not initialize a URL remote")] + Init(#[from] crate::remote::init::Error), + #[error("remote name could not be parsed as URL")] + UrlParse(#[from] gix_url::parse::Error), + #[error("No configured remote could be found, or too many were available")] + ExactlyOneRemoteNotAvailable, + } + } } diff --git a/gix/src/repository/index.rs b/gix/src/repository/index.rs index e823d233e57..a21b138a569 100644 --- a/gix/src/repository/index.rs +++ b/gix/src/repository/index.rs @@ -16,16 +16,27 @@ impl crate::Repository { .map(|value| crate::config::tree::Index::THREADS.try_into_index_threads(value)) .transpose() .with_lenient_default(self.config.lenient_config)?; - gix_index::File::at( + let skip_hash = self + .config + .resolved + .boolean("index", None, "skipHash") + .map(|res| crate::config::tree::Index::SKIP_HASH.enrich_error(res)) + .transpose() + .with_lenient_default(self.config.lenient_config)? + .unwrap_or_default(); + + let index = gix_index::File::at( self.index_path(), self.object_hash(), + skip_hash, gix_index::decode::Options { thread_limit, min_extension_block_in_bytes_for_threading: 0, expected_checksum: None, }, - ) - .map_err(Into::into) + )?; + + Ok(index) } /// Return a shared worktree index which is updated automatically if the in-memory snapshot has become stale as the underlying file diff --git a/gix/src/repository/remote.rs b/gix/src/repository/remote.rs index 74ebbaea027..9d535e3a5b0 100644 --- a/gix/src/repository/remote.rs +++ b/gix/src/repository/remote.rs @@ -28,7 +28,8 @@ impl crate::Repository { Remote::from_fetch_url(url, false, self) } - /// Find the remote with the given `name_or_url` or report an error, similar to [`try_find_remote(…)`][Self::try_find_remote()]. + /// Find the configured remote with the given `name_or_url` or report an error, + /// similar to [`try_find_remote(…)`][Self::try_find_remote()]. /// /// Note that we will obtain remotes only if we deem them [trustworthy][crate::open::Options::filter_config_section()]. pub fn find_remote<'a>(&self, name_or_url: impl Into<&'a BStr>) -> Result, find::existing::Error> { @@ -42,7 +43,7 @@ impl crate::Repository { /// Find the default remote as configured, or `None` if no such configuration could be found. /// - /// See [`remote_default_name()`][Self::remote_default_name()] for more information on the `direction` parameter. + /// See [`remote_default_name()`](Self::remote_default_name()) for more information on the `direction` parameter. pub fn find_default_remote( &self, direction: remote::Direction, @@ -51,8 +52,8 @@ impl crate::Repository { .map(|name| self.find_remote(name.as_ref())) } - /// Find the remote with the given `name_or_url` or return `None` if it doesn't exist, for the purpose of fetching or pushing - /// data to a remote. + /// Find the configured remote with the given `name_or_url` or return `None` if it doesn't exist, + /// for the purpose of fetching or pushing data. /// /// There are various error kinds related to partial information or incorrectly formatted URLs or ref-specs. /// Also note that the created `Remote` may have neither fetch nor push ref-specs set at all. @@ -65,6 +66,35 @@ impl crate::Repository { self.try_find_remote_inner(name_or_url, true) } + /// This method emulate what `git fetch ` does in order to obtain a remote to fetch from. + /// + /// As such, with `name_or_url` being `Some`, it will: + /// + /// * use `name_or_url` verbatim if it is a URL, creating a new remote in memory as needed. + /// * find the named remote if `name_or_url` is a remote name + /// + /// If `name_or_url` is `None`: + /// + /// * use the current `HEAD` branch to find a configured remote + /// * fall back to either a generally configured remote or the only configured remote. + /// + /// Fail if no remote could be found despite all of the above. + pub fn find_fetch_remote(&self, name_or_url: Option<&BStr>) -> Result, find::for_fetch::Error> { + Ok(match name_or_url { + Some(name) => match self.try_find_remote(name).and_then(Result::ok) { + Some(remote) => remote, + None => self.remote_at(gix_url::parse(name)?)?, + }, + None => self + .head()? + .into_remote(remote::Direction::Fetch) + .transpose()? + .map(Ok) + .or_else(|| self.find_default_remote(remote::Direction::Fetch)) + .ok_or_else(|| find::for_fetch::Error::ExactlyOneRemoteNotAvailable)??, + }) + } + /// Similar to [`try_find_remote()`][Self::try_find_remote()], but removes a failure mode if rewritten URLs turn out to be invalid /// as it skips rewriting them. /// Use this in conjunction with [`Remote::rewrite_urls()`] to non-destructively apply the rules and keep the failed urls unchanged. diff --git a/gix/src/worktree/mod.rs b/gix/src/worktree/mod.rs index 18be6dc1fe6..c780d8838e5 100644 --- a/gix/src/worktree/mod.rs +++ b/gix/src/worktree/mod.rs @@ -90,7 +90,11 @@ pub mod open_index { #[error(transparent)] ConfigIndexThreads(#[from] crate::config::key::GenericErrorWithValue), #[error(transparent)] + ConfigSkipHash(#[from] crate::config::boolean::Error), + #[error(transparent)] IndexFile(#[from] gix_index::file::init::Error), + #[error(transparent)] + IndexCorrupt(#[from] gix_index::file::verify::Error), } impl<'repo> crate::Worktree<'repo> { diff --git a/gix/tests/head/mod.rs b/gix/tests/head/mod.rs index b8508a4c70f..14da5604804 100644 --- a/gix/tests/head/mod.rs +++ b/gix/tests/head/mod.rs @@ -9,6 +9,11 @@ mod into_remote { repo.head()?.into_remote(gix::remote::Direction::Fetch).transpose()?, None ); + assert_eq!( + repo.find_fetch_remote(None)?.name().expect("present").as_ref(), + "origin", + "we can fallback to the only available remote" + ); Ok(()) } @@ -19,6 +24,11 @@ mod into_remote { repo.head()?.into_remote(gix::remote::Direction::Fetch).transpose()?, None ); + assert_eq!( + repo.find_fetch_remote(None)?.name().expect("present").as_ref(), + "origin", + "we can fallback to the only available remote" + ); Ok(()) } } diff --git a/gix/tests/reference/remote.rs b/gix/tests/reference/remote.rs index 8e8a09392aa..e75cc6e4ea8 100644 --- a/gix/tests/reference/remote.rs +++ b/gix/tests/reference/remote.rs @@ -78,6 +78,13 @@ fn not_configured() -> crate::Result { assert_eq!(branch.remote_name(gix::remote::Direction::Fetch), None); assert_eq!(branch.remote(gix::remote::Direction::Fetch).transpose()?, None); assert_eq!(head.into_remote(gix::remote::Direction::Fetch).transpose()?, None); + assert!( + matches!( + repo.find_fetch_remote(None), + Err(gix::remote::find::for_fetch::Error::ExactlyOneRemoteNotAvailable) + ), + "there is no remote to be found" + ); Ok(()) } diff --git a/gix/tests/repository/remote.rs b/gix/tests/repository/remote.rs index ae31fd8f737..d72ab61d000 100644 --- a/gix/tests/repository/remote.rs +++ b/gix/tests/repository/remote.rs @@ -291,6 +291,46 @@ mod find_remote { } } +mod find_fetch_remote { + use crate::remote; + + #[test] + fn symbol_name() -> crate::Result { + let repo = remote::repo("clone-no-tags"); + assert_eq!( + repo.find_fetch_remote(Some("origin".into()))? + .name() + .expect("set") + .as_bstr(), + "origin" + ); + Ok(()) + } + + #[test] + fn urls() -> crate::Result { + let repo = remote::repo("clone-no-tags"); + for url in [ + "some-path", + "https://example.com/repo", + "other/path", + "ssh://host/ssh-aliased-repo", + ] { + let remote = repo.find_fetch_remote(Some(url.into()))?; + assert_eq!(remote.name(), None, "this remote is anonymous"); + assert_eq!( + remote + .url(gix::remote::Direction::Fetch) + .expect("url is set") + .to_bstring(), + url, + "if it's not a configured remote, we take it as URL" + ); + } + Ok(()) + } +} + mod find_default_remote { use crate::remote; diff --git a/src/ein.rs b/src/ein.rs index bfb4ba4716d..ebcfa5a9965 100644 --- a/src/ein.rs +++ b/src/ein.rs @@ -1,11 +1,8 @@ #![deny(rust_2018_idioms, unsafe_code)] mod porcelain; -mod shared; -use anyhow::Result; - -fn main() -> Result<()> { +fn main() -> anyhow::Result<()> { porcelain::main() } diff --git a/src/lib.rs b/src/lib.rs index 3f478012391..9a3286676da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,5 +20,9 @@ cfg_attr(doc, doc = ::document_features::document_features!()) )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -#![deny(rust_2018_idioms, missing_docs)] +#![deny(rust_2018_idioms)] +#![allow(missing_docs)] #![forbid(unsafe_code)] + +/// everything in common beteween the `gix` and `ein` binaries. +pub mod shared; diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 38b6ba261b8..542fb402b4f 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -26,7 +26,7 @@ use crate::{ #[cfg(feature = "gitoxide-core-async-client")] pub mod async_util { - use crate::shared::ProgressRange; + use gitoxide::shared::ProgressRange; #[cfg(not(feature = "prodash-render-line"))] compile_error!("BUG: Need at least a line renderer in async mode"); @@ -39,7 +39,7 @@ pub mod async_util { Option, gix_features::progress::DoOrDiscard, ) { - use crate::shared::{self, STANDARD_RANGE}; + use gitoxide::shared::{self, STANDARD_RANGE}; shared::init_env_logger(); if verbose { @@ -447,6 +447,7 @@ pub fn main() -> Result<()> { free::index::Subcommands::FromList { force, index_output_path, + skip_hash, file, } => prepare_and_run( "index-from-list", @@ -455,7 +456,9 @@ pub fn main() -> Result<()> { progress, progress_keep_open, None, - move |_progress, _out, _err| core::repository::index::from_list(file, index_output_path, force), + move |_progress, _out, _err| { + core::repository::index::from_list(file, index_output_path, force, skip_hash) + }, ), free::index::Subcommands::CheckoutExclusive { directory, @@ -1164,6 +1167,7 @@ pub fn main() -> Result<()> { index::Subcommands::FromTree { force, index_output_path, + skip_hash, spec, } => prepare_and_run( "index-from-tree", @@ -1173,7 +1177,13 @@ pub fn main() -> Result<()> { progress_keep_open, None, move |_progress, _out, _err| { - core::repository::index::from_tree(spec, index_output_path, force, repository(Mode::Strict)?) + core::repository::index::from_tree( + repository(Mode::Strict)?, + spec, + index_output_path, + force, + skip_hash, + ) }, ), }, diff --git a/src/plumbing/options/free.rs b/src/plumbing/options/free.rs index 9f945c5c640..64aa443d4de 100644 --- a/src/plumbing/options/free.rs +++ b/src/plumbing/options/free.rs @@ -39,7 +39,7 @@ pub mod index { #[derive(Debug, clap::Parser)] pub struct Platform { /// The object format to assume when reading files that don't inherently know about it, or when writing files. - #[clap(long, default_value_t = gix::hash::Kind::default(), value_parser = crate::shared::AsHashKind)] + #[clap(long, default_value_t = gix::hash::Kind::default(), value_parser = gitoxide::shared::AsHashKind)] pub object_hash: gix::hash::Kind, /// The path to the index file. @@ -63,6 +63,9 @@ pub mod index { /// back by default, but that requires us to write more of the index to work. #[clap(long, short = 'i')] index_output_path: Option, + /// Don't write the trailing hash for a performance gain. + #[clap(long, short = 's')] + skip_hash: bool, /// The file to read the index entries from, one path per line. file: PathBuf, }, diff --git a/src/plumbing/options/mod.rs b/src/plumbing/options/mod.rs index 048bc60a6e9..54a6d8db3c8 100644 --- a/src/plumbing/options/mod.rs +++ b/src/plumbing/options/mod.rs @@ -16,7 +16,7 @@ pub struct Args { /// /// For example, if `key` is `core.abbrev`, set configuration like `[core] abbrev = key`, /// or `remote.origin.url = foo` to set `[remote "origin"] url = foo`. - #[clap(long, short = 'c', value_parser = crate::shared::AsBString)] + #[clap(long, short = 'c', value_parser = gitoxide::shared::AsBString)] pub config: Vec, #[clap(long, short = 't')] @@ -63,12 +63,12 @@ pub struct Args { long, short = 'f', default_value = "human", - value_parser = crate::shared::AsOutputFormat + value_parser = gitoxide::shared::AsOutputFormat )] pub format: core::OutputFormat, /// The object format to assume when reading files that don't inherently know about it, or when writing files. - #[clap(long, default_value_t = gix::hash::Kind::default(), value_parser = crate::shared::AsHashKind)] + #[clap(long, default_value_t = gix::hash::Kind::default(), value_parser = gitoxide::shared::AsHashKind)] pub object_hash: gix::hash::Kind, #[clap(subcommand)] @@ -237,7 +237,7 @@ pub mod config { /// /// Typical filters are `branch` or `remote.origin` or `remote.or*` - git-style globs are supported /// and comparisons are case-insensitive. - #[clap(value_parser = crate::shared::AsBString)] + #[clap(value_parser = gitoxide::shared::AsBString)] pub filter: Vec, } } @@ -276,7 +276,7 @@ pub mod fetch { pub remote: Option, /// Override the built-in and configured ref-specs with one or more of the given ones. - #[clap(value_parser = crate::shared::AsBString)] + #[clap(value_parser = gitoxide::shared::AsBString)] pub ref_spec: Vec, } @@ -291,11 +291,11 @@ pub mod fetch { pub deepen: Option, /// Cutoff all history past the given date. Can be combined with shallow-exclude. - #[clap(long, help_heading = Some("SHALLOW"), value_parser = crate::shared::AsTime, value_name = "DATE", conflicts_with_all = ["depth", "deepen", "unshallow"])] + #[clap(long, help_heading = Some("SHALLOW"), value_parser = gitoxide::shared::AsTime, value_name = "DATE", conflicts_with_all = ["depth", "deepen", "unshallow"])] pub shallow_since: Option, /// Cutoff all history past the tag-name or ref-name. Can be combined with shallow-since. - #[clap(long, help_heading = Some("SHALLOW"), value_parser = crate::shared::AsPartialRefName, value_name = "REF_NAME", conflicts_with_all = ["depth", "deepen", "unshallow"])] + #[clap(long, help_heading = Some("SHALLOW"), value_parser = gitoxide::shared::AsPartialRefName, value_name = "REF_NAME", conflicts_with_all = ["depth", "deepen", "unshallow"])] pub shallow_exclude: Vec, /// Remove the shallow boundary and fetch the entire history available on the remote. @@ -362,11 +362,11 @@ pub mod clone { pub depth: Option, /// Cutoff all history past the given date. Can be combined with shallow-exclude. - #[clap(long, help_heading = Some("SHALLOW"), value_parser = crate::shared::AsTime, value_name = "DATE")] + #[clap(long, help_heading = Some("SHALLOW"), value_parser = gitoxide::shared::AsTime, value_name = "DATE")] pub shallow_since: Option, /// Cutoff all history past the tag-name or ref-name. Can be combined with shallow-since. - #[clap(long, help_heading = Some("SHALLOW"), value_parser = crate::shared::AsPartialRefName, value_name = "REF_NAME")] + #[clap(long, help_heading = Some("SHALLOW"), value_parser = gitoxide::shared::AsPartialRefName, value_name = "REF_NAME")] pub shallow_exclude: Vec, } @@ -418,7 +418,7 @@ pub mod remote { #[clap(long, short = 'u')] show_unmapped_remote_refs: bool, /// Override the built-in and configured ref-specs with one or more of the given ones. - #[clap(value_parser = crate::shared::AsBString)] + #[clap(value_parser = gitoxide::shared::AsBString)] ref_spec: Vec, }, } @@ -594,7 +594,7 @@ pub mod revision { pub mod attributes { use gix::bstr::BString; - use crate::shared::CheckPathSpec; + use gitoxide::shared::CheckPathSpec; #[derive(Debug, clap::Subcommand)] pub enum Subcommands { @@ -625,7 +625,7 @@ pub mod exclude { use gix::bstr::BString; - use crate::shared::CheckPathSpec; + use gitoxide::shared::CheckPathSpec; #[derive(Debug, clap::Subcommand)] pub enum Subcommands { @@ -656,7 +656,7 @@ pub mod index { use gix::bstr::BString; - use crate::shared::CheckPathSpec; + use gitoxide::shared::CheckPathSpec; pub mod entries { #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] @@ -705,6 +705,9 @@ pub mod index { /// back by default, but that requires us to write more of the index to work. #[clap(long, short = 'i')] index_output_path: Option, + /// Don't write the trailing hash for a performance gain. + #[clap(long, short = 's')] + skip_hash: bool, /// A revspec that points to the to generate the index from. spec: std::ffi::OsString, }, diff --git a/src/plumbing/progress.rs b/src/plumbing/progress.rs index 08c3e70f607..21b56fa71d8 100644 --- a/src/plumbing/progress.rs +++ b/src/plumbing/progress.rs @@ -173,7 +173,7 @@ static GIT_CONFIG: &[Record] = &[ usage: NotApplicable {reason: "parallelism is efficient enough to always run with benefit"}, }, Record { - config: "feature.manyFile", + config: "feature.manyFiles", usage: Planned {note: Some("big repositories are on the roadmap")}, }, Record { @@ -196,10 +196,6 @@ static GIT_CONFIG: &[Record] = &[ config: "index.sparse", usage: Planned {note: Some("we can read sparse indices and support for it will be added early on")}, }, - Record { - config: "index.skipHash", - usage: Planned {note: Some("important to not unnecessarily reject indices just because they are missing a hash (or it is null)")}, - }, Record { config: "merge.renormalize", usage: Planned {note: Some("once merging is being implemented, renormalization should be respected")}, @@ -504,6 +500,10 @@ static GIT_CONFIG: &[Record] = &[ config: "status.renames", usage: Planned { note: Some("the same as diff.renames") } }, + Record { + config: "transfer.credentialsInUrl", + usage: Planned { note: Some("currently we are likely to expose passwords in errors or in other places, and it's better to by default not do that") } + }, Record { config: "diff.*.textconv", usage: Planned { note: None } diff --git a/src/porcelain/main.rs b/src/porcelain/main.rs index abd21e1cf67..6f7224e2b98 100644 --- a/src/porcelain/main.rs +++ b/src/porcelain/main.rs @@ -7,10 +7,8 @@ use anyhow::Result; use clap::Parser; use gitoxide_core as core; -use crate::{ - porcelain::options::{Args, Subcommands}, - shared::pretty::prepare_and_run, -}; +use crate::porcelain::options::{Args, Subcommands}; +use gitoxide::shared::pretty::prepare_and_run; pub fn main() -> Result<()> { let args: Args = Args::parse_from(gix::env::args_os()); @@ -39,7 +37,7 @@ pub fn main() -> Result<()> { verbose, progress, progress_keep_open, - crate::shared::STANDARD_RANGE, + gitoxide::shared::STANDARD_RANGE, move |_progress, _out, _err| panic!("something went very wrong"), ), Subcommands::Init { directory } => core::repository::init(directory).map(|_| ()), @@ -59,7 +57,7 @@ pub fn main() -> Result<()> { verbose, progress, progress_keep_open, - crate::shared::STANDARD_RANGE, + gitoxide::shared::STANDARD_RANGE, move |mut progress, out, mut err| { let engine = query::prepare( &repo_dir, @@ -99,7 +97,7 @@ pub fn main() -> Result<()> { verbose, progress, progress_keep_open, - crate::shared::STANDARD_RANGE, + gitoxide::shared::STANDARD_RANGE, move |progress, out, _err| { hours::estimate( &working_dir, @@ -126,7 +124,7 @@ pub fn main() -> Result<()> { verbose, progress, progress_keep_open, - crate::shared::STANDARD_RANGE, + gitoxide::shared::STANDARD_RANGE, move |progress, out, _err| { organize::discover( root.unwrap_or_else(|| [std::path::Component::CurDir].iter().collect()), @@ -150,7 +148,7 @@ pub fn main() -> Result<()> { verbose, progress, progress_keep_open, - crate::shared::STANDARD_RANGE, + gitoxide::shared::STANDARD_RANGE, move |progress, _out, _err| { organize::run( if execute { diff --git a/src/porcelain/options.rs b/src/porcelain/options.rs index 7c583fe4ced..32533ad890f 100644 --- a/src/porcelain/options.rs +++ b/src/porcelain/options.rs @@ -116,7 +116,7 @@ pub mod tools { #[cfg(feature = "gitoxide-core-tools-query")] pub mod query { - use crate::shared::AsPathSpec; + use gitoxide::shared::AsPathSpec; #[derive(Debug, clap::Subcommand)] pub enum Command { @@ -143,7 +143,7 @@ pub mod tools { #[clap(default_value = ".")] pub working_dir: PathBuf, /// The name of the revision as spec, like 'HEAD' or 'main' at which to start iterating the commit graph. - #[clap(default_value("HEAD"), value_parser = crate::shared::AsBString)] + #[clap(default_value("HEAD"), value_parser = gitoxide::shared::AsBString)] pub rev_spec: BString, /// Ignore github bots which match the `[bot]` search string. #[clap(short = 'b', long)] diff --git a/src/shared.rs b/src/shared.rs index 892e14c034b..550c6c6c5ab 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -101,8 +101,8 @@ pub mod pretty { enable: bool, reverse_lines: bool, progress: &gix::progress::prodash::tree::Root, - ) -> anyhow::Result { - Ok(if enable { + ) -> anyhow::Result<()> { + if enable { let processor = tracing_forest::Printer::new().formatter({ let progress = std::sync::Mutex::new(progress.add_child("tracing")); move |tree: &tracing_forest::tree::Tree| -> Result { @@ -124,10 +124,11 @@ pub mod pretty { }); use tracing_subscriber::layer::SubscriberExt; let subscriber = tracing_subscriber::Registry::default().with(tracing_forest::ForestLayer::from(processor)); - tracing::subscriber::set_default(subscriber) + tracing::subscriber::set_global_default(subscriber)?; } else { - tracing::subscriber::set_default(tracing_subscriber::Registry::default()) - }) + tracing::subscriber::set_global_default(tracing_subscriber::Registry::default())?; + } + Ok(()) } #[cfg(not(feature = "small"))] @@ -158,7 +159,7 @@ pub mod pretty { use crate::shared::{self, STANDARD_RANGE}; let progress = shared::progress_tree(); let sub_progress = progress.add_child(name); - let _trace = init_tracing(trace, false, &progress)?; + init_tracing(trace, false, &progress)?; let handle = shared::setup_line_renderer_range(&progress, range.into().unwrap_or(STANDARD_RANGE)); diff --git a/tests/snapshots/panic-behaviour/expected-failure b/tests/snapshots/panic-behaviour/expected-failure index c09ee20e7bb..1dafa93b97b 100644 --- a/tests/snapshots/panic-behaviour/expected-failure +++ b/tests/snapshots/panic-behaviour/expected-failure @@ -1,2 +1,2 @@ -thread 'main' panicked at 'something went very wrong', src/porcelain/main.rs:43:42 +thread 'main' panicked at 'something went very wrong', src/porcelain/main.rs:41:42 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace \ No newline at end of file diff --git a/tests/snapshots/panic-behaviour/expected-failure-in-thread b/tests/snapshots/panic-behaviour/expected-failure-in-thread index 3c81842d3fe..b99a2650670 100644 --- a/tests/snapshots/panic-behaviour/expected-failure-in-thread +++ b/tests/snapshots/panic-behaviour/expected-failure-in-thread @@ -1,3 +1,3 @@ -thread 'main' panicked at 'something went very wrong', src/porcelain/main.rs:43:42 +thread 'main' panicked at 'something went very wrong', src/porcelain/main.rs:41:42 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace  \ No newline at end of file diff --git a/tests/snapshots/panic-behaviour/expected-failure-in-thread-with-progress b/tests/snapshots/panic-behaviour/expected-failure-in-thread-with-progress index c1649532e46..c892c1ea47d 100644 --- a/tests/snapshots/panic-behaviour/expected-failure-in-thread-with-progress +++ b/tests/snapshots/panic-behaviour/expected-failure-in-thread-with-progress @@ -1,3 +1,3 @@ -[?1049h[?25lthread '' panicked at 'something went very wrong', src/porcelain/main.rs:43:42 +[?1049h[?25lthread '' panicked at 'something went very wrong', src/porcelain/main.rs:41:42 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace [?25h[?1049l \ No newline at end of file