Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
737496d
feat: Add struct `git_odb_stream` and enum `git_odb_stream_t`
tecc Aug 17, 2025
39509e7
feat: Add API from `git2/sys/config.h`
tecc Aug 17, 2025
0d1e96f
feat: Add struct `git_reference_iterator`
tecc Aug 17, 2025
b7ce5e6
fix: Rename `git_commit_nth_gen_ancestor`'s first argument
tecc Aug 18, 2025
d877928
fix: Add `git2/sys/config.h` to systest headers, use extern "C" fn
tecc Aug 18, 2025
a5af476
fix: Add missing argument fromm `git_config_backend`, fix function names
tecc Aug 18, 2025
833ed2f
fix: Add missing `iterator` function to `git_config_backend`
tecc Aug 18, 2025
36aea26
fix: Make `git_odb_stream_t` unsigned
tecc Aug 18, 2025
60d0dc4
fix: Remove int type from `git_odb_stream_t`
tecc Aug 18, 2025
b72fff0
fix(odb): Fix memory leak if `git_odb_add_backend` fails
tecc Aug 15, 2025
5244f6b
feat: Complete rough structure of `odb_backend` module
tecc Aug 16, 2025
7e8e15a
feat: Add `OdbBackend::exists_prefix`
tecc Aug 16, 2025
8526f9b
change: Replace `git_error_code` return types with `c_int`
tecc Aug 16, 2025
04dfe76
change: Direct usages of `libc::{size_t, c_int}` and `std::ffi::c_void`
tecc Aug 16, 2025
d094829
chore: Run `cargo fmt`
tecc Aug 16, 2025
d6ac58a
feat: Add `OdbBackend::write`
tecc Aug 16, 2025
c69030c
feat: Add `OdbBackend::refresh` and `OdbBackend::freshen`
tecc Aug 16, 2025
28e10f7
docs: Add more documentation to `OdbBackend`
tecc Aug 16, 2025
0f4c37a
docs: Fix `OdbBackend::freshen` operation flag
tecc Aug 16, 2025
314813a
feat: Add `OdbBackend::write_multipack_index` (`writemidx`)
tecc Aug 16, 2025
79c0267
refactor: Use `ptr::NonNull` and `marker::PhantomData`
tecc Aug 16, 2025
a95868b
feat: Add `OdbBackend::open_writepack` interface
tecc Aug 17, 2025
5e4fb91
docs: Fix documentation of `SupportedOperations::WRITE_PACK`
tecc Aug 17, 2025
203f81e
feat: Streaming read and writes (`readstream` and `writestream`)
tecc Aug 19, 2025
9d875cf
refactor: Use shorthand for allocating/freeing
tecc Aug 19, 2025
04b79b0
fix: Add cast to `_` for `git_odb_stream.mode`
tecc Aug 19, 2025
88a8919
feat: Add SQLite ODB backend example using rusqlite 0.37
tecc Aug 19, 2025
6a6b9ee
feat: Add `OdbBackend::foreach`
tecc Aug 22, 2025
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
62 changes: 62 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ openssl-probe = { version = "0.1", optional = true }
[dev-dependencies]
clap = { version = "4.4.13", features = ["derive"] }
time = { version = "0.3.37", features = ["formatting"] }
rusqlite = { version = "0.37.0", features = ["bundled"] }
tempfile = "3.1.0"
url = "2.5.4"

Expand Down
137 changes: 137 additions & 0 deletions examples/odb_backend_sqlite.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
//! # ODB backend implementation: SQLite
//! The following is a port of libgit2-backends' `sqlite/sqlite.c` file.

use git2::odb_backend::{OdbBackend, OdbBackendAllocation, OdbBackendContext, SupportedOperations};
use git2::{Error, ErrorClass, ErrorCode, ObjectType, Oid};
use libgit2_sys as raw;
use rusqlite::Error as RusqliteError;
use rusqlite::{params, OptionalExtension};
use std::convert::Infallible;

pub struct SqliteBackend {
conn: rusqlite::Connection,
}

