feat: add option revision to install command (#3966)

Co-authored-by: Klaim (Joël Lamotte) <142265+Klaim@users.noreply.github.com>
This commit is contained in:
Sandrine Pataut 2025-06-13 18:35:59 +02:00 committed by GitHub
parent 2c3ddf1eef
commit e3e5f54540
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 146 additions and 15 deletions

View File

@ -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: 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 <https://github.com/mamba-org/mamba/issues/803>)
- `mamba` and `micromamba` normalize `MatchSpec` strings to the simplest form, whereas `conda` use a more verbose form - `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`. This can lead to slight differences in the output of `conda env export` and `mamba env export`.

View File

@ -35,6 +35,8 @@ namespace mamba
void install(Configuration& config); void install(Configuration& config);
void install_revision(Configuration& config, std::size_t revision);
void install_specs( void install_specs(
Context& ctx, Context& ctx,
ChannelContext& channel_context, ChannelContext& channel_context,
@ -130,6 +132,8 @@ namespace mamba
inline void to_json(nlohmann::json&, const other_pkg_mgr_spec&) inline void to_json(nlohmann::json&, const other_pkg_mgr_spec&)
{ {
} }
void install_revision(Context& ctx, ChannelContext& channel_context, std::size_t revision);
} }
} }

View File

@ -66,8 +66,9 @@ namespace mamba
*/ */
struct PackageDiff struct PackageDiff
{ {
std::unordered_map<std::string, specs::PackageInfo> removed_pkg_diff; using package_diff_map = std::unordered_map<std::string, specs::PackageInfo>;
std::unordered_map<std::string, specs::PackageInfo> installed_pkg_diff; package_diff_map removed_pkg_diff;
package_diff_map installed_pkg_diff;
[[nodiscard]] static PackageDiff [[nodiscard]] static PackageDiff
from_revision(const std::vector<History::UserRequest>& user_requests, std::size_t target_revision); from_revision(const std::vector<History::UserRequest>& user_requests, std::size_t target_revision);

View File

@ -14,6 +14,7 @@
#include "mamba/core/context.hpp" #include "mamba/core/context.hpp"
#include "mamba/core/env_lockfile.hpp" #include "mamba/core/env_lockfile.hpp"
#include "mamba/core/environments_manager.hpp" #include "mamba/core/environments_manager.hpp"
#include "mamba/core/history.hpp"
#include "mamba/core/output.hpp" #include "mamba/core/output.hpp"
#include "mamba/core/package_cache.hpp" #include "mamba/core/package_cache.hpp"
#include "mamba/core/package_database_loader.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) void install(Configuration& config)
{ {
auto& ctx = config.context(); auto& ctx = config.context();
@ -542,18 +561,18 @@ namespace mamba
}; };
add_spdlog_logger_to_database(db); add_spdlog_logger_to_database(db);
auto exp_load = load_channels(ctx, channel_context, db, package_caches); auto maybe_load = load_channels(ctx, channel_context, db, package_caches);
if (!exp_load) 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); auto maybe_prefix_data = PrefixData::create(ctx.prefix_params.target_prefix, channel_context);
if (!exp_prefix_data) 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); 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 // context. We need to create channels from the specs to be able
// to download packages. // to download packages.
init_channels_from_package_urls(ctx, channel_context, specs); init_channels_from_package_urls(ctx, channel_context, specs);
auto exp_prefix_data = PrefixData::create(ctx.prefix_params.target_prefix, channel_context); auto maybe_prefix_data = PrefixData::create(ctx.prefix_params.target_prefix, channel_context);
if (!exp_prefix_data) if (!maybe_prefix_data)
{ {
// TODO: propagate tl::expected mechanism // TODO: propagate tl::expected mechanism
throw std::runtime_error( 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); 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<specs::PackageInfo> pkgs_to_remove;
std::vector<specs::PackageInfo> 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 } // detail
} // mamba } // mamba

View File

@ -1,3 +1,5 @@
#include <optional>
#include "mamba/api/configuration.hpp" #include "mamba/api/configuration.hpp"
#include "mamba/api/install.hpp" #include "mamba/api/install.hpp"
@ -24,5 +26,16 @@ set_install_command(CLI::App* subcom, Configuration& config)
force_reinstall.description() force_reinstall.description()
); );
subcom->callback([&] { return mamba::install(config); }); static std::optional<std::size_t> 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.");
} }

View File

@ -7,6 +7,7 @@ from pathlib import Path
from packaging.version import Version from packaging.version import Version
import pytest import pytest
import re
# Need to import everything to get fixtures # Need to import everything to get fixtures
from .helpers import * # noqa: F403 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` # Check that the packages are not installed using `pip`
res = helpers.umamba_run("-p", env_prefix, "pip", "list") res = helpers.umamba_run("-p", env_prefix, "pip", "list")
assert "numpy" not in res 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