{"record":{"id":"68e80fe7f505c799","repo":"GitoxideLabs/gitoxide","slug":"cannot-derive-archive-format-from-a-file-without-e","errorCode":null,"errorMessage":"Cannot derive archive format from a file without extension","messagePattern":"Cannot derive archive format from a file without extension","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gitoxide-core/src/repository/archive.rs","lineNumber":97,"sourceCode":"}\n\nfn fetch_rev_info(\n    object: gix::Object<'_>,\n) -> anyhow::Result<(Option<gix::date::SecondsSinceUnixEpoch>, gix::ObjectId)> {\n    Ok(match object.kind {\n        gix::object::Kind::Commit => {\n            let commit = object.into_commit();\n            (Some(commit.committer()?.seconds()), commit.tree_id()?.detach())\n        }\n        gix::object::Kind::Tree => (None, object.id),\n        gix::object::Kind::Tag => fetch_rev_info(object.peel_to_kind(gix::object::Kind::Commit)?)?,\n        gix::object::Kind::Blob => bail!(\"Cannot derive commit or tree from blob at {}\", object.id),\n    })\n}\n\nfn format_from_ext(path: &Path) -> anyhow::Result<archive::Format> {\n    Ok(match path.extension().and_then(std::ffi::OsStr::to_str) {\n        None => bail!(\"Cannot derive archive format from a file without extension\"),\n        Some(\"tar\") => archive::Format::Tar,\n        Some(\"gz\") => archive::Format::TarGz {\n            compression_level: None,\n        },\n        Some(\"zip\") => archive::Format::Zip {\n            compression_level: None,\n        },\n        Some(\"stream\") => archive::Format::InternalTransientNonPersistable,\n        Some(ext) => bail!(\"Format for extension '{ext}' is unsupported\"),\n    })\n}\n","sourceCodeStart":79,"sourceCodeEnd":109,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gitoxide-core/src/repository/archive.rs#L79-L109","documentation":"`format_from_ext` derives the archive format from the output filename's extension. If the path has no extension at all, no format can be inferred and the function bails. gitoxide does not guess archive formats from content.","triggerScenarios":"Calling `archive::stream` with an output path lacking a `.tar`/`.gz`/`.zip`/`.stream` extension - e.g. `out`, `archive.v1`, or a path whose final component has no dot.","commonSituations":"Writing to a named pipe or versioned filename without extension; deriving output names in scripts from commit ids; users expecting a default tar format.","solutions":["Rename the output file to include a supported extension (.tar, .gz, .zip, .stream)","Use `.tar` explicitly when a plain uncompressed archive is wanted","Generate the filename programmatically to always append a valid extension"],"exampleFix":"// before\nlet out = Path::new(\"archive-2024\");\n// after\nlet out = Path::new(\"archive-2024.tar\");","handlingStrategy":"validation","validationCode":"fn ensure_supported_name(path: &Path) -> anyhow::Result<()> {\n    let ext = path.extension().and_then(|e| e.to_str()).unwrap_or(\"\");\n    anyhow::ensure!(matches!(ext, \"tar\" | \"gz\" | \"zip\" | \"stream\"), \"output path needs a supported archive extension\");\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"match archive::stream(repo, rev, out, path_without_ext) {\n    Err(e) if e.to_string().contains(\"without extension\") => {\n        // append \".tar\" and retry\n    }\n    other => other?,\n}","preventionTips":["Always generate archive output names with an explicit extension","Whitelist tar/gz/zip/stream in filename builders","Never derive output filenames from commit ids without appending an extension"],"tags":["git","archive","filename","validation"],"backgroundTag":"missing-required-argument","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}