~aw/patches

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
6 2

[PATCH mygit 0/5] resolve multiple panics, simplify

Johann Galle
Details
Message ID
<20210314225537.23237-1-johann@qwertqwefsday.eu>
DKIM signature
pass
Download raw message
This is a bundle of multiple patches:
Patches 1, 2 are some cleanup left over from earlier.
Patch 3 simplifies accessing the configuration data.
Patch 4 adds readme formatting for plaintext and HTML.
Patch 5 resolves an issue with bare repositories (which are not uncommon on
git servers).

[PATCH mygit 1/5] add license metadata

Johann Galle
Details
Message ID
<20210314225537.23237-2-johann@qwertqwefsday.eu>
In-Reply-To
<20210314225537.23237-1-johann@qwertqwefsday.eu> (view parent)
DKIM signature
pass
Download raw message
Patch: +1 -0
From: Johann150 <johann@qwertqwefsday.eu>

---
 Cargo.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Cargo.toml b/Cargo.toml
index 31d585a..4b37336 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,6 +3,7 @@ name = "mygit"
version = "0.1.0"
authors = ["alex wennerberg <alex@alexwennerberg.com>"]
edition = "2018"
license = "MIT"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

-- 
2.20.1

[PATCH mygit 2/5] check for export file

Johann Galle
Details
Message ID
<20210314225537.23237-3-johann@qwertqwefsday.eu>
In-Reply-To
<20210314225537.23237-1-johann@qwertqwefsday.eu> (view parent)
DKIM signature
pass
Download raw message
Patch: +6 -1
From: Johann150 <johann@qwertqwefsday.eu>

---
 src/main.rs | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/main.rs b/src/main.rs
