Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions dvs-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,16 @@ pub enum Command {
},
/// Retrieves the given files from dvs storage. You can use a glob or paths.
/// If you pass a directory and a glob, the glob will be ran from that directory.
/// At least one path or --glob must be provided
/// At least one path or --glob must be provided.
Get {
#[clap(required_unless_present = "glob")]
paths: Vec<PathBuf>,
#[clap(long, short)]
glob: Option<String>,
/// Recursively include files in subdirectories for directory inputs.
/// Without this flag, directories return only their direct children.
/// Has no effect when no explicit paths are given.
#[clap(long, short)]
recursive: bool,
/// Show what would be retrieved without making any actual changes
#[clap(long)]
dry_run: bool,
Expand Down Expand Up @@ -405,14 +409,22 @@ fn try_main() -> Result<()> {
Command::Get {
paths,
glob,
recursive,
dry_run,
} => {
if paths.is_empty() && glob.is_none() {
bail!(
"dvs get with no path or --glob would restore every tracked file. \
If that's the intent, use `dvs get --glob '**/*'`."
);
}
let config =
Config::find(&current_dir).ok_or_else(|| anyhow!("Not in a DVS repository"))??;
let dvs_paths = DvsPaths::from_cwd(&config)?;
let all_paths: Vec<_> = resolve_paths_for_get(paths, glob.as_deref(), &dvs_paths)?
.into_iter()
.collect();
let all_paths: Vec<_> =
resolve_paths_for_get(paths, glob.as_deref(), &dvs_paths, recursive)?
.into_iter()
.collect();
if all_paths.is_empty() {
return Err(anyhow!("No files to get"));
}
Expand Down
3 changes: 2 additions & 1 deletion dvs-rpkg/R/dvs-commands.R
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ dvs_status <- function(
#' @inherit dvs_get_impl title description params
#' @rdname dvs_get
#' @export
dvs_get <- function(paths = character(0), glob = NULL, dry_run = NULL) {
dvs_get <- function(paths = character(0), glob = NULL, recursive = NULL, dry_run = NULL) {
dvs_set_threads_impl(getOption("dvs.num_threads"))
progress_callback <- NULL
if (!isTRUE(dry_run)) {
Expand All @@ -90,6 +90,7 @@ dvs_get <- function(paths = character(0), glob = NULL, dry_run = NULL) {
get_data_frame <- dvs_get_impl(
paths = paths,
glob = glob,
recursive = recursive,
dry_run = dry_run,
progress_callback = progress_callback
)
Expand Down
31 changes: 21 additions & 10 deletions dvs-rpkg/R/dvs-wrappers.R
Original file line number Diff line number Diff line change
Expand Up @@ -139,26 +139,33 @@ dvs_add_impl <- function(paths = character(0), message = NULL, glob = NULL, dry_
.val
}

# Generated from Rust fn `dvs_get` (lib.rs:436:15)
# Generated from Rust fn `dvs_get` (lib.rs:447:15)
#' @title Retrieve files from DVS storage into the working directory
#' @description Fetches the specified files from DVS storage and writes them to their original paths in the working directory.
#' @description Fetches the specified files from DVS storage and writes them to their original paths in the working directory. Calling `dvs_get()` with no arguments restores every tracked file in the repository.
#' @param paths Character vector of file paths to retrieve from DVS storage.
#' @param glob Optional glob pattern to select files (e.g. `"data/*.csv"`).
#' Globs use a literal path separator: `*.csv` only matches files in the
#' target directory and will not match `subdir/file.csv`. Use `**/*.csv` to
#' match recursively across subdirectories.
#' @param recursive If `TRUE`, directory inputs include all descendants;
#' if `FALSE` or `NULL` (default), only direct children of the directory
#' are returned. The flag only constrains descendants of paths passed
#' explicitly — when `paths` is empty, every tracked file is returned
#' regardless of nesting depth, and `recursive` has no effect.
#' @param dry_run If `TRUE`, report what would be retrieved without writing files.
#' @param progress_callback Optional handle to enable progress bar display.
#' @keywords internal
#' @source Generated by miniextendr from Rust fn `dvs_get`
dvs_get_impl <- function(paths = character(0), glob = NULL, dry_run = NULL, progress_callback = NULL) {
dvs_get_impl <- function(paths = character(0), glob = NULL, recursive = NULL, dry_run = NULL, progress_callback = NULL) {
stopifnot(
"'glob' must be NULL or character" = is.null(glob) || is.character(glob),
"'glob' must be NULL or have length 1" = is.null(glob) || length(glob) == 1L,
"'recursive' must be NULL or logical" = is.null(recursive) || is.logical(recursive),
"'recursive' must be NULL or have length 1" = is.null(recursive) || length(recursive) == 1L,
"'dry_run' must be NULL or logical" = is.null(dry_run) || is.logical(dry_run),
"'dry_run' must be NULL or have length 1" = is.null(dry_run) || length(dry_run) == 1L
)
.val <- .Call(C_dvs_get, .call = match.call(), paths, glob, dry_run, progress_callback)
.val <- .Call(C_dvs_get, .call = match.call(), paths, glob, recursive, dry_run, progress_callback)
if (inherits(.val, "rust_error_value") && isTRUE(attr(.val, "__rust_error__"))) return(.miniextendr_raise_condition(.val, sys.call()))
.val
}
Expand Down Expand Up @@ -192,11 +199,15 @@ dvs_init_impl <- function(storage_path, root_dir = NULL, group = NULL, metadata_
invisible(.val)
}

# Generated from Rust fn `dvs_status` (lib.rs:294:15)
# Generated from Rust fn `dvs_status` (lib.rs:298:15)
#' @title Report the sync status of DVS-managed files
#' @description Reports the sync status of DVS-managed files. By default all statuses are shown; pass a character vector of status names (e.g. `c("current", "absent")`) to restrict output.
#' @param paths Character vector of file or directory paths to check status for.
#' @param recursive If `TRUE`, recursively include files in subdirectories.
#' @param recursive If `TRUE`, directory inputs include all descendants;
#' if `FALSE` or `NULL` (default), only direct children of the directory
#' are returned. The flag only constrains descendants of paths passed
#' explicitly — when `paths` is empty, every tracked file is returned
#' regardless of nesting depth, and `recursive` has no effect.
#' @param status Character vector of statuses to include. Valid values are
#' `"current"`, `"absent"`, and `"unsynced"`. When empty (default), all
#' statuses are shown.
Expand All @@ -214,7 +225,7 @@ dvs_status_impl <- function(paths = character(0), recursive = NULL, status = c("
.val
}

# Generated from Rust fn `dvs_version` (lib.rs:494:8)
# Generated from Rust fn `dvs_version` (lib.rs:511:8)
#' @source Generated by miniextendr from Rust fn `dvs_version`
#' @export
dvs_version <- function() {
Expand All @@ -223,7 +234,7 @@ dvs_version <- function() {
.val
}

# Generated from Rust fn `dvs_set_threads` (lib.rs:480:15)
# Generated from Rust fn `dvs_set_threads` (lib.rs:497:15)
#' @title Set the number of threads used by DVS parallel operations
#' @description Controls the thread pool size for add, get, and status operations. Pass `NULL` to revert to automatic detection.
#' @param threads Integer number of threads, or `NULL` to reset.
Expand All @@ -240,7 +251,7 @@ dvs_set_threads_impl <- function(threads = NULL) {
invisible(.val)
}

# Generated from Rust fn `format_byte_size` (lib.rs:488:8)
# Generated from Rust fn `format_byte_size` (lib.rs:505:8)
#' @title Format a byte count as a human-readable size string
#' @param size_bytes non-negative integer representing file sizes in bytes.
#' @source Generated by miniextendr from Rust fn `format_byte_size`
Expand All @@ -256,7 +267,7 @@ format_byte_size <- function(size_bytes) {
.val
}

# Generated from Rust fn `set_dvs_log_level` (lib.rs:523:8)
# Generated from Rust fn `set_dvs_log_level` (lib.rs:540:8)
#' @title Set the log level for DVS internals
#' @description Controls which log messages from the DVS internals are routed to R's console. `error` and `warn` go to [stderr()]; `info`, `debug`, and `trace` go to stdout.
#' @param level Character string giving the desired log level. The default
Expand Down
10 changes: 8 additions & 2 deletions dvs-rpkg/man/dvs_get.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion dvs-rpkg/man/dvs_get_impl.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion dvs-rpkg/man/dvs_status.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion dvs-rpkg/man/dvs_status_impl.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dvs-rpkg/src/rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dvs-rpkg/src/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ connections = ["miniextendr-api/connections"]
# block, or rely on `just` recipes that vendor from the local checkout.
[dependencies]
miniextendr-api = { git = "https://github.com/A2-ai/miniextendr", features = ["serde", "time", "log"] }
dvs = { git = "https://github.com/A2-ai/dvs2", branch = "main", package = "dvs" }
dvs = { git = "https://github.com/A2-ai/dvs2", branch = "feat/get-recursive", package = "dvs" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
anyhow = "1"
Expand Down
27 changes: 22 additions & 5 deletions dvs-rpkg/src/rust/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,11 @@ impl From<StatusChoice> for Status {
/// of status names (e.g. `c("current", "absent")`) to restrict output.
///
/// @param paths Character vector of file or directory paths to check status for.
/// @param recursive If `TRUE`, recursively include files in subdirectories.
/// @param recursive If `TRUE`, directory inputs include all descendants;
/// if `FALSE` or `NULL` (default), only direct children of the directory
/// are returned. The flag only constrains descendants of paths passed
/// explicitly — when `paths` is empty, every tracked file is returned
/// regardless of nesting depth, and `recursive` has no effect.
/// @param status Character vector of statuses to include. Valid values are
/// `"current"`, `"absent"`, and `"unsynced"`. When empty (default), all
/// statuses are shown.
Expand Down Expand Up @@ -422,20 +426,28 @@ impl<'a> From<&'a FileMetadata> for FileMetadataView<'a> {
/// Retrieve files from DVS storage into the working directory.
///
/// Fetches the specified files from DVS storage and writes them
/// to their original paths in the working directory.
/// to their original paths in the working directory. Calling
/// `dvs_get()` with no arguments restores every tracked file in
/// the repository.
///
/// @param paths Character vector of file paths to retrieve from DVS storage.
/// @param glob Optional glob pattern to select files (e.g. `"data/*.csv"`).
/// Globs use a literal path separator: `*.csv` only matches files in the
/// target directory and will not match `subdir/file.csv`. Use `**/*.csv` to
/// match recursively across subdirectories.
/// @param recursive If `TRUE`, directory inputs include all descendants;
/// if `FALSE` or `NULL` (default), only direct children of the directory
/// are returned. The flag only constrains descendants of paths passed
/// explicitly — when `paths` is empty, every tracked file is returned
/// regardless of nesting depth, and `recursive` has no effect.
/// @param dry_run If `TRUE`, report what would be retrieved without writing files.
/// @param progress_callback Optional handle to enable progress bar display.
/// @keywords internal
#[miniextendr(r_name = "dvs_get_impl")]
pub(crate) fn dvs_get(
#[miniextendr(default = "character(0)")] paths: Vec<PathBuf>,
#[miniextendr(default = "NULL")] glob: Option<String>,
#[miniextendr(default = "NULL")] recursive: Option<bool>,
#[miniextendr(default = "NULL")] dry_run: Option<bool>,
#[miniextendr(default = "NULL")] progress_callback: Option<ExternalPtr<ProgressBarCallback>>,
) -> Result<DataFrame<AsSerializeRow<GetResult>>> {
Expand All @@ -444,9 +456,14 @@ pub(crate) fn dvs_get(
let config = Config::find(&current_dir).ok_or_else(|| anyhow!("Not in a DVS repository"))??;
let dvs_paths = DvsPaths::from_cwd(&config)?;

let all_paths: Vec<_> = resolve_paths_for_get(paths, glob.as_deref(), &dvs_paths)?
.into_iter()
.collect();
let all_paths: Vec<_> = resolve_paths_for_get(
paths,
glob.as_deref(),
&dvs_paths,
recursive.unwrap_or(false),
)?
.into_iter()
.collect();
if all_paths.is_empty() {
return Err(anyhow!("No files to get"));
}
Expand Down
13 changes: 13 additions & 0 deletions dvs-rpkg/tests/testthat/test-get.R
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,16 @@ test_that("dvs_get on never-added path errors", {
new_dvs_test_repo()
expect_error(dvs_get(paths = "never-added.csv"))
})

test_that("dvs_get() with no args restores every tracked file at every depth", {
new_dvs_test_repo()
write_theoph("top.csv")
write_theoph_shuffled("data/raw/deep.csv", seed = 1)
dvs_add(paths = file.path(getwd(), c("top.csv", "data/raw/deep.csv")))
file.remove(c("top.csv", "data/raw/deep.csv"))

result <- dvs_get()
expect_s3_class(result, "tbl_df")
expect_setequal(result$path, c("top.csv", "data/raw/deep.csv"))
expect_true(all(file.exists(c("top.csv", "data/raw/deep.csv"))))
})
2 changes: 1 addition & 1 deletion dvs/src/files/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub struct StatusFilter {

/// We need to handle `.`, `./` etc but we can't canonicalize because
/// the path might not exist and we want the path relative to the directory so no symlink resolution
fn normalize_path(p: PathBuf) -> Option<PathBuf> {
pub(crate) fn normalize_path(p: PathBuf) -> Option<PathBuf> {
let mut out = PathBuf::new();
for c in p.components() {
match c {
Expand Down
Loading
Loading