aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKatharina Fey <kookie@spacekookie.de>2021-01-02 22:26:57 +0100
committerKatharina Fey <kookie@spacekookie.de>2021-01-09 01:47:49 +0100
commit3b65ab3fe178f4f08057a67cce063cf81e70d4fd (patch)
tree2619166fb250344395cb40476504ff42725ef357
parent17bb1561b384a92e4c3c50007ac38b2d9b86c95b (diff)
octopus: add supergit dependency and parse repo path via route param
-rw-r--r--apps/servers/octopus/Cargo.lock2
-rw-r--r--apps/servers/octopus/Cargo.toml6
-rw-r--r--apps/servers/octopus/src/git.rs11
-rw-r--r--apps/servers/octopus/src/git/log.rs117
-rw-r--r--apps/servers/octopus/src/git/mod.rs58
-rw-r--r--apps/servers/octopus/src/git/tree.rs176
-rw-r--r--apps/servers/octopus/src/main.rs42
-rw-r--r--apps/servers/octopus/src/pages/files.rs19
-rw-r--r--apps/servers/octopus/src/templ_data/mod.rs2
9 files changed, 68 insertions, 365 deletions
diff --git a/apps/servers/octopus/Cargo.lock b/apps/servers/octopus/Cargo.lock
index 24f6c0c539cf..be8b39311b7a 100644
--- a/apps/servers/octopus/Cargo.lock
+++ b/apps/servers/octopus/Cargo.lock
@@ -2256,7 +2256,9 @@ dependencies = [
"askama",
"env_logger",
"git2",
+ "log",
"markdown",
+ "supergit",
]
[[package]]
diff --git a/apps/servers/octopus/Cargo.toml b/apps/servers/octopus/Cargo.toml
index afc01fdcab4a..c01012945595 100644
--- a/apps/servers/octopus/Cargo.toml
+++ b/apps/servers/octopus/Cargo.toml
@@ -11,8 +11,10 @@ actix-rt = "1.0.0"
actix-web = "2.0.0"
askama = "0.8"
env_logger = "0.6"
+log = "*"
git2 = "0.11"
markdown = { version = "0.3.0", optional = true }
+supergit = { path = "supergit" }
[build-dependencies]
askama = "0.8"
@@ -23,6 +25,6 @@ markdown-readme = ["markdown"]
[workspace]
members = [
- ".",
- "supergit",
+ ".",
+ "supergit",
] \ No newline at end of file
diff --git a/apps/servers/octopus/src/git.rs b/apps/servers/octopus/src/git.rs
new file mode 100644
index 000000000000..f1c8ef9da7ec
--- /dev/null
+++ b/apps/servers/octopus/src/git.rs
@@ -0,0 +1,11 @@
+//! Git abstraction module
+//!
+//! Provide a few utility functions around supergit
+
+use supergit::Repository;
+
+pub(crate) fn open() -> Repository {
+ let path = std::env::var("OCTOPUS_REPOSITORY").unwrap();
+ trace!("Loading bare git repo {}", path);
+ Repository::open(&path).unwrap()
+}
diff --git a/apps/servers/octopus/src/git/log.rs b/apps/servers/octopus/src/git/log.rs
deleted file mode 100644
index c8f4aa37ccf2..000000000000
--- a/apps/servers/octopus/src/git/log.rs
+++ /dev/null
@@ -1,117 +0,0 @@
-//! libgit2 log parsing
-
-use crate::git::{tree::FileNode, Repo};
-use git2::Oid;
-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(Debug, Default)]
-pub(crate) struct CommitGraph {
- /// The correct order of commits in the log
- order: Vec<String>,
- /// List of all files, and the commits in which they were touched
- file_refs: BTreeMap<String, Vec<String>>,
- /// Map of commit IDs to metadata
- commit_refs: BTreeMap<String, CommitNode>,
-}
-
-#[derive(Debug)]
-pub(crate) struct CommitNode {
- id: String,
- author: String,
- message: String,
- touches: BTreeSet<String>,
- time: i64,
-}
-
-fn build_diff_log(repo: &Repo, 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(rev: String, repo: &Repo) -> CommitGraph {
- let mut walker = repo.get_inner().revwalk().unwrap();
- walker.push(Oid::from_str(rev.as_str()).unwrap()).unwrap();
- let mut commits = walker
- .into_iter()
- .map(|oid| {
- let oid = oid.unwrap();
- repo.get_inner().find_commit(oid).unwrap()
- })
- .collect::<Vec<_>>();
- commits.reverse();
-
- let mut initial: Vec<(_, _)> = commits
- .into_iter()
- .map(|commit| {
- let id = format!("{}", commit.id());
- (id.clone(), repo.get_tree(id.as_str()))
- })
- .collect();
-
- // split off rest of the diffs and dissolve the len(1) vec
- let log = initial.split_off(1);
- let previous = initial.remove(0).1;
-
- let mut order = vec![];
- let (commit_refs, file_refs) = log.into_iter().fold(
- (BTreeMap::new(), BTreeMap::new()),
- |(mut cm, mut fm), (cid, current)| {
- let commit_id = format!("{}", cid);
-
- let d = repo
- .get_inner()
- .diff_tree_to_tree(Some(&previous), Some(&current), None)
- .unwrap();
-
- // Store the commit to preserve order
- order.push(commit_id.clone());
-
- // For each file, store this commit as one that touched it
- let touches = d.deltas().fold(BTreeSet::new(), |mut set, delta| {
- let file_id = format!("{}", delta.new_file().id());
- fm.entry(file_id.clone())
- .or_insert(vec![])
- .push(commit_id.clone());
- set.insert(file_id);
- set
- });
-
- // From the commit, build a metadata object
- let commit_u = repo
- .get_inner()
- .find_commit(Oid::from_str(cid.as_str()).unwrap())
- .unwrap();
- let author_u = commit_u.author();
- let commit = CommitNode {
- id: commit_id,
- message: commit_u.message().unwrap().to_owned(),
- author: format!("{} {}", author_u.name().unwrap(), author_u.email().unwrap()),
- touches,
- time: author_u.when().seconds(),
- };
-
- // Insert the metadata object
- cm.insert(cid.clone(), commit);
-
- // We pass both the modified maps into the next commit
- (cm, fm)
- },
- );
-
- CommitGraph {
- order,
- file_refs,
- commit_refs,
- }
-}
diff --git a/apps/servers/octopus/src/git/mod.rs b/apps/servers/octopus/src/git/mod.rs
deleted file mode 100644
index 244e2f45e6c5..000000000000
--- a/apps/servers/octopus/src/git/mod.rs
+++ /dev/null
@@ -1,58 +0,0 @@
-//! Wrappers for libgit2
-
-pub mod log;
-pub mod tree;
-
-use git2::{self, Repository};
-use log::CommitGraph;
-use tree::Tree;
-
-/// A top-level wrapper API for all libgit2 functions
-pub struct Repo {
- inner: Repository,
- commits: Option<CommitGraph>,
- rev: Option<String>,
-}
-
-impl Repo {
- pub(crate) fn new(path: &str) -> Self {
- Self {
- inner: Repository::open(path).expect(&format!("`{}` is not a valid git repo", path)),
- commits: None,
- rev: None,
- }
- }
-
- pub(self) fn get_inner(&self) -> &Repository {
- &self.inner
- }
-
- pub(self) fn get_tree<'r>(&'r self, rev: &str) -> git2::Tree<'r> {
- self.inner
- .revparse_single(rev)
- .unwrap()
- .peel_to_tree()
- .unwrap()
- }
-
- pub(crate) fn clear_cache(&mut self) {
- self.rev = None;
- self.commits = None;
- }
-
- /// Load and cache commits for a specific rev
- pub(crate) fn load_commits(&mut self, rev: String) {
- self.rev = Some(rev.clone());
- self.commits = Some(log::create_commit_log(rev, &self));
- }
-
- /// Load the tree of files for the current rev
- ///
- /// Will fail if no rev was previously cached
- pub(crate) fn get_file_tree(&self) -> Tree {
- tree::parse_tree(
- self.get_tree(self.rev.as_ref().unwrap().as_str()),
- self.get_inner(),
- )
- }
-}
diff --git a/apps/servers/octopus/src/git/tree.rs b/apps/servers/octopus/src/git/tree.rs
deleted file mode 100644
index 5343a57c2463..000000000000
--- a/apps/servers/octopus/src/git/tree.rs
+++ /dev/null
@@ -1,176 +0,0 @@
-//! 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
- })
- }
-
- /// Get all the commits that touch a file
- pub(crate) fn grab_path_history(&self, mut path: String) -> String {
- let mut path: Vec<String> = path
- .split("/")
- .filter_map(|seg| match seg {
- "" => None,
- val => Some(val.into()),
- })
- .collect();
-
- let leaf = if path.len() > 0 {
- let rest = path.split_off(1);
- let mut curr = self.inner.get(&path[0]).unwrap();
-
- for dir in rest {
- match curr {
- TreeNode::Dir(d) => curr = d.children.inner.get(&dir).unwrap(),
- TreeNode::File(_) => break, // we reached the leaf
- }
- }
-
- curr
- } else {
- panic!("No valid path!");
- };
-
- match leaf {
- TreeNode::File(f) => f.id.clone(),
- _ => panic!("Not a leaf!"),
- }
- }
-}
-
-#[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/apps/servers/octopus/src/main.rs b/apps/servers/octopus/src/main.rs
index 8ed6445dd4a8..c8660bb92218 100644
--- a/apps/servers/octopus/src/main.rs
+++ b/apps/servers/octopus/src/main.rs
@@ -1,26 +1,52 @@
-//mod git;
-mod pages;
-mod repo;
-mod templ_data;
+//! Octopus git monorepo explorer
+//!
+//! This file is the entry-point to the octopus server (previously
+//! called `webgit` - some mentions to this name are still in the
+//! code).
+//!
+//! Because of the way that the libgit2 rust abstraction handles
+//! state, we can't embed a handle to the repository into the
+//! application app state; instead each endpoint (meaning route) will
+//! load and index the repository itself. Because all operations in
+//! supergit are lazy this does not add too much overhead. If there
+//! are operations that take too long, we can start building an
+//! explicit cache for them.
-mod project;
+#[macro_use]
+extern crate log;
+
+pub(crate) mod git;
+pub(crate) mod pages;
+pub(crate) mod project;
+pub(crate) mod repo;
+pub(crate) mod state;
+pub(crate) mod templ_data;
use actix_files as fs;
-use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
+use actix_web::{web, App, HttpServer};
use std::io;
use std::path::PathBuf;
#[actix_rt::main]
async fn main() -> io::Result<()> {
- std::env::set_var("RUST_LOG", "actix_server=info,octopus=debug");
+ std::env::set_var("RUST_LOG", "actix_server=info,webgit=trace");
env_logger::init();
let root = PathBuf::new();
+ // Check that we know where the repo is.
+ if std::env::var("OCTOPUS_REPOSITORY").is_err() {
+ error!("Failed to determine repository path! Please set the `OCTOPUS_REPOSITORY` env variable!");
+ std::process::exit(2);
+ }
+
+ // Start a new server with a few basic routes
HttpServer::new(move || {
App::new()
.service(fs::Files::new("/static", root.join("static")))
.service(web::resource("/").route(web::get().to(pages::overview)))
- .service(web::resource("/tree").route(web::get().to(pages::files)))
+
+ // Match on tree/* where paths can be arbitrarily recursive
+ .service(web::resource("/tree/{path:[^{}]*}").route(web::get().to(pages::files)))
.default_service(web::resource("").route(web::get().to(pages::p404)))
})
.bind("127.0.0.1:8080")?
diff --git a/apps/servers/octopus/src/pages/files.rs b/apps/servers/octopus/src/pages/files.rs
index 73a86a46918e..0f50d6b4722c 100644
--- a/apps/servers/octopus/src/pages/files.rs
+++ b/apps/servers/octopus/src/pages/files.rs
@@ -1,17 +1,30 @@
//! The main file browser
-use crate::templ_data::{files::Files, BaseData};
+use crate::{
+ git,
+ templ_data::{files::Files, BaseData},
+};
use actix_web::{web, HttpRequest, HttpResponse, Result};
use askama::Template;
-pub async fn render(req: HttpRequest) -> Result<HttpResponse> {
+pub async fn render((req, path): (HttpRequest, web::Path<String>)) -> Result<HttpResponse> {
+ let repo = git::open();
+ let branch = repo.get_branch("main".into()).unwrap(); // FIXME: this is baaaaad
+ let head = branch.get_head();
+ let tree = head.get_tree();
+
+ debug!("Loading path: `{}`", path);
+ let _yield = tree.load(&path).unwrap();
+ debug!("{:#?}", _yield);
+
+
let files = Files {
base: BaseData {
sitename: "dev.spacekookie.de".into(),
..BaseData::default()
},
readme: None,
- path: "".into(),
+ path: "/".into(),
}
.render()
.unwrap();
diff --git a/apps/servers/octopus/src/templ_data/mod.rs b/apps/servers/octopus/src/templ_data/mod.rs
index 7645e95ef824..41a96b572445 100644
--- a/apps/servers/octopus/src/templ_data/mod.rs
+++ b/apps/servers/octopus/src/templ_data/mod.rs
@@ -30,7 +30,7 @@ impl Default for BaseData {
version: env!("CARGO_PKG_VERSION").into(),
source: env::var("_OCTOPUS_SOURCE")
.unwrap_or("https://dev.spacekookie.de/web/octopus".to_string()),
- siteurl: env::var("_OCTOPUS_SITE_URL").unwrap_or("localhost:8080".to_string()),
+ siteurl: env::var("_OCTOPUS_SITE_URL").unwrap_or("http://localhost:8080".to_string()),
sitename: env::var("_OCTOPUS_SITE_NAME").unwrap_or("test-octopus".to_string()),
has_wiki: true,
}