index 11c2be2..0d1f074 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -39,7 +39,12 @@ async fn index(req: Request<()>) -> tide::Result {
        .map(|entries| {
            entries
                .filter_map(|entry| Some(entry.ok()?.path()))
                // TODO check for git-daemon-export-ok file
                .filter(|entry| {
                    // check for the export file
                    let mut path = entry.clone();
                    path.push("git-daemon-export-ok");
                    path.exists()
                })
                .filter_map(|entry| Repository::open(entry).ok())
                .collect::<Vec<_>>()
        })
-- 
2.20.1

[PATCH mygit 4/5] add readme formats: plaintext, HTML

Johann Galle
Details
Message ID
<20210314225537.23237-5-johann@qwertqwefsday.eu>
In-Reply-To
<20210314225537.23237-1-johann@qwertqwefsday.eu> (view parent)
DKIM signature
pass
Download raw message
Patch: +51 -14
From: Johann150 <johann@qwertqwefsday.eu>

Made RepoHomeTemplate::readme_text a String because for plaintext and
markdown formatting the content has to be turned into a String anyway.
---
 src/main.rs | 65 +++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 51 insertions(+), 14 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index f2696d5..cd2f327 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,7 +3,6 @@ use askama::Template;
use git2::{Commit, Diff, DiffDelta, DiffFormat, Oid, Reference, Repository, Tree, TreeEntry};
use once_cell::sync::Lazy;
use pico_args;
use pulldown_cmark::{html, Options, Parser};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
@@ -79,9 +78,9 @@ async fn index(req: Request<()>) -> tide::Result {

#[derive(Template)]
#[template(path = "repo.html")] // using the template in this path, relative
struct RepoHomeTemplate<'a> {
    repo: &'a Repository,
    readme_text: &'a str,
struct RepoHomeTemplate {
    repo: Repository,
    readme_text: String,
}

fn repo_from_request(repo_name: &str) -> Result<Repository> {
@@ -92,18 +91,56 @@ fn repo_from_request(repo_name: &str) -> Result<Repository> {
}

async fn repo_home(req: Request<()>) -> tide::Result {
    use pulldown_cmark::{escape::escape_html, html::push_html, Options, Parser};

    enum ReadmeFormat {
        Plaintext,
        Html,
        Markdown,
    }

    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 = 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();
    html::push_html(&mut html_output, parser);
    let tmpl = RepoHomeTemplate {
        repo: &repo,
        readme_text: &html_output,

    let readme_text = {
        let mut format = ReadmeFormat::Plaintext;
        let readme = repo
            .revparse_single("HEAD:README")
            .or_else(|_| repo.revparse_single("HEAD:README.txt"))
            .or_else(|_| {
                format = ReadmeFormat::Markdown;
                repo.revparse_single("HEAD:README.md")
            })
            .or_else(|_| repo.revparse_single("HEAD:README.mdown"))
            .or_else(|_| repo.revparse_single("HEAD:README.markdown"))
            .or_else(|_| {
                format = ReadmeFormat::Html;
                repo.revparse_single("HEAD:README.html")
            })
            .or_else(|_| repo.revparse_single("HEAD:README.htm"))?;
        let readme_text = str::from_utf8(readme.as_blob().unwrap().content())?;

        // render the file contents to HTML
        match format {
            // render plaintext as preformatted text
            ReadmeFormat::Plaintext => {
                let mut output = "<pre>".to_string();
                escape_html(&mut output, readme_text)?;
                output.push_str("</pre>");
                output
            }
            // already is HTML
            ReadmeFormat::Html => readme_text.to_string(),
            // render Markdown to HTML
            ReadmeFormat::Markdown => {
                let mut output = String::new();
                let parser = Parser::new_ext(readme_text, Options::empty());
                push_html(&mut output, parser);
                output
            }
        }
    };
    Ok(tmpl.into())

    Ok(RepoHomeTemplate { repo, readme_text }.into())
}

#[derive(Template)]
-- 
2.20.1

[PATCH mygit 3/5] use configuration static directly

Johann Galle
Details
Message ID
<20210314225537.23237-4-johann@qwertqwefsday.eu>
In-Reply-To
<20210314225537.23237-1-johann@qwertqwefsday.eu> (view parent)
DKIM signature
pass
Download raw message
Patch: +35 -54
From: Johann150 <johann@qwertqwefsday.eu>

This gets rid of te config variable in all the structs by using the
static variable in the basic template. To be able to access the variable
more easily use a once_cell::sync::Lazy instead of a OnceCEll because
Lazy implements Deref letting us directly access the struct fields.
---
 src/main.rs         | 87 ++++++++++++++++++---------------------------
 templates/base.html |  2 +-
 2 files changed, 35 insertions(+), 54 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 0d1f074..f2696d5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,7 +1,7 @@
use anyhow::Result;
use askama::Template;
use git2::{Commit, Diff, DiffDelta, DiffFormat, Oid, Reference, Repository, Tree, TreeEntry};
use once_cell::sync::OnceCell;
use once_cell::sync::Lazy;
use pico_args;
use pulldown_cmark::{html, Options, Parser};
use serde::{Deserialize, Serialize};
@@ -18,24 +18,46 @@ pub struct Config {
    emoji_favicon: String,
}

static CONFIG: OnceCell<Config> = OnceCell::new();
const HELP: &str = "\
mygit

FLAGS:
  -h, --help            Prints help information
OPTIONS:
  -c                    Path to config file
";

static CONFIG: Lazy<Config> = Lazy::new(args);

fn args() -> Config {
    // TODO cli

impl Config {
    pub fn global() -> &'static Config {
        CONFIG.get().expect("Config is not initialized")
    let mut pargs = pico_args::Arguments::from_env();

    if pargs.contains(["-h", "--help"]) {
        print!("{}", HELP);
        std::process::exit(0);
    }

    let toml_text =
        fs::read_to_string("mygit.toml").expect("expected configuration file mygit.toml");
    match toml::from_str(&toml_text) {
        Ok(config) => config,
        Err(e) => {
            eprintln!("could not read configuration file: {}", e);
            std::process::exit(1);
        }
    }
}

#[derive(Template)]
#[template(path = "index.html")] // using the template in this path, relative
struct IndexTemplate<'a> {
struct IndexTemplate {
    repos: Vec<Repository>,
    config: &'a Config,
}

async fn index(req: Request<()>) -> tide::Result {
    let config = &Config::global();
    let repos = fs::read_dir(&config.repo_directory)
    let repos = fs::read_dir(&CONFIG.repo_directory)
        .map(|entries| {
            entries
                .filter_map(|entry| Some(entry.ok()?.path()))
@@ -50,7 +72,7 @@ async fn index(req: Request<()>) -> tide::Result {
        })
        .map_err(|e| tide::log::warn!("can not read repositories: {}", e))
        .unwrap_or_default();
    let mut index_template = IndexTemplate { repos, config };
    let index_template = IndexTemplate { repos };

    Ok(index_template.into())
}
@@ -60,18 +82,16 @@ async fn index(req: Request<()>) -> tide::Result {
struct RepoHomeTemplate<'a> {
    repo: &'a Repository,
    readme_text: &'a str,
    config: &'a Config,
}

fn repo_from_request(repo_name: &str) -> Result<Repository> {
    let repo_path = Path::new(&Config::global().repo_directory).join(repo_name);
    let repo_path = Path::new(&CONFIG.repo_directory).join(repo_name);
    // TODO CLEAN PATH! VERY IMPORTANT! DONT FORGET!
    let r = Repository::open(repo_path)?;
    Ok(r)
}

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 = std::str::from_utf8(readme.as_blob().unwrap().content())?;
@@ -82,7 +102,6 @@ async fn repo_home(req: Request<()>) -> tide::Result {
    let tmpl = RepoHomeTemplate {
        repo: &repo,
        readme_text: &html_output,
        config,
    };
    Ok(tmpl.into())
}
@@ -91,12 +110,10 @@ async fn repo_home(req: Request<()>) -> tide::Result {
#[template(path = "log.html")] // using the template in this path, relative
struct RepoLogTemplate<'a> {
    repo: &'a Repository,
    config: &'a Config,
    commits: Vec<Commit<'a>>,
}

async fn repo_log(req: Request<()>) -> tide::Result {
    let config = &Config::global();
    let repo = repo_from_request(&req.param("repo_name")?)?;
    let mut revwalk = repo.revwalk()?;
    match req.param("ref") {
@@ -109,7 +126,6 @@ async fn repo_log(req: Request<()>) -> tide::Result {
        .collect();
    let tmpl = RepoLogTemplate {
        repo: &repo,
        config,
        commits,
    };
    Ok(tmpl.into())
@@ -119,12 +135,10 @@ async fn repo_log(req: Request<()>) -> tide::Result {
#[template(path = "refs.html")] // using the template in this path, relative
struct RepoRefTemplate<'a> {
    repo: &'a Repository,
    config: &'a Config,
    branches: Vec<Reference<'a>>,
    tags: Vec<Reference<'a>>,
}
async fn repo_refs(req: Request<()>) -> tide::Result {
    let config = &Config::global();
    let repo = repo_from_request(&req.param("repo_name")?)?;
    let branches = repo
        .references()?
@@ -138,7 +152,6 @@ async fn repo_refs(req: Request<()>) -> tide::Result {
        .collect();
    let tmpl = RepoRefTemplate {
        repo: &repo,
        config,
        branches,
        tags,
    };
@@ -149,22 +162,16 @@ async fn repo_refs(req: Request<()>) -> tide::Result {
#[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 spec = req.param("ref").unwrap_or("HEAD");
    let commit = repo.revparse_single(spec)?.peel_to_commit()?;
    let tree = commit.tree()?;
    let tmpl = RepoTreeTemplate {
        repo: &repo,
        config,
        tree,
    };
    let tmpl = RepoTreeTemplate { repo: &repo, tree };
    Ok(tmpl.into())
}

@@ -172,7 +179,6 @@ async fn repo_tree(req: Request<()>) -> tide::Result {
#[template(path = "commit.html")] // using the template in this path, relative
struct RepoCommitTemplate<'a> {
    repo: &'a Repository,
    config: &'a Config,
    commit: Commit<'a>,
    parent: Commit<'a>,
    diff: &'a Diff<'a>,
@@ -180,7 +186,6 @@ struct RepoCommitTemplate<'a> {
}

async fn repo_commit(req: Request<()>) -> tide::Result {
    let config = &Config::global();
    let repo = repo_from_request(req.param("repo_name")?)?;
    let commit = repo
        .revparse_single(req.param("commit")?)?
@@ -197,7 +202,6 @@ async fn repo_commit(req: Request<()>) -> tide::Result {
    // TODO accept reference or commit id
    let tmpl = RepoCommitTemplate {
        repo: &repo,
        config,
        commit,
        parent,
        diff: &diff,
@@ -243,31 +247,8 @@ mod filters {
    }
}

const HELP: &str = "\
mygit

FLAGS:
  -h, --help            Prints help information
OPTIONS:
  -c                    Path to config file
";

#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
    let mut pargs = pico_args::Arguments::from_env();

    if pargs.contains(["-h", "--help"]) {
        print!("{}", HELP);
        std::process::exit(0);
    }

    // TODO cli

    let toml_text =
        fs::read_to_string("mygit.toml").expect("expected configuration file mygit.toml");
    let config: Config = toml::from_str(&toml_text)?;
    CONFIG.set(config).unwrap();

    tide::log::start();
    let mut app = tide::new();
    app.at("/").get(index);
diff --git a/templates/base.html b/templates/base.html
index fa13a3a..e47495c 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -4,7 +4,7 @@
    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" href="/style.css" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0,user-scalable=0" />
    <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>{{config.emoji_favicon}}</text></svg>">
    <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>{{crate::CONFIG.emoji_favicon}}</text></svg>">
    <meta name="description" content="My self-hosted git repositories">
    <title>{% block title %}My Site{% endblock %}</title>
    {% block head %}{% endblock %}
-- 
2.20.1

[PATCH mygit 5/5] correctly get name for bare repos

Johann Galle
Details
Message ID
<20210314225537.23237-6-johann@qwertqwefsday.eu>
In-Reply-To
<20210314225537.23237-1-johann@qwertqwefsday.eu> (view parent)
DKIM signature
pass
Download raw message
Patch: +14 -2
From: Johann150 <johann@qwertqwefsday.eu>

workdir is only correct for repositories that have one. Bare repositories
do not have a workdir and so we have to use the directory name instead.
---
 src/main.rs          | 13 +++++++++++++
 templates/index.html |  3 +--
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index cd2f327..6ad1499 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -246,7 +246,10 @@ async fn repo_commit(req: Request<()>) -> tide::Result {
    };
    Ok(tmpl.into())
}

mod filters {
    use super::*;

    pub fn format_datetime(time: &git2::Time, format: &str) -> ::askama::Result<String> {
        use chrono::{FixedOffset, TimeZone};
        let offset = FixedOffset::west(time.offset_minutes() * 60);
@@ -282,6 +285,16 @@ mod filters {
        output[i] = 0x2d; // -
        return Ok(std::str::from_utf8(&output).unwrap().to_owned());
    }

    pub fn repo_name(repo: &Repository) -> askama::Result<&str> {
        repo.workdir()
            // use the path for bare repositories
            .unwrap_or_else(|| repo.path())
            .file_name()
            .unwrap()
            .to_str()
            .ok_or(askama::Error::Fmt(std::fmt::Error))
    }
}

#[async_std::main]
diff --git a/templates/index.html b/templates/index.html
index ac28f6e..8f5a106 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -6,9 +6,8 @@
  <div style=>
  <table>
  {% for repo in repos %}
  {% let name = repo.workdir().unwrap().file_name().unwrap().to_str().unwrap() %}
  <tr>
    <td class="repo-link"><a href="{{name}}">{{ name }}</a></td>
    <td class="repo-link"><a href="{{repo|repo_name|urlencode_strict}}">{{repo|repo_name}}</a></td>
    <td class="repo-description">a cool repository</td>
    <td class="repo-last-updated">updated 2021-02-01</td>
  </tr>
-- 
2.20.1

Re: [PATCH mygit 5/5] correctly get name for bare repos

Details
Message ID
<C9XLBEZE5PF8.S9HJYF0ORFVU@debian-alex>
In-Reply-To
<20210314225537.23237-6-johann@qwertqwefsday.eu> (view parent)
DKIM signature
missing
Download raw message
Thanks for all these patches! Much appreciated, this is very helpful!
Let me know if you have any other feedback or questions.

All the best,

Alex
Reply to thread Export thread (mbox)