Skip to content

Commit d4077d7

Browse files
authored
feat: Add ls option to backup command (#1771)
Adds the new option `--ls` to the `backup` command. With this option enabled, no backup is performed, but instead the files-to-backup are just listed, like in the `ls` command. The differences to the `--dry-run` option are: - `--ls` doesn't read or process any file contents, it only lists the files. It is therefore much faster than `--dry-run`. - `--dry-run` does every step a real backup would do except writing to the backend. It can therefore also show details about the size which would be added and about new or changed/unchanged files. closes #1757 closes #1676
1 parent e994d7e commit d4077d7

4 files changed

Lines changed: 62 additions & 24 deletions

File tree

src/commands/backup.rs

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::fmt::Display;
55
use std::path::PathBuf;
66
use std::{collections::BTreeMap, env};
77

8+
use crate::commands::ls::LsCmd;
89
use crate::repository::IndexedIdsRepo;
910
use crate::{
1011
Application, RUSTIC_APP,
@@ -22,7 +23,7 @@ use comfy_table::Cell;
2223
use conflate::{Merge, MergeFrom};
2324
use log::{debug, error, info, warn};
2425
use rustic_backend::OpenDALBackend;
25-
use rustic_core::{ChildStdoutSource, Excludes, LocalSource, StdinSource, StringList};
26+
use rustic_core::{ChildStdoutSource, Excludes, LocalSource, ReadSource, StdinSource, StringList};
2627
use serde::{Deserialize, Serialize};
2728
use serde_with::serde_as;
2829

@@ -56,6 +57,12 @@ pub struct BackupCmd {
5657
#[serde(skip)]
5758
cli_name: Vec<String>,
5859

60+
/// Don't run the backup, but only list files which would be backup'ed.
61+
#[clap(long)]
62+
#[merge(skip)]
63+
#[serde(skip)]
64+
ls: bool,
65+
5966
#[clap(skip)]
6067
#[merge(skip)]
6168
name: Option<String>,
@@ -336,34 +343,34 @@ impl BackupCmd {
336343
fn backup_source(
337344
source: &PathList,
338345
options: BTreeMap<String, String>,
346+
ls: bool,
339347
backup_opts: BackupOptions,
340-
snap: SnapshotFile,
348+
snap: &mut SnapshotFile,
341349
repo: &IndexedIdsRepo,
342-
) -> Result<SnapshotFile> {
350+
) -> Result<()> {
343351
let backup_stdin = PathList::from_string("-")?;
344352
let source = source
345353
.clone()
346354
.sanitize()
347355
.with_context(|| format!("error sanitizing source=s\"{:?}\"", source))?
348356
.merge();
349357

350-
let snap = if source.len() == 1
358+
if source.len() == 1
351359
// TODO: This check should not be done on PathList, but in the sources list directly
352360
&& let Some(path) = source[0].to_string_lossy().strip_prefix("opendal:")
353361
{
354362
let source = OpenDALBackend::new(path, options)?.as_source()?;
355-
repo.archive(&backup_opts, &source, snap, &[PathBuf::new()])?
363+
Self::archive(repo, &backup_opts, ls, &source, snap, &[PathBuf::new()])?;
356364
} else if source == backup_stdin {
357365
let path = PathBuf::from(&backup_opts.stdin_filename);
358366
let backup_paths = vec![path.clone()];
359367
if let Some(command) = &backup_opts.stdin_command {
360368
let src = ChildStdoutSource::new(command, path)?;
361-
let res = repo.archive(&backup_opts, &src, snap, &backup_paths)?;
369+
Self::archive(repo, &backup_opts, ls, &src, snap, &backup_paths)?;
362370
src.finish()?;
363-
res
364371
} else {
365372
let src = StdinSource::new(path);
366-
repo.archive(&backup_opts, &src, snap, &backup_paths)?
373+
Self::archive(repo, &backup_opts, ls, &src, snap, &backup_paths)?;
367374
}
368375
} else {
369376
let backup_path = source.paths();
@@ -373,9 +380,36 @@ impl BackupCmd {
373380
&backup_opts.ignore_filter_opts,
374381
&backup_path,
375382
)?;
376-
repo.archive(&backup_opts, &src, snap, &backup_path)?
383+
Self::archive(repo, &backup_opts, ls, &src, snap, &backup_path)?;
377384
};
378-
Ok(snap)
385+
Ok(())
386+
}
387+
388+
pub fn archive<R>(
389+
repo: &IndexedIdsRepo,
390+
opts: &BackupOptions,
391+
ls: bool,
392+
src: &R,
393+
snap: &mut SnapshotFile,
394+
backup_paths: &[PathBuf],
395+
) -> Result<()>
396+
where
397+
R: ReadSource + 'static,
398+
<R as ReadSource>::Open: Send,
399+
<R as ReadSource>::Iter: Send,
400+
{
401+
if ls {
402+
let lister = LsCmd {
403+
long: true,
404+
..Default::default()
405+
};
406+
lister.display(src.entries().map(|e| Ok(e?.as_tree_entry())))?;
407+
} else {
408+
let snapshot = std::mem::take(snap);
409+
let snapshot = repo.archive(opts, src, snapshot, backup_paths)?;
410+
*snap = snapshot;
411+
}
412+
Ok(())
379413
}
380414

381415
fn backup_snapshot(mut self, source: PathList, repo: &IndexedIdsRepo) -> Result<()> {
@@ -420,12 +454,14 @@ impl BackupCmd {
420454
.no_scan(self.no_scan)
421455
.dry_run(config.global.dry_run);
422456

423-
let snap = self.snap_opts.to_snapshot()?;
424-
let snap = hooks.use_with(|| -> Result<_> {
425-
Self::backup_source(&source, self.options, backup_opts, snap, repo)
457+
let mut snap = self.snap_opts.to_snapshot()?;
458+
hooks.use_with(|| {
459+
Self::backup_source(&source, self.options, self.ls, backup_opts, &mut snap, repo)
426460
})?;
427461

428-
if config.global.progress_options.json_progress {
462+
if self.ls {
463+
// no output here
464+
} else if config.global.progress_options.json_progress {
429465
write_json_progress_summary(&snap)?;
430466
} else if self.json {
431467
let mut stdout = std::io::stdout();

src/commands/ls.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,29 +38,29 @@ mod constants {
3838
use constants::{S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR};
3939

4040
/// `ls` subcommand
41-
#[derive(clap::Parser, Command, Debug)]
41+
#[derive(clap::Parser, Command, Debug, Default)]
4242
pub(crate) struct LsCmd {
4343
/// Snapshot:path to list (uses local path if no snapshot is given)
4444
///
4545
/// The snapshot can be an id: "01a2b3c4" or "latest" or "latest~N" (N >= 0)
4646
#[clap(value_name = "SNAPSHOT[:PATH]|PATH")]
47-
snap: String,
47+
pub snap: String,
4848

4949
/// show summary
5050
#[clap(long, short = 's', conflicts_with = "json")]
51-
summary: bool,
51+
pub summary: bool,
5252

5353
/// show long listing
5454
#[clap(long, short = 'l', conflicts_with = "json")]
55-
long: bool,
55+
pub long: bool,
5656

5757
/// show listing in json
5858
#[clap(long, conflicts_with_all = ["summary", "long"])]
59-
json: bool,
59+
pub json: bool,
6060

6161
/// show uid/gid instead of user/group
6262
#[clap(long, long("numeric-uid-gid"))]
63-
numeric_id: bool,
63+
pub numeric_id: bool,
6464

6565
/// recursively list the dir
6666
#[clap(long)]
@@ -69,14 +69,14 @@ pub(crate) struct LsCmd {
6969
#[cfg(feature = "tui")]
7070
/// Run in interactive UI mode
7171
#[clap(long, short)]
72-
interactive: bool,
72+
pub interactive: bool,
7373

7474
#[clap(flatten, next_help_heading = "Exclude options")]
7575
/// exclude options
7676
pub excludes: Excludes,
7777

7878
#[clap(flatten, next_help_heading = "Exclude options for local source")]
79-
ignore_opts: LocalSourceFilterOptions,
79+
pub ignore_opts: LocalSourceFilterOptions,
8080
}
8181

8282
impl Runnable for LsCmd {
@@ -212,7 +212,7 @@ impl LsCmd {
212212
Ok(())
213213
}
214214

215-
fn inner_run_local(&self, path: Option<&str>) -> Result<()> {
215+
pub fn inner_run_local(&self, path: Option<&str>) -> Result<()> {
216216
#[cfg(feature = "tui")]
217217
if self.interactive {
218218
anyhow::bail!("interactive ls with local path is not yet implemented!");
@@ -233,7 +233,7 @@ impl LsCmd {
233233
Ok(())
234234
}
235235

236-
fn display(
236+
pub fn display(
237237
&self,
238238
tree_streamer: impl Iterator<Item = RusticResult<(PathBuf, Node)>>,
239239
) -> Result<()> {

src/snapshots/rustic_rs__config__tests__default_config_passes.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ RusticConfig {
8989
backup: BackupCmd {
9090
cli_sources: [],
9191
cli_name: [],
92+
ls: false,
9293
name: None,
9394
stdin_filename: "",
9495
stdin_command: None,

src/snapshots/rustic_rs__config__tests__global_env_roundtrip_passes-3.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ RusticConfig {
100100
backup: BackupCmd {
101101
cli_sources: [],
102102
cli_name: [],
103+
ls: false,
103104
name: None,
104105
stdin_filename: "",
105106
stdin_command: None,

0 commit comments

Comments
 (0)