From e3e5f54540cbe7610d1ebfcae7c8f59c3c8d0858 Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Fri, 13 Jun 2025 18:35:59 +0200 Subject: [PATCH] feat: add option revision to install command (#3966) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Klaim (Joël Lamotte) <142265+Klaim@users.noreply.github.com> --- README.md | 1 - libmamba/include/mamba/api/install.hpp | 4 + libmamba/include/mamba/core/history.hpp | 5 +- libmamba/src/api/install.cpp | 118 +++++++++++++++++++++--- micromamba/src/install.cpp | 15 ++- micromamba/tests/test_install.py | 18 ++++ 6 files changed, 146 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 44360ae64..45722231c 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,6 @@ It can significantly reduce your CI setup time by: While `mamba` and `micromamba` are generally a drop-in replacement for `conda` there are some differences: -- `mamba` and `micromamba` do no support revisions (for discussions, see ) - `mamba` and `micromamba` normalize `MatchSpec` strings to the simplest form, whereas `conda` use a more verbose form This can lead to slight differences in the output of `conda env export` and `mamba env export`. diff --git a/libmamba/include/mamba/api/install.hpp b/libmamba/include/mamba/api/install.hpp index 60fe010b4..0306e2bcd 100644 --- a/libmamba/include/mamba/api/install.hpp +++ b/libmamba/include/mamba/api/install.hpp @@ -35,6 +35,8 @@ namespace mamba void install(Configuration& config); + void install_revision(Configuration& config, std::size_t revision); + void install_specs( Context& ctx, ChannelContext& channel_context, @@ -130,6 +132,8 @@ namespace mamba inline void to_json(nlohmann::json&, const other_pkg_mgr_spec&) { } + + void install_revision(Context& ctx, ChannelContext& channel_context, std::size_t revision); } } diff --git a/libmamba/include/mamba/core/history.hpp b/libmamba/include/mamba/core/history.hpp index 774de1f44..5aa79b85c 100644 --- a/libmamba/include/mamba/core/history.hpp +++ b/libmamba/include/mamba/core/history.hpp @@ -66,8 +66,9 @@ namespace mamba */ struct PackageDiff { - std::unordered_map removed_pkg_diff; - std::unordered_map installed_pkg_diff; + using package_diff_map = std::unordered_map; + package_diff_map removed_pkg_diff; + package_diff_map installed_pkg_diff; [[nodiscard]] static PackageDiff from_revision(const std::vector& user_requests, std::size_t target_revision); diff --git a/libmamba/src/api/install.cpp b/libmamba/src/api/install.cpp index 99d8ef4d6..8f6dcad45 100644 --- a/libmamba/src/api/install.cpp +++ b/libmamba/src/api/install.cpp @@ -14,6 +14,7 @@ #include "mamba/core/context.hpp" #include "mamba/core/env_lockfile.hpp" #include "mamba/core/environments_manager.hpp" +#include "mamba/core/history.hpp" #include "mamba/core/output.hpp" #include "mamba/core/package_cache.hpp" #include "mamba/core/package_database_loader.hpp" @@ -304,6 +305,24 @@ namespace mamba } } + void install_revision(Configuration& config, std::size_t revision) + { + config.at("use_target_prefix_fallback").set_value(true); + config.at("use_default_prefix_fallback").set_value(true); + config.at("use_root_prefix_fallback").set_value(true); + config.at("target_prefix_checks") + .set_value( + MAMBA_ALLOW_EXISTING_PREFIX | MAMBA_NOT_ALLOW_MISSING_PREFIX + | MAMBA_NOT_ALLOW_NOT_ENV_PREFIX | MAMBA_EXPECT_EXISTING_PREFIX + ); + config.load(); + + auto& context = config.context(); + auto channel_context = ChannelContext::make_conda_compatible(context); + + detail::install_revision(context, channel_context, revision); + } + void install(Configuration& config) { auto& ctx = config.context(); @@ -542,18 +561,18 @@ namespace mamba }; add_spdlog_logger_to_database(db); - auto exp_load = load_channels(ctx, channel_context, db, package_caches); - if (!exp_load) + auto maybe_load = load_channels(ctx, channel_context, db, package_caches); + if (!maybe_load) { - throw std::runtime_error(exp_load.error().what()); + throw std::runtime_error(maybe_load.error().what()); } - auto exp_prefix_data = PrefixData::create(ctx.prefix_params.target_prefix, channel_context); - if (!exp_prefix_data) + auto maybe_prefix_data = PrefixData::create(ctx.prefix_params.target_prefix, channel_context); + if (!maybe_prefix_data) { - throw std::runtime_error(exp_prefix_data.error().what()); + throw std::runtime_error(maybe_prefix_data.error().what()); } - PrefixData& prefix_data = exp_prefix_data.value(); + PrefixData& prefix_data = maybe_prefix_data.value(); load_installed_packages_in_database(ctx, db, prefix_data); @@ -741,15 +760,15 @@ namespace mamba // context. We need to create channels from the specs to be able // to download packages. init_channels_from_package_urls(ctx, channel_context, specs); - auto exp_prefix_data = PrefixData::create(ctx.prefix_params.target_prefix, channel_context); - if (!exp_prefix_data) + auto maybe_prefix_data = PrefixData::create(ctx.prefix_params.target_prefix, channel_context); + if (!maybe_prefix_data) { // TODO: propagate tl::expected mechanism throw std::runtime_error( - fmt::format("could not load prefix data: {}", exp_prefix_data.error().what()) + fmt::format("could not load prefix data: {}", maybe_prefix_data.error().what()) ); } - PrefixData& prefix_data = exp_prefix_data.value(); + PrefixData& prefix_data = maybe_prefix_data.value(); MultiPackageCache pkg_caches(ctx.pkgs_dirs, ctx.validation_params); @@ -1164,5 +1183,82 @@ namespace mamba } } } + + void + get_all_pkg_info(PackageDiff::package_diff_map::value_type& pkg, solver::libsolv::Database& db) + { + const auto ms = pkg.second.name + "==" + pkg.second.version + " =" + + pkg.second.build_string; + db.for_each_package_matching( + specs::MatchSpec::parse(ms).value(), + [&](specs::PackageInfo&& pkg_info) { pkg.second = pkg_info; } + ); + } + + void + install_revision(Context& ctx, ChannelContext& channel_context, std::size_t target_revision) + { + auto maybe_prefix_data = PrefixData::create(ctx.prefix_params.target_prefix, channel_context); + if (!maybe_prefix_data) + { + throw std::runtime_error(maybe_prefix_data.error().what()); + } + PrefixData& prefix_data = maybe_prefix_data.value(); + const auto user_requests = prefix_data.history().get_user_requests(); + + PackageDiff pkg_diff = PackageDiff::from_revision(user_requests, target_revision); + auto removed_pkg_diff = pkg_diff.removed_pkg_diff; + auto installed_pkg_diff = pkg_diff.installed_pkg_diff; + + MultiPackageCache package_caches{ ctx.pkgs_dirs, ctx.validation_params }; + + solver::libsolv::Database db{ channel_context.params() }; + add_spdlog_logger_to_database(db); + + auto maybe_load = load_channels(ctx, channel_context, db, package_caches); + if (!maybe_load) + { + throw std::runtime_error(maybe_load.error().what()); + } + + load_installed_packages_in_database(ctx, db, prefix_data); + + for (auto& pkg : removed_pkg_diff) + { + get_all_pkg_info(pkg, db); + } + for (auto& pkg : installed_pkg_diff) + { + get_all_pkg_info(pkg, db); + } + + auto execute_transaction = [&](MTransaction& transaction) + { + if (ctx.output_params.json) + { + transaction.log_json(); + } + + auto prompt_entry = transaction.prompt(ctx, channel_context); + if (prompt_entry) + { + transaction.execute(ctx, channel_context, prefix_data); + } + return prompt_entry; + }; + + std::vector pkgs_to_remove; + std::vector pkgs_to_install; + for (const auto& [_, pkg] : installed_pkg_diff) + { + pkgs_to_remove.push_back(pkg); + } + for (const auto& [_, pkg] : removed_pkg_diff) + { + pkgs_to_install.push_back(pkg); + } + auto transaction = MTransaction(ctx, db, pkgs_to_remove, pkgs_to_install, package_caches); + execute_transaction(transaction); + } } // detail } // mamba diff --git a/micromamba/src/install.cpp b/micromamba/src/install.cpp index 56e095340..ffdfc1bd2 100644 --- a/micromamba/src/install.cpp +++ b/micromamba/src/install.cpp @@ -1,3 +1,5 @@ +#include + #include "mamba/api/configuration.hpp" #include "mamba/api/install.hpp" @@ -24,5 +26,16 @@ set_install_command(CLI::App* subcom, Configuration& config) force_reinstall.description() ); - subcom->callback([&] { return mamba::install(config); }); + static std::optional revision = std::nullopt; + subcom->callback( + [&] + { + if (revision.has_value()) + { + return mamba::install_revision(config, revision.value()); + } + return mamba::install(config); + } + ); + subcom->add_option("--revision", revision, "Revert to the specified revision."); } diff --git a/micromamba/tests/test_install.py b/micromamba/tests/test_install.py index d5e58dc99..79ec201b2 100644 --- a/micromamba/tests/test_install.py +++ b/micromamba/tests/test_install.py @@ -7,6 +7,7 @@ from pathlib import Path from packaging.version import Version import pytest +import re # Need to import everything to get fixtures from .helpers import * # noqa: F403 @@ -843,3 +844,20 @@ def test_dry_run_pip_section(tmp_home, tmp_root_prefix, tmp_path): # Check that the packages are not installed using `pip` res = helpers.umamba_run("-p", env_prefix, "pip", "list") assert "numpy" not in res + + +def test_install_revision(tmp_home, tmp_root_prefix): + env_name = "myenv" + helpers.create("-n", env_name, "python=3.8") + helpers.install("-n", env_name, "xtl=0.7.2", "nlohmann_json=3.12.0") + helpers.update("-n", env_name, "xtl") + helpers.uninstall("-n", env_name, "nlohmann_json") + helpers.install("-n", env_name, "--revision", "1") + res = helpers.umamba_list( + "-n", + env_name, + ) + + xtl_regex = re.compile(r"xtl\s+0.7.2") + assert xtl_regex.search(res) + assert "nlohmann_json" in res