Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
ecfd322
chore(workspace): update Cargo.lock for handler benchmarks
mroczect Aug 20, 2026
29efd32
chore(handler): add criterion dev-dependency and benchmark target
mroczect Aug 20, 2026
8550155
style(handler): remove unnecessary blank lines
mroczect Aug 20, 2026
2f5946b
style(handler): remove unnecessary blank lines
mroczect Aug 20, 2026
720336c
style(handler): remove unnecessary blank line
mroczect Aug 20, 2026
a448c7f
refactor(handler): use alloc and reorder imports
mroczect Aug 20, 2026
5d4345e
refactor(handler): add extern crate and test imports
mroczect Aug 20, 2026
febecd5
style(handler): remove unnecessary blank lines
mroczect Aug 20, 2026
927be07
style(handler): reorder imports
mroczect Aug 20, 2026
f5bf9be
style(handler): reorder imports
mroczect Aug 20, 2026
2a7c8ce
style(handler): reorder imports
mroczect Aug 20, 2026
a04bf14
refactor(handler): add Clone bound to Entry and clean up
mroczect Aug 20, 2026
c1e66e3
style(handler): remove unnecessary blank lines
mroczect Aug 20, 2026
7f1c597
style(handler): reorder imports
mroczect Aug 20, 2026
10ecc1c
style(handler): reorder imports
mroczect Aug 20, 2026
30fca02
style(handler): remove unnecessary blank lines
mroczect Aug 20, 2026
39ebab7
style(handler): remove unnecessary blank lines
mroczect Aug 20, 2026
d965e5d
style(handler): reorder imports
mroczect Aug 20, 2026
292e769
refactor(handler): use HashSet for parent deduplication
mroczect Aug 20, 2026
fa727ca
refactor(handler): use specific imports and iter types
mroczect Aug 20, 2026
137b4c1
refactor(handler): reorder imports and use wrapping_add
mroczect Aug 20, 2026
8bbd536
style(handler): remove unnecessary blank line
mroczect Aug 20, 2026
1389f45
fix(handler): improve duplicate detection in Tree
mroczect Aug 20, 2026
ed51e37
style(handler): remove unnecessary blank lines
mroczect Aug 20, 2026
bcf304d
fix(handler): validate ref name components more strictly
mroczect Aug 20, 2026
11c7f88
bench(handler): add handler benchmarks
mroczect Aug 20, 2026
6820a6f
test(handler): add blob tests
mroczect Aug 20, 2026
979cbac
test(handler): add commit tests
mroczect Aug 20, 2026
411bd26
test(handler): add commit_meta tests
mroczect Aug 20, 2026
bdc7660
test(handler): add common test utilities
mroczect Aug 20, 2026
9cec122
test(handler): add delta tests
mroczect Aug 20, 2026
694fc69
test(handler): add entry_kind tests
mroczect Aug 20, 2026
ccdd3ea
test(handler): add errors tests
mroczect Aug 20, 2026
2a75c12
test(handler): add hash tests
mroczect Aug 20, 2026
531fa3e
test(handler): add criterion import to hash_validation
mroczect Aug 20, 2026
bb06f8c
test(handler): add criterion import to type_validation
mroczect Aug 20, 2026
de206ae
test(handler): add tag_reflog tests
mroczect Aug 20, 2026
e856189
test(handler): add traits_index tests
mroczect Aug 20, 2026
9c345be
test(handler): add tree tests
mroczect Aug 20, 2026
2aad95a
test(handler): add user_id tests
mroczect Aug 20, 2026
20cfd64
test(handler): add validation tests
mroczect Aug 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion libvctrl_handler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,11 @@ keywords = ["version-control", "vcs", "library", "traits"]
categories = ["development-tools", "data-structures"]

[lints]
workspace = true
workspace = true

[dev-dependencies]
criterion = { version = "0.8", default-features = false, features = ["cargo_bench_support"] }

[[bench]]
name = "handler_bench"
harness = false
129 changes: 129 additions & 0 deletions libvctrl_handler/benches/handler_bench.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#![allow(missing_docs)]

use core::hint::black_box;
use core::str::FromStr;

use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
use libvctrl_handler::{
Blob, Commit, EntryKind, HASH_LENGTH, Hash, Tree, TreeEntry, UserID, validate_ref_name,
};

fn build_tree_entries(count: usize) -> Vec<TreeEntry> {
let hash = Hash::from([0_u8; HASH_LENGTH]);
let mut entries = Vec::with_capacity(count);
for i in 0..count {
let name = format!("file_{i:06}");
if let Ok(entry) = TreeEntry::new(name, EntryKind::Blob, hash) {
entries.push(entry);
}
}
entries
}

