aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKatharina Fey <kookie@spacekookie.de>2020-06-22 06:23:04 +0200
committerKatharina Fey <kookie@spacekookie.de>2020-06-22 06:23:04 +0200
commite30713b84bc9e66f7a8e8d2f51e953472cac28e4 (patch)
tree3098b1c77a978dcad0c828f57386d1c8999aa26b /src
parent84a9a0ccee713e26a28ff5e54ea3776085d93b5f (diff)
Committing all the libgit2 progress before throwing it away
I don't think libgit2 is the way forward to make any of this work. There's so much work involved in parsing the git k-v store, and the library itself is essentially of zero help for most of the heavy lifting.
Diffstat (limited to '')
-rw-r--r--src/git/log.rs61
-rw-r--r--src/git/mod.rs5
-rw-r--r--src/git/repo.rs19
-rw-r--r--src/git/tree.rs144
-rw-r--r--src/main.rs22
-rw-r--r--src/pages/repo/about.rs33
-rw-r--r--src/pages/repo/details.rs50
-rw-r--r--src/pages/repo/mod.rs8
-rw-r--r--src/templ_data/mod.rs30
-rw-r--r--src/templ_data/repo.rs80
-rw-r--r--src/types/mod.rs30
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,
-}