ephemeris_forge_forgejo/config/repository.rs
1use facet::Facet;
2
3const MAX_TOPIC_LENGTH: usize = 35;
4
5/// A repository topic in the form Forgejo stores.
6#[derive(Clone, Debug, Eq, Facet, Ord, PartialEq, PartialOrd)]
7#[facet(proxy = String)]
8pub struct TopicName(String);
9
10/// A topic Forgejo would rewrite or drop.
11#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
12#[error(
13 "topic \"{name}\" must be at most 35 characters of lowercase letters, digits, hyphens and \
14 dots, starting with a letter or digit"
15)]
16pub struct InvalidTopic {
17 /// The rejected topic.
18 pub name: String,
19}
20
21impl TopicName {
22 /// Whether Forgejo stores `topic` unchanged.
23 #[must_use]
24 pub fn is_canonical(topic: &str) -> bool {
25 let mut chars = topic.chars();
26 topic.len() <= MAX_TOPIC_LENGTH
27 && chars
28 .next()
29 .is_some_and(|first| first.is_ascii_lowercase() || first.is_ascii_digit())
30 && chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || matches!(c, '-' | '.'))
31 }
32
33 /// Borrow the topic.
34 #[must_use]
35 pub fn as_str(&self) -> &str {
36 &self.0
37 }
38}
39
40impl TryFrom<String> for TopicName {
41 type Error = InvalidTopic;
42
43 fn try_from(name: String) -> Result<Self, Self::Error> {
44 if Self::is_canonical(&name) {
45 Ok(Self(name))
46 } else {
47 Err(InvalidTopic { name })
48 }
49 }
50}
51
52impl From<&TopicName> for String {
53 fn from(value: &TopicName) -> Self {
54 value.0.clone()
55 }
56}
57
58ephemeris_forge::string_enum! {
59 /// The merge strategy preselected in the merge dialog.
60 pub enum MergeStyle("merge style") {
61 /// Merge only when the branch can fast-forward.
62 FastForwardOnly = "fast-forward-only",
63 /// Create a merge commit.
64 Merge = "merge",
65 /// Rebase the commits onto the base branch.
66 Rebase = "rebase",
67 /// Rebase the commits, then create a merge commit.
68 RebaseMerge = "rebase-merge",
69 /// Squash the commits into one.
70 Squash = "squash",
71 }
72}
73
74ephemeris_forge::string_enum! {
75 /// The strategy used to update an out-of-date pull request.
76 pub enum UpdateStyle("update style") {
77 /// Merge the base branch into the pull request branch.
78 Merge = "merge",
79 /// Rebase the pull request branch onto the base branch.
80 Rebase = "rebase",
81 }
82}
83
84ephemeris_forge::string_enum! {
85 /// How an external issue tracker numbers its issues.
86 pub enum TrackerStyle("tracker style") {
87 /// Issues are numbers such as `123`.
88 Numeric = "numeric",
89 /// Issues are letters and numbers such as `ABC-123`.
90 Alphanumeric = "alphanumeric",
91 /// Issues are found with a regular expression.
92 Regexp = "regexp",
93 }
94}
95
96/// Settings of the built-in issue tracker. Null leaves a setting alone.
97#[derive(Clone, Copy, Debug, Eq, Facet, PartialEq)]
98#[facet(deny_unknown_fields)]
99pub struct InternalTracker {
100 /// Whether time tracking is enabled.
101 pub enable_time_tracker: Option<bool>,
102 /// Whether only contributors may track time.
103 pub allow_only_contributors_to_track_time: Option<bool>,
104 /// Whether issues and pull requests may depend on each other.
105 pub enable_issue_dependencies: Option<bool>,
106}
107
108impl InternalTracker {
109 /// Whether any setting is declared.
110 #[must_use]
111 pub const fn declares_any(self) -> bool {
112 self.enable_time_tracker.is_some()
113 || self.allow_only_contributors_to_track_time.is_some()
114 || self.enable_issue_dependencies.is_some()
115 }
116}
117
118/// Settings of an external issue tracker.
119#[derive(Clone, Debug, Eq, Facet, PartialEq)]
120#[facet(deny_unknown_fields)]
121pub struct ExternalTracker {
122 /// The URL of the tracker.
123 pub external_tracker_url: String,
124 /// The issue URL format with the placeholders `{user}`, `{repo}` and
125 /// `{index}`.
126 pub external_tracker_format: String,
127 /// How the tracker numbers its issues.
128 pub external_tracker_style: Option<TrackerStyle>,
129 /// The expression that finds issues when the style is `regexp`.
130 pub external_tracker_regexp_pattern: Option<String>,
131}
132
133/// Settings of an external wiki.
134#[derive(Clone, Debug, Eq, Facet, PartialEq)]
135#[facet(deny_unknown_fields)]
136pub struct ExternalWiki {
137 /// The URL of the wiki.
138 pub external_wiki_url: String,
139}
140
141/// Desired settings for a repository.
142#[derive(Clone, Debug, Eq, Facet, PartialEq)]
143#[facet(deny_unknown_fields)]
144pub struct Repository {
145 /// The one-line summary shown on the repository's landing page.
146 pub description: Option<String>,
147 /// Topic labels used for discovery and filtering.
148 pub topics: Option<Vec<TopicName>>,
149 /// Whether a merge is rejected unless the branch can fast-forward.
150 pub allow_fast_forward_only_merge: Option<bool>,
151 /// Whether merging is allowed to create a merge commit.
152 pub allow_merge_commits: Option<bool>,
153 /// Whether merging is allowed to rebase commits onto the base branch.
154 pub allow_rebase: Option<bool>,
155 /// Whether rebase merges may be done with an explicit merge commit.
156 pub allow_rebase_explicit: Option<bool>,
157 /// Whether a pull request branch may be updated by rebasing it.
158 pub allow_rebase_update: Option<bool>,
159 /// Whether merging is allowed to squash commits into one.
160 pub allow_squash_merge: Option<bool>,
161 /// Whether the source branch is deleted automatically after merging.
162 pub default_delete_branch_after_merge: Option<bool>,
163 /// The merge strategy preselected in the merge dialog.
164 pub default_merge_style: Option<MergeStyle>,
165 /// The strategy preselected for updating an out-of-date pull request.
166 pub default_update_style: Option<UpdateStyle>,
167 /// Whether CI/CD actions are enabled.
168 pub has_actions: Option<bool>,
169 /// Whether the issue tracker is enabled.
170 pub has_issues: Option<bool>,
171 /// Whether the package registry is enabled.
172 pub has_packages: Option<bool>,
173 /// Whether project boards are enabled.
174 pub has_projects: Option<bool>,
175 /// Whether pull requests are enabled.
176 pub has_pull_requests: Option<bool>,
177 /// Whether releases are enabled.
178 pub has_releases: Option<bool>,
179 /// Whether the wiki is enabled.
180 pub has_wiki: Option<bool>,
181 /// The URL with more information about the repository.
182 pub website: Option<String>,
183 /// Whether the repository is private. Null leaves visibility alone.
184 pub private: Option<bool>,
185 /// Whether the repository is a template.
186 pub template: Option<bool>,
187 /// The default branch. Null leaves it alone.
188 pub default_branch: Option<String>,
189 /// Whether the repository is archived.
190 pub archived: Option<bool>,
191 /// The external wiki. Null leaves the wiki unit alone.
192 pub external_wiki: Option<ExternalWiki>,
193 /// The built-in issue tracker settings. They apply when the external
194 /// tracker is null.
195 pub internal_tracker: InternalTracker,
196 /// The external issue tracker. Null uses the built-in tracker.
197 pub external_tracker: Option<ExternalTracker>,
198 /// Whether whitespace is ignored when checking pull requests for
199 /// conflicts.
200 pub ignore_whitespace_conflicts: Option<bool>,
201 /// Whether a pull request may be marked as merged by hand. Forgejo does
202 /// not report the setting. Null leaves it alone.
203 pub allow_manual_merge: Option<bool>,
204 /// Whether manual merges are detected automatically. Forgejo does not
205 /// report the setting. Null leaves it alone.
206 pub autodetect_manual_merge: Option<bool>,
207 /// Whether maintainers may edit pull requests by default.
208 pub default_allow_maintainer_edit: Option<bool>,
209 /// Whether anyone with write access may edit the wiki.
210 pub globally_editable_wiki: Option<bool>,
211 /// The branch that holds the wiki. Null leaves it alone.
212 pub wiki_branch: Option<String>,
213 /// The mirror sync interval, such as `8h30m0s`. Null leaves it alone.
214 pub mirror_interval: Option<String>,
215 /// Whether a mirror removes obsolete remote-tracking references. Forgejo
216 /// does not report the setting. Null leaves it alone.
217 pub enable_prune: Option<bool>,
218}