From 94b697e20c559e0d2199104bda2fb73830603fb5 Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Wed, 7 Feb 2024 19:34:47 +0100 Subject: [PATCH 1/7] feat(core): make a repository dir for each network This is the more sane thing to do because each network can have a different repository version. Link: https://github.com/coffee-tools/coffee/issues/234 Signed-off-by: Vincenzo Palazzo --- coffee_core/src/coffee.rs | 3 ++- coffee_core/src/config.rs | 6 ++++-- coffee_lib/src/utils.rs | 9 +++++++++ git-bugreport-2024-02-07-1353.txt | 32 +++++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 git-bugreport-2024-02-07-1353.txt diff --git a/coffee_core/src/coffee.rs b/coffee_core/src/coffee.rs index 1f0f01ca..01e05999 100644 --- a/coffee_core/src/coffee.rs +++ b/coffee_core/src/coffee.rs @@ -432,7 +432,8 @@ impl PluginManager for CoffeeManager { if self.repos.contains_key(name) { return Err(error!("repository with name: {name} already exists")); } - let url = URL::new(&self.config.root_path, url, name); + let local_path = format!("{}/{}", self.config.root_path, self.config.network); + let url = URL::new(&local_path, url, name); log::debug!("remote adding: {} {}", name, &url.url_string); let mut repo = Github::new(name, &url); repo.init().await?; diff --git a/coffee_core/src/config.rs b/coffee_core/src/config.rs index c0bb9b03..bd43eb1e 100644 --- a/coffee_core/src/config.rs +++ b/coffee_core/src/config.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use std::env; use crate::CoffeeOperation; -use coffee_lib::utils::check_dir_or_make_if_missing; +use coffee_lib::utils::{check_dir_or_make_if_missing, move_dir_if_exist}; use coffee_lib::{errors::CoffeeError, plugin::Plugin}; use crate::CoffeeArgs; @@ -65,7 +65,9 @@ impl CoffeeConf { check_dir_or_make_if_missing(format!("{def_path}/{}", coffee.network)).await?; check_dir_or_make_if_missing(format!("{def_path}/{}/plugins", coffee.network)).await?; - check_dir_or_make_if_missing(format!("{def_path}/repositories")).await?; + let repo_dir = format!("{def_path}/{}/repositories", coffee.network); + move_dir_if_exist(&format!("{def_path}/repositories"), &repo_dir).await?; + check_dir_or_make_if_missing(repo_dir).await?; // after we know all the information regarding // the configuration we try to see if there is // something stored already to the disk. diff --git a/coffee_lib/src/utils.rs b/coffee_lib/src/utils.rs index 3f46a6d5..5c5c69ed 100644 --- a/coffee_lib/src/utils.rs +++ b/coffee_lib/src/utils.rs @@ -2,6 +2,7 @@ use super::macros::error; use std::path::Path; use tokio::fs::create_dir; +use tokio::fs::rename; use crate::errors::CoffeeError; @@ -28,6 +29,14 @@ pub async fn check_dir_or_make_if_missing(path: String) -> Result<(), CoffeeErro Ok(()) } +pub async fn move_dir_if_exist(origin: &str, destination: &str) -> Result<(), CoffeeError> { + if Path::exists(Path::new(&origin)) { + rename(origin, destination).await?; + log::debug!("move dir from {origin} to {destination}"); + } + Ok(()) +} + #[cfg(test)] mod tests { use std::fs::create_dir_all; diff --git a/git-bugreport-2024-02-07-1353.txt b/git-bugreport-2024-02-07-1353.txt new file mode 100644 index 00000000..f278341c --- /dev/null +++ b/git-bugreport-2024-02-07-1353.txt @@ -0,0 +1,32 @@ +Thank you for filling out a Git bug report! +Please answer the following questions to help us understand your issue. + +What did you do before the bug happened? (Steps to reproduce your issue) + +What did you expect to happen? (Expected behavior) + +What happened instead? (Actual behavior) + +What's different between what you expected and what actually happened? + +Anything else you want to add: + +Please review the rest of the bug report below. +You can delete any lines you don't wish to share. + + +[System Info] +git version: +git version 2.43.0 +cpu: x86_64 +no commit associated with this build +sizeof-long: 8 +sizeof-size_t: 8 +shell-path: /bin/sh +uname: Linux 6.6.15-2-lts #1 SMP PREEMPT_DYNAMIC Fri, 02 Feb 2024 17:04:24 +0000 x86_64 +compiler info: gnuc: 13.2 +libc info: glibc: 2.39 +$SHELL (typically, interactive shell): /usr/bin/zsh + + +[Enabled Hooks] From a5fc3daf26836c69ae282a1931d237383a3b5c62 Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Wed, 7 Feb 2024 20:08:50 +0100 Subject: [PATCH 2/7] feat(core): nurse clean up the global repository Adding a new case for the coffe nurse command to clean up the global repository directory and move it insied the network subdirectory. Signed-off-by: Vincenzo Palazzo --- coffee_cmd/src/coffee_term/command_show.rs | 4 +++ coffee_cmd/src/main.rs | 2 +- coffee_core/src/coffee.rs | 29 +++++++++++++++----- coffee_core/src/config.rs | 6 ++-- coffee_core/src/nurse/chain.rs | 7 +++-- coffee_core/src/nurse/strategy.rs | 29 ++++++++++++++++++++ coffee_github/src/repository.rs | 4 +++ coffee_httpd/src/httpd/server.rs | 4 ++- coffee_lib/src/plugin_manager.rs | 2 +- coffee_lib/src/repository.rs | 4 +++ coffee_lib/src/types/mod.rs | 20 ++++++++++++++ coffee_lib/src/utils.rs | 19 +++++++++---- coffee_plugin/src/plugin/plugin_mod.rs | 6 +++- git-bugreport-2024-02-07-1353.txt | 32 ---------------------- 14 files changed, 115 insertions(+), 53 deletions(-) delete mode 100644 git-bugreport-2024-02-07-1353.txt diff --git a/coffee_cmd/src/coffee_term/command_show.rs b/coffee_cmd/src/coffee_term/command_show.rs index 30b6011f..09776009 100644 --- a/coffee_cmd/src/coffee_term/command_show.rs +++ b/coffee_cmd/src/coffee_term/command_show.rs @@ -111,10 +111,14 @@ pub fn show_nurse_result( NurseStatus::RepositoryLocallyRemoved(_) => { "Removed from local storage".to_string() } + NurseStatus::MovingGlobalRepostoryTo(_) => { + "Moving Global repository directory".to_string() + } }; let repos_str = match status { NurseStatus::RepositoryLocallyRestored(repos) | NurseStatus::RepositoryLocallyRemoved(repos) => repos.join(", "), + NurseStatus::MovingGlobalRepostoryTo(network) => network.to_owned(), }; table.push([ diff --git a/coffee_cmd/src/main.rs b/coffee_cmd/src/main.rs index c64106e6..a9d8c6ae 100644 --- a/coffee_cmd/src/main.rs +++ b/coffee_cmd/src/main.rs @@ -82,7 +82,7 @@ async fn run(args: CoffeeArgs, mut coffee: CoffeeManager) -> Result<(), CoffeeEr match action { Some(RemoteAction::Add { name, url }) => { let mut spinner = term::spinner(format!("Fetch remote from {url}")); - let result = coffee.add_remote(&name, &url).await; + let result = coffee.add_remote(&name, &url, false).await; if let Err(err) = &result { spinner.error(format!("Error while add remote: {err}")); return result; diff --git a/coffee_core/src/coffee.rs b/coffee_core/src/coffee.rs index 01e05999..92cdc03f 100644 --- a/coffee_core/src/coffee.rs +++ b/coffee_core/src/coffee.rs @@ -2,7 +2,6 @@ use std::collections::HashMap; use std::fmt::Debug; use std::vec::Vec; -use tokio::fs; use async_trait::async_trait; use clightningrpc_common::client::Client; @@ -12,6 +11,7 @@ use log; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serde_json::json; +use tokio::fs; use tokio::process::Command; use coffee_github::repository::Github; @@ -20,6 +20,7 @@ use coffee_lib::plugin_manager::PluginManager; use coffee_lib::repository::Repository; use coffee_lib::types::response::*; use coffee_lib::url::URL; +use coffee_lib::utils::rm_dir_if_exist; use coffee_lib::{commit_id, error, get_repo_info, sh}; use coffee_storage::model::repository::{Kind, Repository as RepositoryInfo}; use coffee_storage::nosql_db::NoSQlStorage; @@ -424,12 +425,8 @@ impl PluginManager for CoffeeManager { Ok(()) } - async fn add_remote(&mut self, name: &str, url: &str) -> Result<(), CoffeeError> { - // FIXME: we should allow some error here like - // for the add remote command the no found error for the `repository` - // directory is fine. - - if self.repos.contains_key(name) { + async fn add_remote(&mut self, name: &str, url: &str, force: bool) -> Result<(), CoffeeError> { + if !force && self.repos.contains_key(name) { return Err(error!("repository with name: {name} already exists")); } let local_path = format!("{}/{}", self.config.root_path, self.config.network); @@ -546,6 +543,24 @@ impl PluginManager for CoffeeManager { let mut actions = self.patch_repository_locally_absent(repos.to_vec()).await?; nurse_actions.append(&mut actions); } + + Defect::CoffeeGlobalRepoCleanup(networks) => { + let iter = self + .repos + .iter() + .map(|(name, repo)| (name.to_owned(), repo.url())) + .collect::>(); + for (network, _) in networks { + log::debug!("reindexing repository for the network `{:?}`", network); + for (name, url) in &iter { + self.add_remote(&name, &url.url_string, true).await?; + } + nurse_actions + .push(NurseStatus::MovingGlobalRepostoryTo(network.to_owned())); + } + let global_repo = format!("{}/repositories", self.config.root_path); + rm_dir_if_exist(&global_repo).await?; + } } } let mut nurse = CoffeeNurse { diff --git a/coffee_core/src/config.rs b/coffee_core/src/config.rs index bd43eb1e..5f9e2874 100644 --- a/coffee_core/src/config.rs +++ b/coffee_core/src/config.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use std::env; use crate::CoffeeOperation; -use coffee_lib::utils::{check_dir_or_make_if_missing, move_dir_if_exist}; +use coffee_lib::utils::{check_dir_or_make_if_missing, copy_dir_if_exist}; use coffee_lib::{errors::CoffeeError, plugin::Plugin}; use crate::CoffeeArgs; @@ -66,7 +66,9 @@ impl CoffeeConf { check_dir_or_make_if_missing(format!("{def_path}/{}", coffee.network)).await?; check_dir_or_make_if_missing(format!("{def_path}/{}/plugins", coffee.network)).await?; let repo_dir = format!("{def_path}/{}/repositories", coffee.network); - move_dir_if_exist(&format!("{def_path}/repositories"), &repo_dir).await?; + // older version of coffee has a repository inside the directory + copy_dir_if_exist(&format!("{def_path}/repositories"), &repo_dir).await?; + // FIXME: nurse should clean up the `{def_path}/repositories`. check_dir_or_make_if_missing(repo_dir).await?; // after we know all the information regarding // the configuration we try to see if there is diff --git a/coffee_core/src/nurse/chain.rs b/coffee_core/src/nurse/chain.rs index 921d3820..e46f6edb 100644 --- a/coffee_core/src/nurse/chain.rs +++ b/coffee_core/src/nurse/chain.rs @@ -33,7 +33,7 @@ use async_trait::async_trait; use coffee_lib::errors::CoffeeError; use coffee_lib::types::response::{ChainOfResponsibilityStatus, Defect}; -use super::strategy::GitRepositoryLocallyAbsentStrategy; +use super::strategy::{CoffeeRepositoryDirCleanUpStrategy, GitRepositoryLocallyAbsentStrategy}; use crate::coffee::CoffeeManager; #[async_trait] @@ -52,7 +52,10 @@ impl RecoveryChainOfResponsibility { /// Create a new instance of the chain of responsibility pub async fn new() -> Result { Ok(Self { - handlers: vec![Arc::new(GitRepositoryLocallyAbsentStrategy)], + handlers: vec![ + Arc::new(CoffeeRepositoryDirCleanUpStrategy), + Arc::new(GitRepositoryLocallyAbsentStrategy), + ], }) } diff --git a/coffee_core/src/nurse/strategy.rs b/coffee_core/src/nurse/strategy.rs index a1e85d4d..f5c6a85c 100644 --- a/coffee_core/src/nurse/strategy.rs +++ b/coffee_core/src/nurse/strategy.rs @@ -74,3 +74,32 @@ impl Handler for GitRepositoryLocallyAbsentStrategy { } } } + +/// Stategy for migration of the repository global directory +/// to a local directory for each network. See [related issue] +/// +/// This is a strategy that returns the list of networks that +/// need to be migrated to to the new repository configuration. +/// +/// [related issue]: https://github.com/coffee-tools/coffee/issues/234 +pub struct CoffeeRepositoryDirCleanUpStrategy; + +#[async_trait] +impl Handler for CoffeeRepositoryDirCleanUpStrategy { + async fn can_be_applied( + self: Arc, + coffee: &CoffeeManager, + ) -> Result, CoffeeError> { + let network = coffee.config.network.clone(); + // Check whether there exists a network-specific repositories folder for each network. + let mut directory_moving = vec![]; + let subpath_repo = format!("{}/{network}/repositories", coffee.config.root_path); + if !Path::exists(Path::new(&subpath_repo)) { + directory_moving.push((network.to_string(), subpath_repo)); + } + if directory_moving.is_empty() { + return Ok(None); + } + Ok(Some(Defect::CoffeeGlobalRepoCleanup(directory_moving))) + } +} diff --git a/coffee_github/src/repository.rs b/coffee_github/src/repository.rs index be237e65..1e6d5ffa 100644 --- a/coffee_github/src/repository.rs +++ b/coffee_github/src/repository.rs @@ -339,6 +339,10 @@ impl Repository for Github { fn as_any(&self) -> &dyn Any { self } + + fn plugins(&mut self) -> &mut Vec { + &mut self.plugins + } } impl From for Github { diff --git a/coffee_httpd/src/httpd/server.rs b/coffee_httpd/src/httpd/server.rs index dc429fd5..ebfaed0c 100644 --- a/coffee_httpd/src/httpd/server.rs +++ b/coffee_httpd/src/httpd/server.rs @@ -130,7 +130,9 @@ async fn coffee_remote_add( let repository_url = &body.repository_url; let mut coffee = data.coffee.lock().await; - let result = coffee.add_remote(repository_name, repository_url).await; + let result = coffee + .add_remote(repository_name, repository_url, false) + .await; handle_httpd_response!(result, "Repository '{repository_name}' added successfully") } diff --git a/coffee_lib/src/plugin_manager.rs b/coffee_lib/src/plugin_manager.rs index 103b77e1..78efa0df 100644 --- a/coffee_lib/src/plugin_manager.rs +++ b/coffee_lib/src/plugin_manager.rs @@ -28,7 +28,7 @@ pub trait PluginManager { async fn upgrade(&mut self, repo: &str, verbose: bool) -> Result; /// add the remote repository to the plugin manager. - async fn add_remote(&mut self, name: &str, url: &str) -> Result<(), CoffeeError>; + async fn add_remote(&mut self, name: &str, url: &str, force: bool) -> Result<(), CoffeeError>; /// remove the remote repository from the plugin manager. async fn rm_remote(&mut self, name: &str) -> Result<(), CoffeeError>; diff --git a/coffee_lib/src/repository.rs b/coffee_lib/src/repository.rs index dbb9bec7..ffa7ee8e 100644 --- a/coffee_lib/src/repository.rs +++ b/coffee_lib/src/repository.rs @@ -41,4 +41,8 @@ pub trait Repository: Any { fn url(&self) -> URL; fn as_any(&self) -> &dyn Any; + + /// Return the vector of plugin + /// that are inside the repository + fn plugins(&mut self) -> &mut Vec; } diff --git a/coffee_lib/src/types/mod.rs b/coffee_lib/src/types/mod.rs index f47bcdce..c8a2560c 100644 --- a/coffee_lib/src/types/mod.rs +++ b/coffee_lib/src/types/mod.rs @@ -150,6 +150,8 @@ pub mod response { // A patch operation when a git repository is present in the coffee configuration // but is absent from the local storage. RepositoryLocallyAbsent(Vec), + /// (Affected network, path) + CoffeeGlobalRepoCleanup(Vec<(String, String)>), // TODO: Add more patch operations } @@ -178,6 +180,16 @@ pub mod response { write!(f, " {}", repo)?; } } + Defect::CoffeeGlobalRepoCleanup(networks) => { + writeln!( + f, + "Global repository migration completed for the networks: {}", + networks + .iter() + .map(|(network, _)| network.to_owned()) + .collect::() + )?; + } } } Ok(()) @@ -192,6 +204,7 @@ pub mod response { pub enum NurseStatus { RepositoryLocallyRestored(Vec), RepositoryLocallyRemoved(Vec), + MovingGlobalRepostoryTo(String), } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -218,6 +231,7 @@ pub mod response { NurseStatus::RepositoryLocallyRestored(repos) => { repositories_locally_restored.append(&mut repos.clone()) } + NurseStatus::MovingGlobalRepostoryTo(_) => {} } } if !repositories_locally_removed.is_empty() { @@ -243,6 +257,12 @@ pub mod response { NurseStatus::RepositoryLocallyRemoved(val) => { write!(f, "Repositories removed locally: {}", val.join(" ")) } + NurseStatus::MovingGlobalRepostoryTo(net) => { + write!( + f, + "Global repository directory moved to subdirectory for network `{net}`" + ) + } } } } diff --git a/coffee_lib/src/utils.rs b/coffee_lib/src/utils.rs index 5c5c69ed..f951aae8 100644 --- a/coffee_lib/src/utils.rs +++ b/coffee_lib/src/utils.rs @@ -1,8 +1,7 @@ use super::macros::error; use std::path::Path; -use tokio::fs::create_dir; -use tokio::fs::rename; +use tokio::fs; use crate::errors::CoffeeError; @@ -23,16 +22,24 @@ pub fn get_plugin_info_from_path(path: &Path) -> Result<(String, String), Coffee pub async fn check_dir_or_make_if_missing(path: String) -> Result<(), CoffeeError> { if !Path::exists(Path::new(&path.to_owned())) { - create_dir(path.clone()).await?; + fs::create_dir(path.clone()).await?; log::debug!("created dir {path}"); } Ok(()) } -pub async fn move_dir_if_exist(origin: &str, destination: &str) -> Result<(), CoffeeError> { +pub async fn copy_dir_if_exist(origin: &str, destination: &str) -> Result<(), CoffeeError> { if Path::exists(Path::new(&origin)) { - rename(origin, destination).await?; - log::debug!("move dir from {origin} to {destination}"); + fs::copy(origin, destination).await?; + log::debug!("copying dir from {origin} to {destination}"); + } + Ok(()) +} + +pub async fn rm_dir_if_exist(origin: &str) -> Result<(), CoffeeError> { + if Path::exists(Path::new(&origin)) { + fs::remove_dir_all(origin).await?; + log::debug!("rm dir from {origin}"); } Ok(()) } diff --git a/coffee_plugin/src/plugin/plugin_mod.rs b/coffee_plugin/src/plugin/plugin_mod.rs index 4f3fa403..9ad79237 100644 --- a/coffee_plugin/src/plugin/plugin_mod.rs +++ b/coffee_plugin/src/plugin/plugin_mod.rs @@ -119,7 +119,11 @@ fn coffee_remote(plugin: &mut Plugin, request: Value) -> Result coffee.add_remote(&request.name, &request.url()).await, + RemoteCmd::Add => { + coffee + .add_remote(&request.name, &request.url(), false) + .await + } RemoteCmd::Rm => coffee.rm_remote(&request.name).await, } }) diff --git a/git-bugreport-2024-02-07-1353.txt b/git-bugreport-2024-02-07-1353.txt deleted file mode 100644 index f278341c..00000000 --- a/git-bugreport-2024-02-07-1353.txt +++ /dev/null @@ -1,32 +0,0 @@ -Thank you for filling out a Git bug report! -Please answer the following questions to help us understand your issue. - -What did you do before the bug happened? (Steps to reproduce your issue) - -What did you expect to happen? (Expected behavior) - -What happened instead? (Actual behavior) - -What's different between what you expected and what actually happened? - -Anything else you want to add: - -Please review the rest of the bug report below. -You can delete any lines you don't wish to share. - - -[System Info] -git version: -git version 2.43.0 -cpu: x86_64 -no commit associated with this build -sizeof-long: 8 -sizeof-size_t: 8 -shell-path: /bin/sh -uname: Linux 6.6.15-2-lts #1 SMP PREEMPT_DYNAMIC Fri, 02 Feb 2024 17:04:24 +0000 x86_64 -compiler info: gnuc: 13.2 -libc info: glibc: 2.39 -$SHELL (typically, interactive shell): /usr/bin/zsh - - -[Enabled Hooks] From 87b29fe5fe8d6fafd52a0a2b46ade069af906560 Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Tue, 9 Apr 2024 14:26:37 +0200 Subject: [PATCH 3/7] core: fixing the nurse run command Signed-off-by: Vincenzo Palazzo --- coffee_cmd/src/coffee_term/command_show.rs | 48 +++++++++++++++++++++- coffee_cmd/src/main.rs | 4 +- coffee_core/src/coffee.rs | 13 +++--- coffee_core/src/config.rs | 35 ++++++++-------- coffee_github/src/repository.rs | 4 ++ coffee_lib/src/repository.rs | 5 +++ coffee_lib/src/types/mod.rs | 35 ++-------------- coffee_lib/src/url.rs | 4 ++ coffee_lib/src/utils.rs | 35 +++++++++++++++- rust-toolchain | 2 +- 10 files changed, 124 insertions(+), 61 deletions(-) diff --git a/coffee_cmd/src/coffee_term/command_show.rs b/coffee_cmd/src/coffee_term/command_show.rs index 09776009..593a09fa 100644 --- a/coffee_cmd/src/coffee_term/command_show.rs +++ b/coffee_cmd/src/coffee_term/command_show.rs @@ -7,7 +7,10 @@ use term::Element; use coffee_lib::error; use coffee_lib::errors::CoffeeError; -use coffee_lib::types::response::{CoffeeList, CoffeeNurse, CoffeeRemote, CoffeeTip, NurseStatus}; +use coffee_lib::types::response::{ + ChainOfResponsibilityStatus, CoffeeList, CoffeeNurse, CoffeeRemote, CoffeeTip, Defect, + NurseStatus, +}; pub fn show_list(coffee_list: Result) -> Result<(), CoffeeError> { let remotes = coffee_list?; @@ -86,6 +89,49 @@ pub fn show_remote_list(remote_list: Result) -> Resul Ok(()) } +pub fn show_nurse_verify(nurse_verify: &ChainOfResponsibilityStatus) -> Result<(), CoffeeError> { + if nurse_verify.defects.is_empty() { + term::success!("Coffee configuration is not corrupt! No need to run coffee nurse"); + } else { + let mut table = radicle_term::Table::new(TableOptions::bordered()); + table.push([ + term::format::dim(String::from("●")), + term::format::bold(String::from("Defects")), + term::format::bold(String::from("Affected repositories")), + ]); + table.divider(); + + for defect in nurse_verify.defects.iter() { + match defect { + Defect::RepositoryLocallyAbsent(repos) => { + let defect = "Repository missing locally"; + for repo in repos { + table.push([ + term::format::positive("●").into(), + term::format::bold(defect.to_owned()), + term::format::highlight(repo.clone()), + ]); + } + } + Defect::CoffeeGlobalRepoCleanup(networks) => { + let defect = "Network specific repository missing"; + let networks = networks + .iter() + .map(|(network, _)| network.clone()) + .collect::>(); + table.push([ + term::format::positive("●").into(), + term::format::bold(defect.to_owned()), + term::format::highlight(networks.join(", ")), + ]); + } + } + } + table.print(); + } + Ok(()) +} + pub fn show_nurse_result( nurse_result: Result, ) -> Result<(), CoffeeError> { diff --git a/coffee_cmd/src/main.rs b/coffee_cmd/src/main.rs index a9d8c6ae..7e83ae53 100644 --- a/coffee_cmd/src/main.rs +++ b/coffee_cmd/src/main.rs @@ -154,9 +154,9 @@ async fn run(args: CoffeeArgs, mut coffee: CoffeeManager) -> Result<(), CoffeeEr CoffeeCommand::Nurse { verify } => { if verify { let result = coffee.nurse_verify().await?; - term::info!("{}", result); + coffee_term::show_nurse_verify(&result)?; if !result.is_sane() { - term::info!("Coffee local directory is damaged, please run `coffee nurse` to try to fix it"); + term::warning(term::style("Coffee local directory is damaged, please run `coffee nurse` to try to fix it").bold()); } } else { let nurse_result = coffee.nurse().await; diff --git a/coffee_core/src/coffee.rs b/coffee_core/src/coffee.rs index 92cdc03f..b52dcec8 100644 --- a/coffee_core/src/coffee.rs +++ b/coffee_core/src/coffee.rs @@ -79,8 +79,8 @@ pub struct CoffeeManager { } impl CoffeeManager { - pub async fn new(conf: &dyn CoffeeArgs) -> Result { - let conf = CoffeeConf::new(conf).await?; + pub async fn new(conf_args: &dyn CoffeeArgs) -> Result { + let conf = CoffeeConf::new(conf_args).await?; let mut coffee = CoffeeManager { config: conf.clone(), coffee_cln_config: CLNConf::new(conf.config_path, true), @@ -97,6 +97,7 @@ impl CoffeeManager { /// when coffee is configured, run an inventory to collect all the necessary information /// about the coffee ecosystem. async fn inventory(&mut self) -> Result<(), CoffeeError> { + let skip_verify = self.config.skip_verify; let _ = self .storage .load::(&self.config.network) @@ -122,7 +123,7 @@ impl CoffeeManager { if let Err(err) = self.coffee_cln_config.parse() { log::error!("{}", err.cause); } - if !self.config.skip_verify { + if !skip_verify { // Check for the chain of responsibility let status = self.recovery_strategies.scan(self).await?; log::debug!("Chain of responsibility status: {:?}", status); @@ -429,8 +430,7 @@ impl PluginManager for CoffeeManager { if !force && self.repos.contains_key(name) { return Err(error!("repository with name: {name} already exists")); } - let local_path = format!("{}/{}", self.config.root_path, self.config.network); - let url = URL::new(&local_path, url, name); + let url = URL::new(&self.config.path(), url, name); log::debug!("remote adding: {} {}", name, &url.url_string); let mut repo = Github::new(name, &url); repo.init().await?; @@ -567,6 +567,8 @@ impl PluginManager for CoffeeManager { status: nurse_actions, }; nurse.organize(); + // Refesh the status + self.flush().await?; Ok(nurse) } @@ -589,6 +591,7 @@ impl PluginManager for CoffeeManager { .get_mut(repo_name) .ok_or_else(|| error!("repository with name: {repo_name} not found"))?; + repo.change_root_path(&self.config.path()); match repo.recover().await { Ok(_) => { log::info!("repository {} recovered", repo_name.clone()); diff --git a/coffee_core/src/config.rs b/coffee_core/src/config.rs index 5f9e2874..1f5328d7 100644 --- a/coffee_core/src/config.rs +++ b/coffee_core/src/config.rs @@ -1,13 +1,14 @@ //! Coffee configuration utils. -use log::info; -use serde::{Deserialize, Serialize}; use std::env; -use crate::CoffeeOperation; -use coffee_lib::utils::{check_dir_or_make_if_missing, copy_dir_if_exist}; +use serde::{Deserialize, Serialize}; + +use coffee_lib::utils::check_dir_or_make_if_missing; use coffee_lib::{errors::CoffeeError, plugin::Plugin}; use crate::CoffeeArgs; +use crate::CoffeeOperation; + /// Custom coffee configuration, given by a command line list of arguments /// or a coffee configuration file. #[derive(Clone, Debug, Serialize, Deserialize)] @@ -15,23 +16,25 @@ pub struct CoffeeConf { /// Network configuration related /// to core lightning network pub network: String, + /// root path plugin manager + pub root_path: String, /// path of core lightning configuration file /// managed by coffee pub config_path: String, /// path of the core lightning configuration file /// not managed by core lightning - /// (this file included the file managed by coffee) + /// + /// This file included the file managed by coffee pub cln_config_path: Option, /// root cln directory path pub cln_root: Option, - /// root path plugin manager - pub root_path: String, /// all plugins that are installed /// with the plugin manager. pub plugins: Vec, /// A flag that indicates if the /// user wants to skip the verification /// of nurse. + #[serde(skip)] pub skip_verify: bool, } @@ -47,7 +50,7 @@ impl CoffeeConf { def_path = def_path.strip_suffix('/').unwrap_or(&def_path).to_string(); def_path += "/.coffee"; check_dir_or_make_if_missing(def_path.to_string()).await?; - info!("creating coffee home at {def_path}"); + log::info!("creating coffee home at {def_path}"); let mut coffee = CoffeeConf { network: "bitcoin".to_owned(), @@ -62,14 +65,8 @@ impl CoffeeConf { // check the command line arguments and bind them // inside the coffee conf coffee.bind_cmd_line_params(conf)?; - check_dir_or_make_if_missing(format!("{def_path}/{}", coffee.network)).await?; check_dir_or_make_if_missing(format!("{def_path}/{}/plugins", coffee.network)).await?; - let repo_dir = format!("{def_path}/{}/repositories", coffee.network); - // older version of coffee has a repository inside the directory - copy_dir_if_exist(&format!("{def_path}/repositories"), &repo_dir).await?; - // FIXME: nurse should clean up the `{def_path}/repositories`. - check_dir_or_make_if_missing(repo_dir).await?; // after we know all the information regarding // the configuration we try to see if there is // something stored already to the disk. @@ -105,10 +102,12 @@ impl CoffeeConf { } } } - - // FIXME: be able to put the directory also in another place! - // for now it is fixed in the Home/.coffee but another good place - // will be, the .lightning dir Ok(()) } + + /// Return the root path of the coffee manager instance + /// this include also the network path. + pub fn path(&self) -> String { + format!("{}/{}", self.root_path, self.network) + } } diff --git a/coffee_github/src/repository.rs b/coffee_github/src/repository.rs index 1e6d5ffa..9ed1bed4 100644 --- a/coffee_github/src/repository.rs +++ b/coffee_github/src/repository.rs @@ -233,6 +233,10 @@ impl Repository for Github { } } + fn change_root_path(&mut self, path: &str) { + self.url.set_coffee_path(path); + } + async fn upgrade( &mut self, plugins: &Vec, diff --git a/coffee_lib/src/repository.rs b/coffee_lib/src/repository.rs index ffa7ee8e..29f2f4d2 100644 --- a/coffee_lib/src/repository.rs +++ b/coffee_lib/src/repository.rs @@ -34,6 +34,11 @@ pub trait Repository: Any { /// recover the repository from the commit id. async fn recover(&mut self) -> Result<(), CoffeeError>; + /// While migrating there is a possibility that we should + /// move an old repository into a new path. So this + /// is semplyfing this process. + fn change_root_path(&mut self, path: &str); + /// return the name of the repository. fn name(&self) -> String; diff --git a/coffee_lib/src/types/mod.rs b/coffee_lib/src/types/mod.rs index c8a2560c..e266ef09 100644 --- a/coffee_lib/src/types/mod.rs +++ b/coffee_lib/src/types/mod.rs @@ -166,37 +166,6 @@ pub mod response { } } - impl fmt::Display for ChainOfResponsibilityStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.defects.is_empty() { - write!(f, "Coffee is sane") - } else { - writeln!(f, "Coffee has the following defects:")?; - for (i, defect) in self.defects.iter().enumerate() { - match defect { - Defect::RepositoryLocallyAbsent(repos) => { - write!(f, "{}. Repository missing locally: ", i + 1)?; - for repo in repos { - write!(f, " {}", repo)?; - } - } - Defect::CoffeeGlobalRepoCleanup(networks) => { - writeln!( - f, - "Global repository migration completed for the networks: {}", - networks - .iter() - .map(|(network, _)| network.to_owned()) - .collect::() - )?; - } - } - } - Ok(()) - } - } - } - /// This struct is used to represent the status of nurse, /// either sane or not. /// If not sane, return the action that nurse has taken. @@ -231,7 +200,9 @@ pub mod response { NurseStatus::RepositoryLocallyRestored(repos) => { repositories_locally_restored.append(&mut repos.clone()) } - NurseStatus::MovingGlobalRepostoryTo(_) => {} + NurseStatus::MovingGlobalRepostoryTo(status) => { + new_status.push(NurseStatus::MovingGlobalRepostoryTo(status.to_owned())); + } } } if !repositories_locally_removed.is_empty() { diff --git a/coffee_lib/src/url.rs b/coffee_lib/src/url.rs index c7dc4b87..9b4dcd70 100644 --- a/coffee_lib/src/url.rs +++ b/coffee_lib/src/url.rs @@ -57,6 +57,10 @@ impl URL { repo_name: get_repo_name_from_url(url), } } + + pub fn set_coffee_path(&mut self, path: &str) { + self.path_string = format!("{path}/{}", self.repo_name); + } } impl fmt::Display for URL { diff --git a/coffee_lib/src/utils.rs b/coffee_lib/src/utils.rs index f951aae8..ca7717c5 100644 --- a/coffee_lib/src/utils.rs +++ b/coffee_lib/src/utils.rs @@ -1,5 +1,5 @@ use super::macros::error; -use std::path::Path; +use std::path::{Path, PathBuf}; use tokio::fs; @@ -21,6 +21,7 @@ pub fn get_plugin_info_from_path(path: &Path) -> Result<(String, String), Coffee } pub async fn check_dir_or_make_if_missing(path: String) -> Result<(), CoffeeError> { + log::trace!("check_dir_or_make_if_missing: `{path}`"); if !Path::exists(Path::new(&path.to_owned())) { fs::create_dir(path.clone()).await?; log::debug!("created dir {path}"); @@ -29,13 +30,43 @@ pub async fn check_dir_or_make_if_missing(path: String) -> Result<(), CoffeeErro } pub async fn copy_dir_if_exist(origin: &str, destination: &str) -> Result<(), CoffeeError> { + log::trace!("copy_dir_if_exist: from: `{origin}` to `{destination}`"); if Path::exists(Path::new(&origin)) { - fs::copy(origin, destination).await?; + copy_dir_recursive(origin.to_owned(), destination.to_owned()).await?; log::debug!("copying dir from {origin} to {destination}"); } Ok(()) } +async fn copy_dir_recursive(source: String, destination: String) -> Result<(), CoffeeError> { + async fn inner_copy_dir_recursive( + source: PathBuf, + destination: PathBuf, + ) -> Result<(), CoffeeError> { + check_dir_or_make_if_missing(destination.to_string_lossy().to_string()).await?; + + let mut entries = fs::read_dir(source).await?; + while let Some(entry) = entries.next_entry().await? { + let file_type = entry.file_type().await?; + let dest_path = destination.join(entry.file_name()); + log::debug!("copy entry {:?} in {:?}", entry, dest_path); + if file_type.is_dir() { + // Here we use Box::pin to allow recursion + let fut = inner_copy_dir_recursive(entry.path(), dest_path); + Box::pin(fut).await?; + } else if file_type.is_file() { + fs::copy(entry.path(), &dest_path).await?; + } + } + + Ok(()) + } + let source = Path::new(&source); + let destination = Path::new(&destination); + log::info!("{:?} - {:?}", source, destination); + inner_copy_dir_recursive(source.to_path_buf(), destination.to_path_buf()).await +} + pub async fn rm_dir_if_exist(origin: &str) -> Result<(), CoffeeError> { if Path::exists(Path::new(&origin)) { fs::remove_dir_all(origin).await?; diff --git a/rust-toolchain b/rust-toolchain index 07cde984..3245dca3 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.75 +1.77 From 83d65f463bc2cb5bc651c540012ff101ec8ef0b2 Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Sun, 14 Apr 2024 21:44:40 +0200 Subject: [PATCH 4/7] core: migrating the database Signed-off-by: Vincenzo Palazzo --- coffee_core/src/coffee.rs | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/coffee_core/src/coffee.rs b/coffee_core/src/coffee.rs index b52dcec8..22e62f41 100644 --- a/coffee_core/src/coffee.rs +++ b/coffee_core/src/coffee.rs @@ -105,8 +105,7 @@ impl CoffeeManager { .map(|store| { self.config = store.config; }); - // FIXME: check if this exist in a better wai - let _ = self + let global_repositories = self .storage .load::>("repositories") .await @@ -120,6 +119,34 @@ impl CoffeeManager { }); }); + if let Ok(_) = global_repositories { + // HACK: this should be done with the nurse command, but + // due that currently migrating the database with the nurse + // logic is a little bit tricky we do this hack and we try + // to move on, but if you are looking something to do in coffee + // it is possible to take this problem and design a solution. + // FIXME: add the drop method inside nosql_db + } + + let local_repositories = self + .storage + .load::>(&format!( + "{}/repositories", + self.config.network + )) + .await; + if let Ok(repos) = local_repositories { + // FIXME: till we are not able to remove a key from + // the database + self.repos.clear(); + repos.iter().for_each(|repo| match repo.1.kind { + Kind::Git => { + let repo = Github::from(repo.1); + self.repos.insert(repo.name(), Box::new(repo)); + } + }); + } + if let Err(err) = self.coffee_cln_config.parse() { log::error!("{}", err.cause); } @@ -192,7 +219,10 @@ impl CoffeeManager { .store(&self.config.network, &store_info) .await?; self.storage - .store("repositories", &store_info.repositories) + .store( + &format!("{}/repositories", self.config.network), + &store_info.repositories, + ) .await?; Ok(()) } From 4528011e8ad945d6fd9d670f12c7c5280fdd8b3a Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Sun, 14 Apr 2024 23:57:47 +0200 Subject: [PATCH 5/7] tests: write test to making sure that we use local repo everywhere Signed-off-by: Vincenzo Palazzo --- coffee_core/src/nurse/strategy.rs | 5 + coffee_lib/src/plugin.rs | 2 +- coffee_lib/src/plugin_conf.rs | 8 +- coffee_lib/src/types/mod.rs | 4 +- tests/src/coffee_integration_tests.rs | 126 ++++++++++++++++++++++++-- 5 files changed, 129 insertions(+), 16 deletions(-) diff --git a/coffee_core/src/nurse/strategy.rs b/coffee_core/src/nurse/strategy.rs index f5c6a85c..4a5967d3 100644 --- a/coffee_core/src/nurse/strategy.rs +++ b/coffee_core/src/nurse/strategy.rs @@ -91,6 +91,11 @@ impl Handler for CoffeeRepositoryDirCleanUpStrategy { coffee: &CoffeeManager, ) -> Result, CoffeeError> { let network = coffee.config.network.clone(); + let global_dir = coffee.config.root_path.clone(); + let global_dir = format!("{global_dir}/repositories"); + if !Path::exists(Path::new(&global_dir)) { + return Ok(None); + } // Check whether there exists a network-specific repositories folder for each network. let mut directory_moving = vec![]; let subpath_repo = format!("{}/{network}/repositories", coffee.config.root_path); diff --git a/coffee_lib/src/plugin.rs b/coffee_lib/src/plugin.rs index 873fa66c..334862cf 100644 --- a/coffee_lib/src/plugin.rs +++ b/coffee_lib/src/plugin.rs @@ -93,7 +93,7 @@ impl PluginLang { } /// Plugin struct definition -#[derive(Clone, Serialize, Deserialize, Debug)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] pub struct Plugin { name: String, /// root path of the plugin diff --git a/coffee_lib/src/plugin_conf.rs b/coffee_lib/src/plugin_conf.rs index 0df276de..a30d3ec1 100644 --- a/coffee_lib/src/plugin_conf.rs +++ b/coffee_lib/src/plugin_conf.rs @@ -1,13 +1,13 @@ //! Coffee configuration serialization file. use serde::{Deserialize, Serialize}; -#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Eq)] pub struct Conf { pub plugin: Plugin, pub tipping: Option, } -#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Eq)] pub struct Plugin { pub name: String, pub version: String, @@ -19,12 +19,12 @@ pub struct Plugin { pub important: Option, } -#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct Deprecaterd { pub reason: String, } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct Tipping { pub bolt12: String, } diff --git a/coffee_lib/src/types/mod.rs b/coffee_lib/src/types/mod.rs index e266ef09..74c94d48 100644 --- a/coffee_lib/src/types/mod.rs +++ b/coffee_lib/src/types/mod.rs @@ -85,12 +85,12 @@ pub mod response { pub plugins: Vec, } - #[derive(Clone, Debug, Serialize, Deserialize)] + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct CoffeeRemote { pub remotes: Option>, } - #[derive(Clone, Debug, Serialize, Deserialize)] + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct CoffeeListRemote { pub local_name: String, pub url: String, diff --git a/tests/src/coffee_integration_tests.rs b/tests/src/coffee_integration_tests.rs index 126af133..c7f82b01 100644 --- a/tests/src/coffee_integration_tests.rs +++ b/tests/src/coffee_integration_tests.rs @@ -40,7 +40,11 @@ pub async fn init_coffee_test_cmd() -> anyhow::Result<()> { let root_path = manager.root_path().to_owned(); manager .coffee() - .add_remote("folgore", "https://github.com/coffee-tools/folgore.git") + .add_remote( + "folgore", + "https://github.com/coffee-tools/folgore.git", + false, + ) .await .unwrap(); @@ -107,7 +111,11 @@ pub async fn init_coffee_test_add_remote() { manager .coffee() - .add_remote("lightningd", "https://github.com/lightningd/plugins.git") + .add_remote( + "lightningd", + "https://github.com/lightningd/plugins.git", + false, + ) .await .unwrap(); manager @@ -141,7 +149,7 @@ pub async fn test_add_remove_plugins() { let repo_url = "https://github.com/lightningd/plugins.git"; manager .coffee() - .add_remote(repo_name, repo_url) + .add_remote(repo_name, repo_url, false) .await .unwrap(); @@ -260,7 +268,7 @@ pub async fn test_errors_and_show() { let repo_url = "https://github.com/lightningd/plugins.git"; manager .coffee() - .add_remote(repo_name, repo_url) + .add_remote(repo_name, repo_url, false) .await .unwrap(); @@ -346,7 +354,11 @@ pub async fn install_plugin_in_two_networks() -> anyhow::Result<()> { // Add lightningd remote repository manager .coffee() - .add_remote("lightningd", "https://github.com/lightningd/plugins.git") + .add_remote( + "lightningd", + "https://github.com/lightningd/plugins.git", + false, + ) .await .unwrap(); // Install summary plugin @@ -386,7 +398,11 @@ pub async fn install_plugin_in_two_networks() -> anyhow::Result<()> { let result = manager .coffee() - .add_remote("lightningd", "https://github.com/lightningd/plugins.git") + .add_remote( + "lightningd", + "https://github.com/lightningd/plugins.git", + false, + ) .await; assert!(result.is_err(), "{:?}", result); // Install summary plugin @@ -423,7 +439,7 @@ pub async fn test_double_slash() { let repo_url = "https://github.com/lightningd/plugins.git"; manager .coffee() - .add_remote(repo_name, repo_url) + .add_remote(repo_name, repo_url, false) .await .unwrap(); @@ -486,7 +502,11 @@ pub async fn test_plugin_installation_path() { // Add lightningd remote repository manager .coffee() - .add_remote("lightningd", "https://github.com/lightningd/plugins.git") + .add_remote( + "lightningd", + "https://github.com/lightningd/plugins.git", + false, + ) .await .unwrap(); @@ -604,7 +624,11 @@ pub async fn test_nurse_repository_missing_on_disk() { // Add folgore remote repository manager .coffee() - .add_remote("folgore", "https://github.com/coffee-tools/folgore.git") + .add_remote( + "folgore", + "https://github.com/coffee-tools/folgore.git", + false, + ) .await .unwrap(); @@ -700,3 +724,87 @@ pub async fn test_nurse_repository_missing_on_disk() { cln.stop().await.unwrap(); } + +/// Materializing the following test case +/// https://github.com/coffee-tools/coffee/pull/239#issuecomment-2052606147 +#[tokio::test] +#[ntest::timeout(560000)] +pub async fn test_migrated_repository_to_local_one() { + init(); + // We need to run before the testnet and the regtest + // because we work on the same database + // + // WHEN UNIX socket? + let dir = Arc::new(tempfile::tempdir().unwrap()); + + let testnet_conf = CoffeeTestingArgs { + conf: None, + data_dir: dir.path().to_str().unwrap().to_owned(), + network: "testnet".to_owned(), + }; + + let mut coffee_testnet_m = CoffeeTesting::tmp_with_args(&testnet_conf, dir.clone()) + .await + .unwrap(); + let coffee_testnet = coffee_testnet_m.coffee(); + coffee_testnet + .add_remote( + "lightningd", + "https://github.com/lightningd/plugins.git", + false, + ) + .await + .unwrap(); + + let test_remotes = coffee_testnet.list_remotes().await.unwrap(); + std::mem::drop(coffee_testnet_m); + + let regtest_conf = CoffeeTestingArgs { + conf: None, + data_dir: dir.path().to_str().unwrap().to_owned(), + network: "regtest".to_owned(), + }; + + let mut coffee_regtest_m = CoffeeTesting::tmp_with_args(®test_conf, dir.clone()) + .await + .unwrap(); + let coffee_regtest = coffee_regtest_m.coffee(); + + coffee_regtest + .add_remote( + "folgore", + "https://github.com/coffee-tools/folgore.git", + false, + ) + .await + .unwrap(); + + coffee_regtest + .install("folgore", false, false) + .await + .unwrap(); + + let reg_plugins = coffee_regtest.list().await.unwrap(); + let reg_remotes = coffee_regtest.list_remotes().await.unwrap(); + + std::mem::drop(coffee_regtest_m); + // just lightnind + assert_eq!(test_remotes.remotes.unwrap().len(), 1); + // just folgore + assert_eq!(reg_remotes.remotes.clone().unwrap().len(), 1); + + let mut coffee_testnet_m = CoffeeTesting::tmp_with_args(&testnet_conf, dir.clone()) + .await + .unwrap(); + let coffee_testnet = coffee_testnet_m.coffee(); + let test_plugins = coffee_testnet.list().await.unwrap(); + let test_remotes = coffee_testnet.list_remotes().await.unwrap(); + std::mem::drop(coffee_testnet_m); + + // in the regtest plugins we will just a single plugin, and in testnet none + assert_eq!(test_plugins.plugins.len() + 1, reg_plugins.plugins.len()); + assert_eq!( + test_remotes.remotes.unwrap().len(), + reg_remotes.remotes.unwrap().len() + ); +} From 8f36fe54d0b3fc15c27e3407e78c34a3fbcdee8e Mon Sep 17 00:00:00 2001 From: Ifeanyichukwu Date: Wed, 17 Apr 2024 09:25:49 +0100 Subject: [PATCH 6/7] chore(change-table-label): change table label from Affected repositories to Action --- coffee_cmd/src/coffee_term/command_show.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coffee_cmd/src/coffee_term/command_show.rs b/coffee_cmd/src/coffee_term/command_show.rs index 593a09fa..c39f7aee 100644 --- a/coffee_cmd/src/coffee_term/command_show.rs +++ b/coffee_cmd/src/coffee_term/command_show.rs @@ -97,7 +97,7 @@ pub fn show_nurse_verify(nurse_verify: &ChainOfResponsibilityStatus) -> Result<( table.push([ term::format::dim(String::from("●")), term::format::bold(String::from("Defects")), - term::format::bold(String::from("Affected repositories")), + term::format::bold(String::from("Action")), ]); table.divider(); From 8615159f5cc5edfd74044354dd99e7675835a00a Mon Sep 17 00:00:00 2001 From: Ifeanyichukwu Date: Thu, 18 Apr 2024 10:52:33 +0100 Subject: [PATCH 7/7] chore(cmd): change table label --- coffee_cmd/src/coffee_term/command_show.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coffee_cmd/src/coffee_term/command_show.rs b/coffee_cmd/src/coffee_term/command_show.rs index c39f7aee..9237e1aa 100644 --- a/coffee_cmd/src/coffee_term/command_show.rs +++ b/coffee_cmd/src/coffee_term/command_show.rs @@ -97,7 +97,7 @@ pub fn show_nurse_verify(nurse_verify: &ChainOfResponsibilityStatus) -> Result<( table.push([ term::format::dim(String::from("●")), term::format::bold(String::from("Defects")), - term::format::bold(String::from("Action")), + term::format::bold(String::from("Affected")), ]); table.divider();