mirror of
https://github.com/rustic-rs/rustic.git
synced 2025-10-26 11:18:51 +00:00
restore: Add glob options to include/exclude patterns
This commit is contained in:
parent
943d067033
commit
9daee094cd
@ -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<String>,
|
||||
|
||||
/// Same as --glob pattern but ignores the casing of filenames
|
||||
#[clap(long, value_name = "GLOB", help_heading = "EXCLUDE OPTIONS")]
|
||||
iglob: Vec<String>,
|
||||
|
||||
/// 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<String>,
|
||||
|
||||
/// Same as --glob-file ignores the casing of filenames in patterns
|
||||
#[clap(long, value_name = "FILE", help_heading = "EXCLUDE OPTIONS")]
|
||||
iglob_file: Vec<String>,
|
||||
}
|
||||
|
||||
/// [`NodeStreamer`] recursively streams all nodes of a given tree including all subtrees in-order
|
||||
pub struct NodeStreamer<BE>
|
||||
where
|
||||
@ -112,6 +134,7 @@ where
|
||||
inner: std::vec::IntoIter<Node>,
|
||||
path: PathBuf,
|
||||
be: BE,
|
||||
overrides: Option<Override>,
|
||||
}
|
||||
|
||||
impl<BE> NodeStreamer<BE>
|
||||
@ -119,6 +142,10 @@ where
|
||||
BE: IndexedBackend,
|
||||
{
|
||||
pub fn new(be: BE, node: &Node) -> Result<Self> {
|
||||
Self::new_streamer(be, node, None)
|
||||
}
|
||||
|
||||
fn new_streamer(be: BE, node: &Node, overrides: Option<Override>) -> Result<Self> {
|
||||
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<Self> {
|
||||
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)));
|
||||
|
||||
@ -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() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user