diff --git a/coffee_cmd/src/cmd.rs b/coffee_cmd/src/cmd.rs index cf1a474d..bee88288 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/coffee_term/command_show.rs b/coffee_cmd/src/coffee_term/command_show.rs index 30b6011f..b18a8ab5 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()), ]) } diff --git a/coffee_cmd/src/main.rs b/coffee_cmd/src/main.rs index d5708fac..91b80d61 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 b79c4b28..be1192a5 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 d237d49b..ce1281e5 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 987ca5eb..e464d26e 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/repository.rs b/coffee_github/src/repository.rs index be237e65..d4083ea1 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_github/src/utils.rs b/coffee_github/src/utils.rs index 269354a5..0d2ef2ec 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 dc429fd5..0644a6fa 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 103b77e1..c8dcd997 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_lib/src/repository.rs b/coffee_lib/src/repository.rs index dbb9bec7..bf247e04 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; } diff --git a/coffee_plugin/src/plugin/plugin_mod.rs b/coffee_plugin/src/plugin/plugin_mod.rs index 4f3fa403..db8f117d 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 ``` +#### 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 diff --git a/tests/src/coffee_integration_tests.rs b/tests/src/coffee_integration_tests.rs index 126af133..c9f4bd16 100644 --- a/tests/src/coffee_integration_tests.rs +++ b/tests/src/coffee_integration_tests.rs @@ -112,7 +112,7 @@ pub async fn init_coffee_test_add_remote() { .unwrap(); manager .coffee() - .install("summary", true, true) + .install("summary", true, true, None) .await .unwrap(); @@ -167,13 +167,13 @@ pub async fn test_add_remove_plugins() { ); // 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(); @@ -276,7 +276,7 @@ pub async fn test_errors_and_show() { ); // 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); // Get the README file for a plugin that is not installed @@ -285,7 +285,7 @@ pub async fn test_errors_and_show() { assert!(val.starts_with("# Helpme plugin")); // Install a plugin that is not in the repository - let result = manager.coffee().install("x", true, false).await; + let result = manager.coffee().install("x", true, false, None).await; assert!(result.is_err(), "{:?}", result); // Remove helpme plugin @@ -353,7 +353,7 @@ pub async fn install_plugin_in_two_networks() -> 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();