Add pack diff command
This commit is contained in:
145
src/cli.cpp
145
src/cli.cpp
@@ -5,6 +5,7 @@
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
#include <sodium.h>
|
||||
@@ -94,12 +95,151 @@ std::array<std::uint8_t, kAeadKeySize> load_master_key(const std::string& path)
|
||||
return key;
|
||||
}
|
||||
|
||||
struct SnapshotEntry
|
||||
{
|
||||
std::uint64_t size = 0;
|
||||
std::array<std::uint8_t, kHashSize> hash {};
|
||||
};
|
||||
|
||||
using SnapshotMap = std::map<std::string, SnapshotEntry>;
|
||||
|
||||
SnapshotMap snapshot_from_directory(const std::filesystem::path& root)
|
||||
{
|
||||
SnapshotMap snapshot;
|
||||
for (const auto& path : collect_files(root))
|
||||
{
|
||||
const auto bytes = read_file(path.string());
|
||||
snapshot.emplace(normalize_path(root, path), SnapshotEntry {
|
||||
.size = static_cast<std::uint64_t>(bytes.size()),
|
||||
.hash = hash_bytes(bytes),
|
||||
});
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
SnapshotMap snapshot_from_archive(const std::filesystem::path& archive_path)
|
||||
{
|
||||
SnapshotMap snapshot;
|
||||
const auto archive = load_archive(archive_path);
|
||||
for (const auto& entry : archive.entries)
|
||||
{
|
||||
snapshot.emplace(entry.path, SnapshotEntry {
|
||||
.size = entry.original_size,
|
||||
.hash = entry.plaintext_hash,
|
||||
});
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
SnapshotMap load_snapshot(const std::string& input)
|
||||
{
|
||||
const std::filesystem::path path(input);
|
||||
if (std::filesystem::is_directory(path))
|
||||
{
|
||||
return snapshot_from_directory(path);
|
||||
}
|
||||
|
||||
if (std::filesystem::is_regular_file(path) && path.extension() == ".m2p")
|
||||
{
|
||||
return snapshot_from_archive(path);
|
||||
}
|
||||
|
||||
fail("pack_diff supports directories or .m2p archives: " + input);
|
||||
}
|
||||
|
||||
void command_diff(const ParsedArgs& args)
|
||||
{
|
||||
const auto left = require_option(args, "left");
|
||||
const auto right = require_option(args, "right");
|
||||
|
||||
const auto left_snapshot = load_snapshot(left);
|
||||
const auto right_snapshot = load_snapshot(right);
|
||||
|
||||
std::vector<std::string> added;
|
||||
std::vector<std::string> removed;
|
||||
std::vector<std::string> changed;
|
||||
std::size_t unchanged = 0;
|
||||
|
||||
std::set<std::string> all_paths;
|
||||
for (const auto& [path, _] : left_snapshot)
|
||||
{
|
||||
all_paths.insert(path);
|
||||
}
|
||||
for (const auto& [path, _] : right_snapshot)
|
||||
{
|
||||
all_paths.insert(path);
|
||||
}
|
||||
|
||||
for (const auto& path : all_paths)
|
||||
{
|
||||
const auto lit = left_snapshot.find(path);
|
||||
const auto rit = right_snapshot.find(path);
|
||||
|
||||
if (lit == left_snapshot.end())
|
||||
{
|
||||
added.push_back(path);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rit == right_snapshot.end())
|
||||
{
|
||||
removed.push_back(path);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lit->second.size != rit->second.size || lit->second.hash != rit->second.hash)
|
||||
{
|
||||
changed.push_back(path);
|
||||
continue;
|
||||
}
|
||||
|
||||
unchanged += 1;
|
||||
}
|
||||
|
||||
if (args.json)
|
||||
{
|
||||
auto render_array = [](const std::vector<std::string>& values) {
|
||||
std::ostringstream out;
|
||||
out << "[";
|
||||
for (std::size_t i = 0; i < values.size(); ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
out << ",";
|
||||
}
|
||||
out << "\"" << json_escape(values[i]) << "\"";
|
||||
}
|
||||
out << "]";
|
||||
return out.str();
|
||||
};
|
||||
|
||||
std::cout
|
||||
<< "{"
|
||||
<< "\"ok\":true,"
|
||||
<< "\"left\":\"" << json_escape(left) << "\","
|
||||
<< "\"right\":\"" << json_escape(right) << "\","
|
||||
<< "\"added\":" << render_array(added) << ","
|
||||
<< "\"removed\":" << render_array(removed) << ","
|
||||
<< "\"changed\":" << render_array(changed) << ","
|
||||
<< "\"unchanged_count\":" << unchanged
|
||||
<< "}\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout
|
||||
<< "Added: " << added.size() << "\n"
|
||||
<< "Removed: " << removed.size() << "\n"
|
||||
<< "Changed: " << changed.size() << "\n"
|
||||
<< "Unchanged: " << unchanged << "\n";
|
||||
}
|
||||
|
||||
void print_usage()
|
||||
{
|
||||
std::cout
|
||||
<< "m2pack commands:\n"
|
||||
<< " keygen --out-dir <dir> [--json]\n"
|
||||
<< " build --input <dir> --output <file> --key <hex-file> --sign-secret-key <hex-file> [--json]\n"
|
||||
<< " diff --left <dir|archive.m2p> --right <dir|archive.m2p> [--json]\n"
|
||||
<< " list --archive <file> [--json]\n"
|
||||
<< " verify --archive <file> [--public-key <hex-file>] [--key <hex-file>] [--json]\n"
|
||||
<< " extract --archive <file> --output <dir> --key <hex-file> [--json]\n"
|
||||
@@ -359,6 +499,11 @@ int run_cli(int argc, char** argv)
|
||||
command_list(args);
|
||||
return 0;
|
||||
}
|
||||
if (args.command == "diff")
|
||||
{
|
||||
command_diff(args);
|
||||
return 0;
|
||||
}
|
||||
if (args.command == "verify")
|
||||
{
|
||||
command_verify(args);
|
||||
|
||||
Reference in New Issue
Block a user