libvctrl_handler
Fundamental contracts for building a version control system – no implementations, only traits and types
Constitution (handler), reference implementation (core), and plumbing commands
Overview
libvctrl is a modular framework for building version control systems, structured around a strict separation between contracts and implementations. This repository contains the foundational contract layer, packaged as the crate libvctrl_handler.
The handler crate defines:
- A complete set of traits for every major VCS operation: object storage, encoding, decoding, hashing, indexing, reference management, reflogs, remote transport, packing, signing, verification, revision walking, diffing, blame, and configuration.
- Data types representing Git objects (
Blob,Tree,Commit,Tag) and supporting structures (Hash,UserID,TreeEntry, deltas, merges, reflog entries). - Constants enforcing safe size and count limits.
- Validation functions that define the security boundary for names, references, tree entries, and hashes.
- A unified error type (
VctrlError) that all trait implementations must use.
libvctrl_handler contains no concrete implementations. It is the "constitution" upon which all other crates in the libvctrl ecosystem are built. The reference implementation libvctrl_core provides working implementations of the traits (in-memory object store, binary encoder/decoder, SHA-512 hasher, etc.). Higher-level crates libvctrl_plumbing and libvctrl_porcelain build on top of libvctrl_core.
The primary facade crate is libvctrl, which re-exports the handler types and the reference implementation so integrators can use the whole framework with a single dependency.
Architecture
The architecture enforces a one-way dependency flow:
- Handler (contract layer) — this crate — depends only on the Rust standard library.
- Core (reference implementation) depends on the handler and provides concrete implementations.
- Plumbing/Porcelain depend on the core and provide command-level or high-level VCS operations.
The following diagram illustrates the crate dependency structure:
graph TD
subgraph Contract Layer
H[libvctrl_handler]
H --> C[constants]
H --> E[enums]
H --> ER[errors]
H --> M[macros]
H --> T[traits]
H --> TY[types]
H --> V[validation]
end
subgraph Reference Implementation
CORE[libvctrl_core]
CORE --> H
end
subgraph Applications
PL[libvctrl_plumbing]
PO[libvctrl_porcelain]
PL --> CORE
PO --> CORE
end
FACADE[libvctrl facade crate]
FACADE --> H
FACADE --> CORE
Within the handler crate, the module organization is as follows:
graph TD
ROOT[lib.rs]
ROOT --> CONST[constants]
ROOT --> ENUMS[enums]
ROOT --> ERR[errors]
ROOT --> MAC[macros]
ROOT --> TRAITS[traits]
ROOT --> TYPES[types]
ROOT --> VAL[validation]
ENUMS --> EK[enums::core::entry_kind]
TRAITS --> TC[traits::core]
TC --> BLAME[blame]
TC --> CFG[config]
TC --> DEC[decoder]
TC --> DIFF[diff]
TC --> ENC[encoder]
TC --> HASHER[hasher]
TC --> IDX[index]
TC --> OS[object_store]
TC --> PACK[pack]
TC --> RF[ref_store]
TC --> RL[reflog]
TC --> REM[remote]
TC --> RW[revwalk]
TC --> SIGN[signer]
TC --> TRAN[transport]
TC --> VER[verifier]
TYPES --> TYC[types::core]
TYC --> BLOB[blob]
TYC --> COMMIT[commit]
TYC --> DELTA[delta]
TYC --> HASH[hash]
TYC --> MERGE[merge]
TYC --> REFLOG[reflog]
TYC --> TAG[tag]
TYC --> TREE[tree]
TYC --> UID[user_id]
Core Features
- Trait-only contracts — Every VCS subsystem is expressed as a
traitwith clear method signatures, error returns, andSend + Syncbounds. Implementations are decoupled from consumers. - Git-compatible data types —
Blob,Tree,TreeEntry,Commit,Tag,UserID, and their supporting types mirror the Git object model and enforce Git-like invariants. - Fixed-size hash —
Hashis a newtype over[u8; 64](SHA-512 length), with hexadecimal parsing, display, and ordering. - Validation as a security boundary — Dedicated functions validate names, references, tree entry names, and hash lengths. Implementation crates must call these functions, never duplicate the logic.
- Comprehensive error hierarchy —
VctrlErrorcovers invalid lengths, missing objects, corrupted data, I/O failures, serialization issues, tree structure errors, duplicate parents, size limit breaches, and invalid blame ranges. - Strict compile-time guarantees — The crate uses
#![forbid(unsafe_code)],#![deny(missing_docs)], and a suite of Clippy lints to ensure safe, well-documented, idiomatic code.
Technology Stack
- Language: Rust (edition 2024)
- Standard library only: No external dependencies. The handler crate uses only
stdtypes (std::io,std::collections::HashSet,std::path, etc.). - Frameworks/Libraries: None. This crate is a pure contract definition.
- Toolchain: Requires Rust 1.85 or newer (for edition 2024 support).
Project Structure
The source tree of libvctrl_handler is organized as follows:
libvctrl_handler/
├── Cargo.toml
└── src/
├── lib.rs
├── constants.rs
├── enums/
│ ├── mod.rs
│ └── core/
│ ├── mod.rs
│ └── entry_kind.rs
├── errors.rs
├── macros.rs
├── traits/
│ ├── mod.rs
│ └── core/
│ ├── mod.rs
│ ├── blame.rs
│ ├── config.rs
│ ├── decoder.rs
│ ├── diff.rs
│ ├── encoder.rs
│ ├── hasher.rs
│ ├── index.rs
│ ├── object_store.rs
│ ├── pack.rs
│ ├── ref_store.rs
│ ├── reflog.rs
│ ├── remote.rs
│ ├── revwalk.rs
│ ├── signer.rs
│ ├── transport.rs
│ └── verifier.rs
├── types/
│ ├── mod.rs
│ └── core/
│ ├── mod.rs
│ ├── blob.rs
│ ├── commit.rs
│ ├── delta.rs
│ ├── hash.rs
│ ├── merge.rs
│ ├── reflog.rs
│ ├── tag.rs
│ ├── tree.rs
│ └── user_id.rs
└── validation/
├── mod.rs
├── hash.rs
└── name.rs
Getting Started
Prerequisites
- Rust 1.85 or newer — required for the 2024 edition.
- Cargo — the Rust package manager.
- No system-level dependencies are required; the handler crate is pure Rust.
Installation
For most integrators, the recommended entry point is the libvctrl facade crate, which re-exports both the handler contracts and the reference implementation:
[]
= "4.4"
If you are developing an implementation crate and need to depend directly on the contract layer:
[]
= "4.4"
To use the handler from a local checkout or a Git repository:
[]
= { = "https://github.com/mroczect/libvctrl", = "master" }
Configuration
The handler crate itself requires no configuration or environment variables. All size and count limits are defined as constants in libvctrl_handler::constants and are enforced by the validation functions and type constructors.
However, the ConfigStore trait defines an abstraction for reading and writing configuration values. The reference implementation and higher-level crates may use this trait to handle user configuration.
Usage
The handler crate is not meant to be used directly by end users; it is consumed by implementors and integrators. The following examples illustrate how the contracts are used.
Implementing a trait
Implementations must satisfy the trait's method signatures and return VctrlError on failure. The following example shows a minimal in-memory object store:
use ;
use HashMap;
use ;
Constructing a validated data type
Data types enforce validation in their constructors. For example, creating a Commit with metadata:
use ;
use VctrlError;
Encoder/Decoder roundtrip
The Encoder and Decoder traits define how objects are serialized and deserialized. Implementations must adhere to the binary format contract:
use ;
use ;
API Reference / Core Modules
This section documents the public API of the handler crate.
constants
Defines size and count limits and Git entry mode bits.
| Constant | Value | Description |
|---|---|---|
HASH_LENGTH |
64 |
Length of a hash in bytes (SHA-512). |
MAX_NAME_LENGTH |
255 |
Maximum length for names, in bytes. |
MAX_BLOB_SIZE |
100 * 1024 * 1024 |
Maximum blob size in bytes (100 MiB). |
MAX_TREE_ENTRIES |
100_000 |
Maximum number of entries in a tree. |
MAX_MESSAGE_LENGTH |
1024 * 1024 |
Maximum commit/tag message length in bytes (1 MiB). |
MAX_PARENT_COUNT |
65535 |
Maximum number of parent commits. |
constants::entry_mode provides Git mode bits:
| Mode | Value (octal) | Description |
|---|---|---|
BLOB |
0o100_644 |
Regular file. |
EXECUTABLE |
0o100_755 |
Executable file. |
SYMLINK |
0o120_000 |
Symbolic link. |
TREE |
0o40_000 |
Directory (tree). |
SUBMODULE |
0o160_000 |
Submodule commit. |
enums::EntryKind
Represents the kind of an entry in a Git tree.
| Variant | Mode | Description |
|---|---|---|
Blob |
BLOB |
Regular file. |
Executable |
EXECUTABLE |
Executable file. |
Symlink |
SYMLINK |
Symbolic link. |
Tree |
TREE |
Directory. |
Submodule |
SUBMODULE |
Submodule commit. |
Methods: mode() returns the mode bits; from_mode(mode: u32) -> Option<Self> converts raw mode bits.
errors::VctrlError
The unified error type for all operations.
| Variant | Description |
|---|---|
InvalidHashLength(usize) |
Hash length did not match expected 64 bytes. |
InvalidName(String) |
Name invalid (empty, too long, or contains control characters). |
InvalidEmail(String) |
Email invalid. |
ObjectNotFound(Hash) |
Object not found. |
RefNotFound(String) |
Reference not found. |
CorruptedData(String) |
Data corrupted or malformed. |
IoError(Arc<std::io::Error>) |
I/O error. |
SerializationError(String) |
Serialization/deserialization error. |
Other(String) |
Any other error. |
InvalidTreeStructure(String) |
Tree structure invalid (unsorted entries, duplicates). |
InvalidTimezoneOffset(i16) |
Timezone offset out of range (-1440 to 1440). |
DuplicateParent |
Commit contains duplicate parent hashes. |
ExceededMaxSize(String) |
Size or count limit exceeded. |
InvalidBlameRange |
Invalid blame range (zero line count). |
VctrlError implements Display, Error, PartialEq, and Eq. It also provides from_io(e: std::io::Error) -> Self for canonical I/O error conversion.
macros
vctrl_error_other!— Constructs aVctrlError::Otherfrom a format string and arguments.
let err = vctrl_error_other!;
traits::core
The following traits are defined. All are Send + Sync.
Blame
Computes blame information for files.
;
BlameEntry represents a line range attributed to a commit.
ConfigStore
Reads and writes configuration values.
;
;
;
;
;
;
Decoder
Decodes raw Git object bytes into structured types.
;
;
;
;
TreeDiffer
Computes differences between two trees.
type TreeId: Send + Sync;
;
Encoder
Encodes structured Git objects into raw bytes.
;
;
;
;
Hasher
Computes hash values.
;
Index
Manages a Git index (staging area).
type Entry: Send + Sync;
type Path: Send + Sync;
type TreeId: Send + Sync;
;
;
;
;
;
;
;
;
;
;
ObjectStore
Stores and retrieves Git objects.
;
;
;
;
PackWriter / PackReader
Write and read Git pack files.
// PackWriter
type ObjectId: Send + Sync;
;
;
// PackReader
type ObjectId: Send + Sync;
;
RefStore
Manages Git references (branches, tags, etc.).
type RefsIterator: + Send;
;
;
;
;
ReflogStore
Manages reflogs.
type RefName: Send + Sync;
;
;
Remote
Interacts with remote repositories.
type RefSpec: Send + Sync;
type RemoteRef: Send + Sync;
;
;
;
RevWalk
Walks commit history.
type CommitId: Send + Sync;
;
Signer
Signs data.
;
Transport
Transports Git objects.
;
;
Verifier
Verifies signatures.
;
types::core
The following data types are defined:
Hash
Fixed-size hash of 64 bytes (SHA-512 length).
from_bytes(bytes: &[u8]) -> Result<Self, VctrlError>— validates length.as_bytes() -> &[u8; 64]— raw bytes.- Implements
From<[u8; 64]>,TryFrom<&[u8]>,AsRef<[u8]>,FromStr(hex string),Display(hex),Debug(truncated),PartialOrd,Ord.
Blob
Represents a Git blob (file content).
new(data: Vec<u8>) -> Result<Self, VctrlError>— enforcesMAX_BLOB_SIZE.data() -> &[u8],size() -> usize,is_empty() -> bool.
TreeEntry
Represents a single entry in a tree.
new(name: String, kind: EntryKind, hash: Hash) -> Result<Self, VctrlError>— validates tree entry name.- Accessors:
name(),kind(),hash().
Tree
Represents a Git tree (directory listing). Entries are always sorted according to Git ordering rules (tree entries are compared as if their name has a trailing /). Duplicate names are rejected.
new(entries: Vec<TreeEntry>) -> Result<Self, VctrlError>— sorts and validates.- Accessors:
entries() -> &[TreeEntry],len(),is_empty(),get(name: &str) -> Option<&TreeEntry>.
UserID
Represents a user identity (author or committer).
new(name: String, email: String) -> Result<Self, VctrlError>— validates name and email.- Accessors:
name(),email().
CommitMeta
Metadata associated with a commit or tag.
new(timestamp: i64, timezone_offset: i16, encoding: Option<String>) -> Result<Self, VctrlError>— validates timezone offset.- Accessors:
timestamp(),timezone_offset(),encoding().
Commit
Represents a Git commit object.
new(tree, parents, author, committer, message)orwith_meta(...)— validates parent count, message length, and duplicate parents.- Accessors:
tree(),parents(),author(),committer(),message(),meta().
Tag
Represents a Git tag object.
new(name, target, tagger, message)orwith_meta(...)— validates reference name and message length.- Accessors:
name(),target(),tagger(),message(),meta().
ChangeKind
Enum describing the kind of change: Added, Deleted, Modified, TypeChange, Renamed, Copied.
FileDelta
Represents a single file delta between two trees. Provides constructor methods: added, deleted, modified, type_change, renamed, copied. Accessors for path, old path, old hash, new hash, kind, and convenience is_* methods.
TreeDelta
A collection of FileDelta. Supports len, is_empty, iter, changes, IntoIterator.
Conflict
Represents a merge conflict: path, ancestor blob hash, our blob hash, their blob hash.
MergeResult
Enum: Success(Hash) or Conflicts(Vec<Conflict>). Provides is_success, is_conflicts, conflicts.
ReflogEntry
A single reflog entry: old id, new id, reason, timestamp, timezone offset. Constructor validates timezone offset.
validation
Validation functions that define the security boundary.
validate_hash_bytes(bytes: &[u8]) -> Result<(), VctrlError>— ensures exactly 64 bytes.validate_name(name: &str) -> Result<(), VctrlError>— basic name validation.validate_ref_name(name: &str) -> Result<(), VctrlError>— strict Git reference name validation.validate_tree_entry_name(name: &str) -> Result<(), VctrlError>— strict tree entry name validation.
Validation Contract
Validation is the single source of truth for security-critical rules. It is encapsulated in libvctrl_handler::validation and must be used by all implementation crates. Duplicating validation logic in libvctrl_core or elsewhere is forbidden.
Reference Names (validate_ref_name)
Enforces Git-strict rules with additional security hardening.
Forbidden substrings:
..(path traversal)~,^,:,?,*,[,\, space,@{,//<,>,|,"
Forbidden patterns:
- Leading
.(hidden paths) - Leading
/(absolute paths) - Trailing
/ - Trailing
. - Extension
.lock(case-insensitive)
Additional constraints:
- Length must be between 1 and 255 bytes.
- No ASCII control characters.
Tree Entry Names (validate_tree_entry_name)
Stricter than reference names because tree entries map directly to filesystem entries.
Forbidden:
/and\(path separators)- Exact names
.and.. - Length 0 or > 255 bytes
- ASCII control characters
Hash Length (validate_hash_bytes)
- Exactly 64 bytes, corresponding to SHA-512 output length.
Architectural Rule
libvctrl_coreand all implementation crates MUST calllibvctrl_handler::validate_*— never duplicate validation logic.
This rule prevents divergent validation implementations, which could lead to security vulnerabilities or interoperability issues. All type constructors in libvctrl_handler::types already call the appropriate validation functions, so any object created through the public API is guaranteed to be valid.
Testing
The handler crate includes a small set of unit tests, primarily for tree ordering and duplicate rejection, located in src/types/core/tree.rs.
To run the tests:
When developing an implementation crate, you should write additional tests that exercise your concrete implementations against the trait contracts. The handler's validation functions can be used as property-based test oracles.
Contributing
Contributions to libvctrl are welcome. Before submitting a pull request, please ensure:
- Contract stability — Any change to traits or types must be backward-compatible or clearly justified. The handler crate is a foundation; breaking changes propagate to all downstream crates.
- Validation rules — If you modify validation functions, update the Validation Contract section of this README and the test suite accordingly.
- No unsafe code — The crate uses
#![forbid(unsafe_code)]. Do not introduce unsafe blocks. - Documentation — All public items must have doc comments due to
#![deny(missing_docs)]. Ensure new code is documented at the same standard. - Lint compliance — Run
cargo clippywith the project's lint configuration and fix all warnings. - Tests — Add unit tests for new logic and run
cargo testto verify no regressions.
For significant architectural changes, open an issue first to discuss the design with the maintainers.