mygit

[UNMAINTAINED] A cgit/webgit alternative, written in Rust
Log | Files | Refs | README | LICENSE

commit 7f8803ad7ed08f3cd77fc4fa1561ba4e62e7495b
parent f33cda5c3566e96c805586984d9905b5c03be861
Author: alex wennerberg <alex@alexwennerberg.com>
Date:   Sat, 13 Mar 2021 18:40:38 -0800

Add tree page

Diffstat:
MCargo.lock | 1+
MCargo.toml | 1+
Msrc/main.rs | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mtemplates/log.html | 8++------
Mtemplates/static/style.css | 2+-
Atemplates/tree.html | 18++++++++++++++++++
6 files changed, 91 insertions(+), 13 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1124,6 +1124,7 @@ dependencies = [ "askama", "askama_tide", "async-std", + "chrono", "git2", "once_cell", "pico-args", diff --git a/Cargo.toml b/Cargo.toml @@ -11,6 +11,7 @@ anyhow = "1.0" askama = {version = "0.10", features = ["with-tide"]} askama_tide = "0.13" async-std = { version = "1.8.0", features = ["attributes"] } +chrono = "0.4" git2 = {version="0.13", default-features = false} once_cell = "1.7.2" pico-args = "0.4" diff --git a/src/main.rs b/src/main.rs @@ -1,6 +1,6 @@ use anyhow::Result; use askama::Template; -use git2::{Commit, Reference, Repository}; +use git2::{Commit, Reference, Repository, Tree, TreeEntry}; use once_cell::sync::OnceCell; use pico_args; use pulldown_cmark::{html, Options, Parser}; @@ -68,7 +68,7 @@ async fn repo_home(req: Request<()>) -> tide::Result { let config = &Config::global(); let repo = repo_from_request(&req.param("repo_name")?)?; let readme = &repo.revparse_single("HEAD:README.md")?; // TODO allow more incl plaintext - let markdown_input = str::from_utf8(readme.as_blob().unwrap().content())?; + let markdown_input = std::str::from_utf8(readme.as_blob().unwrap().content())?; let mut options = Options::empty(); let parser = Parser::new_ext(markdown_input, options); let mut html_output = String::new(); @@ -139,6 +139,68 @@ async fn repo_refs(req: Request<()>) -> tide::Result { Ok(tmpl.into()) } +#[derive(Template)] +#[template(path = "tree.html")] // using the template in this path, relative +struct RepoTreeTemplate<'a> { + repo: &'a Repository, + config: &'a Config, + tree: Tree<'a>, +} +async fn repo_tree(req: Request<()>) -> tide::Result { + // TODO handle subtrees + let config = &Config::global(); + let repo = repo_from_request(&req.param("repo_name")?)?; + // TODO accept reference or commit id + let commit = match req.param("ref") { + _ => repo.revparse_single("HEAD")?.peel_to_commit()?, + }; + let tree = commit.tree()?; + let tmpl = RepoTreeTemplate { + repo: &repo, + config, + tree, + }; + Ok(tmpl.into()) +} + +mod filters { + pub fn iso_date(i: &i64) -> ::askama::Result<String> { + // UTC date + let datetime: chrono::DateTime<chrono::Utc> = + chrono::DateTime::from_utc(chrono::NaiveDateTime::from_timestamp(*i, 0), chrono::Utc); + Ok(datetime.format("%Y-%m-%d").to_string()) + } + + pub fn unix_perms(m: &i32) -> ::askama::Result<String> { + let mut m = *m; + // manually wrote this bc I couldn't find a library + // acting like I'm writing C for fun + // TODO -- symlinks? + // https://unix.stackexchange.com/questions/450480/file-permission-with-six-bytes-in-git-what-does-it-mean + if m == 0o040000 { + // is directory + return Ok("d---------".to_owned()); + } + let mut output: [u8; 10] = [0; 10]; // ascii string + let mut i = 9; + for _ in 0..3 { + // Go backwards here + for c in &[0x78, 0x77, 0x72] { + // xrw + if m % 2 == 1 { + output[i] = *c; + } else { + output[i] = 0x2d; // - + } + m >>= 1; + i -= 1; + } + } + output[i] = 0x2d; // - + return Ok(std::str::from_utf8(&output).unwrap().to_owned()); + } +} + const HELP: &str = "\ mygit @@ -176,10 +238,10 @@ async fn main() -> Result<(), std::io::Error> { app.at("/:repo_name/refs").get(repo_refs); app.at("/:repo_name/log").get(repo_log); app.at("/:repo_name/log/:ref").get(repo_log); // ref optional - // app.at("/:repo_name/tree/:ref").get(repo_log); ref = master/main when not present - // app.at("/:repo_name/tree/:ref/item/:file").get(repo_log); ref = master/main when not present - // app.at("/:repo_name/refs").get(repo_log); ref = master/main when not present - // Bonus: raw files, patchsets + app.at("/:repo_name/tree").get(repo_tree); + app.at("/:repo_name/tree/:ref").get(repo_tree); + // app.at("/:repo_name/tree/:ref/item/:file").get(repo_log); ref = master/main when not present + // Bonus: raw files, patchsets app.listen("127.0.0.1:8081").await?; Ok(()) } diff --git a/templates/log.html b/templates/log.html @@ -7,14 +7,10 @@ {% for commit in commits %} <tr> <td class="commit-hash"><a href="/{{name}}/commits/{{commit.id()}}">{{commit.id().to_string()[..7]}}</td> - {% let summary = commit.summary().unwrap_or("") %} - {% if summary.len() > 72 %} - <td>{{summary[..69]}}...</td> - {% else %} + {% let summary = commit.summary().unwrap_or("")|truncate(72) %} <td>{{summary}}</td> - {% endif %} <td><a href="mailto:{{commit.author().email().unwrap_or("")}}">{{commit.author().name().unwrap_or("")}}</a></td> - <td>{{commit.time().seconds()}}</td> + <td>{{commit.time().seconds()|iso_date}}</td> </tr> {% endfor %} </table> diff --git a/templates/static/style.css b/templates/static/style.css @@ -12,7 +12,7 @@ body { font-style: italic; } -.repo-link, .git-reference { +.repo-link, .git-reference, .permissions { font-family: "Roboto Mono", monospace; } diff --git a/templates/tree.html b/templates/tree.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block content %} + {% let name = repo.workdir().unwrap().file_name().unwrap().to_str().unwrap() %} + {% include "repo-navbar.html" %} + <table> + {% for entry in tree %} + <tr> + <td class="permissions"> + {{ entry.filemode()|unix_perms }} + </td> + <td> + {{ entry.name().unwrap() }} + </td> + </tr> + {% endfor %} + </table> +{% endblock %}