JavaScript is disabled, refresh for a better experience. ambee/giterated

ambee/giterated

Git repository hosting, collaboration, and discovery for the Fediverse.

Remove unneeded logs

Amber - ⁨2⁩ years ago

parent: tbd commit: ⁨cfba404

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