Skip to content

Commit db6ebc2

Browse files
committed
feat(commands): Add lock command
1 parent 85c3509 commit db6ebc2

File tree

8 files changed

+233
-13
lines changed

8 files changed

+233
-13
lines changed

Cargo.lock

Lines changed: 57 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ rustdoc-args = ["--document-private-items", "--generate-link-to-definition"]
4141

4242
[dependencies]
4343
abscissa_core = { version = "0.7.0", default-features = false, features = ["application"] }
44-
rustic_backend = { version = "0.3.0", features = ["cli"] }
45-
rustic_core = { version = "0.4.0", features = ["cli"] }
44+
rustic_backend = { git = "https://github.com/rustic-rs/rustic_core.git", branch = "lock", features = ["cli"] }
45+
rustic_core = { git = "https://github.com/rustic-rs/rustic_core.git", branch = "lock", features = ["cli"] }
4646

4747
# allocators
4848
jemallocator-global = { version = "0.3.2", optional = true }

src/commands.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub(crate) mod forget;
1313
pub(crate) mod init;
1414
pub(crate) mod key;
1515
pub(crate) mod list;
16+
pub(crate) mod lock;
1617
pub(crate) mod ls;
1718
pub(crate) mod merge;
1819
pub(crate) mod prune;
@@ -39,9 +40,10 @@ use crate::{
3940
commands::{
4041
backup::BackupCmd, cat::CatCmd, check::CheckCmd, completions::CompletionsCmd,
4142
config::ConfigCmd, copy::CopyCmd, diff::DiffCmd, dump::DumpCmd, forget::ForgetCmd,
42-
init::InitCmd, key::KeyCmd, list::ListCmd, ls::LsCmd, merge::MergeCmd, prune::PruneCmd,
43-
repair::RepairCmd, repoinfo::RepoInfoCmd, restore::RestoreCmd, self_update::SelfUpdateCmd,
44-
show_config::ShowConfigCmd, snapshots::SnapshotCmd, tag::TagCmd,
43+
init::InitCmd, key::KeyCmd, list::ListCmd, lock::LockCmd, ls::LsCmd, merge::MergeCmd,
44+
prune::PruneCmd, repair::RepairCmd, repoinfo::RepoInfoCmd, restore::RestoreCmd,
45+
self_update::SelfUpdateCmd, show_config::ShowConfigCmd, snapshots::SnapshotCmd,
46+
tag::TagCmd,
4547
},
4648
config::{progress_options::ProgressOptions, AllRepositoryOptions, RusticConfig},
4749
{Application, RUSTIC_APP},
@@ -113,6 +115,9 @@ enum RusticCmd {
113115
/// List repository files
114116
List(ListCmd),
115117

118+
/// Lock snapshots
119+
Lock(LockCmd),
120+
116121
/// List file contents of a snapshot
117122
Ls(LsCmd),
118123

src/commands/forget.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,13 @@ impl ForgetCmd {
119119
.get_snapshots(&self.ids)?
120120
.into_iter()
121121
.map(|sn| {
122-
if sn.must_keep(now) {
122+
if sn.is_locked(now) {
123+
ForgetSnapshot {
124+
snapshot: sn,
125+
keep: true,
126+
reasons: vec!["locked".to_string()],
127+
}
128+
} else if sn.must_keep(now) {
123129
ForgetSnapshot {
124130
snapshot: sn,
125131
keep: true,

src/commands/lock.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
//! `lock` subcommand
2+
3+
use std::str::FromStr;
4+
5+
use crate::{commands::open_repository, status_err, Application, RUSTIC_APP};
6+
use abscissa_core::{Command, Runnable, Shutdown};
7+
8+
use anyhow::Result;
9+
use chrono::{DateTime, Duration, Local};
10+
11+
use rustic_core::{repofile::KeyId, LockOptions};
12+
13+
/// `lock` subcommand
14+
#[derive(clap::Parser, Command, Debug)]
15+
pub(crate) struct LockCmd {
16+
/// Subcommand to run
17+
#[clap(subcommand)]
18+
cmd: LockSubCmd,
19+
}
20+
21+
impl Runnable for LockCmd {
22+
fn run(&self) {
23+
let config = RUSTIC_APP.config();
24+
if config.global.dry_run {
25+
println!("lock is not supported in dry-run mode");
26+
} else {
27+
self.cmd.run();
28+
}
29+
}
30+
}
31+
32+
/// `lock` subcommand
33+
#[derive(clap::Subcommand, Debug, Runnable)]
34+
enum LockSubCmd {
35+
/// Lock the complete repository
36+
Repository(RepoSubCmd),
37+
/// Lock all key files
38+
Keys(KeysSubCmd),
39+
/// Lock snapshots and relevant pack files
40+
Snapshots(SnapSubCmd),
41+
}
42+
43+
#[derive(clap::Parser, Command, Debug, Clone)]
44+
pub(crate) struct RepoSubCmd {
45+
#[clap(long)]
46+
/// Duration for how long to extend the locks (e.g. "10d"). "forever" is also allowed
47+
duration: LockDuration,
48+
}
49+
50+
impl Runnable for RepoSubCmd {
51+
fn run(&self) {
52+
if let Err(err) = self.inner_run() {
53+
status_err!("{}", err);
54+
RUSTIC_APP.shutdown(Shutdown::Crash);
55+
};
56+
}
57+
}
58+
59+
impl RepoSubCmd {
60+
fn inner_run(&self) -> Result<()> {
61+
let config = RUSTIC_APP.config();
62+
let repo = open_repository(&config.repository)?;
63+
repo.lock_repo(self.duration.0)?;
64+
Ok(())
65+
}
66+
}
67+
68+
#[derive(clap::Parser, Command, Debug, Clone)]
69+
pub(crate) struct KeysSubCmd {
70+
#[clap(long)]
71+
/// Duration for how long to extend the locks (e.g. "10d"). "forever" is also allowed
72+
duration: LockDuration,
73+
}
74+
75+
impl Runnable for KeysSubCmd {
76+
fn run(&self) {
77+
if let Err(err) = self.inner_run() {
78+
status_err!("{}", err);
79+
RUSTIC_APP.shutdown(Shutdown::Crash);
80+
};
81+
}
82+
}
83+
84+
impl KeysSubCmd {
85+
fn inner_run(&self) -> Result<()> {
86+
let config = RUSTIC_APP.config();
87+
let repo = open_repository(&config.repository)?;
88+
repo.lock_repo_files::<KeyId>(self.duration.0)?;
89+
Ok(())
90+
}
91+
}
92+
93+
#[derive(clap::Parser, Command, Debug, Clone)]
94+
pub(crate) struct SnapSubCmd {
95+
/// Extend locks even if the files are already locked long enough
96+
#[clap(long)]
97+
always_extend_lock: bool,
98+
99+
#[clap(long)]
100+
/// Duration for how long to extend the locks (e.g. "10d"). "forever" is also allowed
101+
duration: LockDuration,
102+
103+
/// Snapshots to lock. If none is given, use filter options to filter from all snapshots
104+
#[clap(value_name = "ID")]
105+
ids: Vec<String>,
106+
}
107+
108+
#[derive(Debug, Clone)]
109+
struct LockDuration(Option<DateTime<Local>>);
110+
111+
impl FromStr for LockDuration {
112+
type Err = anyhow::Error;
113+
fn from_str(s: &str) -> Result<Self> {
114+
match s {
115+
"forever" => Ok(Self(None)),
116+
d => {
117+
let duration = humantime::Duration::from_str(d)?;
118+
let duration = Duration::from_std(*duration)?;
119+
Ok(Self(Some(Local::now() + duration)))
120+
}
121+
}
122+
}
123+
}
124+
125+
impl Runnable for SnapSubCmd {
126+
fn run(&self) {
127+
if let Err(err) = self.inner_run() {
128+
status_err!("{}", err);
129+
RUSTIC_APP.shutdown(Shutdown::Crash);
130+
};
131+
}
132+
}
133+
134+
impl SnapSubCmd {
135+
fn inner_run(&self) -> Result<()> {
136+
let config = RUSTIC_APP.config();
137+
let repo = open_repository(&config.repository)?;
138+
139+
let snapshots = if self.ids.is_empty() {
140+
repo.get_matching_snapshots(|sn| config.snapshot_filter.matches(sn))?
141+
} else {
142+
repo.get_snapshots(&self.ids)?
143+
};
144+
145+
let lock_opts = LockOptions::default()
146+
.always_extend_lock(self.always_extend_lock)
147+
.until(self.duration.0);
148+
149+
repo.lock_snaphots(&lock_opts, &snapshots)?;
150+
151+
Ok(())
152+
}
153+
}

src/commands/snapshots.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ pub fn fill_table(snap: &SnapshotFile, mut add_entry: impl FnMut(&str, String))
184184
DeleteOption::NotSet => "not set".to_string(),
185185
DeleteOption::Never => "never".to_string(),
186186
DeleteOption::After(t) => format!("after {}", t.format("%Y-%m-%d %H:%M:%S")),
187+
DeleteOption::LockedUntil(t) => {
188+
format!("locked until {}", t.format("%Y-%m-%d %H:%M:%S"))
189+
}
190+
DeleteOption::LockedForever => "locked forever".to_string(),
187191
};
188192
add_entry("Delete", delete);
189193
add_entry("Paths", snap.paths.formatln());

src/commands/tag.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ impl TagCmd {
104104
println!("would have modified the following snapshots:\n {old_snap_ids:?}");
105105
}
106106
(false, false) => {
107-
repo.save_snapshots(snapshots)?;
107+
_ = repo.save_snapshots(snapshots)?;
108108
repo.delete_snapshots(&old_snap_ids)?;
109109
}
110110
}

src/commands/tui/snapshots.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,7 @@ impl<'a, P: ProgressBars, S: IndexedFull> Snapshots<'a, P, S> {
695695
.zip(self.snaps_status.iter())
696696
.filter_map(|(snap, status)| status.to_forget.then_some(snap.id));
697697
let delete_ids: Vec<_> = old_snap_ids.chain(snap_ids_to_forget).collect();
698-
self.repo.save_snapshots(save_snaps)?;
698+
_ = self.repo.save_snapshots(save_snaps)?;
699699
self.repo.delete_snapshots(&delete_ids)?;
700700
// re-read snapshots
701701
self.reread()

0 commit comments

Comments
 (0)