{"record":{"id":"de2da9746359ed32","repo":"risingwavelabs/risingwave","slug":"the-target-dir-must-not-include-source-dir","errorCode":null,"errorMessage":"the target_dir must not include source_dir","messagePattern":"the target_dir must not include source_dir","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/ctl/src/cmd_impl/hummock/migrate_legacy_object.rs","lineNumber":44,"sourceCode":"use 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;\n    println!(\"Migration is started: from {source_dir} to {target_dir}.\");\n    let mut from_to = Vec::with_capacity(concurrency as usize);\n    let timer = Instant::now();","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/ctl/src/cmd_impl/hummock/migrate_legacy_object.rs#L26-L62","documentation":"After trimming trailing slashes, the migration command checks that target_dir is not nested inside source_dir. If it were, moving objects would overwrite or corrupt the source being iterated, so it refuses to run.","triggerScenarios":"Running migrate-legacy-object with target_dir starting with source_dir, e.g. source 'data' and target 'data/new', or source 'a' and target 'a/b'.","commonSituations":"Migrating to a subdirectory of the legacy layout; typos where target accidentally shares the source prefix; planning an in-place layout change.","solutions":["Choose a target_dir that does not begin with source_dir (e.g. source 'legacy', target 'objects')","Swap direction if you intended the reverse nesting — that is also rejected the same way","Copy to a separate bucket/prefix if a nested layout is genuinely required"],"exampleFix":"// before\n--source-dir 'hummock' --target-dir 'hummock/v2'\n// after\n--source-dir 'hummock' --target-dir 'hummock-v2'","handlingStrategy":"validation","validationCode":"if target_dir.trim_end_matches('/').starts_with(source_dir.trim_end_matches('/')) {\n    panic!(\"target_dir must not be nested within source_dir\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pick target prefixes disjoint from the source prefix","Sketch the before/after key layout before migrating","Avoid in-place subdirectory migrations"],"tags":["cli","validation","object-store","migration"],"backgroundTag":"conflicting-config-options","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"}