Repository: brocode/fw (language: rust)

Issue:
Idea: Bring your own tags
Enable repositories to bring their own labels in the form of a `fw.toml` file:

```toml
tags = ["nvm", "project-a"]
```

Don't know yet if we should add the predefined tags on each sync or just on the first sync.

This would make porting tags a lot easier. The `fw.toml` file could be used for other things in the future.


Discussion / hints:
Any thoughts on the idea?
Yeah this is a good idea but it has some annoying edge cases I'm not sure how to solve.

When to add the tag is one of them (should probably happen on sync but dealing with incoming deletes is super gnarly).

It's also somewhat of a security concern I think that a git pull might run code on your machine (after_clone or after_workon).
But I agree there are big benefits if tags are "crowd sourced"/centralized

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 159fddbd..47c86070 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -17,6 +17,15 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "android_system_properties"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "ansi_term"
 version = "0.12.1"
@@ -113,15 +122,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "chrono"
-version = "0.4.19"
+version = "0.4.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+checksum = "3f725f340c3854e3cb3ab736dc21f0cca183303acea3b3ffec30f141503ac8eb"
 dependencies = [
- "libc",
+ "iana-time-zone",
+ "js-sys",
  "num-integer",
  "num-traits",
  "serde",
  "time 0.1.44",
+ "wasm-bindgen",
  "winapi",
 ]
 
@@ -783,6 +794,19 @@ dependencies = [
  "tokio-native-tls",
 ]
 
+[[package]]
+name = "iana-time-zone"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf7d67cf4a22adc5be66e75ebdf769b3f2ea032041437a7061f97a63dad4b"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "js-sys",
+ "wasm-bindgen",
+ "winapi",
+]
+
 [[package]]
 name = "ident_case"
 version = "1.0.1"
