diff options
Diffstat (limited to '')
-rw-r--r-- | src/git/log.rs | 61 | ||||
-rw-r--r-- | src/git/mod.rs | 5 | ||||
-rw-r--r-- | src/git/repo.rs | 19 | ||||
-rw-r--r-- | src/git/tree.rs | 144 | ||||
-rw-r--r-- | src/main.rs | 22 | ||||
-rw-r--r-- | src/pages/repo/about.rs | 33 | ||||
-rw-r--r-- | src/pages/repo/details.rs | 50 | ||||
-rw-r--r-- | src/pages/repo/mod.rs | 8 | ||||
-rw-r--r-- | src/templ_data/mod.rs | 30 | ||||
-rw-r--r-- | src/templ_data/repo.rs | 80 | ||||
-rw-r--r-- | src/types/mod.rs | 30 |
11 files changed, 395 insertions, 87 deletions
diff --git a/src/git/log.rs b/src/git/log.rs new file mode 100644 index 0000000..dab9e69 --- /dev/null +++ b/src/git/log.rs @@ -0,0 +1,61 @@ +//! libgit2 log parsing + +use crate::git::{self, tree::FileNode}; +use git2::{Oid, Repository}; +use std::collections::{BTreeMap, BTreeSet}; + +/// A file-commit referenced graph thing +/// +/// git is _weird_! It's essentially just a glorified key-value store +/// and it shows. There's no utilities to figure out how thing are +/// related, and all the actual graph things in git are sugar on top +/// of this store. +/// +/// In order to make sense of anything in a repo we need to quite +/// heavily parse the log. This type here is the result of this +/// parsing: you can ask it smart questions like "when did this file +/// change" and it will tell you (sort of). +#[derive(Default)] +pub(crate) struct CommitGraph { + order: Vec<String>, + file_refs: BTreeMap<String, BTreeSet<String>>, + commit_refs: BTreeMap<String, CommitNode>, +} + +pub(crate) struct CommitNode { + id: String, + author: String, + touches: BTreeSet<String>, + date: String, +} + +fn build_diff_log(repo: &Repository, log: Vec<(String, Vec<FileNode>)>) -> Vec<CommitNode> { + todo!() +} + +/// Walk through all commits from a given ref and build a commit graph +pub(crate) fn create_commit_log(id: String, repo: &Repository) -> CommitGraph { + let mut walker = repo.revwalk().unwrap(); + walker.push(Oid::from_str(id.as_str()).unwrap()).unwrap(); + let mut v = walker + .into_iter() + .map(|oid| { + let oid = oid.unwrap(); + repo.find_commit(oid).unwrap() + }) + .collect::<Vec<_>>(); + v.reverse(); + + let log: Vec<_> = v + .into_iter() + .map(|commit| { + let id = format!("{}", commit.id()); + let tree_u = git::repo::get_tree(&repo, id.as_str()); + let tree = git::tree::parse_tree(tree_u, &repo); + (id, tree.flatten()) + }) + .collect(); + + let diffs = build_diff_log(&repo, log); + todo!() +} diff --git a/src/git/mod.rs b/src/git/mod.rs new file mode 100644 index 0000000..1cd9057 --- /dev/null +++ b/src/git/mod.rs @@ -0,0 +1,5 @@ +//! Wrappers for libgit2 + +pub mod log; +pub mod repo; +pub mod tree; diff --git a/src/git/repo.rs b/src/git/repo.rs new file mode 100644 index 0000000..0d04b0d --- /dev/null +++ b/src/git/repo.rs @@ -0,0 +1,19 @@ +use crate::templ_data::repo::RepoData; +use git2::{Oid, Repository, Tree}; + +/// Represents a repo in libgit2 +pub(crate) struct Repo { + pub(crate) inner: Repository, +} + +impl Repo { + pub(crate) fn new(path: &str) -> Self { + Self { + inner: Repository::open(path).expect(&format!("`{}` is not a valid git repo", path)), + } + } +} + +pub(crate) fn get_tree<'r>(repo: &'r Repository, rev: &str) -> Tree<'r> { + repo.revparse_single(rev).unwrap().peel_to_tree().unwrap() +} diff --git a/src/git/tree.rs b/src/git/tree.rs new file mode 100644 index 0000000..457eb40 --- /dev/null +++ b/src/git/tree.rs @@ -0,0 +1,144 @@ +//! Tree handling utilities +//! +//! The way that libgit2 handles trees is super low-level and overkill +//! for what we need. In this module we knock it down a notch or two. +//! +//! This code takes a tree returned by +//! `crate::git::repo::Repo::get_tree()`, and transforms it into a +//! `TreeData` type that the template engine can render. + +use crate::templ_data::repo::{CommitData, FileData, TreeData}; +use git2::{self, ObjectType, TreeWalkMode}; +use std::collections::BTreeMap; + +/// A cache of a repository tree +#[derive(Default, Debug, Clone)] +pub(crate) struct Tree { + inner: BTreeMap<String, TreeNode>, +} + +impl Tree { + /// Insert a node into a subtree with it's full path + fn insert_to_subtree(&mut self, mut path: Vec<String>, name: String, node: TreeNode) { + // If we are given a path, resolve it first + let curr = if path.len() > 0 { + let rest = path.split_off(1); + let mut curr = self.inner.get_mut(&path[0]).unwrap(); + + for dir in rest { + match curr { + TreeNode::Dir(ref mut d) => { + curr = d.children.inner.get_mut(&dir).unwrap(); + } + _ => panic!("Not a tree!"), + } + } + + match curr { + TreeNode::Dir(ref mut d) => &mut d.children, + TreeNode::File(_) => panic!("Not a tree!"), + } + } else { + // If no path was given, we assume the root is meant + self + }; + + curr.inner.insert(name, node); + } + + /// Walk through the tree and only return filenode objects + pub(crate) fn flatten(&self) -> Vec<FileNode> { + self.inner.values().fold(vec![], |mut vec, node| { + match node { + TreeNode::File(f) => vec.push(f.clone()), + TreeNode::Dir(d) => vec.append(&mut d.children.flatten()), + } + + vec + }) + } +} + +#[derive(Clone, Debug)] +pub(crate) enum TreeNode { + File(FileNode), + Dir(DirNode), +} + +impl TreeNode { + fn name(&self) -> String { + match self { + Self::File(f) => f.name.clone(), + Self::Dir(d) => d.name.clone(), + } + } +} + +#[derive(Clone, Debug)] +pub(crate) struct FileNode { + pub id: String, + pub path: Vec<String>, + pub name: String, +} + +#[derive(Clone, Debug)] +pub(crate) struct DirNode { + pub path: Vec<String>, + pub name: String, + pub children: Tree, +} + +impl DirNode { + fn append(&mut self, node: TreeNode) { + self.children.inner.insert(node.name(), node); + } +} + +/// Take a series of path-segments and render a tree at that location +pub(crate) fn parse_tree(tree: git2::Tree, repo: &git2::Repository) -> Tree { + let mut root = Tree::default(); + + tree.walk(TreeWalkMode::PreOrder, |path, entry| { + let path: Vec<String> = path + .split("/") + .filter_map(|seg| match seg { + "" => None, + val => Some(val.into()), + }) + .collect(); + let name = entry.name().unwrap().to_string(); + + match entry.kind() { + // For every tree in the tree we create a new TreeNode with the path we know about + Some(ObjectType::Tree) => { + root.insert_to_subtree( + path.clone(), + name.clone(), + TreeNode::Dir(DirNode { + path, + name, + children: Tree::default(), + }), + ); + } + // If we encounter a blob, this is a file that we can simply insert into the tree + Some(ObjectType::Blob) => { + root.insert_to_subtree( + path.clone(), + name.clone(), + TreeNode::File(FileNode { + id: format!("{}", entry.id()), + path, + name, + }), + ); + } + _ => {} + } + + 0 + }) + .unwrap(); + + root +} diff --git a/src/main.rs b/src/main.rs index 6d0ef79..7e589ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,36 @@ +mod git; mod pages; mod repo; -// mod router; -mod types; +mod templ_data; use actix_files as fs; -use actix_web::{web, App, HttpServer}; +use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer}; use std::io; use std::path::PathBuf; #[actix_rt::main] async fn main() -> io::Result<()> { - std::env::set_var("RUST_LOG", "actix_web=info"); + std::env::set_var("RUST_LOG", "actix_server=info,octopus=debug"); env_logger::init(); let root = PathBuf::new(); + let repo = git::repo::Repo::new("."); + let commits = git::log::create_commit_log( + "84a9a0ccee713e26a28ff5e54ea3776085d93b5f".into(), + &repo.inner, + ); + HttpServer::new(move || { App::new() .service(fs::Files::new("/static", root.join("static"))) + // This is a bit of a hack so that people don't get the + // 404 on the root page. Especially as long as octopus + // doesn't have the account overview yet! + .service(web::resource("/").route(web::get().to(|_: HttpRequest| { + HttpResponse::PermanentRedirect() + .header("Location", "octopus") + .finish() + }))) .service(web::resource("/{repo}").route(web::get().to(pages::repo::about))) .service(web::resource("/{repo}/details").route(web::get().to(pages::repo::details))) .default_service(web::resource("").route(web::get().to(pages::p404))) diff --git a/src/pages/repo/about.rs b/src/pages/repo/about.rs index fa88eb1..1f207e2 100644 --- a/src/pages/repo/about.rs +++ b/src/pages/repo/about.rs @@ -1,32 +1,23 @@ -use super::RepoWrapper; -use crate::types::RepoData; +use crate::templ_data::repo::*; use actix_web::{web, HttpRequest, HttpResponse, Result}; use askama::Template; -#[derive(Template)] -#[template(path = "repo/about.html")] -struct AboutRepo { - repo: RepoWrapper, - readme: String, -} - /// Renders the "repository/about" subpage pub async fn render(req: HttpRequest, path: web::Path<String>) -> Result<HttpResponse> { - let repo = AboutRepo { + let repo = pages::About { readme: include_str!("../../../README").to_string(), - repo: RepoWrapper { - data: RepoData { - owner: "spacekookie".into(), - name: "octopus".into(), - tagline: "A lightweight web frontend for git repositories".into(), - num_commit: 141, - num_branch: 1, - num_tag: 0, - num_contributor: 3, - size: "13.12M".into(), - }, + repo: RepoData { + owner: "spacekookie".into(), + name: "octopus".into(), + tagline: "A lightweight web frontend for git repositories".into(), + num_commit: 141, + num_branch: 1, + num_tag: 0, + num_contributor: 3, + size: "13.12M".into(), logo: "fakeavi.png".into(), }, + base: Default::default(), } .render() .unwrap(); diff --git a/src/pages/repo/details.rs b/src/pages/repo/details.rs index 4745e96..d5822d6 100644 --- a/src/pages/repo/details.rs +++ b/src/pages/repo/details.rs @@ -1,34 +1,36 @@ -use super::RepoWrapper; -use crate::types::{BranchData, CommitData, RepoData}; +use crate::templ_data::repo::*; use actix_web::{web, HttpRequest, HttpResponse, Result}; use askama::Template; -#[derive(Template)] -#[template(path = "repo/details.html")] -struct AboutRepo { - repo: RepoWrapper, - branches: Vec<BranchData>, - commits: Vec<CommitData>, -} - /// Renders the "repository/about" subpage pub async fn render(req: HttpRequest, path: web::Path<String>) -> Result<HttpResponse> { - let repo = AboutRepo { - branches: vec![], - commits: vec![], - repo: RepoWrapper { - data: RepoData { - owner: "spacekookie".into(), - name: "octopus".into(), - tagline: "A lightweight web frontend for git repositories".into(), - num_commit: 141, - num_branch: 1, - num_tag: 0, - num_contributor: 3, - size: "13.12M".into(), - }, + let last_commit = CommitData { + hash: "84a9a0".into(), + message: "Updating just like... a bunch of shit".into(), + author: "Katharina Fey".into(), + date: "Today".into(), + diff: (125, 55) + }; + + let repo = pages::Details { + branches: vec![ BranchData { + name: "develop".into(), + last_commit: last_commit.clone(), + } + ], + commits: vec![ last_commit ], + repo: RepoData { + owner: "spacekookie".into(), + name: "octopus".into(), + tagline: "A lightweight web frontend for git repositories".into(), + num_commit: 141, + num_branch: 1, + num_tag: 0, + num_contributor: 3, + size: "13.12M".into(), logo: "fakeavi.png".into(), }, + base: Default::default(), } .render() .unwrap(); diff --git a/src/pages/repo/mod.rs b/src/pages/repo/mod.rs index 7d90c97..2b93592 100644 --- a/src/pages/repo/mod.rs +++ b/src/pages/repo/mod.rs @@ -5,11 +5,3 @@ mod details; pub use about::render as about; pub use details::render as details; - -use crate::types::RepoData; - -/// A template wrapper for repository data -pub(self) struct RepoWrapper { - pub(self) data: RepoData, - pub(self) logo: String, -} diff --git a/src/templ_data/mod.rs b/src/templ_data/mod.rs new file mode 100644 index 0000000..6c93266 --- /dev/null +++ b/src/templ_data/mod.rs @@ -0,0 +1,30 @@ +//! Octopus template data structures +//! +//! All pages are generated by the server via template files that have +//! data inputs. Because the templates follow a well-defined +//! structure (i.e. `core` extended by `<type>/base` extended by +//! `<type>/<page>`, the structure of these template data structures +//! is the same. +//! +//! The actual page initialisation and rendering is nested in the +//! `page` module, which then uses the appropriate template structures +//! defined here. + +pub(crate) mod repo; + +/// A basic application wide template structure +pub(crate) struct BaseData { + pub version: String, + pub source: String, + pub url: String, +} + +impl Default for BaseData { + fn default() -> Self { + Self { + version: "0.2.0".into(), + source: "".into(), + url: "http://localhost:8080".into(), + } + } +} diff --git a/src/templ_data/repo.rs b/src/templ_data/repo.rs new file mode 100644 index 0000000..989c9b4 --- /dev/null +++ b/src/templ_data/repo.rs @@ -0,0 +1,80 @@ +//! Repository specific template data + +use std::collections::BTreeMap; + +/// A simple overview of a repository +/// +/// This type can be generated by the octopus Repository state wrapper +#[derive(Clone)] +pub(crate) struct RepoData { + pub owner: String, + pub name: String, + pub tagline: String, + pub num_commit: usize, + pub num_branch: usize, + pub num_tag: usize, + pub num_contributor: usize, + pub size: String, + pub logo: String, +} + +/// Data about an individual commit +#[derive(Clone)] +pub(crate) struct CommitData { + pub hash: String, + pub message: String, + pub author: String, + pub date: String, + pub diff: (usize, usize), +} + +/// Data about a branch +#[derive(Clone)] +pub(crate) struct BranchData { + pub name: String, + pub last_commit: CommitData, +} + +/// Data about a repository tree +#[derive(Clone)] +pub(crate) struct TreeData { + /// The path segments in the current directory + curr_dir: Vec<String>, + /// The set of children in this tree segment + files: BTreeMap<String, FileData>, +} + +/// Information about a concrete file in a tree +#[derive(Clone)] +pub(crate) struct FileData { + name: String, + last_commit: CommitData, + is_directory: bool, +} + +pub(crate) mod pages { + use super::*; + use crate::templ_data::BaseData; + use askama::Template; + + #[derive(Template)] + #[template(path = "repo/about.html")] + pub(crate) struct About { + pub base: BaseData, + pub repo: RepoData, + + // Template specific + pub readme: String, + } + + #[derive(Template)] + #[template(path = "repo/details.html")] + pub(crate) struct Details { + pub base: BaseData, + pub repo: RepoData, + + // Template specifics + pub branches: Vec<BranchData>, + pub commits: Vec<CommitData>, + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs deleted file mode 100644 index d764b84..0000000 --- a/src/types/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Octopus data types - -/// A simple overview of a repository -/// -/// This type can be generated by the octopus Repository state wrapper -pub(crate) struct RepoData { - pub owner: String, - pub name: String, - pub tagline: String, - pub num_commit: usize, - pub num_branch: usize, - pub num_tag: usize, - pub num_contributor: usize, - pub size: String, -} - -/// Data about an individual commit -pub(crate) struct CommitData { - pub hash: String, - pub message: String, - pub author: String, - pub date: String, - pub diff: (usize, usize), -} - -/// Data about a branch -pub(crate) struct BranchData { - pub name: String, - pub last_commit: CommitData, -} |