diff --git a/Cargo.lock b/Cargo.lock index ebd6ccc..b414955 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,12 +29,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" - [[package]] name = "aho-corasick" version = "0.7.18" @@ -59,6 +53,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "async-compression" version = "0.3.12" @@ -197,7 +197,7 @@ checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ "libc", "num-integer", - "num-traits", + "num-traits 0.2.14", "winapi", ] @@ -225,23 +225,26 @@ dependencies = [ [[package]] name = "config" -version = "0.12.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ad70579325f1a38ea4c13412b82241c5900700a69785d73e2736bd65a33f86" +checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" dependencies = [ - "async-trait", - "json5", "lazy_static", "nom", - "pathdiff", - "ron", "rust-ini", - "serde", + "serde 1.0.136", + "serde-hjson", "serde_json", "toml", "yaml-rust", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation" version = "0.9.3" @@ -283,6 +286,19 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "deunicode" version = "0.4.3" @@ -298,15 +314,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "dlv-list" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68df3f2b690c1b86e65ef7830956aededf3cb0a16f898f79b9a6f421a7b6211b" -dependencies = [ - "rand", -] - [[package]] name = "encoding_rs" version = "0.8.30" @@ -495,15 +502,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "hashbrown" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.11.2" @@ -637,6 +635,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + [[package]] name = "ignore" version = "0.4.18" @@ -662,7 +666,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ "autocfg", - "hashbrown 0.11.2", + "hashbrown", ] [[package]] @@ -707,23 +711,25 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json5" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" -dependencies = [ - "pest", - "pest_derive", - "serde", -] - [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.121" @@ -793,12 +799,6 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.4.4" @@ -852,12 +852,13 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.1" +version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" dependencies = [ + "lexical-core", "memchr", - "minimal-lexical", + "version_check", ] [[package]] @@ -876,7 +877,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg", - "num-traits", + "num-traits 0.2.14", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.14", ] [[package]] @@ -943,16 +953,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "ordered-multimap" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c672c7ad9ec066e428c00eb917124a06f08db19e2584de982cc34b1f4c12485" -dependencies = [ - "dlv-list", - "hashbrown 0.9.1", -] - [[package]] name = "parking_lot" version = "0.12.0" @@ -985,12 +985,6 @@ dependencies = [ "regex", ] -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - [[package]] name = "percent-encoding" version = "2.1.0" @@ -1103,6 +1097,30 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.36" @@ -1221,7 +1239,7 @@ dependencies = [ "rustls", "rustls-native-certs", "rustls-pemfile 0.3.0", - "serde", + "serde 1.0.136", "serde_json", "serde_urlencoded", "tokio", @@ -1261,24 +1279,18 @@ dependencies = [ ] [[package]] -name = "ron" -version = "0.7.0" +name = "rust-ini" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b861ecaade43ac97886a512b360d01d66be9f41f3c61088b42cedf92e03d678" -dependencies = [ - "base64", - "bitflags", - "serde", -] +checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] -name = "rust-ini" -version = "0.17.0" +name = "rustc_version" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63471c4aa97a1cf8332a5f97709a79a4234698de6a1f5087faf66f2dae810e22" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "cfg-if", - "ordered-multimap", + "semver", ] [[package]] @@ -1387,6 +1399,18 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4" + +[[package]] +name = "serde" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" + [[package]] name = "serde" version = "1.0.136" @@ -1396,6 +1420,18 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-hjson" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" +dependencies = [ + "lazy_static", + "num-traits 0.1.43", + "regex", + "serde 0.8.23", +] + [[package]] name = "serde_derive" version = "1.0.136" @@ -1415,7 +1451,7 @@ checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ "itoa", "ryu", - "serde", + "serde 1.0.136", ] [[package]] @@ -1427,7 +1463,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde", + "serde 1.0.136", ] [[package]] @@ -1500,17 +1536,26 @@ version = "0.1.0" dependencies = [ "actix-rt", "config", + "derive_more", "lazy_static", + "log", "rand", "reqwest", - "serde", + "serde 1.0.136", "serde_json", "tera", "tokio", "trust-dns-resolver", "url", + "validator", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "1.0.89" @@ -1552,7 +1597,7 @@ dependencies = [ "pest_derive", "rand", "regex", - "serde", + "serde 1.0.136", "serde_json", "slug", "unic-segment", @@ -1662,7 +1707,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ - "serde", + "serde 1.0.136", ] [[package]] @@ -1859,6 +1904,49 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "validator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0f08911ab0fee2c5009580f04615fa868898ee57de10692a45da0c3bcc3e5e" +dependencies = [ + "idna", + "lazy_static", + "regex", + "serde 1.0.136", + "serde_derive", + "serde_json", + "url", + "validator_derive", + "validator_types", +] + +[[package]] +name = "validator_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85135714dba11a1bd0b3eb1744169266f1a38977bf4e3ff5e2e1acb8c2b7eee" +dependencies = [ + "if_chain", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn", + "validator_types", +] + +[[package]] +name = "validator_types" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded9d97e1d42327632f5f3bae6403c04886e2de3036261ef42deebd931a6a291" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/config/default.toml b/config/default.toml new file mode 100644 index 0000000..a727feb --- /dev/null +++ b/config/default.toml @@ -0,0 +1,36 @@ +log = "info" # possible values: "info", "warn", "trace", "error", "debug" +source_code = "https://github.com/forgeflux-org/starchart" +allow_new_index = true # allow registration on server +admin_email = "admin@starchart.example.com" + +[server] +# The port at which you want authentication to listen to +# takes a number, choose from 1000-10000 if you dont know what you are doing +port = 7000 +#IP address. Enter 0.0.0.0 to listen on all availale addresses +ip= "0.0.0.0" +# enter your hostname, eg: example.com +domain = "localhost" +proxy_has_tls = false +cookie_secret = "f12d9adf4e364648664442b8f50bf478e748e1d77c4797b2ec1f56803278" +#workers = 2 + +[database] +# This section deals with the database location and how to access it +# Please note that at the moment, we have support for only postgresqa. +# Example, if you are Batman, your config would be: +# hostname = "batcave.org" +# port = "5432" +# username = "batman" +# password = "somereallycomplicatedBatmanpassword" +hostname = "localhost" +port = "5432" +username = "postgres" +password = "password" +name = "postgres" +pool = 4 +database_type = "postgres" + + +[repository] +root = "/tmp/starchart.batsense.net" diff --git a/src/main.rs b/src/main.rs index 91414f9..9aec316 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,11 +16,13 @@ * along with this program. If not, see . */ pub mod gitea; +pub mod settings; pub mod utils; pub mod verify; pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const PKG_NAME: &str = env!("CARGO_PKG_NAME"); +pub const GIT_COMMIT_HASH: &str = env!("GIT_HASH"); pub const DOMAIN: &str = "developer-starchart.forgeflux.org"; #[actix_rt::main] diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000..c216042 --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,330 @@ +/* + * ForgeFlux StarChart - A federated software forge spider + * Copyright (C) 2022 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +use std::path::Path; +use std::{env, fs}; + +use config::{Config, ConfigError, Environment, File}; +use derive_more::Display; +use log::warn; +use serde::Deserialize; +use url::Url; +use validator::Validate; + +#[derive(Debug, Clone, Deserialize)] +pub struct Server { + pub port: u32, + pub domain: String, + pub ip: String, + pub proxy_has_tls: bool, +} + +impl Server { + #[cfg(not(tarpaulin_include))] + pub fn get_ip(&self) -> String { + format!("{}:{}", self.ip, self.port) + } +} + +#[derive(Deserialize, Display, Clone, Debug)] +#[serde(rename_all = "lowercase")] +pub enum LogLevel { + #[display(fmt = "debug")] + Debug, + #[display(fmt = "info")] + Info, + #[display(fmt = "trace")] + Trace, + #[display(fmt = "error")] + Error, + #[display(fmt = "warn")] + Warn, +} + +impl LogLevel { + fn set_log_level(&self) { + const LOG_VAR: &str = "RUST_LOG"; + if env::var(LOG_VAR).is_err() { + env::set_var("RUST_LOG", format!("{}", self)); + } + } +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Repository { + pub root: String, +} + +impl Repository { + fn create_root_dir(&self) { + let root = Path::new(&self.root); + if root.exists() { + if !root.is_dir() { + fs::remove_file(&root).unwrap(); + fs::create_dir_all(&root).unwrap(); + } + } else { + fs::create_dir_all(&root).unwrap(); + } + } +} + +#[derive(Deserialize, Display, PartialEq, Clone, Debug)] +#[serde(rename_all = "lowercase")] +pub enum DBType { + #[display(fmt = "postgres")] + Postgres, + #[display(fmt = "sqlite")] + Sqlite, +} + +impl DBType { + fn from_url(url: &Url) -> Result { + match url.scheme() { + "sqlite" => Ok(Self::Sqlite), + "postgres" => Ok(Self::Postgres), + _ => Err(ConfigError::Message("Unknown database type".into())), + } + } +} + +#[derive(Debug, Clone, Deserialize)] +struct DatabaseBuilder { + pub port: u32, + pub hostname: String, + pub username: String, + pub password: String, + pub name: String, + pub database_type: DBType, +} + +impl DatabaseBuilder { + #[cfg(not(tarpaulin_include))] + fn extract_database_url(url: &Url) -> Self { + log::debug!("Databse name: {}", url.path()); + let mut path = url.path().split('/'); + path.next(); + let name = path.next().expect("no database name").to_string(); + DatabaseBuilder { + port: url.port().expect("Enter database port").into(), + hostname: url.host().expect("Enter database host").to_string(), + username: url.username().into(), + password: url.password().expect("Enter database password").into(), + name, + database_type: DBType::from_url(url).unwrap(), + } + } +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Database { + pub url: String, + pub pool: u32, + pub database_type: DBType, +} + +#[derive(Debug, Validate, Clone, Deserialize)] +pub struct Settings { + pub log: LogLevel, + pub database: Database, + pub allow_new_index: bool, + pub server: Server, + #[validate(url)] + pub source_code: String, + pub repository: Repository, + #[validate(email)] + pub admin_email: String, +} + +#[cfg(not(tarpaulin_include))] +impl Settings { + fn set_source_code(&mut self) { + if !self.source_code.ends_with('/') { + self.source_code.push('/'); + } + let mut base = url::Url::parse(&self.source_code).unwrap(); + base = base.join("tree/").unwrap(); + base = base.join(crate::GIT_COMMIT_HASH).unwrap(); + self.source_code = base.into(); + } + + pub fn new() -> Result { + let mut s = Config::new(); + + // setting default values + #[cfg(test)] + s.set_default("database.pool", 2.to_string()) + .expect("Couldn't get the number of CPUs"); + + const CURRENT_DIR: &str = "./config/default.toml"; + const ETC: &str = "/etc/starchart/config.toml"; + + if let Ok(path) = env::var("STARCHART_CONFIG") { + s.merge(File::with_name(&path))?; + } else if Path::new(CURRENT_DIR).exists() { + // merging default config from file + s.merge(File::with_name(CURRENT_DIR))?; + } else if Path::new(ETC).exists() { + s.merge(File::with_name(ETC))?; + } else { + log::warn!("configuration file not found"); + } + + s.merge(Environment::with_prefix("STARCHART").separator("__"))?; + + check_url(&s); + + match env::var("PORT") { + Ok(val) => { + s.set("server.port", val).unwrap(); + } + Err(e) => warn!("couldn't interpret PORT: {}", e), + } + + match env::var("DATABASE_URL") { + Ok(val) => { + let url = Url::parse(&val).expect("couldn't parse Database URL"); + let database_conf = DatabaseBuilder::extract_database_url(&url); + set_from_database_url(&mut s, &database_conf); + } + Err(e) => warn!("couldn't interpret DATABASE_URL: {}", e), + } + + set_database_url(&mut s); + + let mut settings: Settings = s.try_into()?; + + settings.log.set_log_level(); + settings.repository.create_root_dir(); + settings.validate().unwrap(); + settings.set_source_code(); + settings.validate().unwrap(); + + Ok(settings) + } +} + +#[cfg(not(tarpaulin_include))] +fn check_url(s: &Config) { + let url = s + .get::("source_code") + .expect("Couldn't access source_code"); + + Url::parse(&url).expect("Please enter a URL for source_code in settings"); +} + +#[cfg(not(tarpaulin_include))] +fn set_from_database_url(s: &mut Config, database_conf: &DatabaseBuilder) { + s.set("database.username", database_conf.username.clone()) + .expect("Couldn't set database username"); + s.set("database.password", database_conf.password.clone()) + .expect("Couldn't access database password"); + s.set("database.hostname", database_conf.hostname.clone()) + .expect("Couldn't access database hostname"); + s.set("database.port", database_conf.port as i64) + .expect("Couldn't access database port"); + s.set("database.name", database_conf.name.clone()) + .expect("Couldn't access database name"); + s.set( + "database.database_type", + format!("{}", database_conf.database_type), + ) + .expect("Couldn't access database type"); +} + +#[cfg(not(tarpaulin_include))] +fn set_database_url(s: &mut Config) { + s.set( + "database.url", + format!( + r"{}://{}:{}@{}:{}/{}", + s.get::("database.database_type") + .expect("Couldn't access database database_type"), + s.get::("database.username") + .expect("Couldn't access database username"), + s.get::("database.password") + .expect("Couldn't access database password"), + s.get::("database.hostname") + .expect("Couldn't access database hostname"), + s.get::("database.port") + .expect("Couldn't access database port"), + s.get::("database.name") + .expect("Couldn't access database name") + ), + ) + .expect("Couldn't set databse url"); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::utils::get_random; + + #[test] + fn database_type_test() { + for i in ["sqlite://foo", "postgres://bar", "unknown://"].iter() { + let url = Url::parse(i).unwrap(); + if i.contains("sqlite") { + assert_eq!(DBType::from_url(&url).unwrap(), DBType::Sqlite); + } else if i.contains("unknown") { + assert!(DBType::from_url(&url).is_err()); + } else { + assert_eq!(DBType::from_url(&url).unwrap(), DBType::Postgres); + } + } + } + + #[test] + fn root_dir_is_created_test() { + let dir; + loop { + let mut tmp = env::temp_dir(); + tmp = tmp.join(get_random(10)); + + if tmp.exists() { + continue; + } else { + dir = tmp; + break; + } + } + + let repo = Repository { + root: dir.to_str().unwrap().to_owned(), + }; + + repo.create_root_dir(); + assert!(dir.exists()); + assert!(dir.is_dir()); + let file = dir.join("foo"); + fs::write(&file, "foo").unwrap(); + repo.create_root_dir(); + assert!(dir.exists()); + assert!(dir.is_dir()); + + assert!(file.exists()); + assert!(file.is_file()); + + let repo = Repository { + root: file.to_str().unwrap().to_owned(), + }; + + repo.create_root_dir(); + assert!(file.exists()); + assert!(file.is_dir()); + } +}