mirror of
https://github.com/ouch-org/ouch.git
synced 2025-07-29 06:02:26 +00:00
Allow ignoring hidden files and respecting .gitignore and git exclude
This commit is contained in:
parent
abf1d4e3e4
commit
8a8e9e7db8
102
Cargo.lock
generated
102
Cargo.lock
generated
@ -8,6 +8,15 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
@ -31,6 +40,15 @@ version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
@ -132,6 +150,16 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.15"
|
||||
@ -157,6 +185,12 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "fs-err"
|
||||
version = "2.6.0"
|
||||
@ -174,6 +208,19 @@ dependencies = [
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
@ -198,6 +245,24 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"globset",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex",
|
||||
"same-file",
|
||||
"thread_local",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.7.0"
|
||||
@ -255,6 +320,15 @@ version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lz4_flex"
|
||||
version = "0.9.0"
|
||||
@ -316,6 +390,7 @@ dependencies = [
|
||||
"clap_generate",
|
||||
"flate2",
|
||||
"fs-err",
|
||||
"ignore",
|
||||
"infer",
|
||||
"libc",
|
||||
"linked-hash-map",
|
||||
@ -324,7 +399,6 @@ dependencies = [
|
||||
"rand",
|
||||
"tar",
|
||||
"tempfile",
|
||||
"walkdir",
|
||||
"xz2",
|
||||
"zip",
|
||||
"zstd",
|
||||
@ -433,6 +507,23 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
@ -537,6 +628,15 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "twox-hash"
|
||||
version = "1.6.1"
|
||||
|
@ -17,7 +17,7 @@ clap = "=3.0.0-beta.5" # Keep it pinned while in beta!
|
||||
atty = "0.2.14"
|
||||
fs-err = "2.6.0"
|
||||
once_cell = "1.8.0"
|
||||
walkdir = "2.3.2"
|
||||
ignore = "0.4.18"
|
||||
bzip2 = "0.4.3"
|
||||
libc = "0.2.103"
|
||||
tar = "0.4.37"
|
||||
|
@ -8,13 +8,12 @@ use std::{
|
||||
|
||||
use fs_err as fs;
|
||||
use tar;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::{
|
||||
error::FinalError,
|
||||
info,
|
||||
list::FileInArchive,
|
||||
utils::{self, Bytes},
|
||||
utils::{self, walk_dir, Bytes},
|
||||
QuestionPolicy,
|
||||
};
|
||||
|
||||
@ -70,7 +69,7 @@ pub fn list_archive(reader: Box<dyn Read>) -> crate::Result<Vec<FileInArchive>>
|
||||
}
|
||||
|
||||
/// Compresses the archives given by `input_filenames` into the file given previously to `writer`.
|
||||
pub fn build_archive_from_paths<W>(input_filenames: &[PathBuf], writer: W) -> crate::Result<W>
|
||||
pub fn build_archive_from_paths<W>(input_filenames: &[PathBuf], writer: W, read_hidden_files: bool) -> crate::Result<W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
@ -82,7 +81,7 @@ where
|
||||
// Safe unwrap, input shall be treated before
|
||||
let filename = filename.file_name().unwrap();
|
||||
|
||||
for entry in WalkDir::new(&filename) {
|
||||
for entry in walk_dir(filename, read_hidden_files) {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
|
@ -7,14 +7,13 @@ use std::{
|
||||
};
|
||||
|
||||
use fs_err as fs;
|
||||
use walkdir::WalkDir;
|
||||
use zip::{self, read::ZipFile, ZipArchive};
|
||||
|
||||
use self::utf8::get_invalid_utf8_paths;
|
||||
use crate::{
|
||||
info,
|
||||
list::FileInArchive,
|
||||
utils::{self, dir_is_empty, strip_cur_dir, Bytes},
|
||||
utils::{self, dir_is_empty, strip_cur_dir, walk_dir, Bytes},
|
||||
QuestionPolicy,
|
||||
};
|
||||
|
||||
@ -100,7 +99,7 @@ where
|
||||
}
|
||||
|
||||
/// Compresses the archives given by `input_filenames` into the file given previously to `writer`.
|
||||
pub fn build_archive_from_paths<W>(input_filenames: &[PathBuf], writer: W) -> crate::Result<W>
|
||||
pub fn build_archive_from_paths<W>(input_filenames: &[PathBuf], writer: W, read_hidden_files: bool) -> crate::Result<W>
|
||||
where
|
||||
W: Write + Seek,
|
||||
{
|
||||
@ -121,7 +120,7 @@ where
|
||||
// Safe unwrap, input shall be treated before
|
||||
let filename = filename.file_name().unwrap();
|
||||
|
||||
for entry in WalkDir::new(filename) {
|
||||
for entry in walk_dir(filename, read_hidden_files) {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
|
@ -40,7 +40,7 @@ fn represents_several_files(files: &[PathBuf]) -> bool {
|
||||
/// Entrypoint of ouch, receives cli options and matches Subcommand to decide what to do
|
||||
pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> {
|
||||
match args.cmd {
|
||||
Subcommand::Compress { files, output: output_path } => {
|
||||
Subcommand::Compress { files, output: output_path, hidden } => {
|
||||
// Formats from path extension, like "file.tar.gz.xz" -> vec![Tar, Gzip, Lzma]
|
||||
let mut formats = extension::extensions_from_path(&output_path);
|
||||
|
||||
@ -142,7 +142,7 @@ pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> {
|
||||
formats = new_formats;
|
||||
}
|
||||
}
|
||||
let compress_result = compress_files(files, formats, output_file);
|
||||
let compress_result = compress_files(files, formats, output_file, hidden);
|
||||
|
||||
// If any error occurred, delete incomplete file
|
||||
if compress_result.is_err() {
|
||||
@ -247,7 +247,12 @@ pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> {
|
||||
// files are the list of paths to be compressed: ["dir/file1.txt", "dir/file2.txt"]
|
||||
// formats contains each format necessary for compression, example: [Tar, Gz] (in compression order)
|
||||
// output_file is the resulting compressed file name, example: "compressed.tar.gz"
|
||||
fn compress_files(files: Vec<PathBuf>, formats: Vec<Extension>, output_file: fs::File) -> crate::Result<()> {
|
||||
fn compress_files(
|
||||
files: Vec<PathBuf>,
|
||||
formats: Vec<Extension>,
|
||||
output_file: fs::File,
|
||||
read_hidden_files: bool,
|
||||
) -> crate::Result<()> {
|
||||
let file_writer = BufWriter::with_capacity(BUFFER_CAPACITY, output_file);
|
||||
|
||||
let mut writer: Box<dyn Write> = Box::new(file_writer);
|
||||
@ -282,7 +287,7 @@ fn compress_files(files: Vec<PathBuf>, formats: Vec<Extension>, output_file: fs:
|
||||
io::copy(&mut reader, &mut writer)?;
|
||||
}
|
||||
Tar => {
|
||||
let mut writer = archive::tar::build_archive_from_paths(&files, writer)?;
|
||||
let mut writer = archive::tar::build_archive_from_paths(&files, writer, read_hidden_files)?;
|
||||
writer.flush()?;
|
||||
}
|
||||
Zip => {
|
||||
@ -296,7 +301,7 @@ fn compress_files(files: Vec<PathBuf>, formats: Vec<Extension>, output_file: fs:
|
||||
eprintln!("\tThe design of .zip makes it impossible to compress via stream.");
|
||||
|
||||
let mut vec_buffer = io::Cursor::new(vec![]);
|
||||
archive::zip::build_archive_from_paths(&files, &mut vec_buffer)?;
|
||||
archive::zip::build_archive_from_paths(&files, &mut vec_buffer, read_hidden_files)?;
|
||||
let vec_buffer = vec_buffer.into_inner();
|
||||
io::copy(&mut vec_buffer.as_slice(), &mut writer)?;
|
||||
}
|
||||
|
10
src/error.rs
10
src/error.rs
@ -25,7 +25,7 @@ pub enum Error {
|
||||
/// TO BE REMOVED
|
||||
CompressingRootFolder,
|
||||
/// Specialized walkdir's io::Error wrapper with additional information on the error
|
||||
WalkdirError { reason: String },
|
||||
DirWalkError { reason: String },
|
||||
/// Custom and unique errors are reported in this variant
|
||||
Custom { reason: FinalError },
|
||||
}
|
||||
@ -89,7 +89,7 @@ impl FinalError {
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let err = match self {
|
||||
Error::WalkdirError { reason } => FinalError::with_title(reason),
|
||||
Error::DirWalkError { reason } => FinalError::with_title(reason),
|
||||
Error::NotFound { error_title } => FinalError::with_title(error_title).detail("File not found"),
|
||||
Error::CompressingRootFolder => {
|
||||
FinalError::with_title("It seems you're trying to compress the root folder.")
|
||||
@ -135,9 +135,9 @@ impl From<zip::result::ZipError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<walkdir::Error> for Error {
|
||||
fn from(err: walkdir::Error) -> Self {
|
||||
Self::WalkdirError { reason: err.to_string() }
|
||||
impl From<ignore::Error> for Error {
|
||||
fn from(err: ignore::Error) -> Self {
|
||||
Self::DirWalkError { reason: err.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,10 @@ pub enum Subcommand {
|
||||
/// The resulting file. Its extensions can be used to specify the compression formats.
|
||||
#[clap(required = true, value_hint = ValueHint::FilePath)]
|
||||
output: PathBuf,
|
||||
|
||||
/// If set, `ouch` will *not* compress hidden files or files ignored through .gitignore or .ignore
|
||||
#[clap(short = 'H', long)]
|
||||
hidden: bool,
|
||||
},
|
||||
/// Decompresses one or more files, optionally into another folder.
|
||||
#[clap(alias = "d")]
|
||||
|
@ -8,9 +8,18 @@ use std::{
|
||||
};
|
||||
|
||||
use fs_err as fs;
|
||||
use ignore::{Walk, WalkBuilder};
|
||||
|
||||
use crate::info;
|
||||
|
||||
/// Wrapper over ignore::WalkBuilder
|
||||
/// If `read_ignored_files` is true, then we'll only walk through files that are not hidden or
|
||||
/// contained in a .gitignore file or in a .git/info/exclude file.
|
||||
pub fn walk_dir(path: impl AsRef<Path>, read_ignored_files: bool) -> Walk {
|
||||
let yes = read_ignored_files;
|
||||
WalkBuilder::new(path).hidden(yes).git_ignore(yes).git_exclude(yes).build()
|
||||
}
|
||||
|
||||
/// Checks given path points to an empty directory.
|
||||
pub fn dir_is_empty(dir_path: &Path) -> bool {
|
||||
let is_empty = |mut rd: std::fs::ReadDir| rd.next().is_none();
|
||||
|
@ -7,6 +7,6 @@ mod question_policy;
|
||||
pub use bytes::Bytes;
|
||||
pub use fs::{
|
||||
cd_into_same_dir_as, colors, concatenate_list_of_os_str, create_dir_if_non_existent, dir_is_empty,
|
||||
nice_directory_display, strip_cur_dir, to_utf,
|
||||
nice_directory_display, strip_cur_dir, to_utf, walk_dir,
|
||||
};
|
||||
pub use question_policy::{create_or_ask_overwrite, user_wants_to_overwrite, QuestionPolicy};
|
||||
|
@ -23,7 +23,7 @@ pub fn compress_files(at: &Path, paths_to_compress: &[PathBuf], format: &str) ->
|
||||
let command = Opts {
|
||||
yes: false,
|
||||
no: false,
|
||||
cmd: Subcommand::Compress { files: paths_to_compress.to_vec(), output: archive_path.clone() },
|
||||
cmd: Subcommand::Compress { files: paths_to_compress.to_vec(), output: archive_path.clone(), hidden: false },
|
||||
};
|
||||
run(command, QuestionPolicy::Ask).expect("Failed to compress test dummy files");
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user