Skip to content

Commit 0cbf842

Browse files
committed
Migrate to RocksDB
1 parent 0beef9b commit 0cbf842

17 files changed

+725
-493
lines changed

Cargo.lock

+208-115
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ parking_lot = "0.12"
3232
serde = { version = "1.0", features = ["derive", "rc"] }
3333
sha2 = "0.10"
3434
syntect = "5"
35-
sled = { version = "0.34", features = ["compression"] }
35+
rocksdb = "0.21"
3636
tar = "0.4"
3737
flate2 = "1.0"
3838
time = { version = "0.3", features = ["serde"] }
@@ -45,11 +45,12 @@ tower-service = "0.3"
4545
tower-layer = "0.3"
4646
tower-http = { version = "0.4.4", features = ["cors"] }
4747
tracing = "0.1"
48-
tracing-subscriber = "0.3"
48+
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
4949
unix_mode = "0.1"
5050
uuid = { version = "1.6", features = ["v4"] }
5151
httparse = "1.7"
5252
yoke = { version = "0.7.1", features = ["derive"] }
53+
rand = "0.8.5"
5354

5455
[build-dependencies]
5556
anyhow = "1.0"

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
[See it in action!](https://git.inept.dev/)
66

7-
A gitweb/cgit-like interface for the modern age. Written in Rust using Axum, git2, Askama and Sled.
7+
A gitweb/cgit-like interface for the modern age. Written in Rust using Axum, git2, Askama and RocksDB.
88

99
Includes a dark mode for late night committing.
1010

@@ -35,15 +35,15 @@ Includes a dark mode for late night committing.
3535
## Features
3636

3737
- **Efficient Metadata Storage**
38-
[Sled][] is used to store all metadata about a repository, including commits, branches, and tags. Metadata is reindexed, and the reindex interval is configurable (default: every 5 minutes), resulting in up to 97% faster load times for large repositories.
38+
[RocksDB][] is used to store all metadata about a repository, including commits, branches, and tags. Metadata is reindexed, and the reindex interval is configurable (default: every 5 minutes), resulting in up to 97% faster load times for large repositories.
3939

4040
- **On-Demand Loading**
4141
Files, trees, and diffs are loaded using [git2][] directly upon request. A small in-memory cache is included for rendered READMEs and diffs, enhancing performance.
4242

4343
- **Dark Mode Support**
4444
Enjoy a dark mode for late-night committing, providing a visually comfortable experience during extended coding sessions.
4545

46-
[Sled]: https://github.com/spacejam/sled
46+
[RocksDB]: https://github.com/facebook/rocksdb
4747
[git2]: https://github.com/rust-lang/git2-rs
4848

4949
## Getting Started

doc/man/rgit.1.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ SYNOPSIS
1515
DESCRIPTION
1616
===========
1717

18-
A gitweb/cgit-like interface for the modern age. Written in Rust using Axum, git2, Askama, and Sled.
18+
A gitweb/cgit-like interface for the modern age. Written in Rust using Axum, git2, Askama, and RocksDB.
1919

2020
_bind_address_
2121

@@ -47,9 +47,9 @@ OPTIONS
4747

4848
**-d** _path_, **\--db-store** _path_
4949

50-
: Path to a directory in which the Sled database should be stored, will be created if it doesn't already exist.
50+
: Path to a directory in which the RocksDB database should be stored, will be created if it doesn't already exist.
5151

52-
The Sled database is very quick to generate, so this can be pointed to temporary storage. (Required)
52+
The RocksDB database is very quick to generate, so this can be pointed to temporary storage. (Required)
5353

5454
Example:
5555

src/database/indexer.rs

+45-28
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::{
44
ffi::OsStr,
55
fmt::Debug,
66
path::{Path, PathBuf},
7+
sync::Arc,
78
};
89

910
use anyhow::Context;
@@ -14,20 +15,19 @@ use tracing::{error, info, info_span, instrument, warn};
1415

1516
use crate::database::schema::{
1617
commit::Commit,
17-
prefixes::TreePrefix,
1818
repository::{Repository, RepositoryId},
1919
tag::{Tag, TagTree},
2020
};
2121

22-
pub fn run(scan_path: &Path, db: &sled::Db) {
22+
pub fn run(scan_path: &Path, db: &Arc<rocksdb::DB>) {
2323
let span = info_span!("index_update");
2424
let _entered = span.enter();
2525

2626
info!("Starting index update");
2727

2828
update_repository_metadata(scan_path, db);
29-
update_repository_reflog(scan_path, db);
30-
update_repository_tags(scan_path, db);
29+
update_repository_reflog(scan_path, db.clone());
30+
update_repository_tags(scan_path, db.clone());
3131

3232
info!("Flushing to disk");
3333

@@ -39,7 +39,7 @@ pub fn run(scan_path: &Path, db: &sled::Db) {
3939
}
4040

4141
#[instrument(skip(db))]
42-
fn update_repository_metadata(scan_path: &Path, db: &sled::Db) {
42+
fn update_repository_metadata(scan_path: &Path, db: &rocksdb::DB) {
4343
let mut discovered = Vec::new();
4444
discover_repositories(scan_path, &mut discovered);
4545

@@ -49,7 +49,7 @@ fn update_repository_metadata(scan_path: &Path, db: &sled::Db) {
4949
};
5050

5151
let id = match Repository::open(db, relative) {
52-
Ok(v) => v.map_or_else(|| RepositoryId::new(db), |v| v.get().id),
52+
Ok(v) => v.map_or_else(RepositoryId::new, |v| v.get().id),
5353
Err(error) => {
5454
// maybe we could nuke it ourselves, but we need to instantly trigger
5555
// a reindex and we could enter into an infinite loop if there's a bug
@@ -75,7 +75,7 @@ fn update_repository_metadata(scan_path: &Path, db: &sled::Db) {
7575
}
7676
};
7777

78-
Repository {
78+
let res = Repository {
7979
id,
8080
name,
8181
description,
@@ -88,6 +88,10 @@ fn update_repository_metadata(scan_path: &Path, db: &sled::Db) {
8888
.map(Cow::Owned),
8989
}
9090
.insert(db, relative);
91+
92+
if let Err(error) = res {
93+
warn!(%error, "Failed to insert repository");
94+
}
9195
}
9296
}
9397

@@ -116,8 +120,8 @@ fn find_last_committed_time(repo: &git2::Repository) -> Result<OffsetDateTime, g
116120
}
117121

118122
#[instrument(skip(db))]
119-
fn update_repository_reflog(scan_path: &Path, db: &sled::Db) {
120-
let repos = match Repository::fetch_all(db) {
123+
fn update_repository_reflog(scan_path: &Path, db: Arc<rocksdb::DB>) {
124+
let repos = match Repository::fetch_all(&db) {
121125
Ok(v) => v,
122126
Err(error) => {
123127
error!(%error, "Failed to read repository index to update reflog, consider deleting database directory");
@@ -126,7 +130,7 @@ fn update_repository_reflog(scan_path: &Path, db: &sled::Db) {
126130
};
127131

128132
for (relative_path, db_repository) in repos {
129-
let Some(git_repository) = open_repo(scan_path, &relative_path, db_repository.get(), db)
133+
let Some(git_repository) = open_repo(scan_path, &relative_path, db_repository.get(), &db)
130134
else {
131135
continue;
132136
};
@@ -139,6 +143,8 @@ fn update_repository_reflog(scan_path: &Path, db: &sled::Db) {
139143
}
140144
};
141145

146+
let mut valid_references = Vec::new();
147+
142148
for reference in references.filter_map(Result::ok) {
143149
let reference_name = String::from_utf8_lossy(reference.name_bytes());
144150
if !reference_name.starts_with("refs/heads/")
@@ -147,18 +153,24 @@ fn update_repository_reflog(scan_path: &Path, db: &sled::Db) {
147153
continue;
148154
}
149155

156+
valid_references.push(reference_name.to_string());
157+
150158
if let Err(error) = branch_index_update(
151159
&reference,
152160
&reference_name,
153161
&relative_path,
154162
db_repository.get(),
155-
db,
163+
db.clone(),
156164
&git_repository,
157165
false,
158166
) {
159167
error!(%error, "Failed to update reflog for {relative_path}@{reference_name}");
160168
}
161169
}
170+
171+
if let Err(error) = db_repository.get().replace_heads(&db, &valid_references) {
172+
error!(%error, "Failed to update heads");
173+
}
162174
}
163175
}
164176

@@ -168,20 +180,21 @@ fn branch_index_update(
168180
reference_name: &str,
169181
relative_path: &str,
170182
db_repository: &Repository<'_>,
171-
db: &sled::Db,
183+
db: Arc<rocksdb::DB>,
172184
git_repository: &git2::Repository,
173185
force_reindex: bool,
174186
) -> Result<(), anyhow::Error> {
175187
info!("Refreshing indexes");
176188

189+
let commit_tree = db_repository.commit_tree(db.clone(), reference_name);
190+
177191
if force_reindex {
178-
db.drop_tree(TreePrefix::commit_id(db_repository.id, reference_name))?;
192+
commit_tree.drop_commits()?;
179193
}
180194

181195
let commit = reference.peel_to_commit()?;
182-
let commit_tree = db_repository.commit_tree(db, reference_name)?;
183196

184-
let latest_indexed = if let Some(latest_indexed) = commit_tree.fetch_latest_one() {
197+
let latest_indexed = if let Some(latest_indexed) = commit_tree.fetch_latest_one()? {
185198
if commit.id().as_bytes() == &*latest_indexed.get().hash {
186199
info!("No commits since last index");
187200
return Ok(());
@@ -196,7 +209,7 @@ fn branch_index_update(
196209
revwalk.set_sorting(Sort::REVERSE)?;
197210
revwalk.push_ref(reference_name)?;
198211

199-
let tree_len = commit_tree.len();
212+
let tree_len = commit_tree.len()?;
200213
let mut seen = false;
201214
let mut i = 0;
202215
for rev in revwalk {
@@ -220,7 +233,7 @@ fn branch_index_update(
220233
let author = commit.author();
221234
let committer = commit.committer();
222235

223-
Commit::new(&commit, &author, &committer).insert(&commit_tree, tree_len + i);
236+
Commit::new(&commit, &author, &committer).insert(&commit_tree, tree_len + i)?;
224237
i += 1;
225238
}
226239

@@ -238,12 +251,14 @@ fn branch_index_update(
238251
);
239252
}
240253

254+
commit_tree.update_counter(tree_len + i)?;
255+
241256
Ok(())
242257
}
243258

244259
#[instrument(skip(db))]
245-
fn update_repository_tags(scan_path: &Path, db: &sled::Db) {
246-
let repos = match Repository::fetch_all(db) {
260+
fn update_repository_tags(scan_path: &Path, db: Arc<rocksdb::DB>) {
261+
let repos = match Repository::fetch_all(&db) {
247262
Ok(v) => v,
248263
Err(error) => {
249264
error!(%error, "Failed to read repository index to update tags, consider deleting database directory");
@@ -252,13 +267,17 @@ fn update_repository_tags(scan_path: &Path, db: &sled::Db) {
252267
};
253268

254269
for (relative_path, db_repository) in repos {
255-
let Some(git_repository) = open_repo(scan_path, &relative_path, db_repository.get(), db)
270+
let Some(git_repository) = open_repo(scan_path, &relative_path, db_repository.get(), &db)
256271
else {
257272
continue;
258273
};
259274

260-
if let Err(error) = tag_index_scan(&relative_path, db_repository.get(), db, &git_repository)
261-
{
275+
if let Err(error) = tag_index_scan(
276+
&relative_path,
277+
db_repository.get(),
278+
db.clone(),
279+
&git_repository,
280+
) {
262281
error!(%error, "Failed to update tags for {relative_path}");
263282
}
264283
}
@@ -268,12 +287,10 @@ fn update_repository_tags(scan_path: &Path, db: &sled::Db) {
268287
fn tag_index_scan(
269288
relative_path: &str,
270289
db_repository: &Repository<'_>,
271-
db: &sled::Db,
290+
db: Arc<rocksdb::DB>,
272291
git_repository: &git2::Repository,
273292
) -> Result<(), anyhow::Error> {
274-
let tag_tree = db_repository
275-
.tag_tree(db)
276-
.context("Failed to read tag index tree")?;
293+
let tag_tree = db_repository.tag_tree(db);
277294

278295
let git_tags: HashSet<_> = git_repository
279296
.references()
@@ -282,7 +299,7 @@ fn tag_index_scan(
282299
.filter(|v| v.name_bytes().starts_with(b"refs/tags/"))
283300
.map(|v| String::from_utf8_lossy(v.name_bytes()).into_owned())
284301
.collect();
285-
let indexed_tags: HashSet<String> = tag_tree.list().into_iter().collect();
302+
let indexed_tags: HashSet<String> = tag_tree.list()?.into_iter().collect();
286303

287304
// insert any git tags that are missing from the index
288305
for tag_name in git_tags.difference(&indexed_tags) {
@@ -330,7 +347,7 @@ fn open_repo<P: AsRef<Path> + Debug>(
330347
scan_path: &Path,
331348
relative_path: P,
332349
db_repository: &Repository<'_>,
333-
db: &sled::Db,
350+
db: &rocksdb::DB,
334351
) -> Option<git2::Repository> {
335352
match git2::Repository::open(scan_path.join(relative_path.as_ref())) {
336353
Ok(v) => Some(v),

0 commit comments

Comments
 (0)