soma/repository/
backend.rs1use std::fmt::{self, Display};
2use std::path::{Path, PathBuf};
3
4use fs_extra::dir;
5use git2::{BranchType, ObjectType, Repository as GitRepository, ResetType};
6use remove_dir_all::remove_dir_all;
7use serde::{Deserialize, Serialize};
8use url::Url;
9
10use crate::prelude::*;
11
12#[typetag::serde(tag = "type")]
13pub trait Backend: BackendClone + Display {
14 fn update_at_path(&self, local_path: &Path) -> SomaResult<()>;
15}
16
17pub trait BackendClone {
18 fn clone_box(&self) -> Box<dyn Backend>;
19}
20
21impl<T> BackendClone for T
22where
23 T: 'static + Backend + Clone,
24{
25 fn clone_box(&self) -> Box<dyn Backend> {
26 Box::new(self.clone())
27 }
28}
29
30impl Clone for Box<dyn Backend> {
31 fn clone(&self) -> Box<dyn Backend> {
32 self.clone_box()
33 }
34}
35
36pub trait BackendExt: Backend {
37 fn update_at(&self, local_path: impl AsRef<Path>) -> SomaResult<()> {
38 self.update_at_path(local_path.as_ref())
39 }
40}
41
42impl<T> BackendExt for T where T: ?Sized + Backend {}
43
44pub fn location_to_backend(repo_location: &str) -> SomaResult<(String, Box<dyn Backend>)> {
45 let path = Path::new(repo_location);
46 if path.is_dir() {
47 Ok((
49 path.file_name()
50 .ok_or(SomaError::FileNameNotFound)?
51 .to_str()
52 .ok_or(SomaError::InvalidUnicode)?
53 .to_lowercase(),
54 Box::new(LocalBackend::new(path.canonicalize()?.to_owned())),
55 ))
56 } else {
57 let parsed_url = Url::parse(repo_location).or(Err(SomaError::RepositoryNotFound))?;
59 let last_name = parsed_url
60 .path_segments()
61 .ok_or(SomaError::RepositoryNotFound)?
62 .last()
63 .ok_or(SomaError::FileNameNotFound)?;
64 let repo_name = if last_name.ends_with(".git") {
65 &last_name[..last_name.len() - 4]
66 } else {
67 &last_name
68 };
69 Ok((
70 repo_name.to_lowercase(),
71 Box::new(GitBackend::new(repo_location.to_owned())),
72 ))
73 }
74}
75
76#[derive(Clone, Deserialize, Serialize)]
77pub struct GitBackend {
78 url: String,
79}
80
81impl GitBackend {
82 pub fn new(url: String) -> Self {
83 GitBackend { url }
84 }
85}
86
87#[typetag::serde]
88impl Backend for GitBackend {
89 fn update_at_path(&self, local_path: &Path) -> SomaResult<()> {
90 let git_repo = GitRepository::open(local_path)
91 .or_else(|_| GitRepository::clone(&self.url, local_path))?;
92 git_repo
93 .find_remote("origin")?
94 .fetch(&["master"], None, None)?;
95
96 let origin_master = git_repo.find_branch("origin/master", BranchType::Remote)?;
97 let head_commit = origin_master.get().peel(ObjectType::Commit)?;
98 git_repo.reset(&head_commit, ResetType::Hard, None)?;
99
100 Ok(())
101 }
102}
103
104impl Display for GitBackend {
105 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106 write!(f, "Git: {}", &self.url)
107 }
108}
109
110#[derive(Clone, Deserialize, Serialize)]
113pub struct LocalBackend {
114 origin: PathBuf,
115}
116
117impl LocalBackend {
118 pub fn new(origin: PathBuf) -> Self {
119 LocalBackend { origin }
120 }
121}
122
123#[typetag::serde]
124impl Backend for LocalBackend {
125 fn update_at_path(&self, local_path: &Path) -> SomaResult<()> {
126 if local_path.exists() {
127 remove_dir_all(local_path)?;
128 }
129
130 let mut copy_options = dir::CopyOptions::new();
131 copy_options.copy_inside = true;
132 dir::copy(&self.origin, local_path, ©_options)?;
133
134 Ok(())
135 }
136}
137
138impl Display for LocalBackend {
139 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
140 write!(f, "Local: {}", self.origin.to_string_lossy())
141 }
142}