//! ref: composer/src/Composer/Command/UpdateCommand.php use crate::advisory::Auditor; use crate::command::BumpCommand; use crate::command::base_command::base_command_initialize; use crate::command::{BaseCommand, BaseCommandData}; use crate::composer::PartialComposerHandle; use crate::console::input::InputArgument; use crate::console::input::InputOption; use crate::dependency_resolver::request::UpdateAllowTransitiveDeps; use crate::installer::Installer; use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; use crate::io::io_interface; use crate::package::base_package; use crate::package::loader::RootPackageLoader; use crate::package::version::VersionParser; use crate::package::version::VersionSelector; use crate::plugin::CommandEvent; use crate::plugin::PluginEvents; use crate::repository::CanonicalPackagesTrait; use crate::repository::CompositeRepository; use crate::repository::PlatformRepository; use crate::repository::RepositorySet; use crate::util::HttpDownloader; use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::Preg; use shirabe_external_packages::symfony::console::command::command::Command; use shirabe_external_packages::symfony::console::helper::Table; use shirabe_external_packages::symfony::console::input::InputInterface; use shirabe_external_packages::symfony::console::output::OutputInterface; use shirabe_php_shim::{ InvalidArgumentException, PhpMixed, RuntimeException, array_filter, array_intersect, array_keys, array_merge_map, array_search_in_vec, in_array, strtolower, }; use shirabe_semver::Intervals; use shirabe_semver::constraint::MultiConstraint; use std::cell::RefCell; use std::rc::Rc; #[derive(Debug)] pub struct UpdateCommand { base_command_data: BaseCommandData, } impl Default for UpdateCommand { fn default() -> Self { Self::new() } } impl UpdateCommand { pub fn new() -> Self { let command = UpdateCommand { base_command_data: BaseCommandData::new(None), }; command .configure() .expect("UpdateCommand::configure uses static, valid metadata"); command } } impl Command for UpdateCommand { fn configure(&self) -> anyhow::Result<()> { // TODO(cli-completion): suggest_installed_package(false, true) / suggest_prefer_install self.set_name("update")?; self.set_aliases(vec!["u".to_string(), "upgrade".to_string()])?; self.set_description("Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file"); self.set_definition(&[ InputArgument::new("packages", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Packages that should be updated, if not provided all packages are.", None).unwrap().into(), InputOption::new("with", None, Some(InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED), "Temporary version constraint to add, e.g. foo/bar:1.0.0 or foo/bar=1.0.0", None).unwrap().into(), InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None).unwrap().into(), InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None).unwrap().into(), InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None).unwrap().into(), InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything (implicitly enables --verbose).", None).unwrap().into(), InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).", None).unwrap().into(), InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables installation of require-dev packages.", None).unwrap().into(), InputOption::new("lock", None, Some(InputOption::VALUE_NONE), "Overwrites the lock file hash to suppress warning about the lock file being out of date without updating package versions. Package metadata like mirrors and URLs are updated if they changed.", None).unwrap().into(), InputOption::new("no-install", None, Some(InputOption::VALUE_NONE), "Skip the install step after updating the composer.lock file.", None).unwrap().into(), InputOption::new("no-audit", None, Some(InputOption::VALUE_NONE), "Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).", None).unwrap().into(), InputOption::new("audit-format", None, Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string()))).unwrap().into(), InputOption::new("no-security-blocking", None, Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).", None).unwrap().into(), InputOption::new("no-autoloader", None, Some(InputOption::VALUE_NONE), "Skips autoloader generation", None).unwrap().into(), InputOption::new("no-suggest", None, Some(InputOption::VALUE_NONE), "DEPRECATED: This flag does not exist anymore.", None).unwrap().into(), InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None).unwrap().into(), InputOption::new("with-dependencies", Some(PhpMixed::String("w".to_string())), Some(InputOption::VALUE_NONE), "Update also dependencies of packages in the argument list, except those which are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).", None).unwrap().into(), InputOption::new("with-all-dependencies", Some(PhpMixed::String("W".to_string())), Some(InputOption::VALUE_NONE), "Update also dependencies of packages in the argument list, including those which are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).", None).unwrap().into(), InputOption::new("verbose", Some(PhpMixed::String("v|vv|vvv".to_string())), Some(InputOption::VALUE_NONE), "Shows more details including new commits pulled in when updating packages.", None).unwrap().into(), InputOption::new("optimize-autoloader", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump.", None).unwrap().into(), InputOption::new("classmap-authoritative", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None).unwrap().into(), InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None).unwrap().into(), InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None).unwrap().into(), InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None).unwrap().into(), InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None).unwrap().into(), InputOption::new("prefer-stable", None, Some(InputOption::VALUE_NONE), "Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).", None).unwrap().into(), InputOption::new("prefer-lowest", None, Some(InputOption::VALUE_NONE), "Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).", None).unwrap().into(), InputOption::new("minimal-changes", Some(PhpMixed::String("m".to_string())), Some(InputOption::VALUE_NONE), "Only perform absolutely necessary changes to dependencies. If packages cannot be kept at their currently locked version they are updated. For partial updates the allow-listed packages are always updated fully. (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).", None).unwrap().into(), InputOption::new("patch-only", None, Some(InputOption::VALUE_NONE), "Only allow patch version updates for currently installed dependencies.", None).unwrap().into(), InputOption::new("interactive", Some(PhpMixed::String("i".to_string())), Some(InputOption::VALUE_NONE), "Interactive interface with autocompletion to select the packages to update.", None).unwrap().into(), InputOption::new("root-reqs", None, Some(InputOption::VALUE_NONE), "Restricts the update to your first degree dependencies.", None).unwrap().into(), InputOption::new("bump-after-update", None, Some(InputOption::VALUE_OPTIONAL), "Runs bump after performing the update.", Some(PhpMixed::Bool(false))).unwrap().into(), ]); self.set_help( "The update command reads the composer.json file from the\n\ current directory, processes it, and updates, removes or installs all the\n\ dependencies.\n\n\ shirabe update\n\n\ To limit the update operation to a few packages, you can list the package(s)\n\ you want to update as such:\n\n\ shirabe update vendor/package1 foo/mypackage [...]\n\n\ You may also use an asterisk (*) pattern to limit the update operation to package(s)\n\ from a specific vendor:\n\n\ shirabe update vendor/package1 foo/* [...]\n\n\ To run an update with more restrictive constraints you can use:\n\n\ shirabe update --with vendor/package:1.0.*\n\n\ To run a partial update with more restrictive constraints you can use the shorthand:\n\n\ shirabe update vendor/package:1.0.*\n\n\ To select packages names interactively with auto-completion use -i.\n\n\ Read more at https://getcomposer.org/doc/03-cli.md#update-u-upgrade\n", ); Ok(()) } fn execute( &self, input: Rc>, output: Rc>, ) -> anyhow::Result { let io = self.get_io().clone(); if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) { io.write_error3( "You are using the deprecated option \"--dev\". It has no effect and will break in Composer 3.", true, io_interface::NORMAL, ); } if input .borrow() .get_option("no-suggest")? .as_bool() .unwrap_or(false) { io.write_error3( "You are using the deprecated option \"--no-suggest\". It has no effect and will break in Composer 3.", true, io_interface::NORMAL, ); } let composer_handle = self.require_composer(None, None)?; let composer = crate::composer::composer_full(&composer_handle); if !HttpDownloader::is_curl_enabled() { io.write_error3( "Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.", true, io_interface::NORMAL, ); } let mut packages: Vec = input .borrow() .get_argument("packages")? .as_list() .map(|l| { l.iter() .filter_map(|v| v.as_string().map(|s| s.to_string())) .collect() }) .unwrap_or_default(); let mut reqs: IndexMap = self.format_requirements( input .borrow() .get_option("with")? .as_list() .map(|l| { l.iter() .filter_map(|v| v.as_string().map(|s| s.to_string())) .collect() }) .unwrap_or_default(), )?; // extract --with shorthands from the allowlist if !packages.is_empty() { let allowlist_packages_with_requirements: Vec = array_filter(&packages, |pkg: &String| -> bool { Preg::is_match(r"{\S+[ =:]\S+}", pkg) }); for (package, constraint) in self.format_requirements(allowlist_packages_with_requirements.clone())? { reqs.insert(package, constraint); } // replace the foo/bar:req by foo/bar in the allowlist for package in &allowlist_packages_with_requirements { let package_name = Preg::replace(r"{^([^ =:]+)[ =:].*$}", "$1", package); if let Some(idx) = array_search_in_vec(package, &packages) { packages[idx] = package_name; } } } let root_package = composer.get_package(); root_package.set_references(RootPackageLoader::extract_references( &reqs, root_package.get_references().clone(), )); root_package.set_stability_flags(RootPackageLoader::extract_stability_flags( &reqs, &root_package.get_minimum_stability(), root_package.get_stability_flags().clone(), )); let parser = VersionParser::new(); let mut temporary_constraints: IndexMap = IndexMap::new(); let root_requirements = array_merge_map(root_package.get_requires(), root_package.get_dev_requires()); for (package, constraint) in &reqs { let package = strtolower(package); let parsed_constraint = parser.parse_constraints(constraint)?; temporary_constraints.insert(package.clone(), parsed_constraint.clone()); if let Some(root_req) = root_requirements.get(&package) && !Intervals::have_intersections(&parsed_constraint, root_req.get_constraint())? { io.write_error3( &format!( "The temporary constraint \"{}\" for \"{}\" must be a subset of the constraint in your composer.json ({})", constraint, package, root_req.get_pretty_constraint(), ), true, io_interface::NORMAL, ); io.write(&format!( "Run `composer require {}` or `composer require {}:{}` instead to replace the constraint", package, package, constraint, )); return Ok(crate::command::FAILURE); } } if input .borrow() .get_option("patch-only")? .as_bool() .unwrap_or(false) { if !composer.get_locker().borrow_mut().is_locked() { return Err(InvalidArgumentException { message: "patch-only can only be used with a lock file present".to_string(), code: 0, } .into()); } for package in composer .get_locker() .borrow_mut() .get_locked_repository(true)? .borrow_mut() .get_canonical_packages()? { if package.is_dev() { continue; } let matches = Preg::is_match_with_indexed_captures( r"{^(\d+\.\d+\.\d+)}", &package.get_version(), ); let Some(matches) = matches else { continue; }; let constraint = parser.parse_constraints(&format!( "~{}", matches.get(1).cloned().unwrap_or_default() ))?; if let Some(existing) = temporary_constraints.get(&package.get_name()) { temporary_constraints.insert( package.get_name(), MultiConstraint::create(vec![existing.clone(), constraint], true, None)?, ); } else { temporary_constraints.insert(package.get_name(), constraint); } } } if input .borrow() .get_option("interactive")? .as_bool() .unwrap_or(false) { packages = self.get_packages_interactively( io.clone(), input.clone(), output.clone(), &composer_handle, packages, )?; } if input .borrow() .get_option("root-reqs")? .as_bool() .unwrap_or(false) { let mut requires: Vec = array_keys(&root_package.get_requires()); if !input .borrow() .get_option("no-dev")? .as_bool() .unwrap_or(false) { requires.extend(array_keys(&root_package.get_dev_requires())); } if !packages.is_empty() { packages = array_intersect(&packages, &requires); } else { packages = requires; } } // the arguments lock/nothing/mirrors are not package names but trigger a mirror update instead // they are further mutually exclusive with listing actual package names let filtered_packages: Vec = array_filter(&packages, |package: &String| -> bool { !in_array( PhpMixed::String(package.clone()), &PhpMixed::List(vec![ PhpMixed::String("lock".to_string()), PhpMixed::String("nothing".to_string()), PhpMixed::String("mirrors".to_string()), ]), true, ) }); let update_mirrors = input .borrow() .get_option("lock")? .as_bool() .unwrap_or(false) || filtered_packages.len() != packages.len(); packages = filtered_packages; if update_mirrors && !packages.is_empty() { io.write_error3("You cannot simultaneously update only a selection of packages and regenerate the lock file metadata.", true, io_interface::NORMAL); return Ok(-1); } let mut command_event = CommandEvent::new(PluginEvents::COMMAND, "update", input.clone(), output); let command_event_name = command_event.get_name().to_string(); composer .get_event_dispatcher() .borrow_mut() .dispatch(Some(&command_event_name), Some(&mut command_event))?; composer .get_installation_manager() .borrow_mut() .set_output_progress( !input .borrow() .get_option("no-progress")? .as_bool() .unwrap_or(false), ); let mut install = Installer::create(io.clone(), &composer_handle); let config = composer.get_config(); let (prefer_source, prefer_dist) = self.get_preferred_install_options(&config.borrow(), input.clone(), false)?; let optimize = input .borrow() .get_option("optimize-autoloader")? .as_bool() .unwrap_or(false) || config .borrow_mut() .get("optimize-autoloader") .as_bool() .unwrap_or(false); let authoritative = input .borrow() .get_option("classmap-authoritative")? .as_bool() .unwrap_or(false) || config .borrow_mut() .get("classmap-authoritative") .as_bool() .unwrap_or(false); let apcu_prefix: Option = input .borrow() .get_option("apcu-autoloader-prefix")? .as_string() .map(|s| s.to_string()); let apcu = apcu_prefix.is_some() || input .borrow() .get_option("apcu-autoloader")? .as_bool() .unwrap_or(false) || config .borrow_mut() .get("apcu-autoloader") .as_bool() .unwrap_or(false); let minimal_changes = input .borrow() .get_option("minimal-changes")? .as_bool() .unwrap_or(false) || config .borrow_mut() .get("update-with-minimal-changes") .as_bool() .unwrap_or(false); let mut update_allow_transitive_dependencies = UpdateAllowTransitiveDeps::UpdateOnlyListed; if input .borrow() .get_option("with-all-dependencies")? .as_bool() .unwrap_or(false) { update_allow_transitive_dependencies = UpdateAllowTransitiveDeps::UpdateListedWithTransitiveDeps; } else if input .borrow() .get_option("with-dependencies")? .as_bool() .unwrap_or(false) { update_allow_transitive_dependencies = UpdateAllowTransitiveDeps::UpdateListedWithTransitiveDepsNoRootRequire; } install .set_dry_run( input .borrow() .get_option("dry-run")? .as_bool() .unwrap_or(false), ) .set_verbose( input .borrow() .get_option("verbose")? .as_bool() .unwrap_or(false), ) .set_prefer_source(prefer_source) .set_prefer_dist(prefer_dist) .set_dev_mode( !input .borrow() .get_option("no-dev")? .as_bool() .unwrap_or(false), ) .set_dump_autoloader( !input .borrow() .get_option("no-autoloader")? .as_bool() .unwrap_or(false), ) .set_optimize_autoloader(optimize) .set_class_map_authoritative(authoritative) .set_apcu_autoloader(apcu, apcu_prefix.clone()) .set_update(true) .set_install( !input .borrow() .get_option("no-install")? .as_bool() .unwrap_or(false), ) .set_update_mirrors(update_mirrors) .set_update_allow_list(packages.clone()) .set_update_allow_transitive_dependencies(update_allow_transitive_dependencies)? .set_platform_requirement_filter(self.get_platform_requirement_filter(input.clone())?) .set_prefer_stable( input .borrow() .get_option("prefer-stable")? .as_bool() .unwrap_or(false), ) .set_prefer_lowest( input .borrow() .get_option("prefer-lowest")? .as_bool() .unwrap_or(false), ) .set_temporary_constraints(temporary_constraints) .set_audit_config( self.create_audit_config(&mut composer.get_config().borrow_mut(), input.clone())?, ) .set_minimal_update(minimal_changes); if input .borrow() .get_option("no-plugins")? .as_bool() .unwrap_or(false) { install.disable_plugins(); } let mut result = install.run()?; if result == 0 && !input .borrow() .get_option("lock")? .as_bool() .unwrap_or(false) { let mut bump_after_update = input.borrow().get_option("bump-after-update")?; // PHP: false === $bumpAfterUpdate (strict) if matches!(bump_after_update, PhpMixed::Bool(false)) { bump_after_update = composer.get_config().borrow().get("bump-after-update"); } if !matches!(bump_after_update, PhpMixed::Bool(false)) { io.write_error3( "Bumping dependencies", true, io_interface::NORMAL, ); let bump_command = BumpCommand::new(); bump_command.set_composer(composer_handle.clone()); result = bump_command.do_bump( io.clone(), bump_after_update.as_string() == Some("dev"), bump_after_update.as_string() == Some("no-dev"), input .borrow() .get_option("dry-run")? .as_bool() .unwrap_or(false), input .borrow() .get_argument("packages")? .as_list() .map(|l| { l.iter() .filter_map(|v| v.as_string().map(|s| s.to_string())) .collect() }) .unwrap_or_default(), "--bump-after-update=dev".to_string(), )?; } } Ok(result) } fn initialize( &self, input: Rc>, output: Rc>, ) -> anyhow::Result<()> { base_command_initialize(self, input, output) } shirabe_external_packages::delegate_command_trait_impls_to_inner!(base_command_data); } impl BaseCommand for UpdateCommand { fn command_data( &self, ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { self.base_command_data.command_data() } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); } impl UpdateCommand { /// @param array $packages /// @return array fn get_packages_interactively( &self, io: std::rc::Rc>, input: std::rc::Rc>, output: std::rc::Rc>, composer: &PartialComposerHandle, packages: Vec, ) -> Result> { if !input.borrow().is_interactive() { return Err(InvalidArgumentException { message: "--interactive cannot be used in non-interactive terminals.".to_string(), code: 0, } .into()); } let composer_ref = crate::composer::composer_full(composer); let platform_req_filter = self.get_platform_requirement_filter(input); let stability_flags = composer_ref.get_package().get_stability_flags(); let requires = array_merge_map( composer_ref.get_package().get_requires(), composer_ref.get_package().get_dev_requires(), ); let filter: Option = if !packages.is_empty() { Some(base_package::package_names_to_regexp(&packages, "%s")) } else { None }; io.write_error3( "Loading packages that can be updated...", true, io_interface::NORMAL, ); let mut autocompleter_values: IndexMap = IndexMap::new(); let installed_packages: Vec = if composer_ref.get_locker().borrow_mut().is_locked() { let locked_repo = composer_ref .get_locker() .borrow_mut() .get_locked_repository(true)?; locked_repo.borrow_mut().get_canonical_packages()? } else { composer_ref .get_repository_manager() .borrow() .get_local_repository() .get_packages()? }; let mut version_selector = self.create_version_selector(composer)?; for package in &installed_packages { if let Some(filter) = &filter && !Preg::is_match(filter, &package.get_name()) { continue; } let current_version = package.get_pretty_version(); let constraint = requires .get(&package.get_name()) .map(|link| link.get_pretty_constraint()); let stability = match stability_flags.get(&package.get_name()) { Some(flag) => base_package::STABILITIES .iter() .find(|&(_, v)| v == flag) .map(|(k, _)| k.to_string()) .unwrap_or_default(), None => composer_ref.get_package().get_minimum_stability(), }; let latest_version = version_selector.find_best_candidate( &package.get_name(), constraint, &stability, None, 0, None, PhpMixed::Bool(true), )?; let _ = &platform_req_filter; if let Some(latest) = latest_version && (package.get_version() != latest.get_version() || latest.is_dev()) { autocompleter_values.insert( package.get_name(), format!( "{} => {}", current_version, latest.get_pretty_version(), ), ); } } if installed_packages.is_empty() { for (req, _constraint) in &requires { if PlatformRepository::is_platform_package(req) { continue; } autocompleter_values.insert(req.to_string(), String::new()); } } if autocompleter_values.is_empty() { return Err(RuntimeException { message: "Could not find any package with new versions available".to_string(), code: 0, } .into()); } let select_result = io.select( "Select packages: (Select more than one value separated by comma) ".to_string(), autocompleter_values .keys() .cloned() .collect::>(), PhpMixed::Bool(false), PhpMixed::Int(1), "No package named \"%s\" is installed.".to_string(), true, ); let packages: Vec = match select_result { PhpMixed::List(l) => l .into_iter() .filter_map(|v| v.as_string().map(|s| s.to_string())) .collect(), _ => Vec::new(), }; let mut table = Table::new(output); table.set_headers(vec!["Selected packages".into()]); for package in &packages { table.add_row(PhpMixed::List(vec![PhpMixed::String(package.clone())]).into()); } table.render(); if io.ask_confirmation( format!( "Would you like to continue and update the above package{} [yes]? ", if 1 == packages.len() { "" } else { "s" }, ), true, ) { return Ok(packages); } Err(RuntimeException { message: "Installation aborted.".to_string(), code: 0, } .into()) } fn create_version_selector(&self, composer: &PartialComposerHandle) -> Result { let composer = crate::composer::composer_full(composer); let root_aliases: Vec = composer .get_package() .get_aliases() .into_iter() .map(|alias| crate::repository::RootAliasInput { package: alias.get("package").cloned().unwrap_or_default(), version: alias.get("version").cloned().unwrap_or_default(), alias: alias.get("alias").cloned().unwrap_or_default(), alias_normalized: alias.get("alias_normalized").cloned().unwrap_or_default(), }) .collect(); let mut repository_set = RepositorySet::new( &composer.get_package().get_minimum_stability(), composer.get_package().get_stability_flags().clone(), root_aliases, composer.get_package().get_references().clone(), IndexMap::new(), IndexMap::new(), ); let repositories: Vec = composer .get_repository_manager() .borrow() .get_repositories() .iter() .filter(|repository| !repository.is::()) .cloned() .collect(); repository_set.add_repository(crate::repository::RepositoryInterfaceHandle::new( CompositeRepository::new(repositories), ))?; VersionSelector::new( std::rc::Rc::new(std::cell::RefCell::new(repository_set)), None, ) } }