diff --git a/gitoxide-core/src/repository/index/mod.rs b/gitoxide-core/src/repository/index/mod.rs index bdaa61b5abf..6922b622be7 100644 --- a/gitoxide-core/src/repository/index/mod.rs +++ b/gitoxide-core/src/repository/index/mod.rs @@ -1,3 +1,4 @@ +use anyhow::bail; use std::{ffi::OsString, path::PathBuf}; use gix::prelude::FindExt; @@ -35,5 +36,48 @@ pub fn from_tree( Ok(()) } +pub fn from_list(entries_file: PathBuf, index_path: Option, force: bool) -> anyhow::Result<()> { + use std::io::BufRead; + let object_hash = gix::hash::Kind::Sha1; + + let mut index = gix::index::State::new(object_hash); + for path in std::io::BufReader::new(std::fs::File::open(&entries_file)?).lines() { + let path: PathBuf = path?.into(); + if !path.is_relative() { + bail!("Input paths need to be relative, but {path:?} is not.") + } + let path = gix::path::into_bstr(path); + index.dangerously_push_entry( + gix::index::entry::Stat::default(), + gix::hash::ObjectId::empty_blob(object_hash), + gix::index::entry::Flags::empty(), + gix::index::entry::Mode::FILE, + gix::path::to_unix_separators_on_windows(path).as_ref(), + ) + } + index.sort_entries(); + + let options = gix::index::write::Options::default(); + match index_path { + Some(index_path) => { + if index_path.is_file() && !force { + anyhow::bail!( + "File at \"{}\" already exists, to overwrite use the '-f' flag", + index_path.display() + ); + } + let mut index = gix::index::File::from_state(index, 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)?; + } + } + + Ok(()) +} + pub mod entries; pub use entries::function::entries; diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 6aafdb5f2ce..d77f48ac91c 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -293,6 +293,18 @@ pub fn main() -> Result<()> { index_path, cmd, }) => match cmd { + free::index::Subcommands::FromList { + force, + index_output_path, + file, + } => prepare_and_run( + "index-from-list", + verbose, + progress, + progress_keep_open, + None, + move |_progress, _out, _err| core::repository::index::from_list(file, index_output_path, force), + ), free::index::Subcommands::CheckoutExclusive { directory, empty_files, @@ -300,7 +312,7 @@ pub fn main() -> Result<()> { keep_going, } => prepare_and_run( "index-checkout", - verbose, + auto_verbose, progress, progress_keep_open, None, diff --git a/src/plumbing/options/free.rs b/src/plumbing/options/free.rs index 2869fb0cedf..9f945c5c640 100644 --- a/src/plumbing/options/free.rs +++ b/src/plumbing/options/free.rs @@ -53,6 +53,19 @@ pub mod index { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { + /// Create an index from a list of empty files, one per line of the input. + FromList { + /// Overwrite the specified index file if it already exists. + #[clap(long, short = 'f')] + force: bool, + /// Path to the index file to be written. + /// If none is given it will be kept in memory only as a way to measure performance. One day we will probably write the index + /// back by default, but that requires us to write more of the index to work. + #[clap(long, short = 'i')] + index_output_path: Option, + /// The file to read the index entries from, one path per line. + file: PathBuf, + }, /// Validate constraints and assumptions of an index along with its integrity. Verify, /// Print information about the index structure