Fix branch
parent: tbd commit: ff70fd4
1 | use Error; |
2 | use async_trait; |
3 | use StreamExt; |
4 | |
5 | use BranchType; |
6 | use ; |
7 | |
8 | use |
9 | Commit, DefaultBranch, Description, IssueLabel, LatestCommit, Repository, |
10 | RepositoryCommitBeforeRequest, RepositoryDiff, RepositoryDiffRequest, RepositoryFile, |
11 | RepositoryFileFromIdRequest, RepositoryFileInspectRequest, RepositoryIssue, |
12 | RepositoryIssueLabelsRequest, RepositoryIssuesCountRequest, RepositoryIssuesRequest, |
13 | RepositoryObjectType, RepositoryTreeEntry, RepositoryVisibility, Visibility, |
14 | ; |
15 | use ; |
16 | use ; |
17 | use ; |
18 | use Value; |
19 | use PgPool; |
20 | use |
21 | , |
22 | , | Arc
23 | ; |
24 | use Error; |
25 | use Mutex; |
26 | |
27 | use ; |
28 | |
29 | // TODO: Handle this |
30 | //region database structures |
31 | |
32 | /// Repository in the database |
33 | |
34 | |
35 | #[sqlx(try_from = "String")] |
36 | pub owner_user: User, |
37 | pub name: String, |
38 | pub description: , |
39 | pub visibility: RepositoryVisibility, |
40 | pub default_branch: String, |
41 | |
42 | |
43 | |
44 | // Separate function because "Private" will be expanded later |
45 | /// Checks if the user is allowed to view this repository |
46 | |
47 | !matches! |
48 | || |
49 | && Some == user |
50 | |
51 | |
52 | // This is in it's own function because I assume I'll have to add logic to this later |
53 | |
54 | &self, |
55 | repository_directory: &str, |
56 | |
57 | match open |
58 | "{}/{}/{}/{}" |
59 | repository_directory, self.owner_user.instance.url, self.owner_user.username, self.name |
60 | ) |
61 | Ok => Ok, |
62 | Err => |
63 | let err = FailedOpeningFromDisk; |
64 | error!; |
65 | |
66 | Err |
67 | |
68 | |
69 | |
70 | |
71 | |
72 | //endregion |
73 | |
74 | |
75 | |
76 | |
77 | FailedCreatingRepository, |
78 | |
79 | FailedInsertingIntoDatabase, |
80 | |
81 | RepositoryNotFound , |
82 | |
83 | RepositoryAlreadyExists , |
84 | |
85 | CouldNotDeleteFromDisk, |
86 | |
87 | FailedDeletingFromDatabase, |
88 | |
89 | FailedOpeningFromDisk, |
90 | |
91 | RefNotFound, |
92 | |
93 | PathNotFound, |
94 | |
95 | LastCommitNotFound, |
96 | |
97 | InvalidObjectId, |
98 | |
99 | BlobNotFound, |
100 | |
101 | TreeNotFound, |
102 | |
103 | CommitNotFound, |
104 | |
105 | CommitParentNotFound, |
106 | |
107 | FailedDiffing, |
108 | |
109 | |
110 | |
111 | pub pg_pool: PgPool, |
112 | pub repository_folder: String, |
113 | pub instance: Instance, |
114 | pub settings_provider: , |
115 | |
116 | |
117 | |
118 | |
119 | pg_pool: &PgPool, |
120 | repository_folder: &str, |
121 | instance: impl , |
122 | settings_provider: , |
123 | |
124 | Self |
125 | pg_pool: pg_pool.clone, |
126 | repository_folder: repository_folder.to_string, |
127 | instance: instance.to_owned, |
128 | settings_provider, |
129 | |
130 | |
131 | |
132 | pub async |
133 | &self, |
134 | user: &User, |
135 | repository_name: &str, |
136 | |
137 | if let Ok = query_as! |
138 | r#"SELECT owner_user, name, description, visibility as "visibility: _", default_branch FROM repositories WHERE owner_user = $1 AND name = $2"#, |
139 | user.to_string, repository_name |
140 | .fetch_one |
141 | .await |
142 | Ok |
143 | else |
144 | Err |
145 | owner_user: user.to_string, |
146 | name: repository_name.to_string, |
147 | |
148 | |
149 | |
150 | |
151 | pub async |
152 | &self, |
153 | user: &User, |
154 | repository_name: &str, |
155 | |
156 | if let Err = remove_dir_all |
157 | "{}/{}/{}/{}" |
158 | self.repository_folder, user.instance.url, user.username, repository_name |
159 | ) |
160 | let err = CouldNotDeleteFromDisk; |
161 | error! |
162 | "Couldn't delete repository from disk, this is bad! {:?}", |
163 | err |
164 | ; |
165 | |
166 | return Err; |
167 | |
168 | |
169 | // Delete the repository from the database |
170 | match query! |
171 | "DELETE FROM repositories WHERE owner_user = $1 AND name = $2", |
172 | user.to_string, |
173 | repository_name |
174 | |
175 | .execute |
176 | .await |
177 | |
178 | Ok => Ok, |
179 | Err => Err, |
180 | |
181 | |
182 | |
183 | pub async |
184 | &self, |
185 | owner: &User, |
186 | name: &str, |
187 | requester: , |
188 | |
189 | let repository = match self |
190 | .find_by_owner_user_name |
191 | // &request.owner.instance.url, |
192 | owner, name, |
193 | |
194 | .await |
195 | |
196 | Ok => repository, |
197 | Err => return Err, |
198 | ; |
199 | |
200 | if let Some = requester |
201 | if !repository.can_user_view_repository |
202 | return Err |
203 | owner_user: repository.owner_user.to_string, |
204 | name: repository.name.clone, |
205 | ; |
206 | |
207 | else if matches! |
208 | info!; |
209 | // Unauthenticated users can never view private repositories |
210 | |
211 | return Err |
212 | owner_user: repository.owner_user.to_string, |
213 | name: repository.name.clone, |
214 | ; |
215 | |
216 | |
217 | match repository.open_git2_repository |
218 | Ok => Ok, |
219 | Err => return Err, |
220 | |
221 | |
222 | |
223 | // TODO: Find where this fits |
224 | // TODO: Cache this and general repository tree and invalidate select files on push |
225 | // TODO: Find better and faster technique for this |
226 | |
227 | path: &str, |
228 | git: & Repository, |
229 | start_commit: & Commit, |
230 | |
231 | let mut revwalk = git.revwalk?; |
232 | revwalk.set_sorting?; |
233 | revwalk.push?; |
234 | |
235 | for oid in revwalk |
236 | let oid = oid?; |
237 | let commit = git.find_commit?; |
238 | |
239 | // Merge commits have 2 or more parents |
240 | // Commits with 0 parents are handled different because we can't diff against them |
241 | if commit.parent_count == 0 |
242 | return Ok; |
243 | else if commit.parent_count == 1 |
244 | let tree = commit.tree?; |
245 | let last_tree = commit.parent?.tree?; |
246 | |
247 | // Get the diff between the current tree and the last one |
248 | let diff = git.diff_tree_to_tree?; |
249 | |
250 | for dd in diff.deltas |
251 | // Get the path of the current file we're diffing against |
252 | let current_path = dd.new_file .path .unwrap; |
253 | |
254 | // Path or directory |
255 | if current_path.eq || current_path.starts_with |
256 | return Ok; |
257 | |
258 | |
259 | |
260 | |
261 | |
262 | Err? |
263 | |
264 | |
265 | |
266 | |
267 | |
268 | async |
269 | if let Ok = self |
270 | .find_by_owner_user_name |
271 | .await |
272 | |
273 | Ok |
274 | else |
275 | Ok |
276 | |
277 | |
278 | |
279 | async |
280 | &mut self, |
281 | _user: &User, |
282 | request: &RepositoryCreateRequest, |
283 | |
284 | // Check if repository already exists in the database |
285 | if let Ok = self |
286 | .find_by_owner_user_name |
287 | .await |
288 | |
289 | let err = RepositoryAlreadyExists |
290 | owner_user: repository.owner_user.to_string, |
291 | name: repository.name, |
292 | ; |
293 | error!; |
294 | |
295 | return Err; |
296 | |
297 | |
298 | // Insert the repository into the database |
299 | let _ = match query_as! |
300 | r#"INSERT INTO repositories VALUES ($1, $2, $3, $4, $5) RETURNING owner_user, name, description, visibility as "visibility: _", default_branch"#, |
301 | request.owner.to_string, request.name, request.description, request.visibility as _, "master" |
302 | .fetch_one |
303 | .await |
304 | Ok => repository, |
305 | Err => |
306 | let err = FailedInsertingIntoDatabase; |
307 | error!; |
308 | |
309 | return Err; |
310 | |
311 | ; |
312 | |
313 | // Create bare (server side) repository on disk |
314 | match init_bare |
315 | "{}/{}/{}/{}" |
316 | self.repository_folder, |
317 | request.owner.instance.url, |
318 | request.owner.username, |
319 | request.name |
320 | ) |
321 | Ok => |
322 | debug! |
323 | "Created new repository with the name {}/{}/{}", |
324 | request.owner.instance.url, request.owner.username, request.name |
325 | ; |
326 | Ok |
327 | owner: request.owner.clone, |
328 | name: request.name.clone, |
329 | instance: request.instance.as_ref .unwrap_or .clone, |
330 | |
331 | |
332 | Err => |
333 | let err = FailedCreatingRepository; |
334 | error!; |
335 | |
336 | // Delete repository from database |
337 | self.delete_by_owner_user_name |
338 | .await?; |
339 | |
340 | // ??? |
341 | Err |
342 | |
343 | |
344 | |
345 | |
346 | async |
347 | &mut self, |
348 | repository: &Repository, |
349 | name: &str, |
350 | |
351 | Ok |
352 | if name == value_name |
353 | from_raw |
354 | else if name == value_name |
355 | from_raw |
356 | else if name == value_name |
357 | from_raw |
358 | else if name == value_name |
359 | from_raw |
360 | else |
361 | return Err; |
362 | |
363 | |
364 | |
365 | |
366 | async |
367 | &mut self, |
368 | repository: &Repository, |
369 | name: &str, |
370 | |
371 | let mut provider = self.settings_provider.lock .await; |
372 | |
373 | Ok |
374 | |
375 | |
376 | async |
377 | &mut self, |
378 | repository: &Repository, |
379 | name: &str, |
380 | setting: &Value, |
381 | |
382 | let mut provider = self.settings_provider.lock .await; |
383 | |
384 | provider |
385 | .repository_write |
386 | .await |
387 | |
388 | |
389 | // async fn repository_info( |
390 | // &mut self, |
391 | // requester: Option<&User>, |
392 | // request: &RepositoryInfoRequest, |
393 | // ) -> Result<RepositoryView, Error> { |
394 | // let repository = match self |
395 | // .find_by_owner_user_name( |
396 | // // &request.owner.instance.url, |
397 | // &request.repository.owner, |
398 | // &request.repository.name, |
399 | // ) |
400 | // .await |
401 | // { |
402 | // Ok(repository) => repository, |
403 | // Err(err) => return Err(Box::new(err).into()), |
404 | // }; |
405 | |
406 | // if let Some(requester) = requester { |
407 | // if !repository.can_user_view_repository(Some(&requester)) { |
408 | // return Err(Box::new(GitBackendError::RepositoryNotFound { |
409 | // owner_user: request.repository.owner.to_string(), |
410 | // name: request.repository.name.clone(), |
411 | // }) |
412 | // .into()); |
413 | // } |
414 | // } else if matches!(repository.visibility, RepositoryVisibility::Private) { |
415 | // info!("Unauthenticated"); |
416 | // // Unauthenticated users can never view private repositories |
417 | |
418 | // return Err(Box::new(GitBackendError::RepositoryNotFound { |
419 | // owner_user: request.repository.owner.to_string(), |
420 | // name: request.repository.name.clone(), |
421 | // }) |
422 | // .into()); |
423 | // } |
424 | |
425 | // let git = match repository.open_git2_repository(&self.repository_folder) { |
426 | // Ok(git) => git, |
427 | // Err(err) => return Err(Box::new(err).into()), |
428 | // }; |
429 | |
430 | // let rev_name = match &request.rev { |
431 | // None => { |
432 | // if let Ok(head) = git.head() { |
433 | // head.name().unwrap().to_string() |
434 | // } else { |
435 | // // Nothing in database, render empty tree. |
436 | // return Ok(RepositoryView { |
437 | // name: repository.name, |
438 | // owner: request.repository.owner.clone(), |
439 | // description: repository.description, |
440 | // visibility: repository.visibility, |
441 | // default_branch: repository.default_branch, |
442 | // latest_commit: None, |
443 | // tree_rev: None, |
444 | // tree: vec![], |
445 | // }); |
446 | // } |
447 | // } |
448 | // Some(rev_name) => { |
449 | // // Find the reference, otherwise return GitBackendError |
450 | // match git |
451 | // .find_reference(format!("refs/heads/{}", rev_name).as_str()) |
452 | // .map_err(|_| GitBackendError::RefNotFound(rev_name.to_string())) |
453 | // { |
454 | // Ok(reference) => reference.name().unwrap().to_string(), |
455 | // Err(err) => return Err(Box::new(err).into()), |
456 | // } |
457 | // } |
458 | // }; |
459 | |
460 | // // Get the git object as a commit |
461 | // let rev = match git |
462 | // .revparse_single(rev_name.as_str()) |
463 | // .map_err(|_| GitBackendError::RefNotFound(rev_name.to_string())) |
464 | // { |
465 | // Ok(rev) => rev, |
466 | // Err(err) => return Err(Box::new(err).into()), |
467 | // }; |
468 | // let commit = rev.as_commit().unwrap(); |
469 | |
470 | // // this is stupid |
471 | // let mut current_path = rev_name.replace("refs/heads/", ""); |
472 | |
473 | // // Get the commit tree |
474 | // let git_tree = if let Some(path) = &request.path { |
475 | // // Add it to our full path string |
476 | // current_path.push_str(format!("/{}", path).as_str()); |
477 | // // Get the specified path, return an error if it wasn't found. |
478 | // let entry = match commit |
479 | // .tree() |
480 | // .unwrap() |
481 | // .get_path(&PathBuf::from(path)) |
482 | // .map_err(|_| GitBackendError::PathNotFound(path.to_string())) |
483 | // { |
484 | // Ok(entry) => entry, |
485 | // Err(err) => return Err(Box::new(err).into()), |
486 | // }; |
487 | // // Turn the entry into a git tree |
488 | // entry.to_object(&git).unwrap().as_tree().unwrap().clone() |
489 | // } else { |
490 | // commit.tree().unwrap() |
491 | // }; |
492 | |
493 | // // Iterate over the git tree and collect it into our own tree types |
494 | // let mut tree = git_tree |
495 | // .iter() |
496 | // .map(|entry| { |
497 | // let object_type = match entry.kind().unwrap() { |
498 | // ObjectType::Tree => RepositoryObjectType::Tree, |
499 | // ObjectType::Blob => RepositoryObjectType::Blob, |
500 | // _ => unreachable!(), |
501 | // }; |
502 | // let mut tree_entry = |
503 | // RepositoryTreeEntry::new(entry.name().unwrap(), object_type, entry.filemode()); |
504 | |
505 | // if request.extra_metadata { |
506 | // // Get the file size if It's a blob |
507 | // let object = entry.to_object(&git).unwrap(); |
508 | // if let Some(blob) = object.as_blob() { |
509 | // tree_entry.size = Some(blob.size()); |
510 | // } |
511 | |
512 | // // Could possibly be done better |
513 | // let path = if let Some(path) = current_path.split_once('/') { |
514 | // format!("{}/{}", path.1, entry.name().unwrap()) |
515 | // } else { |
516 | // entry.name().unwrap().to_string() |
517 | // }; |
518 | |
519 | // // Get the last commit made to the entry |
520 | // if let Ok(last_commit) = |
521 | // GitBackend::get_last_commit_of_file(&path, &git, commit) |
522 | // { |
523 | // tree_entry.last_commit = Some(last_commit); |
524 | // } |
525 | // } |
526 | |
527 | // tree_entry |
528 | // }) |
529 | // .collect::<Vec<RepositoryTreeEntry>>(); |
530 | |
531 | // // Sort the tree alphabetically and with tree first |
532 | // tree.sort_unstable_by_key(|entry| entry.name.to_lowercase()); |
533 | // tree.sort_unstable_by_key(|entry| { |
534 | // std::cmp::Reverse(format!("{:?}", entry.object_type).to_lowercase()) |
535 | // }); |
536 | |
537 | // Ok(RepositoryView { |
538 | // name: repository.name, |
539 | // owner: request.repository.owner.clone(), |
540 | // description: repository.description, |
541 | // visibility: repository.visibility, |
542 | // default_branch: repository.default_branch, |
543 | // latest_commit: Some(Commit::from(commit.clone())), |
544 | // tree_rev: Some(rev_name), |
545 | // tree, |
546 | // }) |
547 | // } |
548 | |
549 | async |
550 | &mut self, |
551 | requester: , |
552 | repository: &Repository, |
553 | request: &RepositoryFileInspectRequest, |
554 | |
555 | let git = self |
556 | .open_repository_and_check_permissions |
557 | .await?; |
558 | |
559 | // Try and parse the input as a reference and get the object ID |
560 | let mut tree_id = match &request.rev |
561 | None => |
562 | if let Ok = git.head |
563 | // TODO: Fix for symbolic references |
564 | head.target |
565 | else |
566 | // Nothing in database, render empty tree. |
567 | return Ok; |
568 | |
569 | |
570 | Some => |
571 | // Find the reference, otherwise return GitBackendError |
572 | git.refname_to_id .ok |
573 | |
574 | ; |
575 | |
576 | // If the reference wasn't found, try parsing it as a commit ID |
577 | if tree_id.is_none |
578 | if let Ok = from_str |
579 | tree_id = Some |
580 | |
581 | |
582 | |
583 | // If the commit ID wasn't found, try parsing it as a branch and otherwise return error |
584 | if tree_id.is_none |
585 | match git.find_branch |
586 | Ok => tree_id = branch.get .target, |
587 | Err => |
588 | return Err |
589 | request.rev.clone .unwrap, |
590 | |
591 | .into |
592 | |
593 | |
594 | |
595 | |
596 | // unwrap might be dangerous? |
597 | // Get the commit from the oid |
598 | let commit = git.find_commit .unwrap; |
599 | |
600 | // this is stupid |
601 | let mut current_path = request.rev.clone .unwrap_or_else; |
602 | |
603 | // Get the commit tree |
604 | let git_tree = if let Some = &request.path |
605 | // Add it to our full path string |
606 | current_path.push_str; |
607 | // Get the specified path, return an error if it wasn't found. |
608 | let entry = match commit |
609 | .tree |
610 | .unwrap |
611 | .get_path |
612 | .map_err |
613 | |
614 | Ok => entry, |
615 | Err => return Err, |
616 | ; |
617 | // Turn the entry into a git tree |
618 | entry.to_object .unwrap .as_tree .unwrap .clone |
619 | else |
620 | commit.tree .unwrap |
621 | ; |
622 | |
623 | // Iterate over the git tree and collect it into our own tree types |
624 | let mut tree = git_tree |
625 | .iter |
626 | .map |
627 | let object_type = match entry.kind .unwrap |
628 | => Tree, | Tree
629 | => Blob, | Blob
630 | _ => unreachable!, |
631 | ; |
632 | let mut tree_entry = new |
633 | entry.id .to_string .as_str, |
634 | entry.name .unwrap, |
635 | object_type, |
636 | entry.filemode, |
637 | ; |
638 | |
639 | if request.extra_metadata |
640 | // Get the file size if It's a blob |
641 | let object = entry.to_object .unwrap; |
642 | if let Some = object.as_blob |
643 | tree_entry.size = Some; |
644 | |
645 | |
646 | // Could possibly be done better |
647 | let path = if let Some = current_path.split_once |
648 | format! |
649 | else |
650 | entry.name .unwrap .to_string |
651 | ; |
652 | |
653 | // Get the last commit made to the entry |
654 | if let Ok = |
655 | get_last_commit_of_file |
656 | |
657 | tree_entry.last_commit = Some; |
658 | |
659 | |
660 | |
661 | tree_entry |
662 | |
663 | .; |
664 | |
665 | // Sort the tree alphabetically and with tree first |
666 | tree.sort_unstable_by_key; |
667 | tree.sort_unstable_by_key |
668 | Reverse |
669 | ; |
670 | |
671 | Ok |
672 | |
673 | |
674 | async |
675 | &mut self, |
676 | requester: , |
677 | repository: &Repository, |
678 | request: &RepositoryFileFromIdRequest, |
679 | |
680 | let git = self |
681 | .open_repository_and_check_permissions |
682 | .await?; |
683 | |
684 | // Parse the passed object id |
685 | let oid = match from_str |
686 | Ok => oid, |
687 | Err => |
688 | return Err |
689 | |
690 | ; |
691 | |
692 | // Find the file and turn it into our own struct |
693 | let file = match git.find_blob |
694 | Ok => RepositoryFile |
695 | content: blob.content .to_vec, |
696 | binary: blob.is_binary, |
697 | size: blob.size, |
698 | , |
699 | Err => return Err, |
700 | ; |
701 | |
702 | Ok |
703 | |
704 | |
705 | async |
706 | &mut self, |
707 | requester: , |
708 | repository: &Repository, |
709 | request: &RepositoryDiffRequest, |
710 | |
711 | let git = self |
712 | .open_repository_and_check_permissions |
713 | .await?; |
714 | |
715 | // Parse the passed object ids |
716 | let oid_old = match from_str |
717 | Ok => oid, |
718 | Err => |
719 | return Err |
720 | Box new .into, |
721 | |
722 | |
723 | ; |
724 | let oid_new = match from_str |
725 | Ok => oid, |
726 | Err => |
727 | return Err |
728 | Box new .into, |
729 | |
730 | |
731 | ; |
732 | |
733 | // Get the trees of those object ids |
734 | let tree_old = match git.find_tree |
735 | Ok => tree, |
736 | Err => |
737 | return Err |
738 | |
739 | ; |
740 | let tree_new = match git.find_tree |
741 | Ok => tree, |
742 | Err => |
743 | return Err |
744 | |
745 | ; |
746 | |
747 | // Diff the two trees against each other |
748 | let diff = match git.diff_tree_to_tree |
749 | Ok => diff, |
750 | Err => |
751 | return Err |
752 | oid_old.to_string, |
753 | oid_new.to_string, |
754 | |
755 | .into |
756 | |
757 | ; |
758 | |
759 | // Should be safe to unwrap? |
760 | let stats = diff.stats .unwrap; |
761 | |
762 | // Honestly not quite sure what is going on here, could not find documentation. |
763 | // Print the entire patch |
764 | let mut patch = String new; |
765 | |
766 | diff.print |
767 | patch.push_str; |
768 | true |
769 | |
770 | .unwrap; |
771 | |
772 | Ok |
773 | files_changed: stats.files_changed, |
774 | insertions: stats.insertions, |
775 | deletions: stats.deletions, |
776 | patch, |
777 | |
778 | |
779 | |
780 | async |
781 | &mut self, |
782 | requester: , |
783 | repository: &Repository, |
784 | request: &RepositoryCommitBeforeRequest, |
785 | |
786 | let git = self |
787 | .open_repository_and_check_permissions |
788 | .await?; |
789 | |
790 | // Parse the passed object id |
791 | let oid = match from_str |
792 | Ok => oid, |
793 | Err => |
794 | return Err |
795 | |
796 | ; |
797 | |
798 | // Find the commit using the parsed oid |
799 | let commit = match git.find_commit |
800 | Ok => commit, |
801 | Err => return Err, |
802 | ; |
803 | |
804 | // Get the first parent it has |
805 | let parent = match commit.parent |
806 | Ok => from, |
807 | Err => |
808 | return Err |
809 | |
810 | ; |
811 | |
812 | Ok |
813 | |
814 | |
815 | |
816 | |
817 | |
818 | &mut self, |
819 | _requester: , |
820 | _request: &RepositoryIssuesCountRequest, |
821 | |
822 | todo! |
823 | |
824 | |
825 | |
826 | &mut self, |
827 | _requester: , |
828 | _request: &RepositoryIssueLabelsRequest, |
829 | |
830 | todo! |
831 | |
832 | |
833 | |
834 | &mut self, |
835 | _requester: , |
836 | _request: &RepositoryIssuesRequest, |
837 | |
838 | todo! |
839 | |
840 | |
841 | |
842 | |
843 | |
844 | pub repository: String, |
845 | pub name: String, |
846 | pub value: String, |
847 | |
848 |