🏡 index : ~doyle/rgit.git

author Jordan Doyle <jordan@doyle.la> 2022-07-17 20:39:33.0 +01:00:00
committer Jordan Doyle <jordan@doyle.la> 2022-07-17 20:39:33.0 +01:00:00
commit
a41e966d637b74dec806da7193a54996f0760e56 [patch]
tree
61801167a92368e2c24c45850e980cc771a6e20c
parent
9ad3b95a4ebcf11de1f46b3061bd6d818c835f5d
download
a41e966d637b74dec806da7193a54996f0760e56.tar.gz

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(-)

diff --git a/src/git.rs b/src/git.rs
index 11e3811..dd41468 100644
--- a/src/git.rs
+++ a/src/git.rs
@@ -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();

            // TODO: avoid having to take + 1 and popping the last commit off
            let next_offset = if commits.len() > LIMIT {
                commits.truncate(LIMIT);
                Some(offset + LIMIT)
            } else {
                None
            };

            (commits, next_offset)
        })
        .await
        .unwrap()
    }
}

pub enum PathDestination {
diff --git a/src/database/indexer.rs b/src/database/indexer.rs
index 56bf671..109f197 100644
--- a/src/database/indexer.rs
+++ a/src/database/indexer.rs
@@ -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 mut revwalk = git_repository.revwalk().unwrap();
            // revwalk.set_sorting(Sort::REVERSE).unwrap();
            // revwalk.push_ref(reference).unwrap();
            //
            // for rev in revwalk {
            //     let rev = rev.unwrap();
            //     let commit = git_repository.find_commit(rev).unwrap();
            // }
            let commit_tree = db_repository.commit_tree(db, reference);

            // TODO: only scan revs from the last time we looked
            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();

                // TODO: all these unwrap_or_defaults need to properly handle non-utf8 data
                let author = Author {
                    name: author.name().map(ToString::to_string).unwrap_or_default(),
                    email: author.email().map(ToString::to_string).unwrap_or_default(),
                    // TODO: this needs to deal with offset
                    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(),
                    // TODO: this needs to deal with offset
                    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);
            }
        }
    }
}
diff --git a/src/methods/filters.rs b/src/methods/filters.rs
index 2cb33e2..3287bf4 100644
--- a/src/methods/filters.rs
+++ a/src/methods/filters.rs
@@ -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))
}
diff --git a/src/methods/repo.rs b/src/methods/repo.rs
index 0cf36ec..465decb 100644
--- a/src/methods/repo.rs
+++ a/src/methods/repo.rs
@@ -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,
diff --git a/templates/repo/log.html b/templates/repo/log.html
index f73e143..4239127 100644
--- a/templates/repo/log.html
+++ a/templates/repo/log.html
@@ -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 %}
diff --git a/src/database/schema/commit.rs b/src/database/schema/commit.rs
index eca799a..db513b4 100644
--- a/src/database/schema/commit.rs
+++ a/src/database/schema/commit.rs
@@ -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()
    }
}
diff --git a/src/database/schema/prefixes.rs b/src/database/schema/prefixes.rs
index e088bba..4473e4a 100644
--- a/src/database/schema/prefixes.rs
+++ a/src/database/schema/prefixes.rs
@@ -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
    }
diff --git a/src/database/schema/repository.rs b/src/database/schema/repository.rs
index da272e6..7a063b1 100644
--- a/src/database/schema/repository.rs
+++ a/src/database/schema/repository.rs
@@ -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)| {
                // strip the prefix we've just scanned for
                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)
    }
}