Merge remote-tracking branch 'refs/remotes/origin/main'
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
|
/assets/output.html
|
||||||
|
/assets/index2.html
|
||||||
|
|||||||
44
Cargo.lock
generated
44
Cargo.lock
generated
@@ -65,9 +65,9 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.31"
|
version = "1.2.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2"
|
checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jobserver",
|
"jobserver",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -243,8 +243,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"libgit2-sys",
|
"libgit2-sys",
|
||||||
"log",
|
"log",
|
||||||
"openssl-probe",
|
|
||||||
"openssl-sys",
|
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -480,9 +478,7 @@ checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
"libssh2-sys",
|
|
||||||
"libz-sys",
|
"libz-sys",
|
||||||
"openssl-sys",
|
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -492,20 +488,6 @@ version = "0.2.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libssh2-sys"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "220e4f05ad4a218192533b300327f5150e809b54c4ec83b5a1d91833601811b9"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"libc",
|
|
||||||
"libz-sys",
|
|
||||||
"openssl-sys",
|
|
||||||
"pkg-config",
|
|
||||||
"vcpkg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libz-sys"
|
name = "libz-sys"
|
||||||
version = "1.1.22"
|
version = "1.1.22"
|
||||||
@@ -561,24 +543,6 @@ version = "1.21.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-probe"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-sys"
|
|
||||||
version = "0.9.109"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"libc",
|
|
||||||
"pkg-config",
|
|
||||||
"vcpkg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parse-zoneinfo"
|
name = "parse-zoneinfo"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -785,9 +749,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.21"
|
version = "1.0.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ edition = "2024"
|
|||||||
[[bin]]
|
[[bin]]
|
||||||
name = "frontend"
|
name = "frontend"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "test_render"
|
||||||
|
path = "src/test-render.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
git2 = "0.20.2"
|
git2 = { version = "0.20.2", default-features = false }
|
||||||
serde = { version = "1.0.219", default-features = false, features = ["derive"] }
|
serde = { version = "1.0.219", default-features = false, features = ["derive"] }
|
||||||
#inkjet = "0.11.1"
|
#inkjet = "0.11.1"
|
||||||
#markdown = "1.0.0"
|
#markdown = "1.0.0"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<title>mintea</title>
|
<title>mintea</title>
|
||||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||||
<link rel="stylesheet" href="res/style.css">
|
<link rel="stylesheet" href="res/style.css">
|
||||||
<link rel="stylesheet" href="res/martian.css">
|
<link rel="stylesheet" href="res/mint.css">
|
||||||
<script>
|
<script>
|
||||||
/*
|
/*
|
||||||
@licstart The following is the entire license notice for the
|
@licstart The following is the entire license notice for the
|
||||||
@@ -68,14 +68,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=buttonListWrap>
|
<div class=buttonListWrap>
|
||||||
<ul class=buttonList>
|
<div class="tabList end"><span>code</span>
|
||||||
<li><a href="/">code</a></li>
|
<a href="/">history</a>
|
||||||
<li><a href="/">history</a></li>
|
<a href="/">tags</a>
|
||||||
<li><a href="/">tags</a></li>
|
<a href="/">tickets (17)</a>
|
||||||
<li><a href="/">tickets (17)</a></li>
|
<a href="/">releases</a>
|
||||||
<li><a href="/">releases</a></li>
|
<a href="/">settings</a>
|
||||||
<li><a href="/">settings</a></li>
|
</div>
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
:root {
|
:root {
|
||||||
--glass: #EEEEEE77;
|
--glass: #EEEEEE77;
|
||||||
--background: #EEE;
|
--background: #EEE;
|
||||||
|
--control: #FFF4;
|
||||||
--foreground: #333;
|
--foreground: #333;
|
||||||
--subtle-foreground: #888;
|
--subtle-foreground: #888;
|
||||||
--outline: #00000015;
|
--outline: #00000015;
|
||||||
--shine: #00000015;
|
--shine: #00000015;
|
||||||
--accent: #209D6E;
|
--accent: #209D6E;
|
||||||
--subtle-accent: #209D6E30;
|
--subtle-accent: #209D6E30;
|
||||||
|
--subtler-accent: #209D6E12;
|
||||||
--shadow: 0 4px 32px #03281A12;
|
--shadow: 0 4px 32px #03281A12;
|
||||||
--icon-filter: hue-rotate(157deg) saturate(0.80) brightness(1.3);
|
--icon-filter: hue-rotate(157deg) saturate(0.80) brightness(1.3);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,40 @@ nav {
|
|||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar .buttonList {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabList {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabList.end {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabList a, .tabList span {
|
||||||
|
color: var(--accent);
|
||||||
|
border: 1px solid var(--shine);
|
||||||
|
background-color: var(--control);
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
border-left-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabList span {
|
||||||
|
background-color: var(--subtler-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabList a:first-child, .tabList span:first-child {
|
||||||
|
border-radius: 0.5em 0 0 0.5em;
|
||||||
|
border-left-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabList a:last-child, .tabList span:last-child {
|
||||||
|
border-radius: 0 0.5em 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -96,6 +130,10 @@ header .buttonList {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebarLabel {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
#contentWrap {
|
#contentWrap {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
@@ -109,12 +147,24 @@ header .buttonList {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav .buttonListWrap .buttonList {
|
nav .buttonListWrap .buttonList, nav .buttonListWrap .tabList {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav .buttonListWrap .tabList {
|
||||||
|
justify-content: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav .buttonListWrap .tabList *:first-child {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav .buttonListWrap .tabList *:last-child {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
nav .buttonListWrap::after {
|
nav .buttonListWrap::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="stylesheet" href="res/style.css">
|
<link rel="stylesheet" href="res/style.css">
|
||||||
<link rel="stylesheet" href="res/martian.css">
|
<link rel="stylesheet" href="res/mint.css">
|
||||||
<script>
|
<script>
|
||||||
/*
|
/*
|
||||||
@licstart The following is the entire license notice for the
|
@licstart The following is the entire license notice for the
|
||||||
@@ -78,14 +78,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=buttonListWrap>
|
<div class=buttonListWrap>
|
||||||
<ul class=buttonList>
|
<div class="tabList end"><span>code</span>
|
||||||
<li><a href="./">code</a></li>
|
<a href="./commits">history</a>
|
||||||
<li><a href="./commits">commits</a></li>
|
<a href="./tags">tags</a>
|
||||||
<li><a href="./tags">tags</a></li>
|
<a href="./tickets">tickets {{ ticket_count }}</a></li>
|
||||||
<li><a href="./tickets">issues {{ ticket_count }}</a></li>
|
<a href="./releases">releases</a>
|
||||||
<li><a href="/">releases</a></li>
|
<a href="./settings">settings</a>
|
||||||
<li><a href="/">settings</a></li>
|
</div>
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@@ -94,7 +93,9 @@
|
|||||||
<span>
|
<span>
|
||||||
viewing
|
viewing
|
||||||
<span class=linkedPath>
|
<span class=linkedPath>
|
||||||
<a href="/">{{ owner }}</a>/<a href="/">{{ repo }}</a>/<a href="/">{{ branch }}</a>
|
{% block breadcrumbs %}
|
||||||
|
<a href="/">{{ owner }}</a>/<a href="/">{{ repo }}</a>
|
||||||
|
{% endblock breadcrumbs %}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<ul class=buttonList>
|
<ul class=buttonList>
|
||||||
|
|||||||
@@ -2,11 +2,17 @@
|
|||||||
|
|
||||||
{% block page %}code{% endblock page %}
|
{% block page %}code{% endblock page %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<a href="/">{{ owner }}</a>/<a href="/">{{ repo }}</a>/<a href="/">{{ branch }}</a>
|
||||||
|
{% endblock breadcrumbs %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id=contentWrap>
|
<div id=contentWrap>
|
||||||
<aside>
|
<aside>
|
||||||
<p class=sidebarLabel>
|
<p class=sidebarLabel>
|
||||||
<a href="/"></a><a href="/">{{ directory }}</a>
|
{% for part in directory | split(pat="/") -%}
|
||||||
|
<a href="/">{{ part }}/</a>
|
||||||
|
{%- endfor %}
|
||||||
</p>
|
</p>
|
||||||
<table class=files border=1>
|
<table class=files border=1>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -15,7 +21,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td><a class={{ entry.class }} href="/">{{ entry.path }}</a></td>
|
<td><a class={{ entry.class }} href="/">{{ entry.path }}</a></td>
|
||||||
<td><a class=commit href="./commit/{{ entry.last_commit }}">
|
<td><a class=commit href="./commit/{{ entry.last_commit }}">
|
||||||
{{ entry.last_commit_short }}
|
{{ entry.last_commit_message }}
|
||||||
</a></td>
|
</a></td>
|
||||||
<td><time datetime="{{ entry.last_commit_time }}">
|
<td><time datetime="{{ entry.last_commit_time }}">
|
||||||
{{ entry.last_commit_time | date(format="%Y-%m-%d %H:%M")}}
|
{{ entry.last_commit_time | date(format="%Y-%m-%d %H:%M")}}
|
||||||
|
|||||||
42
assets/templates/repo/tickets.html
Normal file
42
assets/templates/repo/tickets.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block page %}tickets{% endblock page %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id=contentWrap>
|
||||||
|
<aside>
|
||||||
|
<div class=sidebarLabel>
|
||||||
|
tickets
|
||||||
|
<div class=tabList style="display: inline-flex">
|
||||||
|
<span>open</span>
|
||||||
|
<a href="/">closed</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class=files border=1>
|
||||||
|
<tbody>
|
||||||
|
{% block entries_list %}
|
||||||
|
{% for entry in entries %}
|
||||||
|
<tr>
|
||||||
|
<td><a class={{ entry.class }} href="/">{{ entry.path }}</a></td>
|
||||||
|
<td><a class=commit href="./commit/{{ entry.last_commit }}">
|
||||||
|
{{ entry.last_commit_message }}
|
||||||
|
</a></td>
|
||||||
|
<td><time datetime="{{ entry.last_commit_time }}">
|
||||||
|
{{ entry.last_commit_time | date(format="%Y-%m-%d %H:%M")}}
|
||||||
|
</time></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock entries %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</aside>
|
||||||
|
{% block readme %}
|
||||||
|
<main>
|
||||||
|
<article class=preview>
|
||||||
|
<h1>Ticket title</h1>
|
||||||
|
{{ readme_content }}
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
{% endblock readme %}
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
173
assets/tickets.html
Normal file
173
assets/tickets.html
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Copyright (c) 2022 Sasha Koshka <sashakoshka@tebibyte.media>
|
||||||
|
Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
This file is part of Mintee.
|
||||||
|
|
||||||
|
Mintee is free software: you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU Affero General Public License as published by the Free
|
||||||
|
Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
|
later version.
|
||||||
|
|
||||||
|
Mintee is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Mintee. If not, see https://www.gnu.org/licenses/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>mintea</title>
|
||||||
|
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||||
|
<link rel="stylesheet" href="res/style.css">
|
||||||
|
<link rel="stylesheet" href="res/mint.css">
|
||||||
|
<script>
|
||||||
|
/*
|
||||||
|
@licstart The following is the entire license notice for the
|
||||||
|
JavaScript code in this page.
|
||||||
|
|
||||||
|
Copyright (C) 2022 Sasha Koshka <sashakoshka@tebibyte.media>
|
||||||
|
|
||||||
|
The JavaScript code in this page is free software: you can
|
||||||
|
redistribute it and/or modify it under the terms of the GNU
|
||||||
|
General Public License (GNU GPL) as published by the Free Software
|
||||||
|
Foundation, either version 3 of the License, or (at your option)
|
||||||
|
any later version. The code is distributed WITHOUT ANY WARRANTY;
|
||||||
|
without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
|
||||||
|
|
||||||
|
As additional permission under GNU GPL version 3 section 7, you
|
||||||
|
may distribute non-source (e.g., minimized or compacted) forms of
|
||||||
|
that code without the copy of the GNU GPL normally required by
|
||||||
|
section 4, provided you include this license notice and a URL
|
||||||
|
through which recipients can access the Corresponding Source.
|
||||||
|
|
||||||
|
|
||||||
|
@licend The above is the entire license notice
|
||||||
|
for the JavaScript code in this page.
|
||||||
|
*/
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id=stickyWrap>
|
||||||
|
<nav>
|
||||||
|
<div class=buttonListWrap>
|
||||||
|
<ul class=buttonList>
|
||||||
|
<li><a href="/">TiBM</a></li>
|
||||||
|
<li><a class=dashboardButton href="/"><span class=collapse>dashboard</span></a></li>
|
||||||
|
<li><a class=profileButton href="/"><span class=collapse>profile</span></a></li>
|
||||||
|
<li><a class=notificationsButton href="/"><span class=collapse>notifications</span> (17)</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class=buttonListWrap>
|
||||||
|
<div class="tabList end"><span>code</span>
|
||||||
|
<a href="/">history</a>
|
||||||
|
<a href="/">tags</a>
|
||||||
|
<a href="/">tickets (17)</a>
|
||||||
|
<a href="/">releases</a>
|
||||||
|
<a href="/">settings</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<span>
|
||||||
|
viewing
|
||||||
|
<span class=linkedPath>
|
||||||
|
<a href="/">owner</a>/<a href="/">repo</a>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<ul class=buttonList>
|
||||||
|
<li><a class=watchButton href="/">watch</a></li>
|
||||||
|
<li><a class=starButton href="/">star</a></li>
|
||||||
|
<li><a class=forkButton href="/">fork</a></li>
|
||||||
|
<li><a class=cloneButton href="/">clone</a></li>
|
||||||
|
</ul>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div id=contentWrap>
|
||||||
|
<aside>
|
||||||
|
<div class=sidebarLabel>
|
||||||
|
tickets
|
||||||
|
<div class=tabList style="display: inline-flex">
|
||||||
|
<span>open</span>
|
||||||
|
<a href="/">closed</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class=files border=1>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><a class=file href="/">file name</a></td>
|
||||||
|
<td><a class=commit href="/">most recent commit</a></td>
|
||||||
|
<td><time datetime="2023-01-01">in 2 months</time></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a class=directory href="/">directory/</a></td>
|
||||||
|
<td><a class=commit href="/">most recent commit</a></td>
|
||||||
|
<td><time datetime="2023-01-01">in 2 months</time></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a class=file href="/">file name</a></td>
|
||||||
|
<td><a class=commit href="/">most recent commit</a></td>
|
||||||
|
<td><time datetime="2023-01-01">in 2 months</time></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a class=file href="/">file name</a></td>
|
||||||
|
<td><a class=commit href="/">most recent commit</a></td>
|
||||||
|
<td><time datetime="2023-01-01">in 2 months</time></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<article class=preview>
|
||||||
|
<h1>Ticket title</h1>
|
||||||
|
<p>Ratione quis totam tempora sit magnam voluptas. Recusandae qui illo nulla eligendi. Perspiciatis iusto numquam suscipit aspernatur quibusdam ex.</p>
|
||||||
|
<p>Consequuntur atque rerum culpa numquam et et possimus dolor. Quidem possimus quia et consectetur. Debitis autem cupiditate vero maxime et libero quae. Tenetur vero architecto iure dolores numquam assumenda enim.</p>
|
||||||
|
<p>Dolorem facere animi quis repudiandae rerum dolor. Reprehenderit consequatur quae quia quos illum. Labore ducimus ut quaerat et corrupti cupiditate tenetur.</p>
|
||||||
|
<p>Nisi ut aut sunt dignissimos. Alias magnam itaque deleniti alias quibusdam id possimus eos. Reiciendis et eos placeat. Vero eligendi occaecati quaerat vitae voluptatem deserunt.</p>
|
||||||
|
<p>Dolor aut corrupti et officia id. Minus ipsam assumenda fugiat neque. Neque et saepe maiores iusto maiores. Dolor et at reprehenderit exercitationem totam neque.</p>
|
||||||
|
<h2>Heading</h2>
|
||||||
|
<p>Ratione quis totam tempora sit magnam voluptas. Recusandae qui illo nulla eligendi. Perspiciatis iusto numquam suscipit aspernatur quibusdam ex.</p>
|
||||||
|
<p>Consequuntur atque rerum culpa numquam et et possimus dolor. Quidem possimus quia et consectetur. Debitis autem cupiditate vero maxime et libero quae. Tenetur vero architecto iure dolores numquam assumenda enim.</p>
|
||||||
|
<p>Dolorem facere animi quis repudiandae rerum dolor. Reprehenderit consequatur quae quia quos illum. Labore ducimus ut quaerat et corrupti cupiditate tenetur.</p>
|
||||||
|
<p>Nisi ut aut sunt dignissimos. Alias magnam itaque deleniti alias quibusdam id possimus eos. Reiciendis et eos placeat. Vero eligendi occaecati quaerat vitae voluptatem deserunt.</p>
|
||||||
|
<p>Dolor aut corrupti et officia id. Minus ipsam assumenda fugiat neque. Neque et saepe maiores iusto maiores. Dolor et at reprehenderit exercitationem totam neque.</p>
|
||||||
|
<h2>Heading</h2>
|
||||||
|
<p>Ratione quis totam tempora sit magnam voluptas. Recusandae qui illo nulla eligendi. Perspiciatis iusto numquam suscipit aspernatur quibusdam ex.</p>
|
||||||
|
<p>Consequuntur atque rerum culpa numquam et et possimus dolor. Quidem possimus quia et consectetur. Debitis autem cupiditate vero maxime et libero quae. Tenetur vero architecto iure dolores numquam assumenda enim.</p>
|
||||||
|
<p>Dolorem facere animi quis repudiandae rerum dolor. Reprehenderit consequatur quae quia quos illum. Labore ducimus ut quaerat et corrupti cupiditate tenetur.</p>
|
||||||
|
<p>Nisi ut aut sunt dignissimos. Alias magnam itaque deleniti alias quibusdam id possimus eos. Reiciendis et eos placeat. Vero eligendi occaecati quaerat vitae voluptatem deserunt.</p>
|
||||||
|
<p>Dolor aut corrupti et officia id. Minus ipsam assumenda fugiat neque. Neque et saepe maiores iusto maiores. Dolor et at reprehenderit exercitationem totam neque.</p>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let stickyWrap = document.querySelector("#stickyWrap")
|
||||||
|
document.addEventListener ("scroll", () => {
|
||||||
|
if (document.documentElement.scrollTop > 0) {
|
||||||
|
stickyWrap.className = "lifted"
|
||||||
|
} else {
|
||||||
|
stickyWrap.className = ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="https://git.tebibyte.media/meta/mintee">Mintee</a>,
|
||||||
|
the fresh and tasty git frontend. page: #s. template: #s.
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
180
src/backend.rs
180
src/backend.rs
@@ -1,180 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Emma Tebibyte <emma@tebibyte.media>
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
*
|
|
||||||
* This file is part of Mintee.
|
|
||||||
*
|
|
||||||
* Mintee is free software: you can redistribute it and/or modify it under the
|
|
||||||
* terms of the GNU Affero General Public License as published by the Free
|
|
||||||
* Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* Mintee is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
||||||
* details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Mintee. If not, see https://www.gnu.org/licenses/.
|
|
||||||
*
|
|
||||||
* This file incorporates work covered by the following copyright and
|
|
||||||
* permission notice:
|
|
||||||
*
|
|
||||||
* MIT License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2021 Sergey "Shnatsel" Davidoff <shnatsel@gmail.com>
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
* copy of this software and associated documentation files (the
|
|
||||||
* "Software"), to deal in the Software without restriction, including
|
|
||||||
* without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
* permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
* the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included
|
|
||||||
* in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
||||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
error::Error,
|
|
||||||
path::{ PathBuf, Path },
|
|
||||||
};
|
|
||||||
|
|
||||||
use git2::{ Commit, Repository, Sort };
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct GitCommit {
|
|
||||||
author: (Option<String>, Option<String>), // name, e-mail
|
|
||||||
hash: String,
|
|
||||||
message: Option<String>,
|
|
||||||
short_hash: Option<String>,
|
|
||||||
time: i64, // seconds since Unix epoch
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<Commit<'_>> for GitCommit {
|
|
||||||
type Error = git2::Error;
|
|
||||||
|
|
||||||
fn try_from(commit: Commit) -> Result<Self, Self::Error> {
|
|
||||||
let hash = commit.id().to_string();
|
|
||||||
let short_hash = commit.as_object().short_id()?.as_str().map(|f| f.to_owned());
|
|
||||||
let message = commit.message().map(|f| f.to_owned());
|
|
||||||
let commit_signature = commit.author();
|
|
||||||
let name = commit_signature.name().map(|f| f.to_owned());
|
|
||||||
let address = commit_signature.email().map(|f| f.to_owned());
|
|
||||||
let author = (name, address);
|
|
||||||
let time = commit.time().seconds();
|
|
||||||
|
|
||||||
Ok(GitCommit { author, hash, message, short_hash, time })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct GitEntry {
|
|
||||||
last_commit: GitCommit,
|
|
||||||
path: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GitEntry {
|
|
||||||
fn new(commit: GitCommit, path: String) -> Self {
|
|
||||||
GitEntry { last_commit: commit, path }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct GitRepo {
|
|
||||||
branch: Option<String>,
|
|
||||||
entries: Vec<GitEntry>,
|
|
||||||
last_commit: GitCommit,
|
|
||||||
name: String,
|
|
||||||
owner: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GitRepo {
|
|
||||||
fn open(path: PathBuf) -> Result<Self, Box<dyn Error>> {
|
|
||||||
let repo = Repository::open(path.clone())?;
|
|
||||||
let entries = Self::get_entries(&repo)?;
|
|
||||||
|
|
||||||
let full_path = path.clone().as_path().canonicalize()?;
|
|
||||||
|
|
||||||
let relative_path = full_path.strip_prefix("/var/mintee/repos")?;
|
|
||||||
|
|
||||||
let owner = String::from_utf8(
|
|
||||||
relative_path
|
|
||||||
.parent()
|
|
||||||
.unwrap()
|
|
||||||
.to_path_buf()
|
|
||||||
.as_mut_os_string()
|
|
||||||
.as_encoded_bytes()
|
|
||||||
.to_vec()
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let name = String::from_utf8(
|
|
||||||
relative_path
|
|
||||||
.file_name()
|
|
||||||
.unwrap()
|
|
||||||
.to_owned()
|
|
||||||
.as_encoded_bytes()
|
|
||||||
.to_vec()
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let head = repo.head()?;
|
|
||||||
let branch = head.shorthand().map(|f| f.to_owned());
|
|
||||||
|
|
||||||
let last_commit = GitCommit::try_from(head.peel_to_commit()?)?;
|
|
||||||
|
|
||||||
Ok(GitRepo { branch, entries, last_commit, name, owner })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_remote_repo() { // for AP repos
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_entries(repo: &Repository) -> Result<Vec<GitEntry>, Box<dyn Error>> {
|
|
||||||
let mut entries = Vec::new();
|
|
||||||
let mut revwalk = repo.revwalk()?;
|
|
||||||
|
|
||||||
revwalk.set_sorting(Sort::TOPOLOGICAL | Sort::TIME)?;
|
|
||||||
revwalk.push_head()?;
|
|
||||||
|
|
||||||
for commit_id in revwalk {
|
|
||||||
let commit_id = commit_id?;
|
|
||||||
let commit = repo.find_commit(commit_id)?;
|
|
||||||
if commit.parent_count() <= 1 {
|
|
||||||
let tree = commit.tree()?;
|
|
||||||
let prev_tree = match commit.parent_count() {
|
|
||||||
1 => Some(commit.parent(0)?.tree()?),
|
|
||||||
0 => None,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let diff = repo
|
|
||||||
.diff_tree_to_tree(prev_tree.as_ref(), Some(&tree), None)?;
|
|
||||||
|
|
||||||
for delta in diff.deltas() {
|
|
||||||
if let Some(file_path) = delta.new_file().path() {
|
|
||||||
let p = String::from_utf8(file_path
|
|
||||||
.to_path_buf()
|
|
||||||
.as_mut_os_string()
|
|
||||||
.as_encoded_bytes()
|
|
||||||
.to_vec()
|
|
||||||
)?;
|
|
||||||
let c = GitCommit::try_from(commit.clone())?;
|
|
||||||
entries.push(GitEntry::new(c, p));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(entries)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
247
src/git.rs
Normal file
247
src/git.rs
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Emma Tebibyte <emma@tebibyte.media>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This file is part of Mintee.
|
||||||
|
*
|
||||||
|
* Mintee is free software: you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Affero General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* Mintee is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Mintee. If not, see https://www.gnu.org/licenses/.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Sergey "Shnatsel" Davidoff <shnatsel@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
* copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included
|
||||||
|
* in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
|
use git2::{ Branch, Commit, FileMode, Repository, Sort, TreeEntry };
|
||||||
|
use tera::Context;
|
||||||
|
|
||||||
|
/* TODO: implement From<T> for Context where T: all Git* types */
|
||||||
|
|
||||||
|
pub enum GitEntryKind {
|
||||||
|
Directory,
|
||||||
|
File,
|
||||||
|
Link,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for GitEntryKind {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
GitEntryKind::Directory => "directory".to_owned(),
|
||||||
|
GitEntryKind::File => "file".to_owned(),
|
||||||
|
GitEntryKind::Link => "link".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GitEntry {
|
||||||
|
pub entries: Vec<GitEntry>,
|
||||||
|
pub kind: GitEntryKind,
|
||||||
|
pub path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GitEntry {
|
||||||
|
fn new(entries: Vec<GitEntry>, kind: GitEntryKind, path: String) -> Self {
|
||||||
|
GitEntry { entries, kind, path }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<TreeEntry<'_>> for GitEntry {
|
||||||
|
type Error = std::string::FromUtf8Error;
|
||||||
|
|
||||||
|
fn try_from(entry: TreeEntry) -> Result<Self, Self::Error> {
|
||||||
|
let entries = Vec::new();
|
||||||
|
let path = String::from_utf8(entry.name_bytes().to_vec())?;
|
||||||
|
let kind = match entry.filemode() {
|
||||||
|
m if m == <i32>::from(FileMode::Blob) => GitEntryKind::File,
|
||||||
|
m if m == <i32>::from(FileMode::BlobExecutable) => GitEntryKind::File,
|
||||||
|
m if m == <i32>::from(FileMode::BlobGroupWritable) => unreachable!(),
|
||||||
|
m if m == <i32>::from(FileMode::Link) => GitEntryKind::File,
|
||||||
|
m if m == <i32>::from(FileMode::Tree) => GitEntryKind::Directory,
|
||||||
|
m if m == <i32>::from(FileMode::Commit) => GitEntryKind::File,
|
||||||
|
m if m == <i32>::from(FileMode::Unreadable) => todo!(),
|
||||||
|
_ => GitEntryKind::Directory,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self::new(entries, kind, path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GitCommit {
|
||||||
|
pub author: (Option<String>, Option<String>), // name, e-mail
|
||||||
|
pub entries: Vec<GitEntry>,
|
||||||
|
pub hash: String,
|
||||||
|
pub message: Option<String>,
|
||||||
|
pub short_hash: Option<String>,
|
||||||
|
pub time: i64, // seconds since Unix epoch
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GitCommit> for Context {
|
||||||
|
fn from(value: GitCommit) -> Self {
|
||||||
|
let mut ctx = Context::new();
|
||||||
|
|
||||||
|
todo!("proc_macro for populating");
|
||||||
|
|
||||||
|
ctx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Commit<'_>> for GitCommit {
|
||||||
|
type Error = Box<dyn Error>;
|
||||||
|
|
||||||
|
fn try_from(commit: Commit) -> Result<Self, Self::Error> {
|
||||||
|
let hash = commit.id().to_string();
|
||||||
|
let short_hash = commit.as_object().short_id()?.as_str().map(|f| f.to_owned());
|
||||||
|
let message = commit.message().map(|f| f.to_owned());
|
||||||
|
let commit_signature = commit.author();
|
||||||
|
let name = commit_signature.name().map(|f| f.to_owned());
|
||||||
|
let address = commit_signature.email().map(|f| f.to_owned());
|
||||||
|
let author = (name, address);
|
||||||
|
let time = commit.time().seconds();
|
||||||
|
let entries = commit
|
||||||
|
.tree()?
|
||||||
|
.iter()
|
||||||
|
.map(|c| -> Result<GitEntry, std::string::FromUtf8Error> {
|
||||||
|
Ok(GitEntry::try_from(c)?)
|
||||||
|
}).collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
Ok(GitCommit { author, entries, hash, message, short_hash, time })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GitBranch {
|
||||||
|
pub commits: Vec<GitCommit>,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GitBranchWrapper<'a> {
|
||||||
|
branch: Branch<'a>,
|
||||||
|
repo: &'a Repository,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> GitBranchWrapper<'a> {
|
||||||
|
fn new(branch: Branch<'a>, repo: &'a Repository) -> Self {
|
||||||
|
Self { branch, repo }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<GitBranchWrapper<'_>> for GitBranch {
|
||||||
|
type Error = Box<dyn Error>;
|
||||||
|
|
||||||
|
fn try_from(branch: GitBranchWrapper) -> Result<Self, Self::Error> {
|
||||||
|
let name = String::from_utf8(branch.branch.name_bytes()?.to_vec())?;
|
||||||
|
let repo = branch.repo;
|
||||||
|
let branch_oid = branch.branch.get().resolve()?.target().unwrap();
|
||||||
|
|
||||||
|
let mut revwalk = repo.revwalk()?;
|
||||||
|
revwalk.set_sorting(Sort::TOPOLOGICAL | Sort::TIME)?;
|
||||||
|
revwalk.push(branch_oid)?;
|
||||||
|
|
||||||
|
let commits = revwalk
|
||||||
|
.into_iter()
|
||||||
|
.map(|o| -> Result<GitCommit, Box<dyn Error>> {
|
||||||
|
Ok(GitCommit::try_from(repo.find_commit(o?)?)?)
|
||||||
|
}).collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
Ok(GitBranch { commits, name })
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GitRepo {
|
||||||
|
pub branches: Vec<GitBranch>,
|
||||||
|
pub name: String,
|
||||||
|
pub owner: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GitRepo {
|
||||||
|
pub fn open(
|
||||||
|
path: PathBuf, prefix: Option<String>
|
||||||
|
) -> Result<Self, Box<dyn Error>> {
|
||||||
|
let repo = Repository::open(path.clone())?;
|
||||||
|
|
||||||
|
let branches = repo
|
||||||
|
.branches(None)?
|
||||||
|
.map(|b| -> Result<GitBranch, Box<dyn Error>> {
|
||||||
|
Ok(GitBranch::try_from(GitBranchWrapper::new(b?.0, &repo))?)
|
||||||
|
}).collect::<Result<Vec<_>, _>>()?;
|
||||||
|
/*
|
||||||
|
let mut revwalk = repo.revwalk()?;
|
||||||
|
|
||||||
|
revwalk.set_sorting(Sort::TOPOLOGICAL | Sort::TIME)?;
|
||||||
|
revwalk.push_head()?;
|
||||||
|
let commits = revwalk
|
||||||
|
.into_iter()
|
||||||
|
.map(|o| -> Result<GitCommit, Box<dyn Error>> {
|
||||||
|
Ok(GitCommit::try_from(repo.find_commit(o?)?)?)
|
||||||
|
}).collect::<Result<Vec<_>, _>>()?;
|
||||||
|
*/
|
||||||
|
|
||||||
|
let full_path = path.clone().as_path().canonicalize()?;
|
||||||
|
let relative_path = match prefix {
|
||||||
|
Some(p) => full_path.strip_prefix(p)?.canonicalize()?,
|
||||||
|
None => full_path,
|
||||||
|
};
|
||||||
|
|
||||||
|
let owner = String::from_utf8(
|
||||||
|
relative_path
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.to_path_buf()
|
||||||
|
.as_mut_os_string()
|
||||||
|
.as_encoded_bytes()
|
||||||
|
.to_vec()
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let name = String::from_utf8(
|
||||||
|
relative_path
|
||||||
|
.file_name()
|
||||||
|
.unwrap()
|
||||||
|
.to_owned()
|
||||||
|
.as_encoded_bytes()
|
||||||
|
.to_vec()
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(GitRepo { branches, name, owner })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_remote_repo() { // for AP repos
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,56 +16,50 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with Mintee. If not, see https://www.gnu.org/licenses/.
|
* along with Mintee. If not, see https://www.gnu.org/licenses/.
|
||||||
*
|
|
||||||
* This file incorporates work covered by the following copyright and
|
|
||||||
* permission notice:
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
fmt::{ self, Display, Formatter },
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
use git2::Repository;
|
|
||||||
use tera::{ Context, Tera };
|
use tera::{ Context, Tera };
|
||||||
|
|
||||||
trait ToContext {
|
#[non_exhaustive]
|
||||||
fn to_context(self) -> Result<Context, git2::Error>;
|
pub enum PageKind {
|
||||||
|
Code,
|
||||||
|
Dashboard,
|
||||||
|
Tickets,
|
||||||
|
User,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move this data into backend.rs
|
impl PageKind {
|
||||||
impl ToContext for Repository {
|
pub fn render_page(&self, ctx: Context) -> Result<String, Box<dyn Error>> {
|
||||||
fn to_context(self) -> Result<Context, git2::Error> {
|
let page_dir = self.to_string();
|
||||||
let repo = self.commondir();
|
let template = String::from_utf8(Path::new(&page_dir)
|
||||||
let _index = self.index()?;
|
.to_path_buf()
|
||||||
let head = self.head()?;
|
.as_mut_os_string()
|
||||||
let branch = if head.is_branch() {
|
.as_encoded_bytes()
|
||||||
head.shorthand().unwrap()
|
.to_vec()
|
||||||
} else { "detached" };
|
)?;
|
||||||
let head_commit = head.peel_to_commit()?;
|
|
||||||
let entries = get_entries(&self)?;
|
|
||||||
let committer = head_commit.committer().name().unwrap().to_owned();
|
|
||||||
let _author = head_commit.author().name().unwrap().to_owned();
|
|
||||||
|
|
||||||
let mut ctx = Context::new();
|
let tera = Tera::new(&page_dir)?;
|
||||||
|
Ok(tera.render(&template, &ctx)?)
|
||||||
// stub until we have database
|
|
||||||
ctx.insert("user", "anon");
|
|
||||||
ctx.insert("site", "TiB.");
|
|
||||||
ctx.insert("notif_count", "");
|
|
||||||
ctx.insert("ticket_count", "(47)");
|
|
||||||
ctx.insert("owner", &committer);
|
|
||||||
ctx.insert("repo", repo);
|
|
||||||
ctx.insert("branch", &branch);
|
|
||||||
ctx.insert("directory", repo);
|
|
||||||
ctx.insert("entries", &entries);
|
|
||||||
ctx.insert("readme_content", "this is a readme");
|
|
||||||
|
|
||||||
Ok(ctx)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_path(path: PathBuf) -> tera::Result<String> {
|
impl Display for PageKind {
|
||||||
let tera = Tera::new("./assets/templates/repo/*")?;
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
|
||||||
let repo = Repository::discover(path).unwrap();
|
use PageKind::*;
|
||||||
let context = repo.to_context().unwrap();
|
|
||||||
|
|
||||||
tera.render("code.html", &context)
|
let path = match self {
|
||||||
|
Code => "repo/code.html",
|
||||||
|
Dashboard => "dashboard.html",
|
||||||
|
Tickets => "repo/tickets.html",
|
||||||
|
User => "user.html",
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "./assets/templates/{}", path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
103
src/test-render.rs
Normal file
103
src/test-render.rs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Emma Tebibyte <emma@tebibyte.media>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This file is part of Mintee.
|
||||||
|
*
|
||||||
|
* Mintee is free software: you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Affero General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* Mintee is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Mintee. If not, see https://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
env::{ args, current_dir },
|
||||||
|
error::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
use tera::{ Context, Tera };
|
||||||
|
|
||||||
|
#[path="git.rs"]
|
||||||
|
mod git;
|
||||||
|
|
||||||
|
use git::GitRepo;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct SampleEntry {
|
||||||
|
class: String,
|
||||||
|
last_commit_message: String,
|
||||||
|
last_commit: String,
|
||||||
|
last_commit_time: i64,
|
||||||
|
path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GitRepo> for Context {
|
||||||
|
fn from(repo: GitRepo) -> Self {
|
||||||
|
let mut ctx = Context::new();
|
||||||
|
|
||||||
|
let directory = format!("{}/{}", repo.owner, repo.name);
|
||||||
|
|
||||||
|
let main_branch = repo.branches
|
||||||
|
.iter()
|
||||||
|
.find(|b| b.name == "main")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let latest_commit = &main_branch.commits[0];
|
||||||
|
let hash = latest_commit.hash.clone();
|
||||||
|
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
|
||||||
|
for e in latest_commit.entries.iter() {
|
||||||
|
let entry = SampleEntry {
|
||||||
|
class: e.kind.to_string(),
|
||||||
|
last_commit: hash.clone(),
|
||||||
|
last_commit_message: latest_commit.message.clone().unwrap(),
|
||||||
|
last_commit_time: latest_commit.time,
|
||||||
|
path: e.path.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
entries.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stubs til we have a real database */
|
||||||
|
ctx.insert("user", "anon");
|
||||||
|
ctx.insert("site", "TiB.");
|
||||||
|
ctx.insert("notif_count", "");
|
||||||
|
ctx.insert("ticket_count", "(47)");
|
||||||
|
|
||||||
|
ctx.insert("readme_content", "this is a readme");
|
||||||
|
|
||||||
|
ctx.insert("owner", &repo.owner);
|
||||||
|
ctx.insert("branch", &main_branch.name);
|
||||||
|
ctx.insert("repo", &repo.name);
|
||||||
|
ctx.insert("directory", &directory);
|
||||||
|
ctx.insert("entries", &entries);
|
||||||
|
|
||||||
|
ctx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
if let Some(templates) = args().collect::<Vec<_>>().get(1) {
|
||||||
|
let tera = Tera::new(templates.as_str())?;
|
||||||
|
let ctx = Context::from(
|
||||||
|
GitRepo::open(current_dir()?.to_path_buf(), None)?
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("{}", tera.render("code.html", &ctx)?);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
eprintln!("Usage: {} template_glob", args().collect::<Vec<_>>()[0]);
|
||||||
|
std::process::exit(64);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user