Commit
Author: Kevin Schoon [me@kevinschoon.com]
Hash: 161b1139add5178d20da8094fe3e100bb67ae338
Timestamp: Sat, 19 Oct 2024 15:49:28 +0000 (1 month ago)

+158 -21 +/-10 browse
implement git-notes!
1diff --git a/README.md b/README.md
2index 81951d8..e9106a1 100644
3--- a/README.md
4+++ b/README.md
5 @@ -45,7 +45,7 @@ A general development channel `#ayllu` is available on [libera](ircs://irc.liber
6 | ----------------------------- | ------ | ------------------------------------------------------------------ |
7 | git-log | ✅ | |
8 | git-clone (http & ssh) | ✅ | |
9- | git-notes | TODO | |
10+ | git-notes | ✅ | |
11 | git-blame | ✅ | |
12 | git-lfs | ✅ | |
13 | git-verify | ✅ | |
14 @@ -64,12 +64,12 @@ A general development channel `#ayllu` is available on [libera](ircs://irc.liber
15 | activity tracking | ✅ | |
16 | extensible plugin system | ✅ | |
17 | WebFinger | ✅ | |
18- | mailing list support | WIP | |
19+ | mailing list support | WIP | |
20 | xmpp integration | WIP | |
21- | activity pub based federation | TBD | |
22- | continuous integration | TODO | |
23- | graphql api | TODO | |
24- | centralized "hub" | TODO | |
25+ | activity pub based federation | TBD | |
26+ | continuous integration | TODO | |
27+ | programmatic access | TODO | |
28+ | centralized "hub" | TODO | |
29
30 ## Installation
31
32 diff --git a/ayllu/Cargo.toml b/ayllu/Cargo.toml
33index 9c93521..e55335b 100644
34--- a/ayllu/Cargo.toml
35+++ b/ayllu/Cargo.toml
36 @@ -2,6 +2,7 @@
37 name = "ayllu"
38 version = "0.2.1"
39 edition = "2021"
40+ rust-version = "1.70.0"
41
42 [[bin]]
43 name = "ayllu"
44 @@ -13,14 +14,6 @@ ayllu_git = { path = "../crates/git" }
45 ayllu_config = { path = "../crates/config" }
46 ayllu_database = { path = "../crates/database" }
47
48- # ayllu_config = { workspace = true }
49- # ayllu_database = { workspace = true }
50- # ayllu_git = { workspace = true }
51- # ayllu_rpc = { workspace = true }
52- # ayllu_scheduler = {workspace = true}
53- # ayllu-mail = {workspace = true}
54- # ayllu-build = {workspace = true}
55- # ayllu-xmpp = {workspace = true}
56
57 sqlx = { version = "0.8.2", features = [ "runtime-tokio-rustls", "sqlite", "macros", "time" ] }
58 git2 = "0.19.0"
59 diff --git a/ayllu/src/web2/routes/commit.rs b/ayllu/src/web2/routes/commit.rs
60index 9204fa2..84cfb6c 100644
61--- a/ayllu/src/web2/routes/commit.rs
62+++ b/ayllu/src/web2/routes/commit.rs
63 @@ -25,6 +25,10 @@ pub async fn serve(
64 );
65 ctx.insert("commit_hash", &commit_id);
66 let commit = repository.commit(Some(commit_id.to_string()))?.unwrap();
67+ if commit.has_note.is_some_and(|has_note| has_note) {
68+ let note = repository.read_note(commit.id.as_str())?;
69+ ctx.insert("note", &note);
70+ }
71 let (stats, diff) = repository.diff(&commit_id)?;
72 ctx.insert("commit", &commit);
73 ctx.insert("stats", &stats);
74 diff --git a/ayllu/src/web2/routes/refs.rs b/ayllu/src/web2/routes/refs.rs
75index c82710c..b413ce4 100644
76--- a/ayllu/src/web2/routes/refs.rs
77+++ b/ayllu/src/web2/routes/refs.rs
78 @@ -29,6 +29,8 @@ pub async fn refs(
79 ctx.insert("branches", &branches);
80 let tags = repository.tags()?;
81 ctx.insert("tags", &tags);
82+ let notes = repository.notes()?;
83+ ctx.insert("notes", &notes);
84 let body = templates.render("refs.html", &ctx)?;
85 Ok(Html(body))
86 }
87 diff --git a/ayllu/themes/default/templates/commit.html b/ayllu/themes/default/templates/commit.html
88index 73f0eb6..e6e5829 100644
89--- a/ayllu/themes/default/templates/commit.html
90+++ b/ayllu/themes/default/templates/commit.html
91 @@ -2,6 +2,7 @@
92 {% import "macros.html" as macros %}
93 {% block content %}
94 <section class="stretch">
95+ <h5> Commit </h5>
96 <section>
97 <span><b>Author:</b></span>
98 <span class="right">
99 @@ -42,6 +43,23 @@
100 <pre>{{ commit.message }}</pre>
101 </div>
102 {% endif %}
103+ {% if commit.has_note %}
104+ <h5>Note</h5>
105+ <section>
106+ <span><b>Author:</b></span>
107+ <span class="right">
108+ <a href="/{{ collection }}/{{ name }}/log?username={{ note.author_name | urlencode }}&email={{ note.author_email | urlencode }}">{{ note.author_name }}</a>
109+ [<a href="mailto://{{ note.author_email }}">{{ commit.author_email }}</a>]
110+ </span>
111+ </section>
112+ <section>
113+ <span><b>Timestamp:</b></span>
114+ <span class="right">{{ note.author_epoch | format_epoch }} ({{ note.author_epoch | friendly_time }})</span>
115+ </section>
116+ <div class="message">
117+ <pre>{{ note.message }}</pre>
118+ </div>
119+ {% endif %}
120 </section>
121 {{ diff.1 | safe }}
122 </section>
123 diff --git a/ayllu/themes/default/templates/log.html b/ayllu/themes/default/templates/log.html
124index 82e85b5..a9938b2 100644
125--- a/ayllu/themes/default/templates/log.html
126+++ b/ayllu/themes/default/templates/log.html
127 @@ -6,6 +6,7 @@
128 <thead>
129 <tr>
130 <th>ID</th>
131+ <th>Flags</th>
132 <th>Age</th>
133 <th class="collapse">Author</th>
134 <th>Message</th>
135 @@ -20,9 +21,13 @@
136 {% else %}
137 <span class="negative">
138 {% endif %}
139- <a href="/{{ collection }}/{{ name }}/commit/{{ commit.id }}">{{ commit.id | truncate(length=8, end="") }}</a>
140+ <a href="/{{ collection }}/{{ name }}/commit/{{ commit.id }}">{{ commit.id | truncate(length=12, end="") }}</a>
141+ </span>
142 </td>
143- </span>
144+ <td>
145+ {% if commit.has_note %} <div data-tooltip="Note">[N]</div>{% endif %}
146+ {% if commit.is_extended %} <div data-tooltip="Extended">[E]</div>{% endif %}
147+ </td>
148 <td>{{ commit.epoch | friendly_time }}</td>
149 <td class="collapse">
150 <a href="/{{ collection }}/{{ name }}/log?username={{ commit.author_name | urlencode }}&email={{ commit.author_email | urlencode }}">{{ commit.author_name }}</a>
151 diff --git a/ayllu/themes/default/templates/refs.html b/ayllu/themes/default/templates/refs.html
152index 3c6a53f..8553fe8 100644
153--- a/ayllu/themes/default/templates/refs.html
154+++ b/ayllu/themes/default/templates/refs.html
155 @@ -56,5 +56,31 @@
156 {% endfor %}
157 </tbody>
158 </table>
159+ <h4 class="minor-header">Notes</h4>
160+ <table>
161+ <thead>
162+ <tr>
163+ <th>Commit</th>
164+ <th>Author</th>
165+ <th class="collapse">Age</th>
166+ <th>Message</th>
167+ </tr>
168+ </thead>
169+ <tbody>
170+ {% for note in notes %}
171+ <tr>
172+ <td>
173+ <a href="/{{ collection }}/{{ name }}/commit/{{ note.commit.id }}">
174+ {{ note.commit.id | truncate(length=8, end="") }}</a>
175+ </td>
176+ <td>{{note.author_name}}</td>
177+ <td class="collapse">{{ note.author_epoch | friendly_time }}</td>
178+ <td>
179+ <pre>{{ note.message | truncate(length=32, end="...") }}</pre>
180+ </td>
181+ </tr>
182+ {% endfor %}
183+ </tbody>
184+ </table>
185 </section>
186 {% endblock %}
187 diff --git a/ayllu/themes/default/theme.css b/ayllu/themes/default/theme.css
188index e789142..876dbbc 100644
189--- a/ayllu/themes/default/theme.css
190+++ b/ayllu/themes/default/theme.css
191 @@ -479,3 +479,11 @@ thead.collection > tr th {
192 thead.collection > tr th > a {
193 font-size: larger;
194 }
195+
196+ [data-tooltip]:hover::after {
197+ display: block;
198+ position: absolute;
199+ content: attr(data-tooltip);
200+ border: 1px solid black;
201+ padding: .25em;
202+ }
203 diff --git a/crates/git/src/lite.rs b/crates/git/src/lite.rs
204index 8c8646e..97f1c15 100644
205--- a/crates/git/src/lite.rs
206+++ b/crates/git/src/lite.rs
207 @@ -1,6 +1,6 @@
208 use git2::{
209 Blob as GitBlob, Branch as GitBranch, Commit as GitCommit, DiffStats as GitDiffStats,
210- Tag as GitTag,
211+ Note as GitNote, Tag as GitTag,
212 };
213 use serde::Serialize;
214
215 @@ -41,7 +41,7 @@ pub struct BlameLine {
216 pub commit_id: String,
217 }
218
219- #[derive(Clone, Serialize)]
220+ #[derive(Default, Clone, Debug, Serialize)]
221 pub struct Commit {
222 pub id: String,
223 pub author_name: String,
224 @@ -56,6 +56,7 @@ pub struct Commit {
225 pub is_extended: bool,
226 pub gpg_signature: Option<String>,
227 pub is_verified: Option<bool>,
228+ pub has_note: Option<bool>,
229 }
230
231 impl Commit {
232 @@ -102,7 +103,7 @@ impl From<GitCommit<'_>> for Commit {
233 message,
234 is_extended,
235 gpg_signature,
236- is_verified: None,
237+ ..Default::default()
238 }
239 }
240 }
241 @@ -244,3 +245,45 @@ impl From<GitDiffStats> for Stats {
242 }
243 }
244 }
245+
246+ #[derive(Serialize, Clone, Debug)]
247+ pub struct Note {
248+ pub id: String,
249+ pub message: String,
250+ pub author_name: String,
251+ pub author_email: String,
252+ pub author_epoch: i64,
253+ pub committer_name: String,
254+ pub committer_email: String,
255+ pub committer_epoch: i64,
256+ pub commit: Commit,
257+ }
258+
259+ impl From<(GitNote<'_>, GitCommit<'_>)> for Note {
260+ fn from(value: (GitNote, GitCommit)) -> Self {
261+ let (note, commit) = value;
262+ let author = note.author();
263+ let committer = note.committer();
264+ Note {
265+ id: note.id().to_string(),
266+ message: note
267+ .message()
268+ .map_or(String::default(), |message| message.to_string()),
269+ author_name: author
270+ .name()
271+ .map_or(String::default(), |name| name.to_string()),
272+ author_email: author
273+ .email()
274+ .map_or(String::default(), |name| name.to_string()),
275+ author_epoch: author.when().seconds(),
276+ committer_name: committer
277+ .name()
278+ .map_or(String::default(), |name| name.to_string()),
279+ committer_email: committer
280+ .email()
281+ .map_or(String::default(), |email| email.to_string()),
282+ committer_epoch: committer.when().seconds(),
283+ commit: commit.into(),
284+ }
285+ }
286+ }
287 diff --git a/crates/git/src/wrapper.rs b/crates/git/src/wrapper.rs
288index f2f960a..3210239 100644
289--- a/crates/git/src/wrapper.rs
290+++ b/crates/git/src/wrapper.rs
291 @@ -398,7 +398,9 @@ impl Wrapper {
292 Some(hash) => {
293 let hash = Oid::from_str(hash.as_str())?;
294 let commit = self.repository.find_commit(hash)?;
295+ let commit_id = commit.id();
296 let mut commit = lite::Commit::from(commit);
297+ commit.has_note = Some(self.repository.find_note(None, commit_id).is_ok());
298 if commit.gpg_signature.is_some() {
299 commit.is_verified = self.verify(&commit.id)?;
300 }
301 @@ -411,7 +413,10 @@ impl Wrapper {
302 let last_commit = head.peel_to_commit()?;
303 match self.repository.find_commit(last_commit.id()) {
304 Ok(commit) => {
305+ let commit_id = commit.id();
306 let mut commit = lite::Commit::from(commit);
307+ commit.has_note =
308+ Some(self.repository.find_note(None, commit_id).is_ok());
309 if commit.gpg_signature.is_some() {
310 commit.is_verified = self.verify(&commit.id)?;
311 }
312 @@ -640,6 +645,7 @@ impl Wrapper {
313 };
314
315 let mut commit: lite::Commit = commit.into();
316+ commit.has_note = Some(self.repository.find_note(None, id).is_ok());
317 if commit.gpg_signature.is_some() {
318 // only try to verify if signature is present
319 commit.is_verified = self.verify(&commit.id)?;
320 @@ -678,8 +684,9 @@ impl Wrapper {
321 Some(_) => {}
322 None => {
323 hm.insert(commit_id, true);
324- let commit = self.repository.find_commit(commit_id)?;
325- commits.push(commit.into());
326+ let mut commit: lite::Commit = self.repository.find_commit(commit_id)?.into();
327+ commit.has_note = Some(self.repository.find_note(None, commit_id).is_ok());
328+ commits.push(commit);
329 }
330 }
331 }
332 @@ -930,6 +937,37 @@ impl Wrapper {
333 Ok(branches)
334 }
335
336+ /// Return all notes with references to whichever commits the point to
337+ pub fn note_ids(&self) -> Result<Vec<(Oid, Oid)>, Error> {
338+ self.repository
339+ .notes(None)?
340+ .try_fold(Vec::new(), |mut accm, note| {
341+ let (note_id, commit_id) = note?;
342+ accm.push((note_id, commit_id));
343+ Ok(accm)
344+ })
345+ }
346+
347+ pub fn read_note(&self, commit_id: &str) -> Result<lite::Note, Error> {
348+ let commit_id = Oid::from_str(commit_id)?;
349+ let note = self.repository.find_note(None, commit_id)?;
350+ let commit = self.repository.find_commit(commit_id)?;
351+ Ok((note, commit).into())
352+ }
353+
354+ /// Return all notes fully resolved with their associated commits
355+ /// NOTE: unsure on the performance of this
356+ pub fn notes(&self) -> Result<Vec<lite::Note>, Error> {
357+ self.note_ids()?
358+ .iter()
359+ .try_fold(Vec::new(), |mut accm, (_, commit_id)| {
360+ let note = self.repository.find_note(None, *commit_id)?;
361+ let commit = self.repository.find_commit(*commit_id)?;
362+ accm.push((note, commit).into());
363+ Ok(accm)
364+ })
365+ }
366+
367 /// generate an archive of the reference, may be a branch name e.g. main,
368 /// a tag e.g. 0.1.9, or a git hash e.g. c73477ba0c7159002bd6d92dbdcd6de8292a034b
369 /// This uses the git binary directly since the archive