Base protocol refactor complete
parent: tbd commit: 079d544
1 | use Error; |
2 | use async_trait; |
3 | use StreamExt; |
4 | |
5 | use |
6 | |
7 | , | Instance
8 | |
9 | Commit, IssueLabel, Repository, RepositoryIssue, RepositorySummary, |
10 | RepositoryTreeEntry, RepositoryVisibility, |
11 | , |
12 | , |
13 | , |
14 | , |
15 | |
16 | , | RepositoryCreateRequest
17 | |
18 | RepositoryFileInspectRequest, RepositoryIssueLabelsRequest, |
19 | RepositoryIssuesCountRequest, RepositoryIssuesRequest, |
20 | , |
21 | , |
22 | , | AnyValue
23 | ; |
24 | use Value; |
25 | use ; |
26 | use |
27 | , |
28 | , | Arc
29 | ; |
30 | use Error; |
31 | use Mutex; |
32 | |
33 | use ; |
34 | |
35 | // TODO: Handle this |
36 | //region database structures |
37 | |
38 | /// Repository in the database |
39 | |
40 | |
41 | #[sqlx(try_from = "String")] |
42 | pub owner_user: User, |
43 | pub name: String, |
44 | pub description: , |
45 | pub visibility: RepositoryVisibility, |
46 | pub default_branch: String, |
47 | |
48 | |
49 | |
50 | // Separate function because "Private" will be expanded later |
51 | /// Checks if the user is allowed to view this repository |
52 | |
53 | !matches! |
54 | || |
55 | && Some == user |
56 | |
57 | |
58 | // This is in it's own function because I assume I'll have to add logic to this later |
59 | |
60 | &self, |
61 | repository_directory: &str, |
62 | |
63 | match open |
64 | "{}/{}/{}/{}" |
65 | repository_directory, self.owner_user.instance.url, self.owner_user.username, self.name |
66 | ) |
67 | Ok => Ok, |
68 | Err => |
69 | let err = FailedOpeningFromDisk; |
70 | error!; |
71 | |
72 | Err |
73 | |
74 | |
75 | |
76 | |
77 | |
78 | //endregion |
79 | |
80 | |
81 | |
82 | |
83 | FailedCreatingRepository, |
84 | |
85 | FailedInsertingIntoDatabase, |
86 | |
87 | RepositoryNotFound , |
88 | |
89 | RepositoryAlreadyExists , |
90 | |
91 | CouldNotDeleteFromDisk, |
92 | |
93 | FailedDeletingFromDatabase, |
94 | |
95 | FailedOpeningFromDisk, |
96 | |
97 | RefNotFound, |
98 | |
99 | PathNotFound, |
100 | |
101 | LastCommitNotFound, |
102 | |
103 | |
104 | |
105 | pub pg_pool: PgPool, |
106 | pub repository_folder: String, |
107 | pub instance: Instance, |
108 | pub settings_provider: , |
109 | |
110 | |
111 | |
112 | |
113 | pg_pool: &PgPool, |
114 | repository_folder: &str, |
115 | instance: impl , |
116 | settings_provider: , |
117 | |
118 | Self |
119 | pg_pool: pg_pool.clone, |
120 | repository_folder: repository_folder.to_string, |
121 | instance: instance.to_owned, |
122 | settings_provider, |
123 | |
124 | |
125 | |
126 | pub async |
127 | &self, |
128 | user: &User, |
129 | repository_name: &str, |
130 | |
131 | if let Ok = query_as! |
132 | r#"SELECT owner_user, name, description, visibility as "visibility: _", default_branch FROM repositories WHERE owner_user = $1 AND name = $2"#, |
133 | user.to_string, repository_name |
134 | .fetch_one |
135 | .await |
136 | Ok |
137 | else |
138 | Err |
139 | owner_user: user.to_string, |
140 | name: repository_name.to_string, |
141 | |
142 | |
143 | |
144 | |
145 | pub async |
146 | &self, |
147 | user: &User, |
148 | repository_name: &str, |
149 | |
150 | if let Err = remove_dir_all |
151 | "{}/{}/{}/{}" |
152 | self.repository_folder, user.instance.url, user.username, repository_name |
153 | ) |
154 | let err = CouldNotDeleteFromDisk; |
155 | error! |
156 | "Couldn't delete repository from disk, this is bad! {:?}", |
157 | err |
158 | ; |
159 | |
160 | return Err; |
161 | |
162 | |
163 | // Delete the repository from the database |
164 | match query! |
165 | "DELETE FROM repositories WHERE owner_user = $1 AND name = $2", |
166 | user.to_string, |
167 | repository_name |
168 | |
169 | .execute |
170 | .await |
171 | |
172 | Ok => Ok, |
173 | Err => Err, |
174 | |
175 | |
176 | |
177 | // TODO: Find where this fits |
178 | // TODO: Cache this and general repository tree and invalidate select files on push |
179 | // TODO: Find better and faster technique for this |
180 | |
181 | path: &str, |
182 | git: & Repository, |
183 | start_commit: & Commit, |
184 | |
185 | let mut revwalk = git.revwalk?; |
186 | revwalk.set_sorting?; |
187 | revwalk.push?; |
188 | |
189 | for oid in revwalk |
190 | let oid = oid?; |
191 | let commit = git.find_commit?; |
192 | |
193 | // Merge commits have 2 or more parents |
194 | // Commits with 0 parents are handled different because we can't diff against them |
195 | if commit.parent_count == 0 |
196 | return Ok; |
197 | else if commit.parent_count == 1 |
198 | let tree = commit.tree?; |
199 | let last_tree = commit.parent?.tree?; |
200 | |
201 | // Get the diff between the current tree and the last one |
202 | let diff = git.diff_tree_to_tree?; |
203 | |
204 | for dd in diff.deltas |
205 | // Get the path of the current file we're diffing against |
206 | let current_path = dd.new_file .path .unwrap; |
207 | |
208 | // Path or directory |
209 | if current_path.eq || current_path.starts_with |
210 | return Ok; |
211 | |
212 | |
213 | |
214 | |
215 | |
216 | Err? |
217 | |
218 | |
219 | |
220 | |
221 | |
222 | async |
223 | if let Ok = self |
224 | .find_by_owner_user_name |
225 | .await |
226 | |
227 | return Ok; |
228 | else |
229 | return Ok; |
230 | |
231 | |
232 | async |
233 | &mut self, |
234 | _user: &User, |
235 | request: &RepositoryCreateRequest, |
236 | |
237 | // Check if repository already exists in the database |
238 | if let Ok = self |
239 | .find_by_owner_user_name |
240 | .await |
241 | |
242 | let err = RepositoryAlreadyExists |
243 | owner_user: repository.owner_user.to_string, |
244 | name: repository.name, |
245 | ; |
246 | error!; |
247 | |
248 | return Err; |
249 | |
250 | |
251 | // Insert the repository into the database |
252 | let _ = match query_as! |
253 | r#"INSERT INTO repositories VALUES ($1, $2, $3, $4, $5) RETURNING owner_user, name, description, visibility as "visibility: _", default_branch"#, |
254 | request.owner.to_string, request.name, request.description, request.visibility as _, "master" |
255 | .fetch_one |
256 | .await |
257 | Ok => repository, |
258 | Err => |
259 | let err = FailedInsertingIntoDatabase; |
260 | error!; |
261 | |
262 | return Err; |
263 | |
264 | ; |
265 | |
266 | // Create bare (server side) repository on disk |
267 | match init_bare |
268 | "{}/{}/{}/{}" |
269 | self.repository_folder, |
270 | request.owner.instance.url, |
271 | request.owner.username, |
272 | request.name |
273 | ) |
274 | Ok => |
275 | debug! |
276 | "Created new repository with the name {}/{}/{}", |
277 | request.owner.instance.url, request.owner.username, request.name |
278 | ; |
279 | Ok |
280 | owner: request.owner.clone, |
281 | name: request.name.clone, |
282 | instance: request.instance.as_ref .unwrap_or .clone, |
283 | |
284 | |
285 | Err => |
286 | let err = FailedCreatingRepository; |
287 | error!; |
288 | |
289 | // Delete repository from database |
290 | self.delete_by_owner_user_name |
291 | .await?; |
292 | |
293 | // ??? |
294 | Err |
295 | |
296 | |
297 | |
298 | |
299 | async |
300 | &mut self, |
301 | repository: &Repository, |
302 | name: &str, |
303 | |
304 | Ok |
305 | "description" => unsafe |
306 | from_raw |
307 | self.get_setting |
308 | .await? |
309 | .0, |
310 | |
311 | , |
312 | "visibility" => unsafe |
313 | from_raw |
314 | self.get_setting |
315 | .await? |
316 | .0, |
317 | |
318 | , |
319 | _ => |
320 | return Err; |
321 | |
322 | |
323 | |
324 | async |
325 | &mut self, |
326 | repository: &Repository, |
327 | name: &str, |
328 | |
329 | let mut provider = self.settings_provider.lock .await; |
330 | |
331 | Ok |
332 | |
333 | async |
334 | &mut self, |
335 | repository: &Repository, |
336 | name: &str, |
337 | setting: &Value, |
338 | |
339 | let mut provider = self.settings_provider.lock .await; |
340 | |
341 | provider |
342 | .repository_write |
343 | .await |
344 | |
345 | // async fn repository_info( |
346 | // &mut self, |
347 | // requester: Option<&User>, |
348 | // request: &RepositoryInfoRequest, |
349 | // ) -> Result<RepositoryView, Error> { |
350 | // let repository = match self |
351 | // .find_by_owner_user_name( |
352 | // // &request.owner.instance.url, |
353 | // &request.repository.owner, |
354 | // &request.repository.name, |
355 | // ) |
356 | // .await |
357 | // { |
358 | // Ok(repository) => repository, |
359 | // Err(err) => return Err(Box::new(err).into()), |
360 | // }; |
361 | |
362 | // if let Some(requester) = requester { |
363 | // if !repository.can_user_view_repository(Some(&requester)) { |
364 | // return Err(Box::new(GitBackendError::RepositoryNotFound { |
365 | // owner_user: request.repository.owner.to_string(), |
366 | // name: request.repository.name.clone(), |
367 | // }) |
368 | // .into()); |
369 | // } |
370 | // } else if matches!(repository.visibility, RepositoryVisibility::Private) { |
371 | // info!("Unauthenticated"); |
372 | // // Unauthenticated users can never view private repositories |
373 | |
374 | // return Err(Box::new(GitBackendError::RepositoryNotFound { |
375 | // owner_user: request.repository.owner.to_string(), |
376 | // name: request.repository.name.clone(), |
377 | // }) |
378 | // .into()); |
379 | // } |
380 | |
381 | // let git = match repository.open_git2_repository(&self.repository_folder) { |
382 | // Ok(git) => git, |
383 | // Err(err) => return Err(Box::new(err).into()), |
384 | // }; |
385 | |
386 | // let rev_name = match &request.rev { |
387 | // None => { |
388 | // if let Ok(head) = git.head() { |
389 | // head.name().unwrap().to_string() |
390 | // } else { |
391 | // // Nothing in database, render empty tree. |
392 | // return Ok(RepositoryView { |
393 | // name: repository.name, |
394 | // owner: request.repository.owner.clone(), |
395 | // description: repository.description, |
396 | // visibility: repository.visibility, |
397 | // default_branch: repository.default_branch, |
398 | // latest_commit: None, |
399 | // tree_rev: None, |
400 | // tree: vec![], |
401 | // }); |
402 | // } |
403 | // } |
404 | // Some(rev_name) => { |
405 | // // Find the reference, otherwise return GitBackendError |
406 | // match git |
407 | // .find_reference(format!("refs/heads/{}", rev_name).as_str()) |
408 | // .map_err(|_| GitBackendError::RefNotFound(rev_name.to_string())) |
409 | // { |
410 | // Ok(reference) => reference.name().unwrap().to_string(), |
411 | // Err(err) => return Err(Box::new(err).into()), |
412 | // } |
413 | // } |
414 | // }; |
415 | |
416 | // // Get the git object as a commit |
417 | // let rev = match git |
418 | // .revparse_single(rev_name.as_str()) |
419 | // .map_err(|_| GitBackendError::RefNotFound(rev_name.to_string())) |
420 | // { |
421 | // Ok(rev) => rev, |
422 | // Err(err) => return Err(Box::new(err).into()), |
423 | // }; |
424 | // let commit = rev.as_commit().unwrap(); |
425 | |
426 | // // this is stupid |
427 | // let mut current_path = rev_name.replace("refs/heads/", ""); |
428 | |
429 | // // Get the commit tree |
430 | // let git_tree = if let Some(path) = &request.path { |
431 | // // Add it to our full path string |
432 | // current_path.push_str(format!("/{}", path).as_str()); |
433 | // // Get the specified path, return an error if it wasn't found. |
434 | // let entry = match commit |
435 | // .tree() |
436 | // .unwrap() |
437 | // .get_path(&PathBuf::from(path)) |
438 | // .map_err(|_| GitBackendError::PathNotFound(path.to_string())) |
439 | // { |
440 | // Ok(entry) => entry, |
441 | // Err(err) => return Err(Box::new(err).into()), |
442 | // }; |
443 | // // Turn the entry into a git tree |
444 | // entry.to_object(&git).unwrap().as_tree().unwrap().clone() |
445 | // } else { |
446 | // commit.tree().unwrap() |
447 | // }; |
448 | |
449 | // // Iterate over the git tree and collect it into our own tree types |
450 | // let mut tree = git_tree |
451 | // .iter() |
452 | // .map(|entry| { |
453 | // let object_type = match entry.kind().unwrap() { |
454 | // ObjectType::Tree => RepositoryObjectType::Tree, |
455 | // ObjectType::Blob => RepositoryObjectType::Blob, |
456 | // _ => unreachable!(), |
457 | // }; |
458 | // let mut tree_entry = |
459 | // RepositoryTreeEntry::new(entry.name().unwrap(), object_type, entry.filemode()); |
460 | |
461 | // if request.extra_metadata { |
462 | // // Get the file size if It's a blob |
463 | // let object = entry.to_object(&git).unwrap(); |
464 | // if let Some(blob) = object.as_blob() { |
465 | // tree_entry.size = Some(blob.size()); |
466 | // } |
467 | |
468 | // // Could possibly be done better |
469 | // let path = if let Some(path) = current_path.split_once('/') { |
470 | // format!("{}/{}", path.1, entry.name().unwrap()) |
471 | // } else { |
472 | // entry.name().unwrap().to_string() |
473 | // }; |
474 | |
475 | // // Get the last commit made to the entry |
476 | // if let Ok(last_commit) = |
477 | // GitBackend::get_last_commit_of_file(&path, &git, commit) |
478 | // { |
479 | // tree_entry.last_commit = Some(last_commit); |
480 | // } |
481 | // } |
482 | |
483 | // tree_entry |
484 | // }) |
485 | // .collect::<Vec<RepositoryTreeEntry>>(); |
486 | |
487 | // // Sort the tree alphabetically and with tree first |
488 | // tree.sort_unstable_by_key(|entry| entry.name.to_lowercase()); |
489 | // tree.sort_unstable_by_key(|entry| { |
490 | // std::cmp::Reverse(format!("{:?}", entry.object_type).to_lowercase()) |
491 | // }); |
492 | |
493 | // Ok(RepositoryView { |
494 | // name: repository.name, |
495 | // owner: request.repository.owner.clone(), |
496 | // description: repository.description, |
497 | // visibility: repository.visibility, |
498 | // default_branch: repository.default_branch, |
499 | // latest_commit: Some(Commit::from(commit.clone())), |
500 | // tree_rev: Some(rev_name), |
501 | // tree, |
502 | // }) |
503 | // } |
504 | |
505 | async |
506 | &mut self, |
507 | _requester: , |
508 | _request: &RepositoryFileInspectRequest, |
509 | |
510 | todo! |
511 | |
512 | |
513 | async |
514 | &mut self, |
515 | requester: , |
516 | user: &User, |
517 | |
518 | let mut repositories = query_as! |
519 | GitRepository, |
520 | r#"SELECT visibility as "visibility: _", owner_user, name, description, default_branch FROM repositories WHERE owner_user = $1"#, |
521 | user.to_string |
522 | |
523 | .fetch_many; |
524 | |
525 | let mut result = vec!; |
526 | |
527 | while let Some = repositories.next .await |
528 | // Check if the requesting user is allowed to see the repository |
529 | if ! |
530 | repository.visibility, |
531 | | Private | Unlisted
532 | && Some != requester |
533 | |
534 | result.push |
535 | repository: Repository |
536 | owner: repository.owner_user.clone, |
537 | name: repository.name, |
538 | instance: self.instance.clone, |
539 | , |
540 | owner: repository.owner_user.clone, |
541 | visibility: repository.visibility, |
542 | description: repository.description, |
543 | // TODO |
544 | last_commit: None, |
545 | ; |
546 | |
547 | |
548 | |
549 | Ok |
550 | |
551 | |
552 | |
553 | |
554 | |
555 | &mut self, |
556 | _requester: , |
557 | _request: &RepositoryIssuesCountRequest, |
558 | |
559 | todo! |
560 | |
561 | |
562 | |
563 | &mut self, |
564 | _requester: , |
565 | _request: &RepositoryIssueLabelsRequest, |
566 | |
567 | todo! |
568 | |
569 | |
570 | |
571 | &mut self, |
572 | _requester: , |
573 | _request: &RepositoryIssuesRequest, |
574 | |
575 | todo! |
576 | |
577 | |
578 | |
579 | |
580 | |
581 | pub repository: String, |
582 | pub name: String, |
583 | pub value: String, |
584 | |
585 |