Load reflog from sled
Diff
src/git.rs | 43 -------------------------------------------
src/database/indexer.rs | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
src/methods/filters.rs | 4 ++++
src/methods/repo.rs | 23 +++++++++++++++++------
templates/repo/log.html | 10 +++++-----
src/database/schema/commit.rs | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
src/database/schema/prefixes.rs | 10 ++++++----
src/database/schema/repository.rs | 11 +++++------
8 files changed, 142 insertions(+), 83 deletions(-)
@@ -299,49 +299,6 @@
})
.await
}
#[instrument(skip(self))]
pub async fn commits(
self: Arc<Self>,
branch: Option<&str>,
offset: usize,
) -> (Vec<Commit>, Option<usize>) {
const LIMIT: usize = 200;
let ref_name = branch.map(|branch| format!("refs/heads/{}", branch));
tokio::task::spawn_blocking(move || {
let repo = self.repo.lock();
let mut revs = repo.revwalk().unwrap();
if let Some(ref_name) = ref_name.as_deref() {
revs.push_ref(ref_name).unwrap();
} else {
revs.push_head().unwrap();
}
let mut commits: Vec<Commit> = revs
.skip(offset)
.take(LIMIT + 1)
.map(|rev| {
let rev = rev.unwrap();
repo.find_commit(rev).unwrap().into()
})
.collect();
let next_offset = if commits.len() > LIMIT {
commits.truncate(LIMIT);
Some(offset + LIMIT)
} else {
None
};
(commits, next_offset)
})
.await
.unwrap()
}
}
pub enum PathDestination {
@@ -1,31 +1,76 @@
use git2::Sort;
use std::path::{Path, PathBuf};
use time::OffsetDateTime;
use crate::database::schema::commit::{Author, Commit};
use crate::database::schema::repository::{Repository, RepositoryId};
pub fn run_indexer(db: &sled::Db) {
let scan_path = Path::new("/Users/jordan/Code/test-git");
update_repository_metadata(scan_path, &db);
for (relative_path, _repository) in Repository::fetch_all(&db) {
for (relative_path, db_repository) in Repository::fetch_all(&db) {
let git_repository = git2::Repository::open(scan_path.join(relative_path)).unwrap();
for reference in git_repository.references().unwrap() {
let _reference = if let Some(reference) = reference.as_ref().ok().and_then(|v| v.name())
let reference = if let Some(reference) = reference.as_ref().ok().and_then(|v| v.name())
{
reference
} else {
continue;
};
let commit_tree = db_repository.commit_tree(db, reference);
let mut revwalk = git_repository.revwalk().unwrap();
revwalk.set_sorting(Sort::REVERSE).unwrap();
revwalk.push_ref(reference).unwrap();
let mut i = 0;
for rev in revwalk {
let rev = rev.unwrap();
let commit = git_repository.find_commit(rev).unwrap();
let author = commit.author();
let committer = commit.committer();
let author = Author {
name: author.name().map(ToString::to_string).unwrap_or_default(),
email: author.email().map(ToString::to_string).unwrap_or_default(),
time: OffsetDateTime::from_unix_timestamp(author.when().seconds()).unwrap(),
};
let committer = Author {
name: committer
.name()
.map(ToString::to_string)
.unwrap_or_default(),
email: committer
.email()
.map(ToString::to_string)
.unwrap_or_default(),
time: OffsetDateTime::from_unix_timestamp(committer.when().seconds()).unwrap(),
};
let db_commit = Commit {
summary: commit
.summary()
.map(ToString::to_string)
.unwrap_or_default(),
message: commit.body().map(ToString::to_string).unwrap_or_default(),
committer,
author,
hash: commit.id().as_bytes().to_vec(),
};
i += 1;
db_commit.insert(&commit_tree, i);
}
}
}
}
@@ -5,3 +5,7 @@
pub fn file_perms(s: &i32) -> Result<String, askama::Error> {
Ok(unix_mode::to_string(s.unsigned_abs()))
}
pub fn hex(s: &[u8]) -> Result<String, askama::Error> {
Ok(hex::encode(s))
}
@@ -158,21 +158,30 @@
#[template(path = "repo/log.html")]
pub struct LogView {
repo: Repository,
commits: Vec<Commit>,
commits: Vec<crate::database::schema::commit::Commit>,
next_offset: Option<usize>,
branch: Option<String>,
}
pub async fn handle_log(
Extension(repo): Extension<Repository>,
Extension(RepositoryPath(repository_path)): Extension<RepositoryPath>,
Extension(git): Extension<Arc<Git>>,
Extension(db): Extension<sled::Db>,
Query(query): Query<LogQuery>,
) -> Response {
let open_repo = git.repo(repository_path).await;
let (commits, next_offset) = open_repo
.commits(query.branch.as_deref(), query.offset.unwrap_or(0))
.await;
let offset = query.offset.unwrap_or(0);
let reference = format!("refs/heads/{}", query.branch.as_deref().unwrap_or("master"));
let repository =
crate::database::schema::repository::Repository::open(&db, &*repo).unwrap();
let commit_tree = repository.commit_tree(&db, &reference);
let mut commits = commit_tree.fetch_latest(101, offset);
let next_offset = if commits.len() == 101 {
commits.pop();
Some(offset + 100)
} else {
None
};
into_response(&LogView {
repo,
@@ -16,14 +16,14 @@
{% for commit in commits %}
<tr>
<td>
<time datetime="{{ commit.committer().time() }}" title="{{ commit.committer().time() }}">
{{ commit.committer().time()|timeago }}
<time datetime="{{ commit.committer.time }}" title="{{ commit.committer.time }}">
{{ commit.committer.time.clone()|timeago }}
</time>
</td>
<td><a href="/{{ repo.display() }}/commit/?id={{ commit.oid() }}">{{ commit.summary() }}</a></td>
<td><a href="/{{ repo.display() }}/commit/?id={{ commit.hash|hex }}">{{ commit.summary }}</a></td>
<td>
<img src="https://www.gravatar.com/avatar/{{ commit.author().email_md5() }}?s=13&d=retro" width="13" height="13">
{{ commit.author().name() }}
<img src="https://www.gravatar.com/avatar/{{ commit.author.email }}?s=13&d=retro" width="13" height="13">
{{ commit.author.name }}
</td>
</tr>
{% endfor %}
@@ -1,20 +1,63 @@
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use time::OffsetDateTime;
#[derive(Serialize, Deserialize, Debug)]
pub struct Commit {
age: String,
message: String,
author: String,
pub summary: String,
pub message: String,
pub author: Author,
pub committer: Author,
pub hash: Vec<u8>,
}
impl Commit {}
#[derive(Serialize, Deserialize, Debug)]
pub struct Author {
pub name: String,
pub email: String,
pub time: OffsetDateTime,
}
impl Commit {
pub fn insert(&self, database: &CommitTree, id: usize) {
database
.insert(id.to_be_bytes(), bincode::serialize(self).unwrap())
.unwrap();
}
}
pub struct CommitTree(sled::Tree);
pub struct CommitVault {
_tree: sled::Tree,
impl Deref for CommitTree {
type Target = sled::Tree;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl CommitVault {
impl CommitTree {
pub(super) fn new(tree: sled::Tree) -> Self {
Self { _tree: tree }
Self(tree)
}
pub fn fetch_latest(&self, amount: usize, offset: usize) -> Vec<Commit> {
let (latest_key, _) = self.last().unwrap().unwrap();
let mut latest_key_bytes = [0; std::mem::size_of::<usize>()];
latest_key_bytes.copy_from_slice(&latest_key);
let end = usize::from_be_bytes(latest_key_bytes).saturating_sub(offset);
let start = end.saturating_sub(amount);
self.range(start.to_be_bytes()..end.to_be_bytes())
.rev()
.map(|res| {
let (_, value) = res?;
let details = bincode::deserialize(&value).unwrap();
Ok(details)
})
.collect::<Result<Vec<_>, sled::Error>>()
.unwrap()
}
}
@@ -20,15 +20,17 @@
prefixed
}
pub fn commit_id<T: AsRef<[u8]>>(repository: RepositoryId, commit: T) -> Vec<u8> {
let commit = commit.as_ref();
pub fn commit_id<T: AsRef<[u8]>>(repository: RepositoryId, reference: T) -> Vec<u8> {
let reference = reference.as_ref();
let mut prefixed = Vec::with_capacity(
commit.len() + std::mem::size_of::<RepositoryId>() + std::mem::size_of::<TreePrefix>(),
reference.len()
+ std::mem::size_of::<RepositoryId>()
+ std::mem::size_of::<TreePrefix>(),
);
prefixed.push(TreePrefix::Commit as u8);
prefixed.extend_from_slice(&repository.to_ne_bytes());
prefixed.extend_from_slice(&commit);
prefixed.extend_from_slice(&reference);
prefixed
}
@@ -1,4 +1,4 @@
use crate::database::schema::commit::CommitVault;
use crate::database::schema::commit::CommitTree;
use crate::database::schema::prefixes::TreePrefix;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
@@ -27,6 +27,7 @@
.scan_prefix([TreePrefix::Repository as u8])
.filter_map(Result::ok)
.map(|(k, v)| {
let key = String::from_utf8_lossy(&k[1..]).to_string();
let value = bincode::deserialize(&v).unwrap();
@@ -53,14 +54,12 @@
.unwrap()
}
#[allow(dead_code)]
pub fn commit_vault(&self, database: &sled::Db, commit: &str) -> CommitVault {
let commit = hex::decode(commit).unwrap();
pub fn commit_tree(&self, database: &sled::Db, reference: &str) -> CommitTree {
let tree = database
.open_tree(TreePrefix::commit_id(self.id, commit))
.open_tree(TreePrefix::commit_id(self.id, reference))
.unwrap();
CommitVault::new(tree)
CommitTree::new(tree)
}
}