jj-vcs/jj · error
cannot store git submodules
Error message
cannot store git submodules
What it means
tree_value_to_proto in lib/src/simple_backend.rs converts a jj TreeValue enum into the protobuf representation used by the simple (native jj) store. The proto schema has variants for File, Executable, Symlink, Tree, and Conflict — but no representation for GitSubmodule. Since git submodules (gitlink entries, mode 160000) cannot be expressed in the simple backend's on-disk format, attempting to serialize a tree entry containing one panics with 'cannot store git submodules'.
Source
Thrown at lib/src/simple_backend.rs:450
id,
executable,
copy_id,
} => {
proto.value = Some(crate::protos::simple_store::tree_value::Value::File(
crate::protos::simple_store::tree_value::File {
id: id.to_bytes(),
executable: *executable,
copy_id: copy_id.to_bytes(),
},
));
}
TreeValue::Symlink(id) => {
proto.value = Some(crate::protos::simple_store::tree_value::Value::SymlinkId(
id.to_bytes(),
));
}
TreeValue::GitSubmodule(_id) => {
panic!("cannot store git submodules");
}
TreeValue::Tree(id) => {
proto.value = Some(crate::protos::simple_store::tree_value::Value::TreeId(
id.to_bytes(),
));
}
}
proto
}
fn tree_value_from_proto(proto: crate::protos::simple_store::TreeValue) -> TreeValue {
match proto.value.unwrap() {
crate::protos::simple_store::tree_value::Value::TreeId(id) => {
TreeValue::Tree(TreeId::new(id))
}
crate::protos::simple_store::tree_value::Value::File(
crate::protos::simple_store::tree_value::File {
id,View on GitHub (pinned to 6631dbd4a8)
Solutions
- Use a git-backed store/backend for the repo (jj git init / colocated repo) instead of the simple backend, since the simple store format has no submodule representation.
- Strip or replace the GitSubmodule tree entries (e.g. by removing the submodule or converting the gitlink to a regular file/placeholder) before serializing the tree.
- If you control the import, skip or warn on TreeValue::GitSubmodule entries instead of passing them to the simple backend.
- Track/upgrade to a jj version with explicit submodule handling if one becomes available.
Example fix
// before
let proto = tree_value_to_proto(value); // value is TreeValue::GitSubmodule -> panic
// after
match value {
TreeValue::GitSubmodule(_) => {
// skip or substitute: simple backend cannot represent gitlinks
return None;
}
other => Some(tree_value_to_proto(other)),
} Defensive patterns
Strategy: type-guard
Validate before calling
// Before serializing a tree with the simple backend, scan for submodule entries:
fn has_git_submodule(tree: &Tree) -> bool {
tree.entries().any(|e| matches!(e.value(), TreeValue::GitSubmodule(_)))
}
if has_git_submodule(&tree) { /* refuse or strip before tree_value_to_proto */ } Type guard
fn is_simple_backend_storable(value: &TreeValue) -> bool {
!matches!(value, TreeValue::GitSubmodule(_))
} Try / catch
// Panic aborts; use catch_unwind only to convert to a clean error at the FFI/CLI boundary:
match std::panic::catch_unwind(|| tree_value_to_proto(value)) {
Ok(proto) => proto,
Err(_) => return Err(anyhow!("tree contains a git submodule; use the git backend")),
} Prevention
- Prefer a git-backed (colocated) store for repos that contain or may receive submodules.
- When importing git commits, check for mode-160000 entries and handle them explicitly.
- Wrap serialization entry points with the is_simple_backend_storable guard.
When it happens
Trigger: Calling tree_value_to_proto (directly or via writing/serializing a tree Entry through the simple store backend, e.g. simple_store::tree::Entry construction or committing a tree) when a TreeValue::GitSubmodule is present in the tree — typically after importing a git commit that contains a submodule into a jj repo backed by the simple backend.
Common situations: Cloning or importing a Git repository that uses submodules into a jj repo without using the git-backed store; jj colocated vs non-colocated setups where the simple backend is the default; tooling that walks and re-serializes trees hitting an imported gitlink entry.
Related errors
- Conflicting factory definitions for '{}' factory
- SCM-aware Watchman clocks not supported
- Conflict registering revset function '{name}'
- ContentHash cannot be derived for unions.
AI-assisted analysis of jj-vcs/jj@6631dbd4a8 (2026-08-28).
Data as JSON: /api/errors/4fccb2d9a0f07e0b.
Report an issue: GitHub.