mirror of
https://github.com/johndoe6345789/WizardMerge.git
synced 2026-04-25 22:25:03 +00:00
Compare commits
1 Commits
main
...
codex/simu
| Author | SHA1 | Date | |
|---|---|---|---|
| b2d25c6f62 |
@@ -8,14 +8,135 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include <json/json.h>
|
|
||||||
|
|
||||||
namespace wizardmerge {
|
namespace wizardmerge {
|
||||||
namespace git {
|
namespace git {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
std::optional<std::string> extract_object_segment(const std::string& json,
|
||||||
|
const std::string& key) {
|
||||||
|
auto key_pos = json.find("\"" + key + "\"");
|
||||||
|
if (key_pos == std::string::npos) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto brace_pos = json.find('{', key_pos);
|
||||||
|
if (brace_pos == std::string::npos) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
int depth = 0;
|
||||||
|
for (size_t i = brace_pos; i < json.size(); ++i) {
|
||||||
|
if (json[i] == '{') {
|
||||||
|
depth++;
|
||||||
|
} else if (json[i] == '}') {
|
||||||
|
depth--;
|
||||||
|
if (depth == 0) {
|
||||||
|
return json.substr(brace_pos, i - brace_pos + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> extract_array_segment(const std::string& json,
|
||||||
|
const std::string& key) {
|
||||||
|
auto key_pos = json.find("\"" + key + "\"");
|
||||||
|
if (key_pos == std::string::npos) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bracket_pos = json.find('[', key_pos);
|
||||||
|
if (bracket_pos == std::string::npos) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
int depth = 0;
|
||||||
|
for (size_t i = bracket_pos; i < json.size(); ++i) {
|
||||||
|
if (json[i] == '[') {
|
||||||
|
depth++;
|
||||||
|
} else if (json[i] == ']') {
|
||||||
|
depth--;
|
||||||
|
if (depth == 0) {
|
||||||
|
return json.substr(bracket_pos, i - bracket_pos + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> extract_objects_from_array(const std::string& array) {
|
||||||
|
std::vector<std::string> objects;
|
||||||
|
|
||||||
|
int depth = 0;
|
||||||
|
std::optional<size_t> start;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < array.size(); ++i) {
|
||||||
|
if (array[i] == '{') {
|
||||||
|
if (!start) {
|
||||||
|
start = i;
|
||||||
|
}
|
||||||
|
depth++;
|
||||||
|
} else if (array[i] == '}') {
|
||||||
|
depth--;
|
||||||
|
if (depth == 0 && start) {
|
||||||
|
objects.push_back(array.substr(*start, i - *start + 1));
|
||||||
|
start.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string extract_string_field(const std::string& json,
|
||||||
|
const std::string& key,
|
||||||
|
const std::string& default_value = "") {
|
||||||
|
std::regex pattern("\\\\\"" + key + "\\\\\"\\s*:\\s*\\\\\"([^\\\\\"]*)\\\\\"");
|
||||||
|
std::smatch match;
|
||||||
|
|
||||||
|
if (std::regex_search(json, match, pattern) && match.size() >= 2) {
|
||||||
|
return match[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool extract_bool_field(const std::string& json,
|
||||||
|
const std::string& key,
|
||||||
|
bool default_value = false) {
|
||||||
|
std::regex pattern("\\\\\"" + key + "\\\\\"\\s*:\\s*(true|false)");
|
||||||
|
std::smatch match;
|
||||||
|
|
||||||
|
if (std::regex_search(json, match, pattern) && match.size() >= 2) {
|
||||||
|
return match[1] == "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int extract_int_field(const std::string& json,
|
||||||
|
const std::string& key,
|
||||||
|
int default_value = 0) {
|
||||||
|
std::regex pattern("\\\\\"" + key + "\\\\\"\\s*:\\s*(-?\\d+)");
|
||||||
|
std::smatch match;
|
||||||
|
|
||||||
|
if (std::regex_search(json, match, pattern) && match.size() >= 2) {
|
||||||
|
try {
|
||||||
|
return std::stoi(match[1]);
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Simple base64 decoder
|
* @brief Simple base64 decoder
|
||||||
*/
|
*/
|
||||||
@@ -216,41 +337,32 @@ std::optional<PullRequest> fetch_pull_request(
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse JSON response
|
pr.title = extract_string_field(response, "title");
|
||||||
Json::Value root;
|
pr.state = extract_string_field(response, "state");
|
||||||
Json::CharReaderBuilder reader;
|
|
||||||
std::string errs;
|
|
||||||
std::istringstream s(response);
|
|
||||||
|
|
||||||
if (!Json::parseFromStream(reader, s, &root, &errs)) {
|
|
||||||
std::cerr << "Failed to parse PR/MR JSON: " << errs << std::endl;
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
pr.title = root.get("title", "").asString();
|
|
||||||
pr.state = root.get("state", "").asString();
|
|
||||||
|
|
||||||
if (platform == GitPlatform::GitHub) {
|
if (platform == GitPlatform::GitHub) {
|
||||||
if (root.isMember("base") && root["base"].isObject()) {
|
if (auto base_object = extract_object_segment(response, "base")) {
|
||||||
pr.base_ref = root["base"].get("ref", "").asString();
|
pr.base_ref = extract_string_field(*base_object, "ref");
|
||||||
pr.base_sha = root["base"].get("sha", "").asString();
|
pr.base_sha = extract_string_field(*base_object, "sha");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.isMember("head") && root["head"].isObject()) {
|
if (auto head_object = extract_object_segment(response, "head")) {
|
||||||
pr.head_ref = root["head"].get("ref", "").asString();
|
pr.head_ref = extract_string_field(*head_object, "ref");
|
||||||
pr.head_sha = root["head"].get("sha", "").asString();
|
pr.head_sha = extract_string_field(*head_object, "sha");
|
||||||
}
|
}
|
||||||
|
|
||||||
pr.mergeable = root.get("mergeable", false).asBool();
|
pr.mergeable = extract_bool_field(response, "mergeable", false);
|
||||||
pr.mergeable_state = root.get("mergeable_state", "unknown").asString();
|
pr.mergeable_state = extract_string_field(response, "mergeable_state", "unknown");
|
||||||
} else if (platform == GitPlatform::GitLab) {
|
} else if (platform == GitPlatform::GitLab) {
|
||||||
pr.base_ref = root.get("target_branch", "").asString();
|
pr.base_ref = extract_string_field(response, "target_branch");
|
||||||
pr.head_ref = root.get("source_branch", "").asString();
|
pr.head_ref = extract_string_field(response, "source_branch");
|
||||||
pr.base_sha = root.get("diff_refs", Json::Value::null).get("base_sha", "").asString();
|
|
||||||
pr.head_sha = root.get("diff_refs", Json::Value::null).get("head_sha", "").asString();
|
if (auto diff_refs = extract_object_segment(response, "diff_refs")) {
|
||||||
|
pr.base_sha = extract_string_field(*diff_refs, "base_sha");
|
||||||
// GitLab uses different merge status
|
pr.head_sha = extract_string_field(*diff_refs, "head_sha");
|
||||||
std::string merge_status = root.get("merge_status", "").asString();
|
}
|
||||||
|
|
||||||
|
std::string merge_status = extract_string_field(response, "merge_status");
|
||||||
pr.mergeable = (merge_status == "can_be_merged");
|
pr.mergeable = (merge_status == "can_be_merged");
|
||||||
pr.mergeable_state = merge_status;
|
pr.mergeable_state = merge_status;
|
||||||
}
|
}
|
||||||
@@ -263,58 +375,56 @@ std::optional<PullRequest> fetch_pull_request(
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::Value files_root;
|
// Process files based on platform
|
||||||
std::istringstream files_stream(files_response);
|
if (platform == GitPlatform::GitHub) {
|
||||||
|
for (const auto& file_object : extract_objects_from_array(files_response)) {
|
||||||
if (!Json::parseFromStream(reader, files_stream, &files_root, &errs)) {
|
PRFile pr_file;
|
||||||
std::cerr << "Failed to parse files JSON: " << errs << std::endl;
|
pr_file.filename = extract_string_field(file_object, "filename");
|
||||||
return std::nullopt;
|
pr_file.status = extract_string_field(file_object, "status");
|
||||||
|
pr_file.additions = extract_int_field(file_object, "additions");
|
||||||
|
pr_file.deletions = extract_int_field(file_object, "deletions");
|
||||||
|
pr_file.changes = extract_int_field(file_object, "changes");
|
||||||
|
|
||||||
|
pr.files.push_back(pr_file);
|
||||||
|
}
|
||||||
|
} else if (platform == GitPlatform::GitLab) {
|
||||||
|
if (auto changes_array = extract_array_segment(files_response, "changes")) {
|
||||||
|
for (const auto& file_object : extract_objects_from_array(*changes_array)) {
|
||||||
|
PRFile pr_file;
|
||||||
|
std::string new_path = extract_string_field(file_object, "new_path");
|
||||||
|
if (!new_path.empty()) {
|
||||||
|
pr_file.filename = new_path;
|
||||||
|
} else {
|
||||||
|
pr_file.filename = extract_string_field(file_object, "old_path");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool new_file = extract_bool_field(file_object, "new_file", false);
|
||||||
|
bool deleted_file = extract_bool_field(file_object, "deleted_file", false);
|
||||||
|
bool renamed_file = extract_bool_field(file_object, "renamed_file", false);
|
||||||
|
|
||||||
|
if (new_file) {
|
||||||
|
pr_file.status = "added";
|
||||||
|
} else if (deleted_file) {
|
||||||
|
pr_file.status = "removed";
|
||||||
|
} else if (renamed_file) {
|
||||||
|
pr_file.status = "renamed";
|
||||||
|
} else {
|
||||||
|
pr_file.status = "modified";
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_file.additions = 0;
|
||||||
|
pr_file.deletions = 0;
|
||||||
|
pr_file.changes = 0;
|
||||||
|
|
||||||
|
pr.files.push_back(pr_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process files based on platform
|
if (platform == GitPlatform::GitHub || platform == GitPlatform::GitLab) {
|
||||||
if (platform == GitPlatform::GitHub && files_root.isArray()) {
|
// If parsing failed and we have no files, signal an error to callers.
|
||||||
// GitHub format: array of file objects
|
if (pr.files.empty() && !files_response.empty()) {
|
||||||
for (const auto& file : files_root) {
|
std::cerr << "No files parsed from API response" << std::endl;
|
||||||
PRFile pr_file;
|
|
||||||
pr_file.filename = file.get("filename", "").asString();
|
|
||||||
pr_file.status = file.get("status", "").asString();
|
|
||||||
pr_file.additions = file.get("additions", 0).asInt();
|
|
||||||
pr_file.deletions = file.get("deletions", 0).asInt();
|
|
||||||
pr_file.changes = file.get("changes", 0).asInt();
|
|
||||||
|
|
||||||
pr.files.push_back(pr_file);
|
|
||||||
}
|
|
||||||
} else if (platform == GitPlatform::GitLab && files_root.isMember("changes")) {
|
|
||||||
// GitLab format: object with "changes" array
|
|
||||||
const Json::Value& changes = files_root["changes"];
|
|
||||||
if (changes.isArray()) {
|
|
||||||
for (const auto& file : changes) {
|
|
||||||
PRFile pr_file;
|
|
||||||
pr_file.filename = file.get("new_path", file.get("old_path", "").asString()).asString();
|
|
||||||
|
|
||||||
// Determine status from new_file, deleted_file, renamed_file flags
|
|
||||||
bool new_file = file.get("new_file", false).asBool();
|
|
||||||
bool deleted_file = file.get("deleted_file", false).asBool();
|
|
||||||
bool renamed_file = file.get("renamed_file", false).asBool();
|
|
||||||
|
|
||||||
if (new_file) {
|
|
||||||
pr_file.status = "added";
|
|
||||||
} else if (deleted_file) {
|
|
||||||
pr_file.status = "removed";
|
|
||||||
} else if (renamed_file) {
|
|
||||||
pr_file.status = "renamed";
|
|
||||||
} else {
|
|
||||||
pr_file.status = "modified";
|
|
||||||
}
|
|
||||||
|
|
||||||
// GitLab doesn't provide addition/deletion counts in the changes endpoint
|
|
||||||
pr_file.additions = 0;
|
|
||||||
pr_file.deletions = 0;
|
|
||||||
pr_file.changes = 0;
|
|
||||||
|
|
||||||
pr.files.push_back(pr_file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,43 +476,31 @@ std::optional<std::vector<std::string>> fetch_file_content(
|
|||||||
// Handle response based on platform
|
// Handle response based on platform
|
||||||
if (platform == GitPlatform::GitHub) {
|
if (platform == GitPlatform::GitHub) {
|
||||||
// GitHub returns JSON with base64-encoded content
|
// GitHub returns JSON with base64-encoded content
|
||||||
Json::Value root;
|
std::string encoding = extract_string_field(response, "encoding");
|
||||||
Json::CharReaderBuilder reader;
|
std::string encoded_content = extract_string_field(response, "content");
|
||||||
std::string errs;
|
|
||||||
std::istringstream s(response);
|
|
||||||
|
|
||||||
if (!Json::parseFromStream(reader, s, &root, &errs)) {
|
|
||||||
std::cerr << "Failed to parse content JSON: " << errs << std::endl;
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GitHub API returns content as base64 encoded
|
if (encoding.empty() || encoded_content.empty()) {
|
||||||
if (!root.isMember("content") || !root.isMember("encoding")) {
|
|
||||||
std::cerr << "Invalid response format for file content" << std::endl;
|
std::cerr << "Invalid response format for file content" << std::endl;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string encoding = root["encoding"].asString();
|
|
||||||
if (encoding != "base64") {
|
if (encoding != "base64") {
|
||||||
std::cerr << "Unsupported encoding: " << encoding << std::endl;
|
std::cerr << "Unsupported encoding: " << encoding << std::endl;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode base64 content
|
|
||||||
std::string encoded_content = root["content"].asString();
|
|
||||||
|
|
||||||
// Remove newlines from base64 string
|
// Remove newlines from base64 string
|
||||||
encoded_content.erase(std::remove(encoded_content.begin(), encoded_content.end(), '\n'), encoded_content.end());
|
encoded_content.erase(std::remove(encoded_content.begin(), encoded_content.end(), '\n'), encoded_content.end());
|
||||||
encoded_content.erase(std::remove(encoded_content.begin(), encoded_content.end(), '\r'), encoded_content.end());
|
encoded_content.erase(std::remove(encoded_content.begin(), encoded_content.end(), '\r'), encoded_content.end());
|
||||||
|
|
||||||
// Decode base64
|
// Decode base64
|
||||||
std::string decoded_content = base64_decode(encoded_content);
|
std::string decoded_content = base64_decode(encoded_content);
|
||||||
|
|
||||||
if (decoded_content.empty()) {
|
if (decoded_content.empty()) {
|
||||||
std::cerr << "Failed to decode base64 content" << std::endl;
|
std::cerr << "Failed to decode base64 content" << std::endl;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split content into lines
|
// Split content into lines
|
||||||
return split_lines(decoded_content);
|
return split_lines(decoded_content);
|
||||||
} else if (platform == GitPlatform::GitLab) {
|
} else if (platform == GitPlatform::GitLab) {
|
||||||
|
|||||||
Reference in New Issue
Block a user