diff --git a/src/blob/tree.rs b/src/blob/tree.rs index 90b02b3..b0c0afc 100644 --- a/src/blob/tree.rs +++ b/src/blob/tree.rs @@ -6,8 +6,11 @@ use std::path::{Component, Path, PathBuf, Prefix}; use std::str; use anyhow::{anyhow, bail, Result}; +use clap::Parser; use crossbeam_channel::{bounded, unbounded, Receiver, Sender}; use derive_getters::Getters; +use ignore::overrides::{Override, OverrideBuilder}; +use ignore::Match; use indicatif::ProgressBar; use serde::{Deserialize, Deserializer, Serialize}; @@ -103,6 +106,25 @@ impl IntoIterator for Tree { } } +#[derive(Default, Clone, Parser)] +pub struct TreeStreamerOptions { + /// Glob pattern to exclude/include (can be specified multiple times) + #[clap(long, help_heading = "EXCLUDE OPTIONS")] + glob: Vec, + + /// Same as --glob pattern but ignores the casing of filenames + #[clap(long, value_name = "GLOB", help_heading = "EXCLUDE OPTIONS")] + iglob: Vec, + + /// Read glob patterns to exclude/include from this file (can be specified multiple times) + #[clap(long, value_name = "FILE", help_heading = "EXCLUDE OPTIONS")] + glob_file: Vec, + + /// Same as --glob-file ignores the casing of filenames in patterns + #[clap(long, value_name = "FILE", help_heading = "EXCLUDE OPTIONS")] + iglob_file: Vec, +} + /// [`NodeStreamer`] recursively streams all nodes of a given tree including all subtrees in-order pub struct NodeStreamer where @@ -112,6 +134,7 @@ where inner: std::vec::IntoIter, path: PathBuf, be: BE, + overrides: Option, } impl NodeStreamer @@ -119,6 +142,10 @@ where BE: IndexedBackend, { pub fn new(be: BE, node: &Node) -> Result { + Self::new_streamer(be, node, None) + } + + fn new_streamer(be: BE, node: &Node, overrides: Option) -> Result { let inner = if node.is_dir() { Tree::from_backend(&be, node.subtree.unwrap())? .nodes @@ -131,8 +158,37 @@ where open_iterators: Vec::new(), path: PathBuf::new(), be, + overrides, }) } + + pub fn new_with_glob(be: BE, node: &Node, opts: TreeStreamerOptions) -> Result { + let mut override_builder = OverrideBuilder::new("/"); + + for g in opts.glob { + override_builder.add(&g)?; + } + + for file in opts.glob_file { + for line in std::fs::read_to_string(file)?.lines() { + override_builder.add(line)?; + } + } + + override_builder.case_insensitive(true)?; + for g in opts.iglob { + override_builder.add(&g)?; + } + + for file in opts.iglob_file { + for line in std::fs::read_to_string(file)?.lines() { + override_builder.add(line)?; + } + } + let overrides = override_builder.build()?; + + Self::new_streamer(be, node, Some(overrides)) + } } type NodeStreamItem = Result<(PathBuf, Node)>; @@ -149,7 +205,7 @@ where match self.inner.next() { Some(node) => { let path = self.path.join(node.name()); - if let Some(id) = node.subtree() { + let is_dir = if let Some(id) = node.subtree() { self.path.push(node.name()); let be = self.be.clone(); let tree = match Tree::from_backend(&be, *id) { @@ -158,6 +214,15 @@ where }; let old_inner = mem::replace(&mut self.inner, tree.nodes.into_iter()); self.open_iterators.push(old_inner); + true + } else { + false + }; + + if let Some(overrides) = &self.overrides { + if let Match::Ignore(_) = overrides.matched(&path, is_dir) { + continue; + } } return Some(Ok((path, node))); diff --git a/src/commands/restore.rs b/src/commands/restore.rs index 04119d5..57b31cf 100644 --- a/src/commands/restore.rs +++ b/src/commands/restore.rs @@ -15,7 +15,7 @@ use rayon::ThreadPoolBuilder; use super::rustic_config::RusticConfig; use super::{bytes, progress_bytes, progress_counter, warm_up_wait}; use crate::backend::{DecryptReadBackend, FileType, LocalDestination}; -use crate::blob::{Node, NodeStreamer, NodeType, Tree}; +use crate::blob::{Node, NodeStreamer, NodeType, Tree, TreeStreamerOptions}; use crate::commands::helpers::progress_spinner; use crate::crypto::hash; use crate::id::Id; @@ -33,6 +33,9 @@ pub(super) struct Opts { #[clap(long, short = 'n')] dry_run: bool, + #[clap(flatten)] + streamer_opts: TreeStreamerOptions, + /// Remove all files/dirs in destination which are not contained in snapshot. /// WARNING: Use with care, maybe first try this first with --dry-run? #[clap(long)] @@ -284,7 +287,8 @@ fn allocate_and_collect( .filter_map(Result::ok); // TODO: print out the ignored error let mut next_dst = dst_iter.next(); - let mut node_streamer = NodeStreamer::new(index.clone(), node)?; + let mut node_streamer = + NodeStreamer::new_with_glob(index.clone(), node, opts.streamer_opts.clone())?; let mut next_node = node_streamer.next().transpose()?; loop { @@ -410,7 +414,8 @@ fn restore_metadata( opts: &Opts, ) -> Result<()> { // walk over tree in repository and compare with tree in dest - let mut node_streamer = NodeStreamer::new(index, node)?; + let mut node_streamer = + NodeStreamer::new_with_glob(index.clone(), node, opts.streamer_opts.clone())?; let mut dir_stack = Vec::new(); while let Some((path, node)) = node_streamer.next().transpose()? { match node.node_type() {