fn bench_tree_build(c: &mut Criterion) {
let entries = build_tree_entries(5_000);
let _ = c.bench_function("tree/build_5000_entries", |b| {
b.iter_batched(
|| entries.clone(),
|entries| {
let _ = black_box(Tree::new(entries));
},
BatchSize::SmallInput,
);
});
}

fn bench_validate_refs(c: &mut Criterion) {
let valid_refs = [
"refs/heads/main",
"refs/tags/v1.0.0",
"refs/remotes/origin/feature/foo",
"refs/heads/bar",
"refs/heads/a-branch.name",
];
let invalid_refs = [
"refs/heads/.hidden",
"refs/heads/foo.lock/bar",
"@",
"refs/heads//double",
];

let _ = c.bench_function("validation/ref_name_valid", |b| {
b.iter(|| {
for name in &valid_refs {
let _ = black_box(validate_ref_name(name));
}
});
});

let _ = c.bench_function("validation/ref_name_invalid", |b| {
b.iter(|| {
for name in &invalid_refs {
let _ = black_box(validate_ref_name(name));
}
});
});
}

fn bench_hash_parse(c: &mut Criterion) {
let hex_str = "ab".repeat(HASH_LENGTH); // 64 byte hex = 128 char
let _ = c.bench_function("hash/from_hex_string", |b| {
b.iter(|| {
let _ = black_box(Hash::from_str(&hex_str));
});
});
}

fn bench_blob_new(c: &mut Criterion) {
let data = vec![0x42_u8; 1024 * 1024]; // 1 MiB
let _ = c.bench_function("blob/new_1MiB", |b| {
b.iter_batched(
|| data.clone(),
|data| {
let _ = black_box(Blob::new(data));
},
BatchSize::LargeInput,
);
});
}

fn build_user() -> Option<UserID> {
UserID::new("Bench User".into(), "bench@example.com".into()).ok()
}

fn bench_commit_build(c: &mut Criterion) {
let Some(user) = build_user() else {
return;
};
let tree_hash = Hash::from([0_u8; HASH_LENGTH]);
let parents: Vec<Hash> = (0..10).map(|_| Hash::from([1_u8; HASH_LENGTH])).collect();
let message = "benchmark commit".to_string();

let _ = c.bench_function("commit/new_10_parents", |b| {
b.iter_batched(
|| {
(
tree_hash,
parents.clone(),
user.clone(),
user.clone(),
message.clone(),
)
},
|(tree, parents, author, committer, msg)| {
let _ = black_box(Commit::new(tree, parents, author, committer, msg));
},
BatchSize::SmallInput,
);
});
}

criterion_group!(
benches,
bench_tree_build,
bench_validate_refs,
bench_hash_parse,
bench_blob_new,
bench_commit_build
);
criterion_main!(benches);
10 changes: 0 additions & 10 deletions libvctrl_handler/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
pub mod entry_mode {

pub const BLOB: u32 = 0o100_644;

pub const EXECUTABLE: u32 = 0o100_755;

pub const SYMLINK: u32 = 0o120_000;

pub const TREE: u32 = 0o40_000;

pub const SUBMODULE: u32 = 0o160_000;
}

pub const HASH_LENGTH: usize = 64;

pub const MAX_NAME_LENGTH: u64 = 255;

pub const MAX_BLOB_SIZE: u64 = 100 * 1024 * 1024;

pub const MAX_TREE_ENTRIES: u64 = 100_000;

pub const MAX_MESSAGE_LENGTH: u64 = 1024 * 1024;

pub const MAX_PARENT_COUNT: u64 = 0xFFFF;
4 changes: 0 additions & 4 deletions libvctrl_handler/src/enums/core/entry_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,9 @@ use crate::constants::entry_mode;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum EntryKind {
Blob,

Executable,

Symlink,

Tree,

Submodule,
}

Expand Down
1 change: 0 additions & 1 deletion libvctrl_handler/src/enums/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
pub mod core;

pub use core::entry_kind::EntryKind;
22 changes: 5 additions & 17 deletions libvctrl_handler/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,27 @@
use alloc::sync::Arc;
use core::error::Error;
use core::fmt;
use std::io;

use crate::constants::HASH_LENGTH;
use crate::types::Hash;
use std::error::Error;
use std::fmt;
use std::io;
use std::sync::Arc;

#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum VctrlError {
CorruptedData(String),

DuplicateParent,

ExceededMaxSize(String),

InvalidBlameRange,

InvalidEmail(String),

InvalidHashLength(usize),

InvalidName(String),

InvalidTimezoneOffset(i16),

InvalidTreeStructure(String),

IoError(Arc<io::Error>),

ObjectNotFound(Hash),

Other(String),

RefNotFound(String),

SerializationError(String),
}

Expand Down
16 changes: 5 additions & 11 deletions libvctrl_handler/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
pub mod constants;
extern crate alloc;

pub mod enums;
#[cfg(test)]
use criterion as _;

