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
|
// Back - An extremely simple git issue tracking system. Inspired by tvix's
// panettone
//
// Copyright (C) 2024 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 gix::ThreadSafeRepository;
use serde::Deserialize;
use url::Url;
use crate::{
error::{self, Error},
git_bug::dag::is_git_bug,
};
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,
/// A list of the repositories known to back.
/// This list is constructed from the `scan_path` and the `project_list` file.
pub repositories: BackRepositories,
/// The root url this instance of back is hosted on.
/// For example:
/// `issues.foss-syndicate.org`
pub root: Url,
}
pub struct BackRepositories {
repositories: Vec<BackRepository>,
/// The path that is the common parent of all the repositories.
scan_path: PathBuf,
}
impl BackRepositories {
pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter {
self.into_iter()
}
}
impl<'a> IntoIterator for &'a BackRepositories {
type Item = <&'a Vec<BackRepository> as IntoIterator>::Item;
type IntoIter = <&'a Vec<BackRepository> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.repositories.iter()
}
}
impl BackRepositories {
/// Try to get the repository at path `path`.
/// If no repository was registered/found at `path`, returns an error.
pub fn get(&self, path: &Path) -> Result<&BackRepository, error::Error> {
self.repositories
.iter()
.find(|p| p.repo_path == path)
.ok_or(error::Error::RepoFind {
repository_path: path.to_owned(),
})
}
pub fn scan_path(&self) -> &Path {
&self.scan_path
}
}
pub struct BackRepository {
repo_path: PathBuf,
}
impl BackRepository {
pub fn open(&self, scan_path: &Path) -> Result<ThreadSafeRepository, error::Error> {
let repo = ThreadSafeRepository::open(scan_path.join(&self.repo_path)).map_err(|err| {
Error::RepoOpen {
repository_path: self.repo_path.to_owned(),
error: Box::new(err),
}
})?;
if is_git_bug(&repo.to_thread_local())? {
Ok(repo)
} else {
Err(error::Error::NotGitBug {
path: self.repo_path.clone(),
})
}
}
pub fn path(&self) -> &Path {
&self.repo_path
}
}
#[derive(Deserialize)]
struct RawBackConfig {
source_code_repository_url: Url,
root_url: Url,
project_list: PathBuf,
scan_path: PathBuf,
}
impl BackConfig {
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,
})?;
let raw: RawBackConfig =
serde_json::from_str(&value).map_err(|err| Error::ConfigParse {
file: path.to_owned(),
error: err,
})?;
Self::try_from(raw)
}
}
impl TryFrom<RawBackConfig> for BackConfig {
type Error = error::Error;
fn try_from(value: RawBackConfig) -> Result<Self, Self::Error> {
let repositories = fs::read_to_string(&value.project_list)
.map_err(|err| error::Error::ProjectListRead {
error: err,
file: value.project_list,
})?
.lines()
.try_fold(vec![], |mut acc, path| {
acc.push(BackRepository {
repo_path: PathBuf::from(path),
});
Ok::<_, Self::Error>(acc)
})?;
Ok(Self {
source_code_repository_url: value.source_code_repository_url,
repositories: BackRepositories {
repositories,
scan_path: value.scan_path,
},
root: value.root_url,
})
}
}
|