const ST_READ: &str = "SELECT type, size, data FROM 'git2_odb' WHERE oid = ?;";
const ST_READ_HEADER: &str = "SELECT type, size FROM 'git2_odb' WHERE `oid` = ?;";
const ST_WRITE: &str = "INSERT OR IGNORE INTO 'git2_odb' VALUES (?, ?, ?, ?)";

impl SqliteBackend {
pub fn new(conn: rusqlite::Connection) -> rusqlite::Result<Self> {
// Check if we need to create the git2_odb table
if conn.table_exists(None, "git2_odb")? {
// Table exists, do nothing
} else {
conn.execute("CREATE TABLE 'git2_odb' ('oid' CHARACTER(20) PRIMARY KET NOT NULL, 'type' INTEGER NOT NULL, 'size' INTEGER NOT NULL, 'data' BLOB)", params![])?;
}

conn.prepare_cached(ST_READ)?;
conn.prepare_cached(ST_READ_HEADER)?;
conn.prepare_cached(ST_WRITE)?;

Ok(Self { conn })
}
}

impl OdbBackend for SqliteBackend {
type Writepack = Infallible;
type ReadStream = Infallible;
type WriteStream = Infallible;

fn supported_operations(&self) -> SupportedOperations {
SupportedOperations::READ
| SupportedOperations::READ_HEADER
| SupportedOperations::WRITE
| SupportedOperations::EXISTS
}

fn read(
&mut self,
ctx: &OdbBackendContext,
oid: Oid,
object_type: &mut ObjectType,
data: &mut OdbBackendAllocation,
) -> Result<(), Error> {
let row = self
.conn
.prepare_cached(ST_READ)
.map_err(map_sqlite_err)?
.query_one(params![oid.as_bytes()], |row| {
let object_type: raw::git_object_t = row.get(0)?;
let size: usize = row.get(1)?;
let data: Box<[u8]> = row.get(2)?;
Ok((ObjectType::from_raw(object_type).unwrap(), size, data))
})
.map_err(map_sqlite_err)?;
*object_type = row.0;
*data = ctx.try_alloc(row.1)?;
data.as_mut().copy_from_slice(&row.2);
Ok(())
}

fn read_header(
&mut self,
_ctx: &OdbBackendContext,
oid: Oid,
length: &mut usize,
object_type: &mut ObjectType,
) -> Result<(), Error> {
let row = self
.conn
.prepare_cached(ST_READ_HEADER)
.map_err(map_sqlite_err)?
.query_one(params![oid.as_bytes()], |row| {
let object_type: raw::git_object_t = row.get(0)?;
let size: usize = row.get(1)?;
Ok((ObjectType::from_raw(object_type).unwrap(), size))
})
.map_err(map_sqlite_err)?;
*object_type = row.0;
*length = row.1;
Ok(())
}

fn write(
&mut self,
_ctx: &OdbBackendContext,
oid: Oid,
object_type: ObjectType,
data: &[u8],
) -> Result<(), Error> {
self.conn
.prepare_cached(ST_WRITE)
.map_err(map_sqlite_err)?
.execute(params![
oid.as_bytes(),
object_type.raw(),
oid.as_bytes().len(),
data
])
.map_err(map_sqlite_err)?;
Ok(())
}

fn exists(&mut self, _ctx: &OdbBackendContext, oid: Oid) -> Result<bool, Error> {
let row = self
.conn
.prepare_cached(ST_READ_HEADER)
.map_err(map_sqlite_err)?
.query_one(params![oid.as_bytes()], |_| Ok(()))
.optional()
.map_err(map_sqlite_err)?;
Ok(row.is_some())
}
}

fn map_sqlite_err(err: RusqliteError) -> Error {
match err {
RusqliteError::QueryReturnedNoRows => {
Error::new(ErrorCode::NotFound, ErrorClass::None, "not found")
}
_ => Error::new(ErrorCode::GenericError, ErrorClass::Object, err.to_string()),
}
}

fn main() {
todo!("Demonstrate how to use SqliteBackend")
}
Loading
Loading