From e91a9874e77a8040236fa5e2a11e72415dd7d73e Mon Sep 17 00:00:00 2001 From: Tarek Date: Fri, 23 Feb 2024 22:43:43 +0200 Subject: [PATCH 1/4] feat(lib): define a method for Repository to return git head This function will come in handy when we want to restore the state of a repository on disk after upgrading a plugin Signed-off-by: Tarek --- coffee_github/src/repository.rs | 9 +++++++-- coffee_lib/src/repository.rs | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/coffee_github/src/repository.rs b/coffee_github/src/repository.rs index be237e6..d4083ea 100644 --- a/coffee_github/src/repository.rs +++ b/coffee_github/src/repository.rs @@ -21,7 +21,7 @@ use coffee_storage::model::repository::Kind; use coffee_storage::model::repository::Repository as StorageRepository; use crate::utils::clone_recursive_fix; -use crate::utils::git_upgrade; +use crate::utils::git_upgrade_with_branch; pub struct Github { /// the url of the repository to be able @@ -259,7 +259,7 @@ impl Repository for Github { } } // pull the changes from the repository - let status = git_upgrade(&self.url.path_string, &self.branch, verbose).await?; + let status = git_upgrade_with_branch(&self.url.path_string, &self.branch, verbose).await?; self.git_head = Some(status.commit_id()); self.last_activity = Some(status.date()); Ok(CoffeeUpgrade { @@ -336,6 +336,11 @@ impl Repository for Github { None } + /// return the git head of the repository. + fn git_head(&self) -> Option { + self.git_head.clone() + } + fn as_any(&self) -> &dyn Any { self } diff --git a/coffee_lib/src/repository.rs b/coffee_lib/src/repository.rs index dbb9bec..bf247e0 100644 --- a/coffee_lib/src/repository.rs +++ b/coffee_lib/src/repository.rs @@ -40,5 +40,8 @@ pub trait Repository: Any { /// return the url of the repository. fn url(&self) -> URL; + /// return the git head of the repository. + fn git_head(&self) -> Option; + fn as_any(&self) -> &dyn Any; } From 9a97397e0cea6b9c28cfbe6e0a7d2fee3fbd5a3a Mon Sep 17 00:00:00 2001 From: Tarek Date: Fri, 23 Feb 2024 22:49:12 +0200 Subject: [PATCH 2/4] feat(coffee): add field branch to coffee install This commit adds an optional parameter to coffee_install() method that makes it possible to specify a branch when installing a plugin. The logic is: - get information about current status before switching (repoisitory branch and plugin git head) - switch to the branch - restore old information and rollback A function git_upgrade_with_git_head was implemented to handle checking out using a specfic git_head we are using the repository git head to save current status and roll back to it since it's safer than using just the branch (we fetch origin so maybe the branch was updated. we want to be back to the same commit) also, cmd, coffee_plugin and coffee httpd were upgraded to pass the branch value Signed-off-by: Tarek --- coffee_cmd/src/cmd.rs | 9 ++++- coffee_cmd/src/main.rs | 3 +- coffee_core/src/coffee.rs | 46 ++++++++++++++++++++++++-- coffee_core/src/lib.rs | 2 +- coffee_github/src/lib.rs | 2 ++ coffee_github/src/utils.rs | 26 ++++++++++++++- coffee_httpd/src/httpd/server.rs | 2 +- coffee_lib/src/plugin_manager.rs | 1 + coffee_plugin/src/plugin/plugin_mod.rs | 2 +- tests/src/coffee_integration_tests.rs | 20 +++++------ 10 files changed, 95 insertions(+), 18 deletions(-) diff --git a/coffee_cmd/src/cmd.rs b/coffee_cmd/src/cmd.rs index cf1a474..bee8828 100644 --- a/coffee_cmd/src/cmd.rs +++ b/coffee_cmd/src/cmd.rs @@ -29,6 +29,12 @@ pub enum CoffeeCommand { verbose: bool, #[arg(short, long, action = clap::ArgAction::SetTrue)] dynamic: bool, + #[arg( + name = "branch", + long, + help = "The branch to install the plugin from. If not specified, the default branch will be used." + )] + branch: Option, }, /// upgrade a single repository. #[clap(arg_required_else_help = true)] @@ -98,7 +104,8 @@ impl From<&CoffeeCommand> for coffee_core::CoffeeOperation { plugin, verbose, dynamic, - } => Self::Install(plugin.to_owned(), *verbose, *dynamic), + branch, + } => Self::Install(plugin.to_owned(), *verbose, *dynamic, branch.clone()), CoffeeCommand::Upgrade { repo, verbose } => Self::Upgrade(repo.to_owned(), *verbose), CoffeeCommand::List {} => Self::List, CoffeeCommand::Setup { cln_conf } => Self::Setup(cln_conf.to_owned()), diff --git a/coffee_cmd/src/main.rs b/coffee_cmd/src/main.rs index d5708fa..91b80d6 100644 --- a/coffee_cmd/src/main.rs +++ b/coffee_cmd/src/main.rs @@ -20,13 +20,14 @@ async fn run(args: CoffeeArgs, mut coffee: CoffeeManager) -> Result<(), CoffeeEr plugin, verbose, dynamic, + branch, } => { let spinner = if !verbose { Some(term::spinner("Compiling and installing")) } else { None }; - let result = coffee.install(&plugin, verbose, dynamic).await; + let result = coffee.install(&plugin, verbose, dynamic, branch).await; if let Some(spinner) = spinner { if result.is_ok() { spinner.finish(); diff --git a/coffee_core/src/coffee.rs b/coffee_core/src/coffee.rs index b79c4b2..be1192a 100644 --- a/coffee_core/src/coffee.rs +++ b/coffee_core/src/coffee.rs @@ -15,6 +15,7 @@ use serde_json::json; use tokio::process::Command; use coffee_github::repository::Github; +use coffee_github::{git_upgrade_with_branch, git_upgrade_with_git_head}; use coffee_lib::errors::CoffeeError; use coffee_lib::plugin_manager::PluginManager; use coffee_lib::repository::Repository; @@ -246,6 +247,7 @@ impl PluginManager for CoffeeManager { plugin: &str, verbose: bool, try_dynamic: bool, + branch: Option, ) -> Result<(), CoffeeError> { log::debug!("installing plugin: {plugin}"); // keep track if the plugin is successfully installed @@ -253,6 +255,29 @@ impl PluginManager for CoffeeManager { if let Some(mut plugin) = repo.get_plugin_by_name(plugin) { log::trace!("{:?}", plugin); + // if the branch is specified, we need to save current git_head + // and checkout the branch + let current_git_head = repo.git_head(); + let current_plugin_commit = plugin.commit.clone(); + if let Some(branch) = branch.clone() { + log::debug!("checking out branch: {branch}"); + if current_git_head.is_none() { + return Err(error!("unable to get the current git head")); + } + let updated_status = + git_upgrade_with_branch(repo.url().path_string.as_str(), &branch, verbose) + .await?; + log::debug!("updated status: {:?}", updated_status); + + let repository = git2::Repository::open(repo.url().path_string.as_str()) + .map_err(|err| error!("{}", err.message()))?; + + // get commit id after the checkout + let commit = commit_id!(repository); + log::debug!("commit id after the checkout: {commit}"); + plugin.commit = Some(commit); + } + // old_root_path is the path where the plugin is cloned and currently stored // eg. ~/.coffee/repositories// let old_root_path = plugin.root_path.clone(); @@ -291,7 +316,7 @@ impl PluginManager for CoffeeManager { if !try_dynamic { // mark the plugin enabled plugin.enabled = Some(true); - self.config.plugins.push(plugin); + self.config.plugins.push(plugin.clone()); log::debug!("path coffee conf: {}", self.coffee_cln_config.path); self.coffee_cln_config .add_conf("plugin", &path.to_owned()) @@ -302,6 +327,23 @@ impl PluginManager for CoffeeManager { } else { self.start_plugin(&path).await?; } + + if branch.is_some() { + // we can safely unwrap here because we have checked it before + let current_git_head = current_git_head.unwrap(); + log::debug!("checking out to git head: {current_git_head}"); + // rollback to the previous git head + let updated_status = git_upgrade_with_git_head( + repo.url().path_string.as_str(), + ¤t_git_head, + verbose, + ) + .await?; + log::debug!("updated status: {:?}", updated_status); + + plugin.commit = current_plugin_commit; + } + return Ok(()); } None => return Err(error!("exec path not found")), @@ -360,7 +402,7 @@ impl PluginManager for CoffeeManager { UpgradeStatus::Updated(_, _) => { for plugins in status.plugins_effected.iter() { self.remove(plugins).await?; - self.install(plugins, verbose, false).await?; + self.install(plugins, verbose, false, None).await?; } } _ => {} diff --git a/coffee_core/src/lib.rs b/coffee_core/src/lib.rs index d237d49..ce1281e 100644 --- a/coffee_core/src/lib.rs +++ b/coffee_core/src/lib.rs @@ -8,7 +8,7 @@ pub use coffee_lib as lib; #[derive(Clone, Debug)] pub enum CoffeeOperation { /// Install(plugin name, verbose run, dynamic installation) - Install(String, bool, bool), + Install(String, bool, bool, Option), /// List List, // Upgrade(name of the repository, verbose run) diff --git a/coffee_github/src/lib.rs b/coffee_github/src/lib.rs index 987ca5e..e464d26 100644 --- a/coffee_github/src/lib.rs +++ b/coffee_github/src/lib.rs @@ -3,6 +3,8 @@ pub mod repository; mod utils; +pub use utils::{git_upgrade_with_branch, git_upgrade_with_git_head}; + #[cfg(test)] mod tests { use std::{path::Path, sync::Once}; diff --git a/coffee_github/src/utils.rs b/coffee_github/src/utils.rs index 269354a..0d2ef2e 100644 --- a/coffee_github/src/utils.rs +++ b/coffee_github/src/utils.rs @@ -25,7 +25,7 @@ pub async fn clone_recursive_fix(repo: git2::Repository, url: &URL) -> Result<() Ok(()) } -pub async fn git_upgrade( +pub async fn git_upgrade_with_branch( path: &str, branch: &str, verbose: bool, @@ -48,3 +48,27 @@ pub async fn git_upgrade( Ok(UpgradeStatus::Updated(upstream_commit, date)) } } + +pub async fn git_upgrade_with_git_head( + path: &str, + git_head: &str, + verbose: bool, +) -> Result { + use tokio::process::Command; + + let repo = git2::Repository::open(path).map_err(|err| error!("{}", err.message()))?; + + let (local_commit, _) = get_repo_info!(repo); + + let mut cmd = format!("git fetch origin\n"); + cmd += &format!("git reset --hard {}", git_head); + sh!(path, cmd, verbose); + + let (upstream_commit, date) = get_repo_info!(repo); + + if local_commit == upstream_commit { + Ok(UpgradeStatus::UpToDate(upstream_commit, date)) + } else { + Ok(UpgradeStatus::Updated(upstream_commit, date)) + } +} diff --git a/coffee_httpd/src/httpd/server.rs b/coffee_httpd/src/httpd/server.rs index dc429fd..0644a6f 100644 --- a/coffee_httpd/src/httpd/server.rs +++ b/coffee_httpd/src/httpd/server.rs @@ -93,7 +93,7 @@ async fn coffee_install( let try_dynamic = body.try_dynamic; let mut coffee = data.coffee.lock().await; - let result = coffee.install(plugin, false, try_dynamic).await; + let result = coffee.install(plugin, false, try_dynamic, None).await; handle_httpd_response!(result, "Plugin '{plugin}' installed successfully") } diff --git a/coffee_lib/src/plugin_manager.rs b/coffee_lib/src/plugin_manager.rs index 103b77e..c8dcd99 100644 --- a/coffee_lib/src/plugin_manager.rs +++ b/coffee_lib/src/plugin_manager.rs @@ -16,6 +16,7 @@ pub trait PluginManager { plugins: &str, verbose: bool, try_dynamic: bool, + branch: Option, ) -> Result<(), CoffeeError>; // remove a plugin by name, return an error if some error happens. diff --git a/coffee_plugin/src/plugin/plugin_mod.rs b/coffee_plugin/src/plugin/plugin_mod.rs index 4f3fa40..db8f117 100644 --- a/coffee_plugin/src/plugin/plugin_mod.rs +++ b/coffee_plugin/src/plugin/plugin_mod.rs @@ -91,7 +91,7 @@ fn coffee_install(plugin: &mut Plugin, request: Value) -> Result anyhow::Result<()> { // This should install summary plugin for regtest network manager .coffee() - .install("summary", true, true) + .install("summary", true, true, None) .await .unwrap(); // Ensure that summary is installed for regtest network @@ -393,7 +393,7 @@ pub async fn install_plugin_in_two_networks() -> anyhow::Result<()> { // This should install summary plugin for testnet network manager .coffee() - .install("summary", true, true) + .install("summary", true, true, None) .await .unwrap(); // Ensure that summary is installed for testnet network @@ -428,13 +428,13 @@ pub async fn test_double_slash() { .unwrap(); // Install summary plugin - let result = manager.coffee().install("summary", true, false).await; + let result = manager.coffee().install("summary", true, false, None).await; assert!(result.is_ok(), "{:?}", result); // Install helpme plugin manager .coffee() - .install("helpme", true, false) + .install("helpme", true, false, None) .await .unwrap(); @@ -493,7 +493,7 @@ pub async fn test_plugin_installation_path() { // Install summary plugin for regtest network manager .coffee() - .install("summary", true, false) + .install("summary", true, false, None) .await .unwrap(); From 7e301a291e76399f1ccc632d6e29861f0705c1d3 Mon Sep 17 00:00:00 2001 From: Tarek Date: Fri, 23 Feb 2024 22:50:34 +0200 Subject: [PATCH 3/4] feat(cmd): print git HEAD of the plugin with coffee list Since we now may have separate git heads for plugin and repository, we print the git HEAD of the plugin to the user Signed-off-by: Tarek --- coffee_cmd/src/coffee_term/command_show.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/coffee_cmd/src/coffee_term/command_show.rs b/coffee_cmd/src/coffee_term/command_show.rs index 30b6011..b18a8ab 100644 --- a/coffee_cmd/src/coffee_term/command_show.rs +++ b/coffee_cmd/src/coffee_term/command_show.rs @@ -22,6 +22,7 @@ pub fn show_list(coffee_list: Result) -> Result<(), Cof term::format::bold(String::from("Language")), term::format::bold(String::from("Name")), term::format::bold(String::from("Enabled")), + term::format::bold(String::from("Git HEAD")), term::format::bold(String::from("Exec path")), ]); table.divider(); @@ -30,6 +31,8 @@ pub fn show_list(coffee_list: Result) -> Result<(), Cof // Get whether the plugin is enabled // If enabled is None, it means the plugin is enabled by default for backward compatibility. let enabled = plugin.enabled.unwrap_or(true); + let mut commit = plugin.commit.clone().unwrap_or_default(); + commit = commit.chars().take(7).collect::(); table.push([ term::format::positive("●").into(), term::format::highlight(plugin.lang.to_string()), @@ -39,6 +42,7 @@ pub fn show_list(coffee_list: Result) -> Result<(), Cof } else { term::format::negative("no").into() }, + term::format::primary(commit), term::format::highlight(plugin.exec_path.to_owned()), ]) } From d77053b8a5954b4fe179bde87c733751fcddfd9b Mon Sep 17 00:00:00 2001 From: Tarek Date: Fri, 23 Feb 2024 23:00:54 +0200 Subject: [PATCH 4/4] docs: add docs for coffee install --branch Signed-off-by: Tarek --- docs/docs-book/src/using-coffee.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/docs-book/src/using-coffee.md b/docs/docs-book/src/using-coffee.md index 4a7e0d4..7f7720a 100644 --- a/docs/docs-book/src/using-coffee.md +++ b/docs/docs-book/src/using-coffee.md @@ -104,6 +104,14 @@ To install a plugin statically, you simply need to run. coffee install ``` +#### Specifying a branch for installation + +To install a plugin from a specific branch, you simply need to run. + +```bash +coffee install --branch +``` + ### Removing a Plugin > ✅ Implemented