{"record":{"id":"432a2eea6d01de04","repo":"risingwavelabs/risingwave","slug":"the-source-dir-and-target-dir-must-not-be-empty","errorCode":null,"errorMessage":"the source_dir and target_dir must not be empty","messagePattern":"the source_dir and target_dir must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/ctl/src/cmd_impl/hummock/migrate_legacy_object.rs","lineNumber":41,"sourceCode":"};\nuse risingwave_object_store::object::object_metrics::ObjectStoreMetrics;\nuse risingwave_object_store::object::prefix::opendal_engine::get_object_prefix;\nuse risingwave_object_store::object::{\n    ObjectStoreImpl, OpendalObjectStore, build_remote_object_store,\n};\n\npub async fn migrate_legacy_object(\n    url: String,\n    source_dir: String,\n    target_dir: String,\n    concurrency: u32,\n) -> anyhow::Result<()> {\n    let source_dir = source_dir.trim_end_matches('/');\n    let target_dir = target_dir.trim_end_matches('/');\n    println!(\"Normalized source_dir: {source_dir}.\");\n    println!(\"Normalized target_dir: {target_dir}.\");\n    if source_dir.is_empty() || target_dir.is_empty() {\n        return Err(anyhow!(\"the source_dir and target_dir must not be empty\"));\n    }\n    if target_dir.starts_with(source_dir) {\n        return Err(anyhow!(\"the target_dir must not include source_dir\"));\n    }\n    let mut config = ObjectStoreConfig::default();\n    config.s3.developer.use_opendal = true;\n    let store = build_remote_object_store(\n        &url,\n        ObjectStoreMetrics::unused().into(),\n        \"migrate_legacy_object\",\n        config.into(),\n    )\n    .await;\n    let ObjectStoreImpl::Opendal(opendal) = store else {\n        return Err(anyhow!(\"OpenDAL is required\"));\n    };\n    let mut iter = opendal.list(source_dir, None, None).await?;\n    let mut count = 0;","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/ctl/src/cmd_impl/hummock/migrate_legacy_object.rs#L23-L59","documentation":"The legacy object migration command trims trailing slashes from source_dir/target_dir and rejects either being empty, because migrating to or from the bucket root is not supported by the path-relative rewrite logic. It fails fast before touching the object store.","triggerScenarios":"Running `rw hummock migrate-legacy-object` with an empty or '/'-only source_dir or target_dir argument.","commonSituations":"Omitting the --source-dir/--target-dir flags entirely; passing '/' intending 'whole bucket'; script variables defaulting to empty strings.","solutions":["Pass explicit non-empty relative directory prefixes for both source_dir and target_dir","If you truly need whole-bucket migration, restructure so objects live under a common prefix","Check shell quoting/variable expansion isn't producing an empty argument"],"exampleFix":"// before\nrw hummock migrate-legacy-object --source-dir '' --target-dir 'new/'\n// after\nrw hummock migrate-legacy-object --source-dir 'legacy' --target-dir 'new'","handlingStrategy":"validation","validationCode":"let s = source_dir.trim_end_matches('/');\nlet t = target_dir.trim_end_matches('/');\nif s.is_empty() || t.is_empty() {\n    panic!(\"source_dir and target_dir must be non-empty (not '/')\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pass explicit relative prefixes, never '/' or empty strings","Default script variables to real values, not empty strings","Echo the normalized dirs before running migration"],"tags":["cli","validation","object-store","migration"],"backgroundTag":"empty-required-field","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}