feat: add revisions flag to list command (#3800)

This commit is contained in:
Sandrine Pataut 2025-03-05 16:11:22 +01:00 committed by GitHub
parent c6ff043d71
commit b117f3acae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 141 additions and 27 deletions

View File

@ -38,6 +38,7 @@ namespace mamba
static UserRequest prefilled(const Context& context);
std::string date;
int revision_num = 0;
std::string cmd;
std::string conda_version;

View File

@ -29,6 +29,7 @@ namespace mamba
bool md5 = false;
bool canonical = false;
bool export_ = false;
bool revisions = false;
};
struct formatted_pkg
@ -153,31 +154,52 @@ namespace mamba
if (ctx.output_params.json)
{
auto jout = nlohmann::json::array();
std::vector<std::string> keys;
keys = get_record_keys(options, all_records);
for (const auto& key : keys)
if (options.revisions)
{
auto obj = nlohmann::json();
const auto& pkg_info = all_records.find(key)->second;
auto user_requests = prefix_data.history().get_user_requests();
if (accept_package(pkg_info))
for (auto r : user_requests)
{
auto channels = channel_context.make_channel(pkg_info.package_url);
assert(channels.size() == 1); // A URL can only resolve to one channel
if ((r.link_dists.size() > 0) || (r.unlink_dists.size() > 0))
{
auto obj = nlohmann::json();
obj["channel"] = get_formatted_channel(pkg_info, channels.front());
obj["base_url"] = get_base_url(pkg_info, channels.front());
obj["url"] = pkg_info.package_url;
obj["md5"] = pkg_info.md5;
obj["build_number"] = pkg_info.build_number;
obj["build_string"] = pkg_info.build_string;
obj["dist_name"] = pkg_info.str();
obj["name"] = pkg_info.name;
obj["platform"] = pkg_info.platform;
obj["version"] = pkg_info.version;
jout.push_back(obj);
obj["date"] = r.date;
obj["install"] = r.link_dists;
obj["remove"] = r.unlink_dists;
obj["rev"] = r.revision_num;
jout.push_back(obj);
}
}
}
else
{
std::vector<std::string> keys;
keys = get_record_keys(options, all_records);
for (const auto& key : keys)
{
auto obj = nlohmann::json();
const auto& pkg_info = all_records.find(key)->second;
if (accept_package(pkg_info))
{
auto channels = channel_context.make_channel(pkg_info.package_url);
assert(channels.size() == 1); // A URL can only resolve to one channel
obj["channel"] = get_formatted_channel(pkg_info, channels.front());
obj["base_url"] = get_base_url(pkg_info, channels.front());
obj["url"] = pkg_info.package_url;
obj["md5"] = pkg_info.md5;
obj["build_number"] = pkg_info.build_number;
obj["build_string"] = pkg_info.build_string;
obj["dist_name"] = pkg_info.str();
obj["name"] = pkg_info.name;
obj["platform"] = pkg_info.platform;
obj["version"] = pkg_info.version;
jout.push_back(obj);
}
}
}
std::cout << jout.dump(4) << std::endl;
@ -215,7 +237,42 @@ namespace mamba
std::sort(packages.begin(), packages.end(), comparator);
// format and print output
if (options.explicit_)
if (options.revisions)
{
if (options.explicit_)
{
LOG_WARNING
<< "Option --explicit ignored because --revisions was also provided.";
}
if (options.canonical)
{
LOG_WARNING
<< "Option --canonical ignored because --revisions was also provided.";
}
if (options.export_)
{
LOG_WARNING
<< "Option --export ignored because --revisions was also provided.";
}
auto user_requests = prefix_data.history().get_user_requests();
for (auto r : user_requests)
{
if ((r.link_dists.size() > 0) || (r.unlink_dists.size() > 0))
{
std::cout << r.date << " (rev " << r.revision_num << ")" << std::endl;
for (auto ud : r.unlink_dists)
{
std::cout << "-" << ud << std::endl;
}
for (auto ld : r.link_dists)
{
std::cout << "+" << ld << std::endl;
}
std::cout << std::endl;
}
}
}
else if (options.explicit_)
{
if (options.canonical)
{
@ -305,6 +362,7 @@ namespace mamba
options.md5 = config.at("md5").value<bool>();
options.canonical = config.at("canonical").value<bool>();
options.export_ = config.at("export").value<bool>();
options.revisions = config.at("revisions").value<bool>();
auto channel_context = ChannelContext::make_conda_compatible(config.context());
detail::list_packages(config.context(), regex, channel_context, std::move(options));

View File

@ -203,6 +203,7 @@ namespace mamba
std::vector<History::UserRequest> History::get_user_requests()
{
std::vector<UserRequest> res;
int revision_num = 0;
for (const auto& el : parse())
{
UserRequest r;
@ -223,6 +224,10 @@ namespace mamba
r.link_dists.push_back(x.substr(1));
}
}
if ((r.link_dists.size() > 0) || (r.unlink_dists.size() > 0))
{
r.revision_num = revision_num++;
}
res.push_back(r);
}
// TODO add some stuff here regarding version of conda?

View File

@ -39,10 +39,13 @@ init_list_parser(CLI::App* subcom, Configuration& config)
);
subcom->add_flag("--reverse", reverse.get_cli_config<bool>(), reverse.description());
auto& explicit_ = config.insert(Configurable("explicit", false)
.group("cli")
.description("List explicitly all installed packages with URL."
));
auto& explicit_ = config.insert(
Configurable("explicit", false)
.group("cli")
.description(
"List explicitly all installed packages with URL. Ignored if --revisions is also provided."
)
);
subcom->add_flag("--explicit", explicit_.get_cli_config<bool>(), explicit_.description());
auto& md5 = config.insert(
@ -53,7 +56,8 @@ init_list_parser(CLI::App* subcom, Configuration& config)
auto& canonical = config.insert(
Configurable("canonical", false)
.group("cli")
.description("Output canonical names of packages only. Ignored if --explicit is also provided."
.description(
"Output canonical names of packages only. Ignored if --revisions or --explicit is also provided."
)
);
subcom->add_flag("-c,--canonical", canonical.get_cli_config<bool>(), canonical.description());
@ -62,10 +66,15 @@ init_list_parser(CLI::App* subcom, Configuration& config)
Configurable("export", false)
.group("cli")
.description(
"Output explicit, machine-readable requirement strings instead of human-readable lists of packages. Ignored if --explicit or --canonical is also provided."
"Output explicit, machine-readable requirement strings instead of human-readable lists of packages. Ignored if --revisions, --explicit or --canonical is also provided."
)
);
subcom->add_flag("-e,--export", export_.get_cli_config<bool>(), export_.description());
auto& revisions = config.insert(
Configurable("revisions", false).group("cli").description("List the revision history.")
);
subcom->add_flag("--revisions", revisions.get_cli_config<bool>(), revisions.description());
}
void

View File

@ -3,6 +3,7 @@ import subprocess
import sys
import pytest
import re
from . import helpers
@ -209,3 +210,43 @@ def test_regex(tmp_home, tmp_root_prefix, tmp_xtensor_env, quiet_flag):
filtered_res = helpers.umamba_list("^xt", "--json", quiet_flag)
filtered_names = sorted([i["name"] for i in filtered_res])
assert filtered_names == ["xtensor", "xtl"]
@pytest.mark.parametrize("revisions_flag", ["", "--revisions"])
@pytest.mark.parametrize("json_flag", ["", "--json"])
def test_revisions(revisions_flag, json_flag):
env_name = "myenv"
helpers.create("-n", env_name, "python=3.8")
helpers.install("-n", env_name, "xeus=2.0")
helpers.update("-n", env_name, "xeus=4.0")
helpers.uninstall("-n", env_name, "xeus")
res = helpers.umamba_list("-n", env_name, revisions_flag, json_flag)
if revisions_flag == "--revisions":
if json_flag == "--json":
# print(res)
assert all(res[i]["rev"] == i for i in range(len(res)))
assert any("python-3.8" in i for i in res[0]["install"])
assert any("xeus-2.0" in i for i in res[2]["remove"])
assert any("xeus-4.0" in i for i in res[2]["install"])
assert any("xeus-4.0" in i for i in res[3]["remove"])
assert len(res[3]["install"]) == 0
else:
# Splitting on dates (e.g. 2025-02-18) which are at the beginning of each new revision
revisions = re.split(r"\d{4}-\d{2}-\d{2}", res)[1:]
assert all("rev" in revisions[i] for i in range(len(revisions)))
assert "python-3.8" in revisions[0]
assert revisions[0].count("+") == len(revisions[0].strip().split("\n")) - 1
rev_2 = revisions[2].split("\n")[1:]
assert "xeus-2.0" in revisions[2]
assert "xeus-4.0" in revisions[2]
for line in rev_2:
if "xeus-2.0" in line:
assert line.startswith("-")
elif "xeus-4.0" in line:
assert line.startswith("+")
assert "xeus-4.0" in revisions[3]
assert "+" not in revisions[3]
else:
assert "xeus" not in res