pub mod constants;
pub mod enums;
pub mod errors;

pub mod macros;

pub mod traits;

pub mod types;

pub mod validation;

pub use constants::{
HASH_LENGTH, MAX_BLOB_SIZE, MAX_MESSAGE_LENGTH, MAX_NAME_LENGTH, MAX_PARENT_COUNT,
MAX_TREE_ENTRIES,
};

pub use enums::EntryKind;

pub use errors::VctrlError;

pub use traits::core::{
blame::{Blame, BlameEntry},
config::ConfigStore,
Expand All @@ -39,12 +35,10 @@ pub use traits::core::{
transport::Transport,
verifier::Verifier,
};

pub use types::{
Blob, ChangeKind, Commit, CommitMeta, Conflict, FileDelta, Hash, MergeResult, ReflogEntry, Tag,
Tree, TreeDelta, TreeEntry, UserID,
};

pub use validation::{
validate_hash_bytes, validate_name, validate_ref_name, validate_tree_entry_name,
};
5 changes: 0 additions & 5 deletions libvctrl_handler/src/traits/core/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@ use crate::errors::VctrlError;

pub trait ConfigStore: Send + Sync {
fn get_string(&self, section: &str, key: &str) -> Result<Option<String>, VctrlError>;

fn set_string(&mut self, section: &str, key: &str, value: &str) -> Result<(), VctrlError>;

fn get_bool(&self, section: &str, key: &str) -> Result<Option<bool>, VctrlError>;

fn set_bool(&mut self, section: &str, key: &str, value: bool) -> Result<(), VctrlError>;

fn remove(&mut self, section: &str, key: &str) -> Result<(), VctrlError>;

fn exists(&self, section: &str, key: &str) -> Result<bool, VctrlError>;
}
6 changes: 2 additions & 4 deletions libvctrl_handler/src/traits/core/decoder.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use std::io::Read;

use crate::errors::VctrlError;
use crate::types::{Blob, Commit, Tag, Tree};
use std::io::Read;

pub trait Decoder: Send + Sync {
fn decode_blob<R: Read + Send>(&self, reader: R) -> Result<Blob, VctrlError>;

fn decode_tree<R: Read + Send>(&self, reader: R) -> Result<Tree, VctrlError>;

fn decode_commit<R: Read + Send>(&self, reader: R) -> Result<Commit, VctrlError>;

fn decode_tag<R: Read + Send>(&self, reader: R) -> Result<Tag, VctrlError>;
}
6 changes: 2 additions & 4 deletions libvctrl_handler/src/traits/core/encoder.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
use std::io::Write;

use crate::errors::VctrlError;
use crate::types::{Blob, Commit, Tag, Tree};
use std::io::Write;

pub trait Encoder: Send + Sync {
fn encode_blob<W: Write + Send>(&self, blob: &Blob, writer: &mut W) -> Result<(), VctrlError>;

fn encode_tree<W: Write + Send>(&self, tree: &Tree, writer: &mut W) -> Result<(), VctrlError>;

fn encode_commit<W: Write + Send>(
&self,
commit: &Commit,
writer: &mut W,
) -> Result<(), VctrlError>;

fn encode_tag<W: Write + Send>(&self, tag: &Tag, writer: &mut W) -> Result<(), VctrlError>;
}
3 changes: 2 additions & 1 deletion libvctrl_handler/src/traits/core/hasher.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::io::Read;

use crate::errors::VctrlError;
use crate::types::Hash;
use std::io::Read;

pub trait Hasher: Send + Sync {
fn hash<R: Read + Send>(&self, reader: R) -> Result<Hash, VctrlError>;
Expand Down
13 changes: 1 addition & 12 deletions libvctrl_handler/src/traits/core/index.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,20 @@
use crate::errors::VctrlError;

pub trait Index: Send + Sync {
type Entry: Send + Sync;

type Entry: Clone + Send + Sync;
type Path: Send + Sync;

type TreeId: Send + Sync;

fn add(&mut self, entry: Self::Entry) -> Result<(), VctrlError>;

fn remove(&mut self, path: &Self::Path) -> Result<(), VctrlError>;

fn clear(&mut self) -> Result<(), VctrlError>;

fn get(&self, path: &Self::Path) -> Result<Option<Self::Entry>, VctrlError>;

fn contains(&self, path: &Self::Path) -> Result<bool, VctrlError>;

fn len(&self) -> Result<usize, VctrlError>;

fn is_empty(&self) -> Result<bool, VctrlError> {
Ok(self.len()? == 0)
}

fn entries(&self) -> Result<Vec<Self::Entry>, VctrlError>;

fn write_tree(&self) -> Result<Self::TreeId, VctrlError>;

fn read_tree(&mut self, tree: &Self::TreeId) -> Result<(), VctrlError>;
}
Loading