diff --git a/source/src/commands/publish-template.cpp b/source/src/commands/publish-template.cpp index 86032be..4814882 100644 --- a/source/src/commands/publish-template.cpp +++ b/source/src/commands/publish-template.cpp @@ -158,6 +158,38 @@ static bool check_file_exists(const std::string& server_url, const std::string& output.find("\"exists\": true") != std::string::npos; } +// Get unpacked hash for a template:tag from server metadata +// Returns empty string if not found or error +static std::string get_remote_unpacked_hash(const std::string& server_url, const std::string& labeltag) { + std::string cmd = "curl -s \"" + server_url + "/meta/" + labeltag + "\""; + std::string output; + int http_code; + + if (!run_curl(cmd, output, http_code) || http_code != 200) { + return ""; + } + + // Parse unpackedhash from JSON response + // Look for "unpackedhash":"" or "unpackedhash": "" + size_t pos = output.find("\"unpackedhash\""); + if (pos == std::string::npos) { + return ""; + } + + // Find the colon and opening quote + pos = output.find(':', pos); + if (pos == std::string::npos) return ""; + + pos = output.find('"', pos); + if (pos == std::string::npos) return ""; + + pos++; // skip opening quote + size_t end = output.find('"', pos); + if (end == std::string::npos) return ""; + + return output.substr(pos, end - pos); +} + // Upload a new file to the server static bool upload_file(const std::string& server_url, const std::string& token, const std::string& file_path, const std::vector& labeltags, @@ -275,9 +307,9 @@ static bool is_valid_template_dir(const std::filesystem::path& dir_path) { } // Publish a single template directory -// Returns: 0 = success, 1 = error, 2 = skipped (not a template) +// Returns: 0 = success, 1 = error, 2 = skipped (not a template), 3 = unchanged (already up to date) static int publish_single_template(const std::string& template_dir, const std::string& server_url, - const std::string& token, bool quiet = false) { + const std::string& token, bool quiet = false, bool skip_if_unchanged = false) { std::filesystem::path dir_path(template_dir); std::string template_name = dir_path.filename().string(); @@ -305,6 +337,18 @@ static int publish_single_template(const std::string& template_dir, const std::s return 1; } if (!quiet) info << " Hash: " << unpacked_hash << std::endl; + + // Check if unchanged (compare with remote :latest) + if (skip_if_unchanged) { + std::string remote_hash = get_remote_unpacked_hash(server_url, template_name + ":latest"); + if (!remote_hash.empty() && remote_hash == unpacked_hash) { + if (!quiet) { + info << " ✓ Unchanged (matches remote)" << std::endl; + std::cout << std::endl; + } + return 3; // unchanged + } + } if (!quiet) std::cout << std::endl; // Create temp directory and tarball @@ -502,13 +546,16 @@ int publish_template_handler(const CommandContext& ctx) { std::cout << std::endl; int success_count = 0; + int unchanged_count = 0; int fail_count = 0; std::vector failed_templates; for (const auto& tdir : template_dirs) { - int result = publish_single_template(tdir.string(), server_url, effective_token, false); + int result = publish_single_template(tdir.string(), server_url, effective_token, false, true); if (result == 0) { success_count++; + } else if (result == 3) { + unchanged_count++; } else { fail_count++; failed_templates.push_back(tdir.filename().string()); @@ -518,9 +565,14 @@ int publish_template_handler(const CommandContext& ctx) { // Summary std::cout << std::endl; maketitle("Publish Summary"); - info << "Successfully published: " << success_count << " template(s)" << std::endl; + if (success_count > 0) { + info << "Published: " << success_count << " template(s)" << std::endl; + } + if (unchanged_count > 0) { + info << "Unchanged: " << unchanged_count << " template(s)" << std::endl; + } if (fail_count > 0) { - error << "Failed to publish: " << fail_count << " template(s)" << std::endl; + error << "Failed: " << fail_count << " template(s)" << std::endl; for (const auto& name : failed_templates) { error << " - " << name << std::endl; }