1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
// Back - An extremely simple git bug visualization system. Inspired by TVL's
// panettone.
//
// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This file is part of Back.
//
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/agpl.txt>.
use std::{
fs,
path::{Path, PathBuf},
};
use git_bug::{entities::issue::Issue, replica::Replica};
use serde::Deserialize;
use url::Url;
use crate::error::{self, Error};
#[derive(Debug, Deserialize)]
pub struct BackConfig {
/// The url to the source code of back. This is needed, because back is licensed under the
/// AGPL.
pub source_code_repository_url: Url,
/// The root url this instance of back is hosted on.
/// For example:
/// `issues.foss-syndicate.org`
pub root_url: Url,
/// The file that list all the projects.
///
/// This is `cgit`'s `project-list` setting.
pub project_list: PathBuf,
/// The path that is the common parent of all the repositories.
///
/// This is `cgit`'s `scan-path` setting.
pub scan_path: PathBuf,
}
#[derive(Debug)]
pub struct BackRepositories {
repositories: Vec<BackRepository>,
}
impl BackRepositories {
#[must_use]
pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter {
self.into_iter()
}
}
impl<'a> IntoIterator for &'a BackRepositories {
type IntoIter = <&'a Vec<BackRepository> as IntoIterator>::IntoIter;
type Item = <&'a Vec<BackRepository> as IntoIterator>::Item;
fn into_iter(self) -> Self::IntoIter {
self.repositories.iter()
}
}
impl BackRepositories {
/// Try to get the repository at path `path`.
///
/// # Errors
/// - If no repository was registered/found at `path`
pub fn get(&self, path: &Path) -> Result<&BackRepository, Error> {
self.repositories
.iter()
.find(|p| p.repo_path == path)
.ok_or(Error::RepoFind {
repository_path: path.to_owned(),
})
}
}
#[derive(Debug)]
pub struct BackRepository {
repo_path: PathBuf,
}
impl BackRepository {
/// Open this repository.
///
/// # Errors
///
/// This function will return an error if the repository could not be opened.
pub fn open(&self, scan_path: &Path) -> Result<Replica, Error> {
let path = {
let base = scan_path.join(&self.repo_path);
if base.is_dir() {
base
} else {
PathBuf::from(base.display().to_string() + ".git")
}
};
let repo = Replica::from_path(path).map_err(|err| Error::RepoOpen {
repository_path: self.repo_path.clone(),
error: Box::new(err),
})?;
// We could also check that Identity data is in the Replica,
// but Issue existent is paramount.
if repo.contains::<Issue>()? {
Ok(repo)
} else {
Err(Error::NotGitBug {
path: self.repo_path.clone(),
})
}
}
#[must_use]
pub fn path(&self) -> &Path {
&self.repo_path
}
}
impl BackConfig {
/// Returns the repositories of this [`BackConfig`].
///
/// # Note
/// This will always re-read the `projects.list` file, to pick up repositories that were added
/// in the mean time.
///
/// # Errors
///
/// This function will return an error if the associated IO operations fail.
pub fn repositories(&self) -> error::Result<BackRepositories> {
let repositories = fs::read_to_string(&self.project_list)
.map_err(|err| Error::ProjectListRead {
error: err,
file: self.project_list.clone(),
})?
.lines()
.try_fold(vec![], |mut acc, path| {
acc.push(BackRepository {
repo_path: PathBuf::from(path),
});
Ok::<_, Error>(acc)
})?;
Ok(BackRepositories { repositories })
}
/// Construct this [`BackConfig`] from a config file.
///
/// # Errors
///
/// This function will return an error if the associated IO operations fail.
pub fn from_config_file(path: &Path) -> error::Result<Self> {
let value = fs::read_to_string(path).map_err(|err| Error::ConfigRead {
file: path.to_owned(),
error: err,
})?;
serde_json::from_str(&value).map_err(|err| Error::ConfigParse {
file: path.to_owned(),
error: err,
})
}
}
|