-
Notifications
You must be signed in to change notification settings - Fork 83
/
Copy pathfind.rs
151 lines (134 loc) · 5.04 KB
/
find.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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 search 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),
};
}
}
}
}