@@ -847,9 +871,9 @@ dependencies = [
 
 [[package]]
 name = "itoa"
-version = "1.0.2"
+version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
+checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
 
 [[package]]
 name = "jobserver"
@@ -877,9 +901,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.126"
+version = "0.2.131"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
+checksum = "04c3b4822ccebfa39c02fc03d1534441b22ead323fa0f48bb7ddd8e6ba076a40"
 
 [[package]]
 name = "libflate"
@@ -1282,18 +1306,18 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.42"
+version = "1.0.43"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b"
+checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.20"
+version = "1.0.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
+checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
 dependencies = [
  "proc-macro2",
 ]
@@ -1527,24 +1551,24 @@ dependencies = [
 
 [[package]]
 name = "rustls-pemfile"
-version = "1.0.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9"
+checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
 dependencies = [
  "base64",
 ]
 
 [[package]]
 name = "rustversion"
-version = "1.0.8"
+version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24c8ad4f0c00e1eb5bc7614d236a7f1300e3dbd76b68cac8e06fb00b015ad8d8"
+checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
 
 [[package]]
 name = "ryu"
-version = "1.0.10"
+version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
+checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
 
 [[package]]
 name = "same-file"
@@ -1624,18 +1648,18 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.140"
+version = "1.0.143"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03"
+checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.140"
+version = "1.0.143"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da"
+checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1644,9 +1668,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.82"
+version = "1.0.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
+checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
 dependencies = [
  "itoa",
  "ryu",
@@ -1743,7 +1767,7 @@ dependencies = [
  "slog",
  "term",
  "thread_local",
- "time 0.3.12",
+ "time 0.3.13",
 ]
 
 [[package]]
@@ -1808,9 +1832,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
 
 [[package]]
 name = "syn"
-version = "1.0.98"
+version = "1.0.99"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
+checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1875,18 +1899,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
 
 [[package]]
 name = "thiserror"
-version = "1.0.31"
+version = "1.0.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
+checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.31"
+version = "1.0.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
+checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1915,12 +1939,11 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.3.12"
+version = "0.3.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74b7cc93fc23ba97fde84f7eea56c55d1ba183f495c6715defdfc7b9cb8c870f"
+checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45"
 dependencies = [
  "itoa",
- "js-sys",
  "libc",
  "num_threads",
  "time-macros",
@@ -2088,9 +2111,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.2"
+version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
+checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
 
 [[package]]
 name = "unicode-normalization"
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 52206d61..f7b9daf3 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -189,7 +189,8 @@ For further information please have a look at our README https://github.com/broc
             .long("after-clone")
             .takes_value(true)
             .required(false),
-        ),
+        )
+        .arg(Arg::new("trusted").long("trusted").takes_value(false).required(false)),
     )
     .subcommand(
       Command::new("remove")
diff --git a/src/config/metadata_from_repository.rs b/src/config/metadata_from_repository.rs
new file mode 100644
index 00000000..3f53bd8f
--- /dev/null
+++ b/src/config/metadata_from_repository.rs
@@ -0,0 +1,8 @@
+use std::collections::BTreeSet;
+
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct MetadataFromRepository {
+  pub tags: Option<BTreeSet<String>>,
+}
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 51cebae1..5f7163e8 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -9,6 +9,7 @@ use walkdir::WalkDir;
 
 static CONF_MODE_HEADER: &str = "# -*- mode: Conf; -*-\n";
 
+pub mod metadata_from_repository;
 mod path;
 pub mod project;
 pub mod settings;
@@ -373,6 +374,7 @@ mod tests {
       override_path: None,
       additional_remotes: None,
       bare: None,
+      trusted: false,
       project_config_path: "".to_string(),
     };
     let project2 = Project {
@@ -384,6 +386,7 @@ mod tests {
       override_path: None,
       additional_remotes: None,
       bare: None,
+      trusted: false,
       project_config_path: "".to_string(),
     };
     let project3 = Project {
@@ -395,6 +398,7 @@ mod tests {
       override_path: None,
       additional_remotes: None,
       bare: None,
+      trusted: false,
       project_config_path: "".to_string(),
     };
     let project4 = Project {
@@ -406,6 +410,7 @@ mod tests {
       override_path: None,
       additional_remotes: None,
       bare: None,
+      trusted: false,
       project_config_path: "".to_string(),
     };
     let project5 = Project {
@@ -417,6 +422,7 @@ mod tests {
       override_path: None,
       additional_remotes: None,
       bare: None,
+      trusted: false,
       project_config_path: "".to_string(),
     };
     let tag1 = Tag {
diff --git a/src/config/project.rs b/src/config/project.rs
index 7468adb8..1c824c54 100644
--- a/src/config/project.rs
+++ b/src/config/project.rs
@@ -13,6 +13,9 @@ pub struct Project {
   #[serde(skip)]
   pub name: String,
 
+  #[serde(default)]
+  pub trusted: bool,
+
   pub git: String,
   pub after_clone: Option<String>,
   pub after_workon: Option<String>,
@@ -39,6 +42,7 @@ impl Project {
         git: "git@...".to_string(),
       }]),
       bare: Some(false),
+      trusted: false,
       project_config_path: "".to_string(), // ignored
     }
   }
diff --git a/src/main.rs b/src/main.rs
index 4123fa8f..d1c14baa 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -69,7 +69,8 @@ fn _main() -> i32 {
       let tags: Option<BTreeSet<String>> = subcommand_matches
         .get_many::<String>("tag")
         .map(|v| v.into_iter().map(ToOwned::to_owned).collect());
-      project::add_entry(config, name, url, after_workon, after_clone, override_path, tags, &subcommand_logger)
+      let trusted = subcommand_matches.contains_id("trusted");
+      project::add_entry(config, name, url, after_workon, after_clone, override_path, tags, trusted, &subcommand_logger)
     }
     "remove" => project::remove_project(
       config,
diff --git a/src/project/mod.rs b/src/project/mod.rs
index 12284bc9..1126a355 100644
--- a/src/project/mod.rs
+++ b/src/project/mod.rs
@@ -17,6 +17,7 @@ pub fn add_entry(
   after_clone: Option<String>,
   override_path: Option<String>,
   tags: Option<BTreeSet<String>>,
+  trusted: bool,
   logger: &Logger,
 ) -> Result<(), AppError> {
   let name = maybe_name
@@ -48,6 +49,7 @@ pub fn add_entry(
       tags: project_tags,
       bare: None,
       additional_remotes: None,
+      trusted,
       project_config_path: "default".to_string(),
     })?;
     Ok(())
@@ -140,6 +142,7 @@ pub fn update_entry(
       override_path: override_path.or(old_project_config.override_path),
       tags: old_project_config.tags,
       bare: old_project_config.bare,
+      trusted: old_project_config.trusted,
       additional_remotes: old_project_config.additional_remotes,
       project_config_path: old_project_config.project_config_path,
     })?;
diff --git a/src/setup/mod.rs b/src/setup/mod.rs
index e19ed0fd..df5e47ac 100644
--- a/src/setup/mod.rs
+++ b/src/setup/mod.rs
@@ -145,6 +145,7 @@ pub fn gitlab_import(maybe_config: Result<Config, AppError>, state: ProjectState
       tags: tags.clone(),
       additional_remotes: None,
       bare: None,
+      trusted: false,
       project_config_path: "gitlab".to_string(),
     };
 
@@ -190,6 +191,7 @@ pub fn org_import(maybe_config: Result<Config, AppError>, org_name: &str, includ
       tags: tags.clone(),
       additional_remotes: None,
       bare: None,
+      trusted: false,
       project_config_path: org_name.to_string(),
     };
 
@@ -240,6 +242,7 @@ fn load_project(maybe_settings: Option<Settings>, path_to_repo: PathBuf, name: &
     additional_remotes: None, // TODO: use remotes
     tags: maybe_settings.and_then(|s| s.default_tags),
     bare: None,
+    trusted: false,
     project_config_path: "default".to_string(),
   })
 }
diff --git a/src/sync/mod.rs b/src/sync/mod.rs
index c23c92a0..a5dbad19 100644
--- a/src/sync/mod.rs
+++ b/src/sync/mod.rs
@@ -1,6 +1,10 @@
+use crate::config;
+use crate::config::metadata_from_repository::MetadataFromRepository;
 use crate::config::{project::Project, Config};
 use crate::errors::AppError;
 use std::collections::BTreeSet;
+use std::fs::read_to_string;
+use std::path::Path;
 use std::time::Duration;
 
 use crate::git::{clone_project, update_project_remotes};
@@ -32,14 +36,36 @@ fn sync_project(config: &Config, project: &Project, logger: &Logger, only_new: b
     if only_new {
       Ok(())
     } else {
-      update_project_remotes(project, &path, &project_logger, ff_merge)
+      update_project_remotes(project, &path, &project_logger, ff_merge).and_then(|_| synchronize_metadata_if_trusted(project, &path))
     }
   } else {
-    clone_project(config, project, &path, &project_logger)
+    clone_project(config, project, &path, &project_logger).and_then(|_| synchronize_metadata_if_trusted(project, &path))
   };
   result.map_err(|e| AppError::RuntimeError(format!("Failed to sync {}: {}", project.name, e)))
 }
 
+pub fn synchronize_metadata_if_trusted(project: &Project, path: &Path) -> Result<(), AppError> {
+  if !project.trusted {
+    Ok(())
+  } else {
+    let metadata_file = path.join("fw.toml");
+
+    if metadata_file.exists() {
+      let content = read_to_string(metadata_file)?;
+      let metadata_from_repository = toml::from_str::<MetadataFromRepository>(&content)?;
+
+      let new_project = Project {
+        tags: metadata_from_repository.tags,
+        ..project.to_owned()
+      };
+
+      config::write_project(&new_project)
+    } else {
+      Ok(())
+    }
+  }
+}
+
 pub fn synchronize(
   maybe_config: Result<Config, AppError>,
   no_progress_bar: bool,


Test patch: