diff --git a/Cargo.lock b/Cargo.lock index 49f90f32712..23988d4e944 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1526,6 +1526,7 @@ dependencies = [ "git-diff 0.13.0", "git-features 0.19.1", "git-hash 0.9.1", + "git-index", "git-lock 1.0.1", "git-object 0.17.0", "git-odb 0.26.0", diff --git a/git-bitmap/src/ewah.rs b/git-bitmap/src/ewah.rs index e504050da2c..7575a59e2d6 100644 --- a/git-bitmap/src/ewah.rs +++ b/git-bitmap/src/ewah.rs @@ -119,6 +119,6 @@ mod access { pub struct Vec { num_bits: u32, bits: std::vec::Vec, - /// RLW is an offset into the `bits` buffer, so `1` translates into &bits[1] essentially. + /// RLW is an offset into the `bits` buffer, so `1` translates into &bits\[1] essentially. rlw: usize, } diff --git a/git-hash/src/lib.rs b/git-hash/src/lib.rs index ac101385fd0..c4f5b2495a2 100644 --- a/git-hash/src/lib.rs +++ b/git-hash/src/lib.rs @@ -96,6 +96,14 @@ impl FromStr for Kind { } } +impl std::fmt::Display for Kind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Kind::Sha1 => f.write_str("SHA1"), + } + } +} + impl Kind { /// Returns the shortest hash we support #[inline] diff --git a/git-index/Cargo.toml b/git-index/Cargo.toml index 5f61bf2564c..1e42bb8fe6d 100644 --- a/git-index/Cargo.toml +++ b/git-index/Cargo.toml @@ -22,7 +22,7 @@ path = "tests/index-single-threaded.rs" required-features = ["internal-testing-to-avoid-being-run-by-cargo-test-all"] [features] -serde1 = ["serde"] +serde1 = ["serde", "smallvec/serde", "git-hash/serde1"] internal-testing-git-features-parallel = ["git-features/parallel"] internal-testing-to-avoid-being-run-by-cargo-test-all = [] diff --git a/git-index/src/entry.rs b/git-index/src/entry.rs index 6710d845e7d..5d155859712 100644 --- a/git-index/src/entry.rs +++ b/git-index/src/entry.rs @@ -61,6 +61,7 @@ pub(crate) mod at_rest { bitflags! { /// In-memory flags pub struct Flags: u32 { + const STAGE_MASK = 0x3000; // TODO: could we use the pathlen ourselves to save 8 bytes? And how to handle longer paths than that? 0 as sentinel maybe? const PATH_LEN = 0x0fff; const UPDATE = 1 << 16; @@ -88,11 +89,21 @@ bitflags! { } } +impl Flags { + pub fn stage(&self) -> u32 { + (*self & Flags::STAGE_MASK).bits >> 12 + } +} + +#[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Time { pub secs: u32, pub nsecs: u32, } +#[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Stat { pub mtime: Time, pub ctime: Time, diff --git a/git-index/src/extension/fs_monitor.rs b/git-index/src/extension/fs_monitor.rs new file mode 100644 index 00000000000..a58885bdb6a --- /dev/null +++ b/git-index/src/extension/fs_monitor.rs @@ -0,0 +1,37 @@ +use bstr::BString; + +use crate::{ + extension::{FsMonitor, Signature}, + util::{read_u32, read_u64, split_at_byte_exclusive}, +}; + +pub enum Token { + V1 { nanos_since_1970: u64 }, + V2 { token: BString }, +} + +pub const SIGNATURE: Signature = *b"FSMN"; + +pub fn decode(data: &[u8]) -> Option { + let (version, data) = read_u32(data)?; + let (token, data) = match version { + 1 => { + let (nanos_since_1970, data) = read_u64(data)?; + (Token::V1 { nanos_since_1970 }, data) + } + 2 => { + let (token, data) = split_at_byte_exclusive(data, 0)?; + (Token::V2 { token: token.into() }, data) + } + _ => return None, + }; + + let (ewah_size, data) = read_u32(data)?; + let (entry_dirty, data) = git_bitmap::ewah::decode(&data[..ewah_size as usize]).ok()?; + + if !data.is_empty() { + return None; + } + + FsMonitor { token, entry_dirty }.into() +} diff --git a/git-index/src/extension/mod.rs b/git-index/src/extension/mod.rs index 7959a692b16..ba0b48b10a4 100644 --- a/git-index/src/extension/mod.rs +++ b/git-index/src/extension/mod.rs @@ -15,10 +15,10 @@ pub struct Iter<'a> { /// It allows to more quickly build trees by avoiding as it can quickly re-use portions of the index and its associated tree ids /// if there was no change to them. Portions of this tree are invalidated as the index is changed. pub struct Tree { - name: SmallVec<[u8; 23]>, + pub name: SmallVec<[u8; 23]>, /// Only set if there are any entries in the index we are associated with. - id: Option, - children: Vec, + pub id: Option, + pub children: Vec, } pub struct Link { @@ -50,45 +50,7 @@ pub struct FsMonitor { mod iter; -pub(crate) mod fs_monitor { - use bstr::BString; - - use crate::{ - extension::{FsMonitor, Signature}, - util::{read_u32, read_u64, split_at_byte_exclusive}, - }; - - pub enum Token { - V1 { nanos_since_1970: u64 }, - V2 { token: BString }, - } - - pub const SIGNATURE: Signature = *b"FSMN"; - - pub fn decode(data: &[u8]) -> Option { - let (version, data) = read_u32(data)?; - let (token, data) = match version { - 1 => { - let (nanos_since_1970, data) = read_u64(data)?; - (Token::V1 { nanos_since_1970 }, data) - } - 2 => { - let (token, data) = split_at_byte_exclusive(data, 0)?; - (Token::V2 { token: token.into() }, data) - } - _ => return None, - }; - - let (ewah_size, data) = read_u32(data)?; - let (entry_dirty, data) = git_bitmap::ewah::decode(&data[..ewah_size as usize]).ok()?; - - if !data.is_empty() { - return None; - } - - FsMonitor { token, entry_dirty }.into() - } -} +pub(crate) mod fs_monitor; pub(crate) mod decode; diff --git a/git-index/src/extension/tree.rs b/git-index/src/extension/tree.rs index f7d97fbefe2..f26c8cc785c 100644 --- a/git-index/src/extension/tree.rs +++ b/git-index/src/extension/tree.rs @@ -9,9 +9,9 @@ pub const SIGNATURE: Signature = *b"TREE"; pub struct NodeId { /// The id of the directory tree of the associated tree object. - id: git_hash::ObjectId, + pub id: git_hash::ObjectId, /// The amount of non-tree entries contained within, and definitely not zero. - entry_count: u32, + pub entry_count: u32, } /// A recursive data structure diff --git a/git-index/src/lib.rs b/git-index/src/lib.rs index f1beb940933..ef2237b1edc 100644 --- a/git-index/src/lib.rs +++ b/git-index/src/lib.rs @@ -7,12 +7,12 @@ use filetime::FileTime; pub mod file; -pub(crate) mod extension; +pub mod extension; pub mod entry; mod access { - use crate::{Entry, State, Version}; + use crate::{extension, Entry, State, Version}; impl State { pub fn version(&self) -> Version { @@ -22,6 +22,21 @@ mod access { pub fn entries(&self) -> &[Entry] { &self.entries } + pub fn tree(&self) -> Option<&extension::Tree> { + self.tree.as_ref() + } + pub fn link(&self) -> Option<&extension::Link> { + self.link.as_ref() + } + pub fn resolve_undo(&self) -> Option<&extension::resolve_undo::Paths> { + self.resolve_undo.as_ref() + } + pub fn untracked(&self) -> Option<&extension::UntrackedCache> { + self.untracked.as_ref() + } + pub fn fs_monitor(&self) -> Option<&extension::FsMonitor> { + self.fs_monitor.as_ref() + } } } diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index 55f4aed9f60..f4bfcee8fca 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -14,8 +14,8 @@ test = true [features] default = ["max-performance", "one-stop-shop"] -unstable = [] -serde1 = ["git-pack/serde1", "git-object/serde1", "git-protocol/serde1", "git-transport/serde1", "git-ref/serde1", "git-odb/serde1"] +unstable = ["git-index"] +serde1 = ["git-pack/serde1", "git-object/serde1", "git-protocol/serde1", "git-transport/serde1", "git-ref/serde1", "git-odb/serde1", "git-index/serde1"] # enable when https://github.com/RustCrypto/asm-hashes/issues/17 is fixed # max-performance = ["git-features/parallel", "git-features/zlib-ng-compat", "git-features/fast-sha1"] max-performance = ["git-features/parallel", "git-features/zlib-ng-compat", "git-pack/pack-cache-lru-static", "git-pack/pack-cache-lru-dynamic"] @@ -54,6 +54,9 @@ git-transport = { version = "^0.15.0", path = "../git-transport", optional = tru git-diff = { version = "^0.13.0", path = "../git-diff", optional = true } git-features = { version = "^0.19.1", path = "../git-features", features = ["progress"] } +# unstable only +git-index = { version ="^0.1.0", path = "../git-index", optional = true } + signal-hook = { version = "0.3.9", default-features = false } thiserror = "1.0.26" clru = "0.5.0" diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 8f47c687b0c..935897a75a1 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -86,6 +86,7 @@ //! * [`url`] //! * [`actor`] //! * [`bstr`][bstr] +//! * [`index`] //! * [`objs`] //! * [`odb`] //! * [`pack`][odb::pack] @@ -118,6 +119,8 @@ pub use git_features::{parallel, progress, progress::Progress, threading}; pub use git_hash as hash; #[doc(inline)] pub use git_hash::{oid, ObjectId}; +#[cfg(all(feature = "unstable", feature = "git-index"))] +pub use git_index as index; pub use git_lock as lock; pub use git_object as objs; pub use git_object::bstr; diff --git a/gitoxide-core/src/index/entries.rs b/gitoxide-core/src/index/entries.rs new file mode 100644 index 00000000000..01e718824c5 --- /dev/null +++ b/gitoxide-core/src/index/entries.rs @@ -0,0 +1,63 @@ +use git_repository as git; + +#[cfg(feature = "serde1")] +pub(crate) fn to_json( + mut out: &mut impl std::io::Write, + file: &git::index::File, + entry: &git::index::Entry, + is_last: bool, +) -> anyhow::Result<()> { + use git_repository::bstr::ByteSlice; + + #[cfg_attr(feature = "serde1", derive(serde::Serialize))] + struct Entry<'a> { + stat: &'a git::index::entry::Stat, + hex_id: String, + flags: u32, + mode: u32, + path: std::borrow::Cow<'a, str>, + } + + serde_json::to_writer( + &mut out, + &Entry { + stat: &entry.stat, + hex_id: entry.id.to_hex().to_string(), + flags: entry.flags.bits(), + mode: entry.mode.bits(), + path: entry.path(&file.state).to_str_lossy(), + }, + )?; + + if is_last { + out.write_all(b"\n")?; + } else { + out.write_all(b",\n")?; + } + Ok(()) +} + +pub(crate) fn to_human( + out: &mut impl std::io::Write, + file: &git::index::File, + entry: &git::index::Entry, +) -> std::io::Result<()> { + writeln!( + out, + "{} {}{:?} {} {}", + match entry.flags.stage() { + 0 => "BASE ", + 1 => "OURS ", + 2 => "THEIRS ", + _ => "UNKNOWN", + }, + if entry.flags.is_empty() { + "".to_string() + } else { + format!("{:?} ", entry.flags) + }, + entry.mode, + entry.id, + entry.path(&file.state) + ) +} diff --git a/gitoxide-core/src/index/information.rs b/gitoxide-core/src/index/information.rs new file mode 100644 index 00000000000..189facf709d --- /dev/null +++ b/gitoxide-core/src/index/information.rs @@ -0,0 +1,159 @@ +pub struct Options { + pub index: super::Options, + /// If true, show exstension in detail. + pub extension_details: bool, +} + +#[cfg(feature = "serde1")] +mod serde_only { + use git_repository as git; + mod ext { + #[derive(serde::Serialize)] + pub(crate) struct Tree { + name: String, + /// Only set if there are any entries in the index we are associated with. + id: Option, + children: Vec, + } + + mod tree { + use git_repository as git; + use git_repository::bstr::ByteSlice; + + impl<'a> From<&'a git::index::extension::Tree> for super::Tree { + fn from(t: &'a git_repository::index::extension::Tree) -> Self { + super::Tree { + name: t.name.as_bstr().to_string(), + id: t.id.as_ref().map(|id| NodeId { + entry_count: id.entry_count, + id: id.id.to_hex().to_string(), + }), + children: t.children.iter().map(|t| t.into()).collect(), + } + } + } + + #[derive(serde::Serialize, serde::Deserialize)] + pub struct NodeId { + /// The id of the directory tree of the associated tree object. + id: String, + /// The amount of non-tree entries contained within, and definitely not zero. + entry_count: u32, + } + } + } + + #[derive(serde::Serialize)] + pub(crate) struct EntryKind { + dir: usize, + file: usize, + executable: usize, + symlink: usize, + submodule: usize, + other: usize, + } + + #[derive(serde::Serialize)] + pub(crate) struct EntryFlag { + intent_to_add: usize, + skip_worktree: usize, + } + + #[derive(serde::Serialize)] + pub struct Entries { + stage_0: usize, + stage_1: usize, + stage_2: usize, + kind: EntryKind, + flags: EntryFlag, + } + + #[derive(serde::Serialize)] + pub struct Extensions { + names: Vec<&'static str>, + tree: Option, + } + + #[derive(serde::Serialize)] + pub struct Collection { + version: u8, + checksum: String, + entries: Entries, + extensions: Extensions, + } + + impl Collection { + pub fn try_from_file(f: git::index::File, extension_details: bool) -> anyhow::Result { + Ok(Collection { + version: f.version() as u8, + checksum: f.checksum.to_hex().to_string(), + extensions: { + let mut names = Vec::new(); + let tree = f.tree().and_then(|tree| { + names.push("tree (TREE)"); + extension_details.then(|| tree.into()) + }); + if f.link().is_some() { + names.push("link"); + }; + if f.resolve_undo().is_some() { + names.push("resolve-undo (REUC)"); + }; + if f.untracked().is_some() { + names.push("untracked (UNTR)"); + }; + if f.fs_monitor().is_some() { + names.push("fs-monitor (FSMN)"); + }; + Extensions { names, tree } + }, + entries: { + let (mut stage_0, mut stage_1, mut stage_2) = (0, 0, 0); + let (mut dir, mut file, mut executable, mut symlink, mut submodule, mut other) = (0, 0, 0, 0, 0, 0); + let (mut intent_to_add, mut skip_worktree) = (0, 0); + for entry in f.entries() { + match entry.flags.stage() { + 0 => stage_0 += 1, + 1 => stage_1 += 1, + 2 => stage_2 += 1, + invalid => anyhow::bail!("Invalid stage {} encountered", invalid), + } + match entry.mode { + git::index::entry::Mode::DIR => dir += 1, + git::index::entry::Mode::FILE => file += 1, + git::index::entry::Mode::FILE_EXECUTABLE => executable += 1, + git::index::entry::Mode::SYMLINK => symlink += 1, + git::index::entry::Mode::COMMIT => submodule += 1, + _ => other += 1, + } + if entry.flags.contains(git::index::entry::Flags::INTENT_TO_ADD) { + intent_to_add += 1; + } + if entry.flags.contains(git::index::entry::Flags::SKIP_WORKTREE) { + skip_worktree += 1; + } + } + Entries { + stage_0, + stage_1, + stage_2, + kind: EntryKind { + dir, + file, + executable, + symlink, + submodule, + other, + }, + flags: EntryFlag { + intent_to_add, + skip_worktree, + }, + } + }, + }) + } + } +} +#[cfg(feature = "serde1")] +pub(crate) use serde_only::Collection; diff --git a/gitoxide-core/src/index/mod.rs b/gitoxide-core/src/index/mod.rs new file mode 100644 index 00000000000..a1a9f1fa4d0 --- /dev/null +++ b/gitoxide-core/src/index/mod.rs @@ -0,0 +1,75 @@ +use std::path::Path; + +use git_repository as git; + +pub struct Options { + pub object_hash: git::hash::Kind, + pub format: crate::OutputFormat, +} + +mod entries; + +pub mod information; + +#[cfg_attr(not(feature = "serde1"), allow(unused_variables))] +pub fn information( + index_path: impl AsRef, + out: impl std::io::Write, + information::Options { + index: Options { object_hash, format }, + extension_details, + }: information::Options, +) -> anyhow::Result<()> { + use crate::OutputFormat::*; + match format { + Human => { + anyhow::bail!("Only JSON output is implemented"); + } + #[cfg(feature = "serde1")] + Json => { + let info = information::Collection::try_from_file(parse_file(index_path, object_hash)?, extension_details)?; + serde_json::to_writer_pretty(out, &info)?; + Ok(()) + } + } +} + +pub fn entries( + index_path: impl AsRef, + mut out: impl std::io::Write, + Options { object_hash, format }: Options, +) -> anyhow::Result<()> { + use crate::OutputFormat::*; + let file = parse_file(index_path, object_hash)?; + + #[cfg(feature = "serde1")] + if let Json = format { + out.write_all(b"[\n")?; + } + + let mut entries = file.entries().iter().peekable(); + while let Some(entry) = entries.next() { + match format { + Human => entries::to_human(&mut out, &file, entry)?, + #[cfg(feature = "serde1")] + Json => entries::to_json(&mut out, &file, entry, entries.peek().is_none())?, + } + } + + #[cfg(feature = "serde1")] + if let Json = format { + out.write_all(b"]\n")?; + } + Ok(()) +} + +fn parse_file(index_path: impl AsRef, object_hash: git::hash::Kind) -> anyhow::Result { + git::index::File::at( + index_path.as_ref(), + git::index::decode::Options { + object_hash, + ..Default::default() + }, + ) + .map_err(Into::into) +} diff --git a/gitoxide-core/src/lib.rs b/gitoxide-core/src/lib.rs index 6977191ee70..0f89d1d0f19 100644 --- a/gitoxide-core/src/lib.rs +++ b/gitoxide-core/src/lib.rs @@ -40,6 +40,7 @@ pub mod net; pub mod commitgraph; #[cfg(feature = "estimate-hours")] pub mod hours; +pub mod index; #[cfg(feature = "organize")] pub mod organize; pub mod pack; diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 6824f0abdbf..2fff55607bd 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -15,7 +15,7 @@ use gitoxide_core::pack::verify; #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] use crate::plumbing::options::remote; use crate::{ - plumbing::options::{commitgraph, pack, repo, Args, Subcommands}, + plumbing::options::{commitgraph, index, pack, repo, Args, Subcommands}, shared::pretty::prepare_and_run, }; @@ -73,6 +73,39 @@ pub fn main() -> Result<()> { })?; match cmd { + Subcommands::Index(index::Platform { + object_hash, + index_path, + cmd, + }) => match cmd { + index::Subcommands::Info { no_details } => prepare_and_run( + "index-entries", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| { + core::index::information( + index_path, + out, + core::index::information::Options { + index: core::index::Options { object_hash, format }, + extension_details: !no_details, + }, + ) + }, + ), + index::Subcommands::Entries => prepare_and_run( + "index-entries", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| { + core::index::entries(index_path, out, core::index::Options { object_hash, format }) + }, + ), + }, Subcommands::Repository(subcommands) => match subcommands { repo::Subcommands::Verify { args: diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index e241c5b99d2..df5b4d1769e 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -37,14 +37,14 @@ pub struct Args { 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 = "sha1", possible_values(&["sha1"]))] + #[clap(long, default_value_t = git_repository::hash::Kind::default(), possible_values(&["SHA1"]))] pub object_hash: git_repository::hash::Kind, #[clap(subcommand)] pub cmd: Subcommands, } -#[derive(Debug, clap::Parser)] +#[derive(Debug, clap::Subcommand)] pub enum Subcommands { /// Subcommands for interacting with packs and their indices. #[clap(subcommand)] @@ -56,6 +56,8 @@ pub enum Subcommands { /// Subcommands for interacting with commit-graphs #[clap(subcommand)] CommitGraph(commitgraph::Subcommands), + /// Subcommands for interacting with a worktree index, typically at .git/index + Index(index::Platform), /// Subcommands for interacting with entire git repositories #[clap(subcommand)] Repository(repo::Subcommands), @@ -65,10 +67,9 @@ pub enum Subcommands { pub mod pack { use std::{ffi::OsString, path::PathBuf}; - use clap::AppSettings; use gitoxide_core as core; - #[derive(Debug, clap::Parser)] + #[derive(Debug, clap::Subcommand)] pub enum Subcommands { /// Subcommands for interacting with pack indices (.idx) #[clap(subcommand)] @@ -77,7 +78,6 @@ pub mod pack { #[clap(subcommand)] MultiIndex(multi_index::Subcommands), /// Create a new pack with a set of objects. - #[clap(setting = AppSettings::DisableVersionFlag)] Create { #[clap(long, short = 'r')] /// the directory containing the '.git' repository from which objects should be read. @@ -137,7 +137,6 @@ pub mod pack { tips: Vec, }, /// Use the git-protocol to receive a pack, emulating a clone. - #[clap(setting = AppSettings::DisableVersionFlag)] #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] Receive { /// The protocol version to use. Valid values are 1 and 2 @@ -170,7 +169,6 @@ pub mod pack { /// /// Note that this effectively removes delta compression for an average compression of 2x, creating one file per object in the process. /// Thus this should only be done to dissolve small packs after a fetch. - #[clap(setting = AppSettings::DisableVersionFlag)] Explode { #[clap(long)] /// Read written objects back and assert they match their source. Fail the operation otherwise. @@ -208,7 +206,6 @@ pub mod pack { object_path: Option, }, /// Verify the integrity of a pack, index or multi-index file - #[clap(setting = AppSettings::DisableVersionFlag)] Verify { #[clap(flatten)] args: VerifyOptions, @@ -254,18 +251,14 @@ pub mod pack { pub mod multi_index { use std::path::PathBuf; - use clap::AppSettings; - - #[derive(Debug, clap::Parser)] + #[derive(Debug, clap::Subcommand)] pub enum Subcommands { /// Verify a multi-index quickly without inspecting objects themselves - #[clap(setting = AppSettings::DisableVersionFlag)] Verify { /// The path to the multi-pack-index to verify. multi_index_path: PathBuf, }, /// Create a multi-pack index from one or more pack index files - #[clap(setting = AppSettings::DisableVersionFlag)] Create { /// The path to which the multi-index file should be written, overwriting any possibly existing file. /// @@ -284,13 +277,11 @@ pub mod pack { pub mod index { use std::path::PathBuf; - use clap::AppSettings; use gitoxide_core as core; - #[derive(Debug, clap::Parser)] + #[derive(Debug, clap::Subcommand)] pub enum Subcommands { /// create a pack index from a pack data file. - #[clap(setting = AppSettings::DisableVersionFlag)] Create { /// Specify how to iterate the pack, defaults to 'verify' /// @@ -328,13 +319,10 @@ pub mod pack { pub mod repo { use std::path::PathBuf; - use clap::AppSettings; - - #[derive(Debug, clap::Parser)] + #[derive(Debug, clap::Subcommand)] #[clap(alias = "repo")] pub enum Subcommands { /// Verify the integrity of the entire repository - #[clap(setting = AppSettings::DisableVersionFlag)] Verify { #[clap(flatten)] args: super::pack::VerifyOptions, @@ -345,15 +333,44 @@ pub mod repo { } /// -pub mod commitgraph { +pub mod index { use std::path::PathBuf; - use clap::AppSettings; - #[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 = git_repository::hash::Kind::default(), possible_values(&["SHA1"]))] + pub object_hash: git_repository::hash::Kind, + + /// The path to the index file. + #[clap(short = 'i', long, default_value = ".git/index")] + pub index_path: PathBuf, + + /// Subcommands + #[clap(subcommand)] + pub cmd: Subcommands, + } + + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Print all entries to standard output + Entries, + /// Print information about the index structure + Info { + /// Do not extract specific extension information to gain only a superficial idea of the index's composition. + #[clap(long)] + no_details: bool, + }, + } +} + +/// +pub mod commitgraph { + use std::path::PathBuf; + + #[derive(Debug, clap::Subcommand)] pub enum Subcommands { /// Verify the integrity of a commit graph - #[clap(setting = AppSettings::DisableVersionFlag)] Verify { /// The path to '.git/objects/info/', '.git/objects/info/commit-graphs/', or '.git/objects/info/commit-graph' to validate. #[clap(parse(from_os_str))] @@ -368,16 +385,14 @@ pub mod commitgraph { /// #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] pub mod remote { - use clap::AppSettings; use gitoxide_core as core; - #[derive(Debug, clap::Parser)] + #[derive(Debug, clap::Subcommand)] pub enum Subcommands { /// List remote references from a remote identified by a url. /// /// This is the plumbing equivalent of `git ls-remote`. /// Supported URLs are documented here: - #[clap(setting = AppSettings::DisableVersionFlag)] RefList { /// The protocol version to use. Valid values are 1 and 2 #[clap(long, short = 'p')] diff --git a/src/porcelain/options.rs b/src/porcelain/options.rs index a49cd0f369c..3fb403b68f1 100644 --- a/src/porcelain/options.rs +++ b/src/porcelain/options.rs @@ -24,11 +24,10 @@ pub struct Args { pub cmd: Subcommands, } -#[derive(Debug, clap::Parser)] +#[derive(Debug, clap::Subcommand)] pub enum Subcommands { /// Initialize the repository in the current directory. #[clap(visible_alias = "initialize")] - #[clap(setting = AppSettings::DisableVersionFlag)] Init { /// The directory in which to initialize a new git repository. /// @@ -44,12 +43,11 @@ pub enum Subcommands { } #[cfg(feature = "gitoxide-core-tools")] -#[derive(Debug, clap::Parser)] -#[clap(setting = AppSettings::DisableVersionFlag, setting = AppSettings::SubcommandRequired)] +#[derive(Debug, clap::Subcommand)] +#[clap(setting = AppSettings::SubcommandRequired)] #[clap(visible_alias = "t")] pub enum ToolCommands { /// Find all repositories in a given directory. - #[clap(setting = AppSettings::DisableVersionFlag)] Find { /// The directory in which to find all git repositories. /// @@ -57,7 +55,6 @@ pub enum ToolCommands { root: Option, }, /// Move all repositories found in a directory into a structure matching their clone URLs. - #[clap(setting = AppSettings::DisableVersionFlag)] Organize { #[clap(long)] /// The operation will be in dry-run mode unless this flag is set.