mirror of
https://github.com/rustic-rs/rustic.git
synced 2025-10-26 11:18:51 +00:00
Merge pull request #93 from rustic-rs/snapshot-path
Allow specifying snapshot with path for some commands
This commit is contained in:
commit
fcdb2ded5d
@ -1,6 +1,6 @@
|
||||
use std::fs::File;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{bail, Result};
|
||||
use async_trait::async_trait;
|
||||
|
||||
use super::{FileType, Id, ReadBackend, WriteBackend};
|
||||
@ -17,16 +17,13 @@ use ChooseBackend::{Local, Rclone, Rest};
|
||||
|
||||
impl ChooseBackend {
|
||||
pub fn from_url(url: &str) -> Result<Self> {
|
||||
if let Some(path) = url.strip_prefix("rclone:") {
|
||||
return Ok(Rclone(RcloneBackend::new(path)?));
|
||||
}
|
||||
if let Some(path) = url.strip_prefix("rest:") {
|
||||
return Ok(Rest(RestBackend::new(path)));
|
||||
}
|
||||
if let Some(path) = url.strip_prefix("local:") {
|
||||
return Ok(Local(LocalBackend::new(path)));
|
||||
}
|
||||
Ok(Local(LocalBackend::new(url)))
|
||||
Ok(match url.split_once(':') {
|
||||
Some(("rclone", path)) => Rclone(RcloneBackend::new(path)?),
|
||||
Some(("rest", path)) => Rest(RestBackend::new(path)),
|
||||
Some(("local", path)) => Local(LocalBackend::new(path)),
|
||||
Some((backend, _)) => bail!("backend {backend} is not supported!"),
|
||||
None => Local(LocalBackend::new(url)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
use std::mem;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::pin::Pin;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
@ -56,6 +56,24 @@ impl Tree {
|
||||
|
||||
Ok(serde_json::from_slice(&data)?)
|
||||
}
|
||||
|
||||
pub async fn subtree_id(be: &impl IndexedBackend, mut id: Id, path: &Path) -> Result<Id> {
|
||||
for p in path.iter() {
|
||||
let p = p.to_str().unwrap();
|
||||
// TODO: check for root instead
|
||||
if p == "/" {
|
||||
continue;
|
||||
}
|
||||
let tree = Tree::from_backend(be, id).await?;
|
||||
let node = tree
|
||||
.nodes()
|
||||
.iter()
|
||||
.find(|node| node.name() == p)
|
||||
.ok_or_else(|| anyhow!("{} not found", p))?;
|
||||
id = node.subtree().ok_or_else(|| anyhow!("{} is no dir", p))?;
|
||||
}
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
|
||||
/// NodeStreamer recursively streams all nodes of a given tree including all subtrees in-order
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use indicatif::ProgressBar;
|
||||
|
||||
@ -36,11 +36,9 @@ struct IdOpt {
|
||||
|
||||
#[derive(Parser)]
|
||||
struct TreeOpts {
|
||||
/// snapshot id
|
||||
id: String,
|
||||
|
||||
/// path within snapshot
|
||||
path: PathBuf,
|
||||
/// snapshot/path to restore
|
||||
#[clap(value_name = "SNAPSHOT[:PATH]")]
|
||||
snap: String,
|
||||
}
|
||||
|
||||
pub(super) async fn execute(be: &impl DecryptReadBackend, opts: Opts) -> Result<()> {
|
||||
@ -76,25 +74,10 @@ async fn cat_blob(be: &impl DecryptReadBackend, tpe: BlobType, opt: IdOpt) -> Re
|
||||
}
|
||||
|
||||
async fn cat_tree(be: &impl DecryptReadBackend, opts: TreeOpts) -> Result<()> {
|
||||
let snap = SnapshotFile::from_str(be, &opts.id, |_| true, progress_counter()).await?;
|
||||
let (id, path) = opts.snap.split_once(':').unwrap_or((&opts.snap, ""));
|
||||
let snap = SnapshotFile::from_str(be, id, |_| true, progress_counter()).await?;
|
||||
let index = IndexBackend::new(be, progress_counter()).await?;
|
||||
let mut id = snap.tree;
|
||||
|
||||
for p in opts.path.iter() {
|
||||
let p = p.to_str().unwrap();
|
||||
// TODO: check for root instead
|
||||
if p == "/" {
|
||||
continue;
|
||||
}
|
||||
let tree = Tree::from_backend(&index, id).await?;
|
||||
let node = tree
|
||||
.nodes()
|
||||
.iter()
|
||||
.find(|node| node.name() == p)
|
||||
.ok_or_else(|| anyhow!("{} not found", p))?;
|
||||
id = node.subtree().ok_or_else(|| anyhow!("{} is no dir", p))?;
|
||||
}
|
||||
|
||||
let id = Tree::subtree_id(&index, snap.tree, Path::new(path)).await?;
|
||||
let data = index.blob_from_backend(&BlobType::Tree, &id).await?;
|
||||
println!("{}", String::from_utf8(data)?);
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use futures::StreamExt;
|
||||
@ -5,30 +7,37 @@ use vlog::*;
|
||||
|
||||
use super::progress_counter;
|
||||
use crate::backend::DecryptReadBackend;
|
||||
use crate::blob::{NodeStreamer, NodeType};
|
||||
use crate::blob::{NodeStreamer, NodeType, Tree};
|
||||
use crate::index::IndexBackend;
|
||||
use crate::repo::SnapshotFile;
|
||||
|
||||
#[derive(Parser)]
|
||||
pub(super) struct Opts {
|
||||
/// reference snapshot
|
||||
id1: String,
|
||||
/// reference snapshot/path
|
||||
#[clap(value_name = "SNAPSHOT1[:PATH1]")]
|
||||
snap1: String,
|
||||
|
||||
/// new snapshot
|
||||
id2: String,
|
||||
/// new snapshot/path [default for PATH2: PATH1]
|
||||
#[clap(value_name = "SNAPSHOT2[:PATH2]")]
|
||||
snap2: String,
|
||||
}
|
||||
|
||||
pub(super) async fn execute(be: &(impl DecryptReadBackend + Unpin), opts: Opts) -> Result<()> {
|
||||
let (id1, path1) = opts.snap1.split_once(':').unwrap_or((&opts.snap1, ""));
|
||||
let (id2, path2) = opts.snap2.split_once(':').unwrap_or((&opts.snap2, path1));
|
||||
|
||||
v1!("getting snapshots...");
|
||||
let snaps = SnapshotFile::from_ids(be, &[opts.id1, opts.id2]).await?;
|
||||
let snaps = SnapshotFile::from_ids(be, &[id1.to_string(), id2.to_string()]).await?;
|
||||
|
||||
let snap1 = &snaps[0];
|
||||
let snap2 = &snaps[1];
|
||||
|
||||
let index = IndexBackend::new(be, progress_counter()).await?;
|
||||
let id1 = Tree::subtree_id(&index, snap1.tree, Path::new(path1)).await?;
|
||||
let id2 = Tree::subtree_id(&index, snap2.tree, Path::new(path2)).await?;
|
||||
|
||||
let mut tree_streamer1 = NodeStreamer::new(index.clone(), snap1.tree).await?;
|
||||
let mut tree_streamer2 = NodeStreamer::new(index, snap2.tree).await?;
|
||||
let mut tree_streamer1 = NodeStreamer::new(index.clone(), id1).await?;
|
||||
let mut tree_streamer2 = NodeStreamer::new(index, id2).await?;
|
||||
|
||||
let mut item1 = tree_streamer1.next().await.transpose()?;
|
||||
let mut item2 = tree_streamer2.next().await.transpose()?;
|
||||
|
||||
@ -1,24 +1,28 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use futures::StreamExt;
|
||||
use std::path::Path;
|
||||
|
||||
use super::progress_counter;
|
||||
use crate::backend::DecryptReadBackend;
|
||||
use crate::blob::NodeStreamer;
|
||||
use crate::blob::{NodeStreamer, Tree};
|
||||
use crate::index::IndexBackend;
|
||||
use crate::repo::SnapshotFile;
|
||||
|
||||
#[derive(Parser)]
|
||||
pub(super) struct Opts {
|
||||
/// snapshot to ls
|
||||
id: String,
|
||||
/// snapshot/path to ls
|
||||
#[clap(value_name = "SNAPSHOT[:PATH]")]
|
||||
snap: String,
|
||||
}
|
||||
|
||||
pub(super) async fn execute(be: &(impl DecryptReadBackend + Unpin), opts: Opts) -> Result<()> {
|
||||
let snap = SnapshotFile::from_str(be, &opts.id, |_| true, progress_counter()).await?;
|
||||
let (id, path) = opts.snap.split_once(':').unwrap_or((&opts.snap, ""));
|
||||
let snap = SnapshotFile::from_str(be, id, |_| true, progress_counter()).await?;
|
||||
let index = IndexBackend::new(be, progress_counter()).await?;
|
||||
let tree = Tree::subtree_id(&index, snap.tree, Path::new(path)).await?;
|
||||
|
||||
let mut tree_streamer = NodeStreamer::new(index, snap.tree).await?;
|
||||
let mut tree_streamer = NodeStreamer::new(index, tree).await?;
|
||||
while let Some(item) = tree_streamer.next().await {
|
||||
let (path, _) = item?;
|
||||
println!("{:?} ", path);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
use std::num::NonZeroU32;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use clap::Parser;
|
||||
@ -12,7 +12,7 @@ use vlog::*;
|
||||
|
||||
use super::{bytes, progress_bytes, progress_counter};
|
||||
use crate::backend::{DecryptReadBackend, FileType, LocalBackend};
|
||||
use crate::blob::{Node, NodeStreamer, NodeType};
|
||||
use crate::blob::{Node, NodeStreamer, NodeType, Tree};
|
||||
use crate::crypto::hash;
|
||||
use crate::id::Id;
|
||||
use crate::index::{IndexBackend, IndexedBackend};
|
||||
@ -32,21 +32,25 @@ pub(super) struct Opts {
|
||||
#[clap(long)]
|
||||
numeric_id: bool,
|
||||
|
||||
/// snapshot to restore
|
||||
id: String,
|
||||
/// snapshot/path to restore
|
||||
#[clap(value_name = "SNAPSHOT[:PATH]")]
|
||||
snap: String,
|
||||
|
||||
/// restore destination
|
||||
dest: String,
|
||||
}
|
||||
|
||||
pub(super) async fn execute(be: &(impl DecryptReadBackend + Unpin), opts: Opts) -> Result<()> {
|
||||
let snap = SnapshotFile::from_str(be, &opts.id, |_| true, progress_counter()).await?;
|
||||
let (id, path) = opts.snap.split_once(':').unwrap_or((&opts.snap, ""));
|
||||
let snap = SnapshotFile::from_str(be, id, |_| true, progress_counter()).await?;
|
||||
|
||||
let index = IndexBackend::new(be, progress_counter()).await?;
|
||||
let tree = Tree::subtree_id(&index, snap.tree, Path::new(path)).await?;
|
||||
|
||||
let dest = LocalBackend::new(&opts.dest);
|
||||
let index = IndexBackend::new(be, progress_counter()).await?;
|
||||
|
||||
v1!("collecting restore information and allocating non-existing files...");
|
||||
let file_infos = allocate_and_collect(&dest, index.clone(), snap.tree, &opts).await?;
|
||||
let file_infos = allocate_and_collect(&dest, index.clone(), tree, &opts).await?;
|
||||
v1!("total restore size: {}", bytes(file_infos.total_size));
|
||||
if file_infos.matched_size > 0 {
|
||||
v1!(
|
||||
@ -63,7 +67,7 @@ pub(super) async fn execute(be: &(impl DecryptReadBackend + Unpin), opts: Opts)
|
||||
}
|
||||
|
||||
v1!("setting metadata...");
|
||||
restore_metadata(&dest, index, snap.tree, &opts).await?;
|
||||
restore_metadata(&dest, index, tree, &opts).await?;
|
||||
|
||||
v1!("done.");
|
||||
Ok(())
|
||||
|
||||
Loading…
Reference in New Issue
Block a user