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
|
// 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,
};
#[derive(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,
project_list: PathBuf,
/// The path that is the common parent of all the repositories.
pub scan_path: PathBuf,
}
pub struct BackRepositories {
repositories: Vec<BackRepository>,
}
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 struct BackRepository {
repo_path: PathBuf,
}
impl BackRepository {
pub fn open(&self, scan_path: &Path) -> Result<ThreadSafeRepository, error::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 = ThreadSafeRepository::open(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
}
}
impl BackConfig {
pub fn repositories(&self) -> error::Result<BackRepositories> {
let repositories = fs::read_to_string(&self.project_list)
.map_err(|err| error::Error::ProjectListRead {
error: err,
file: self.project_list.to_owned(),
})?
.lines()
.try_fold(vec![], |mut acc, path| {
acc.push(BackRepository {
repo_path: PathBuf::from(path),
});
Ok::<_, error::Error>(acc)
})?;
Ok(BackRepositories { repositories })
}
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,
})
}
}
|