-
Notifications
You must be signed in to change notification settings - Fork 79
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds the new command `find`. This commands allows to search for glob pattern using `--glob`/`--iglob` or given paths using `--path` in a list of snapshots. It displays all finds and is able accumulate snapshots with identical search result. This allows to use this command as a history search: `rustic find --path /my/path` shows (only) all changes of that path.
- Loading branch information
Showing
5 changed files
with
173 additions
and
14 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
//! `find` subcommand | ||
use std::path::{Path, PathBuf}; | ||
|
||
use crate::{commands::open_repository_indexed, status_err, Application, RUSTIC_APP}; | ||
|
||
use abscissa_core::{Command, Runnable, Shutdown}; | ||
use anyhow::Result; | ||
use globset::{Glob, GlobBuilder, GlobSetBuilder}; | ||
use itertools::Itertools; | ||
|
||
use rustic_core::{ | ||
repofile::{Node, SnapshotFile}, | ||
FindMatches, FindNode, SnapshotGroupCriterion, | ||
}; | ||
|
||
use super::ls::print_node; | ||
|
||
/// `find` subcommand | ||
#[derive(clap::Parser, Command, Debug)] | ||
pub(crate) struct FindCmd { | ||
/// pattern to find (can be specified multiple times) | ||
#[clap(long, value_name = "PATTERN", conflicts_with = "path")] | ||
glob: Vec<String>, | ||
|
||
/// pattern to find case-insensitive (can be specified multiple times) | ||
#[clap(long, value_name = "PATTERN", conflicts_with = "path")] | ||
iglob: Vec<String>, | ||
|
||
/// exact path to find | ||
#[clap(long, value_name = "PATH")] | ||
path: Option<PathBuf>, | ||
|
||
/// Snapshots to serach in. If none is given, use filter options to filter from all snapshots | ||
#[clap(value_name = "ID")] | ||
ids: Vec<String>, | ||
|
||
/// Group snapshots by any combination of host,label,paths,tags | ||
#[clap( | ||
long, | ||
short = 'g', | ||
value_name = "CRITERION", | ||
default_value = "host,label,paths" | ||
)] | ||
group_by: SnapshotGroupCriterion, | ||
|
||
/// Show all snapshots instead of summarizing snapshots with identical search results | ||
#[clap(long)] | ||
all: bool, | ||
|
||
/// Also show snapshots which don't contain a search result. | ||
#[clap(long)] | ||
show_misses: bool, | ||
|
||
/// Show uid/gid instead of user/group | ||
#[clap(long, long("numeric-uid-gid"))] | ||
numeric_id: bool, | ||
} | ||
|
||
impl Runnable for FindCmd { | ||
fn run(&self) { | ||
if let Err(err) = self.inner_run() { | ||
status_err!("{}", err); | ||
RUSTIC_APP.shutdown(Shutdown::Crash); | ||
}; | ||
} | ||
} | ||
|
||
impl FindCmd { | ||
fn inner_run(&self) -> Result<()> { | ||
let config = RUSTIC_APP.config(); | ||
let repo = open_repository_indexed(&config.repository)?; | ||
|
||
let groups = repo.get_snapshot_group(&self.ids, self.group_by, |sn| { | ||
config.snapshot_filter.matches(sn) | ||
})?; | ||
for (group, mut snapshots) in groups { | ||
snapshots.sort_unstable(); | ||
if !group.is_empty() { | ||
println!("\nsearching in snapshots group {group}..."); | ||
} | ||
let ids = snapshots.iter().map(|sn| sn.tree); | ||
if let Some(path) = &self.path { | ||
let FindNode { nodes, matches } = repo.find_nodes_from_path(ids, path)?; | ||
for (idx, g) in &matches | ||
.iter() | ||
.zip(snapshots.iter()) | ||
.group_by(|(idx, _)| *idx) | ||
{ | ||
self.print_identical_snapshots(idx.iter(), g.into_iter().map(|(_, sn)| sn)); | ||
if let Some(idx) = idx { | ||
print_node(&nodes[*idx], path, self.numeric_id); | ||
} | ||
} | ||
} else { | ||
let mut builder = GlobSetBuilder::new(); | ||
for glob in &self.glob { | ||
_ = builder.add(Glob::new(glob)?); | ||
} | ||
for glob in &self.iglob { | ||
_ = builder.add(GlobBuilder::new(glob).case_insensitive(true).build()?); | ||
} | ||
let globset = builder.build()?; | ||
let matches = |path: &Path, _: &Node| { | ||
globset.is_match(path) || path.file_name().is_some_and(|f| globset.is_match(f)) | ||
}; | ||
let FindMatches { | ||
paths, | ||
nodes, | ||
matches, | ||
} = repo.find_matching_nodes(ids, &matches)?; | ||
for (idx, g) in &matches | ||
.iter() | ||
.zip(snapshots.iter()) | ||
.group_by(|(idx, _)| *idx) | ||
{ | ||
self.print_identical_snapshots(idx.iter(), g.into_iter().map(|(_, sn)| sn)); | ||
for (path_idx, node_idx) in idx { | ||
print_node(&nodes[*node_idx], &paths[*path_idx], self.numeric_id); | ||
} | ||
} | ||
} | ||
} | ||
Ok(()) | ||
} | ||
|
||
fn print_identical_snapshots<'a>( | ||
&self, | ||
mut idx: impl Iterator, | ||
mut g: impl Iterator<Item = &'a SnapshotFile>, | ||
) { | ||
let empty_result = idx.next().is_none(); | ||
let not = if empty_result { "not " } else { "" }; | ||
if self.show_misses || !empty_result { | ||
if self.all { | ||
for sn in g { | ||
let time = sn.time.format("%Y-%m-%d %H:%M:%S"); | ||
println!("{not}found in {} from {time}", sn.id); | ||
} | ||
} else { | ||
let sn = g.next().unwrap(); | ||
let count = g.count(); | ||
let time = sn.time.format("%Y-%m-%d %H:%M:%S"); | ||
match count { | ||
0 => println!("{not}found in {} from {time}", sn.id), | ||
count => println!("{not}found in {} from {time} (+{count})", sn.id), | ||
}; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters