Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions libmamba/src/api/configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1382,6 +1382,11 @@ namespace mamba
.set_single_op_lifetime()
.description("Packages specification"));

insert(Configurable("clone_env", std::string(""))
.group("Basic")
.set_single_op_lifetime()
.description("Environment name or path to clone from"));

insert(Configurable("others_pkg_mgrs_specs", std::vector<detail::other_pkg_mgr_spec>({}))
.group("Basic")
.set_single_op_lifetime()
Expand Down
101 changes: 96 additions & 5 deletions libmamba/src/api/create.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
#include "mamba/api/install.hpp"
#include "mamba/core/channel_context.hpp"
#include "mamba/core/context.hpp"
#include "mamba/core/environments_manager.hpp"
#include "mamba/core/prefix_data.hpp"
#include "mamba/core/util.hpp"
#include "mamba/util/path_manip.hpp"

namespace mamba
{
Expand All @@ -30,13 +33,98 @@ namespace mamba
config.load();

auto& create_specs = config.at("specs").value<std::vector<std::string>>();
auto& clone_env_name = config.at("clone_env").value<std::string>();
auto& use_explicit = config.at("explicit_install").value<bool>();
auto& json_format = config.at("json").get_cli_config<bool>();
auto& env_vars = config.at("spec_file_env_vars").value<std::map<std::string, std::string>>();
auto& no_env = config.at("no_env").value<bool>();

auto channel_context = ChannelContext::make_conda_compatible(ctx);

// Handle clone environment logic
std::vector<std::string> clone_specs;
if (!clone_env_name.empty())
Copy link
Member

@JohanMabille JohanMabille Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move this code into a dedicated function so that we can write something like

auto clone_specs = get_clone_specs(/* parameters needed */);

The idea is to have the create function "short" and easy to read (it would require some further refactoring, but this is orthogonal to your PR).

{
// Validate that no other specs are provided when cloning
if (!create_specs.empty())
{
const auto message = "Cannot specify packages when cloning an environment. Use --clone only.";
LOG_ERROR << message;
throw mamba_error(message, mamba_error_code::incorrect_usage);
}

auto& file_specs = config.at("file_specs").value<std::vector<std::string>>();
if (!file_specs.empty())
{
const auto message = "Cannot specify environment file when cloning an environment. Use --clone only.";
LOG_ERROR << message;
throw mamba_error(message, mamba_error_code::incorrect_usage);
}

// Determine source environment path
fs::u8path source_prefix;
if (fs::exists(clone_env_name))
{
// Direct path provided
source_prefix = clone_env_name;
}
else
{
// Environment name provided, look for it
if (clone_env_name == "base")
{
source_prefix = ctx.prefix_params.root_prefix;
}
else
{
// Search in envs directories
bool found = false;
for (const auto& envs_dir : ctx.envs_dirs)
{
auto potential_path = envs_dir / clone_env_name;
if (fs::exists(potential_path) && is_conda_environment(potential_path))
{
source_prefix = potential_path;
found = true;
break;
}
}
if (!found)
{
const auto message = "Environment '" + clone_env_name + "' not found.";
LOG_ERROR << message;
throw mamba_error(message, mamba_error_code::incorrect_usage);
}
}
}

// Validate source environment exists and is a conda environment
if (!fs::exists(source_prefix) || !is_conda_environment(source_prefix))
{
const auto message = "Source environment '" + source_prefix.string() + "' is not a valid conda environment.";
LOG_ERROR << message;
throw mamba_error(message, mamba_error_code::incorrect_usage);
}

// Get package list from source environment
auto maybe_source_prefix_data = PrefixData::create(source_prefix, channel_context);
if (!maybe_source_prefix_data)
{
const auto message = "Could not load prefix data from source environment: " + maybe_source_prefix_data.error().what();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const auto message = "Could not load prefix data from source environment: " + maybe_source_prefix_data.error().what();
const auto message = std::string("Could not load prefix data from source environment: ") + maybe_source_prefix_data.error().what();

LOG_ERROR << message;
throw mamba_error(message, mamba_error_code::incorrect_usage);
}
auto records = maybe_source_prefix_data.value().sorted_records();

// Convert records to specs for installation
for (const auto& record : records)
{
clone_specs.push_back(record.name + "=" + record.version + "=" + record.build_string);
}

LOG_INFO << "Cloning environment '" << source_prefix.string() << "' with " << clone_specs.size() << " packages.";
}

bool remove_prefix_on_failure = false;
bool create_env = true;

Expand Down Expand Up @@ -82,7 +170,7 @@ namespace mamba
throw mamba_error(message, mamba_error_code::incorrect_usage);
}
}
if (create_specs.empty())
if (create_specs.empty() && clone_specs.empty())
{
detail::create_empty_target(ctx, ctx.prefix_params.target_prefix, env_vars, no_env);
}
Expand All @@ -98,7 +186,7 @@ namespace mamba
}
else
{
if (create_specs.empty() && json_format)
if (create_specs.empty() && clone_specs.empty() && json_format)
{
// Just print the JSON
nlohmann::json output;
Expand All @@ -124,14 +212,17 @@ namespace mamba
remove_prefix_on_failure
);
}
else if (!create_specs.empty())
else if (!create_specs.empty() || !clone_specs.empty())
{
// Use clone_specs if available, otherwise use create_specs
const auto& specs_to_install = !clone_specs.empty() ? clone_specs : create_specs;

if (use_explicit)
{
install_explicit_specs(
ctx,
channel_context,
create_specs,
specs_to_install,
create_env,
remove_prefix_on_failure
);
Expand All @@ -142,7 +233,7 @@ namespace mamba
ctx,
channel_context,
config,
create_specs,
specs_to_install,
create_env,
remove_prefix_on_failure
);
Expand Down
5 changes: 5 additions & 0 deletions micromamba/src/common_options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,11 @@ init_install_options(CLI::App* subcom, Configuration& config)
->type_size(1)
->allow_extra_args(false);

auto& clone_env = config.at("clone_env");
subcom
->add_option("--clone", clone_env.get_cli_config<std::string>(), clone_env.description())
->option_text("ENV_NAME_OR_PATH");

auto& no_pin = config.at("no_pin");
subcom->add_flag("--no-pin,!--pin", no_pin.get_cli_config<bool>(), no_pin.description());

Expand Down
Loading