Commit
Author: Kevin Schoon [me@kevinschoon.com]
Hash: 534aeed7466a0144a3e52a080aa6ad4474289923
Timestamp: Fri, 05 Jul 2024 20:28:09 +0000 (8 months ago)

+107 -1 +/-9 browse
detect git mirrors to display them in the ui and handle them differently
detect git mirrors to display them in the ui and handle them differently

Mirrors are important for building software but it is not necessarily
desirable to treat them as though they are cannonical repositories since they
are not "owned" by a given Ayllu instance.

This adds the ability to detect when a repository is considered a "mirror" to
display this in the UI as well as hide it from RSS feeds, the remote API,
and prevent certain jobs from being run against it.
1diff --git a/ayllu/src/web2/routes/index.rs b/ayllu/src/web2/routes/index.rs
2index ca1cf86..e300ca9 100644
3--- a/ayllu/src/web2/routes/index.rs
4+++ b/ayllu/src/web2/routes/index.rs
5 @@ -32,6 +32,7 @@ struct Repository {
6 timestamp: Option<u64>,
7 age: String,
8 activity: String,
9+ is_mirror: bool,
10 }
11
12 async fn load_repositories(collection_path: &str, db: &Database) -> Result<Vec<Repository>, Error> {
13 @@ -52,6 +53,7 @@ async fn load_repositories(collection_path: &str, db: &Database) -> Result<Vec<R
14 .unwrap_or(String::from("no description present"));
15 let timestamp = repository.last_modified()?;
16 let age = timestamp.map_or(String::from("?"), timeutil::friendly);
17+ let is_mirror = git_config.is_mirror;
18 // TODO move to plugin based system
19 let db_activity: Vec<(i64, i64, i64)> = match latest_hash {
20 Some(hash) => {
21 @@ -72,6 +74,7 @@ async fn load_repositories(collection_path: &str, db: &Database) -> Result<Vec<R
22 timestamp,
23 age,
24 activity,
25+ is_mirror,
26 });
27 }
28 repositories.sort_by(|a, b| {
29 diff --git a/ayllu/src/web2/routes/rss.rs b/ayllu/src/web2/routes/rss.rs
30index e309cf3..4a45b70 100644
31--- a/ayllu/src/web2/routes/rss.rs
32+++ b/ayllu/src/web2/routes/rss.rs
33 @@ -153,9 +153,14 @@ impl Builder {
34 for entry in entries {
35 let repository = Wrapper::new(entry.0.as_path())?;
36 let config = repository.config()?;
37+ // skip hidden repositories
38 if config.hidden.is_some_and(|hidden| hidden) {
39 continue;
40 };
41+ // skip mirrors
42+ if config.is_mirror {
43+ continue;
44+ }
45 for tag in repository.tags_range(Some((
46 start.unix_timestamp(),
47 self.current_time.unix_timestamp(),
48 @@ -207,6 +212,9 @@ impl Builder {
49 if config.hidden.is_some_and(|hidden| hidden) {
50 continue;
51 };
52+ if config.is_mirror {
53+ continue;
54+ }
55 let tags =
56 repository.tags_range(Some((start.unix_timestamp(), end.unix_timestamp())))?;
57 let commits = repository
58 diff --git a/ayllu/themes/default/templates/index.html b/ayllu/themes/default/templates/index.html
59index dd5fc44..e5c5d11 100644
60--- a/ayllu/themes/default/templates/index.html
61+++ b/ayllu/themes/default/templates/index.html
62 @@ -27,6 +27,7 @@
63 <article class="segmented-4">
64 <div class="name">
65 <a href="/{{ collection.name }}/{{ repo.name }}">{{ repo.name }}</a>
66+ {%- if repo.is_mirror %} <span class="tiny-highlight">[mirror]</span> {%- endif -%}
67 </div>
68 <div class="description">{{ repo.description }}</div>
69 <div class="age">{{ repo.age }}</div>
70 diff --git a/ayllu/themes/default/theme.css b/ayllu/themes/default/theme.css
71index 9ea20ef..0ce6f03 100644
72--- a/ayllu/themes/default/theme.css
73+++ b/ayllu/themes/default/theme.css
74 @@ -79,6 +79,15 @@ span.hint {
75 text-decoration: underline;
76 }
77
78+ span.tiny-highlight {
79+ font-size: var(--font-size-0);
80+ font-weight: var(--font-weight-0);
81+ color: var(--text-2);
82+ text-shadow:
83+ 0 0 10px var(--green-5),
84+ 0 0 25px var(--green-7);
85+ }
86+
87 .rss-links {
88 display: flex;
89 align-items: center;
90 diff --git a/crates/git/src/config.rs b/crates/git/src/config.rs
91index aa5794c..1bd8223 100644
92--- a/crates/git/src/config.rs
93+++ b/crates/git/src/config.rs
94 @@ -36,6 +36,25 @@ pub struct ChatLink {
95 pub kind: ChatKind,
96 }
97
98+ /// represents a remote as defined in the local git configuration
99+ #[derive(Serialize, Clone, Debug)]
100+ pub struct Remote {
101+ pub url: String,
102+ pub mirror: bool,
103+ }
104+
105+ /// array of remotes
106+ pub struct Remotes(pub Vec<Remote>);
107+
108+ impl Remotes {
109+ /// determine if any of the remotes are mirrors which usually would
110+ /// indicate that the repository is a mirror rather than a cannonical
111+ /// source.
112+ pub fn has_mirror(&self) -> bool {
113+ self.0.iter().any(|remote| remote.mirror)
114+ }
115+ }
116+
117 // ayllu specific configuration from a git repository
118 #[derive(Serialize, Clone, Debug)]
119 pub struct Config {
120 @@ -46,6 +65,41 @@ pub struct Config {
121 pub hidden: Option<bool>,
122 pub default_branch: Option<String>,
123 pub sites: Sites,
124+ /// an array of remotes where mirror = true, typically one when a
125+ /// repository is cloned with with the mirror flag, i.e.
126+ /// git clone --mirror ...
127+ pub remotes: Vec<Remote>,
128+ /// if the repository looks to be a mirror
129+ pub is_mirror: bool,
130+ }
131+
132+ /// return a vec of all the remotes defined in the git configuration
133+ pub fn remotes(cfg: &GitConfig) -> Remotes {
134+ let entries: Vec<(String, String)> =
135+ cfg.entries(Some(r#"remote.*.url"#))
136+ .map_or(Vec::new(), |entries| {
137+ let mut remotes: Vec<(String, String)> = Vec::new();
138+ let _ = entries.for_each(|entry| {
139+ if let Some(name) = entry.name() {
140+ remotes.push((
141+ name.replace(".url", "").to_string(),
142+ entry.value().unwrap_or("").to_string(),
143+ ));
144+ };
145+ });
146+ remotes
147+ });
148+ Remotes(
149+ entries
150+ .into_iter()
151+ .map(|entry| Remote {
152+ url: entry.1.clone(),
153+ mirror: cfg
154+ .get_bool(&(entry.0 + ".mirror"))
155+ .is_ok_and(|mirror| mirror),
156+ })
157+ .collect(),
158+ )
159 }
160
161 pub fn string(cfg: &GitConfig, path: &str) -> Option<String> {
162 diff --git a/crates/git/src/wrapper.rs b/crates/git/src/wrapper.rs
163index 9112ec7..f2f960a 100644
164--- a/crates/git/src/wrapper.rs
165+++ b/crates/git/src/wrapper.rs
166 @@ -496,6 +496,8 @@ impl Wrapper {
167 .map(|entry| config::Email(entry.clone()))
168 .collect()
169 });
170+ let remotes = config::remotes(git_config);
171+ let is_mirror = remotes.has_mirror();
172 Ok(config::Config {
173 description: config::string(git_config, "ayllu.description"),
174 homepage: config::string(git_config, "ayllu.homepage"),
175 @@ -508,6 +510,8 @@ impl Wrapper {
176 content: config::string(git_config, "ayllu-sites.content"),
177 branch: config::string(git_config, "ayllu-sites.branch"),
178 },
179+ remotes: remotes.0,
180+ is_mirror,
181 })
182 }
183
184 diff --git a/www/content/docs/configuration.md b/www/content/docs/configuration.md
185index d6811fd..c6cce19 100644
186--- a/www/content/docs/configuration.md
187+++ b/www/content/docs/configuration.md
188 @@ -200,3 +200,25 @@ diff = """
189 (command) @variable.builtin
190 """
191 ```
192+
193+ # Mirroring
194+
195+ Git mirrors will be automatically detected by looking at git configuration to
196+ determine if the repository has a `mirror = true` flag present in any of it's
197+ configured remotes. If any remote is flagged as a mirror then the repository
198+ will be considered a mirror. Mirror repositories will not be listed in RSS
199+ feeds, be subject to certain jobs types, or be available in the remote API.
200+
201+ ## Creating a Mirror
202+
203+ The simplest way to create a mirror is by cloning a repository with the
204+ `--mirror` flag. For example: `git clone https://ayllu-forge.org/ayllu/ayllu --mirror`
205+ will result in a git config such as:
206+
207+ ```
208+ [remote "origin"]
209+ url = https://ayllu-forge.org/ayllu/ayllu
210+ fetch = +refs/*:refs/*
211+ mirror = true
212+
213+ ```
214 diff --git a/www/public/docs/configuration/index.html b/www/public/docs/configuration/index.html
215index 1f99c9a..d5f74a4 100644
216--- a/www/public/docs/configuration/index.html
217+++ b/www/public/docs/configuration/index.html
218 @@ -79,4 +79,9 @@ diff = """
219 (location) @attribute
220 (command) @variable.builtin
221 """
222+ </code></pre><h1 id=mirroring><a aria-label="Anchor link for: mirroring" class=zola-anchor href=#mirroring>Mirroring</a></h1><p>Git mirrors will be automatically detected by looking at git configuration to determine if the repository has a <code>mirror = true</code> flag present in any of it's configured remotes. If any remote is flagged as a mirror then the repository will be considered a mirror. Mirror repositories will not be listed in RSS feeds, be subject to certain jobs types, or be available in the remote API.<h2 id=creating-a-mirror><a aria-label="Anchor link for: creating-a-mirror" class=zola-anchor href=#creating-a-mirror>Creating a Mirror</a></h2><p>The simplest way to create a mirror is by cloning a repository with the <code>--mirror</code> flag. For example: <code>git clone https://ayllu-forge.org/ayllu/ayllu --mirror</code> will result in a git config such as:<pre><code>[remote "origin"]
223+ url = https://ayllu-forge.org/ayllu/ayllu
224+ fetch = +refs/*:refs/*
225+ mirror = true
226+
227 </code></pre></div></div></main><footer class=page-footer>2024, <a href=/ayllu/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
228\ No newline at end of file
229 diff --git a/www/public/docs/mail/index.html b/www/public/docs/mail/index.html
230index 1bdbdcc..8dbbad2 100644
231--- a/www/public/docs/mail/index.html
232+++ b/www/public/docs/mail/index.html
233 @@ -1 +1 @@
234- <!doctype html><html lang=en><head><meta charset=utf-8><link href=/main.css rel=stylesheet><link href=/assets/ayllu_logo.svg rel=icon type=image/svg+xml><link href=/assets/ayllu_logo.png rel=icon sizes=any type=image/png><title>Ayllu</title></head><nav><ul><li><a href=/> <img class=logo src=/assets/ayllu_logo.png> </a></ul><ul><li><a href=https://ayllu-forge.org/browse>browse</a><li><a href=/docs>docs</a></ul></nav><body class=container><div class=wrapper><main class=page-body><div class=documentation><div class=side-panel><ul><li><a href=https://ayllu-forge.org/docs/architecture/>Architecture</a><li><a href=https://ayllu-forge.org/docs/builds/>Builds</a><li><a href=https://ayllu-forge.org/docs/configuration/>Configuration</a><li><a href=https://ayllu-forge.org/docs/developers/>Developers</a><li><a href=https://ayllu-forge.org/docs/installation/>Installation</a><li><a href=https://ayllu-forge.org/docs/faq/>FAQ</a><li class=active><a href=https://ayllu-forge.org/docs/mail/>Mail</a><li><a href=https://ayllu-forge.org/docs/proxy/>Reverse Proxy</a><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=mailing-list-support><a aria-label="Anchor link for: mailing-list-support" class=zola-anchor href=#mailing-list-support>Mailing List Support</a></h1><p>Ayllu has full featured support for email based development workflows. It is based on the excellent <a href=https://git.meli-email.org/meli/mailpot>mailpot</a> mailing list manager.</div></div></main><footer class=page-footer>2024, <a href=/ayllu/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
235\ No newline at end of file
236+ <!doctype html><html lang=en><head><meta charset=utf-8><link href=/main.css rel=stylesheet><link href=/assets/ayllu_logo.svg rel=icon type=image/svg+xml><link href=/assets/ayllu_logo.png rel=icon sizes=any type=image/png><title>Ayllu</title></head><nav><ul><li><a href=/> <img class=logo src=/assets/ayllu_logo.png> </a></ul><ul><li><a href=https://ayllu-forge.org/browse>browse</a><li><a href=/docs>docs</a></ul></nav><body class=container><div class=wrapper><main class=page-body><div class=documentation><div class=side-panel><ul><li><a href=https://ayllu-forge.org/docs/architecture/>Architecture</a><li><a href=https://ayllu-forge.org/docs/builds/>Builds</a><li><a href=https://ayllu-forge.org/docs/configuration/>Configuration</a><li><a href=https://ayllu-forge.org/docs/developers/>Developers</a><li><a href=https://ayllu-forge.org/docs/installation/>Installation</a><li><a href=https://ayllu-forge.org/docs/faq/>FAQ</a><li class=active><a href=https://ayllu-forge.org/docs/mail/>Mail</a><li><a href=https://ayllu-forge.org/docs/proxy/>Reverse Proxy</a><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=mailing-list-support><a aria-label="Anchor link for: mailing-list-support" class=zola-anchor href=#mailing-list-support>Mailing List Support</a></h1><p>Ayllu has full featured support for email based development workflows. It is based on the excellent <a href=https://git.meli-email.org/meli/mailpot>mailpot</a> mailing list manager.<h2 id=configuration><a aria-label="Anchor link for: configuration" class=zola-anchor href=#configuration>Configuration</a></h2><p>The configuration of mail servers can be complex. Ayllu encapsulates most of these settings in the <a href=https://ayllu-forge.org/ayllu/ayllu/tree/main/containers/multiuser>mutliuser</a> container which can be used as a reference for configuring your own mail server.<h3 id=minimum-recommended-security-settings><a aria-label="Anchor link for: minimum-recommended-security-settings" class=zola-anchor href=#minimum-recommended-security-settings>Minimum Recommended Security Settings</a></h3><h4 id=dkim><a aria-label="Anchor link for: dkim" class=zola-anchor href=#dkim>DKIM</a></h4><h4 id=spf-validation><a aria-label="Anchor link for: spf-validation" class=zola-anchor href=#spf-validation>SPF Validation</a></h4><p>The container provides SPF validation and the recommended DNS configuration for a single host e.g. <code>ayllu-forge.org</code> is <code>"v=spf1 a mx ~all"</code>.</div></div></main><footer class=page-footer>2024, <a href=/ayllu/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
237\ No newline at end of file