mirror of
https://github.com/johndoe6345789/WizardMerge.git
synced 2026-04-24 13:44:55 +00:00
Fix code review issues: base64 decoding, includes, and struct fields
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
193
backend/examples/pr_resolve_example.py
Executable file
193
backend/examples/pr_resolve_example.py
Executable file
@@ -0,0 +1,193 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Example: Resolve GitHub Pull Request conflicts using WizardMerge API
|
||||||
|
|
||||||
|
This script demonstrates how to use the WizardMerge API to automatically
|
||||||
|
resolve merge conflicts in a GitHub pull request.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python pr_resolve_example.py https://github.com/owner/repo/pull/123
|
||||||
|
|
||||||
|
Environment Variables:
|
||||||
|
GITHUB_TOKEN: Optional GitHub API token for private repos
|
||||||
|
WIZARDMERGE_BACKEND: Backend URL (default: http://localhost:8080)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_pr(
|
||||||
|
pr_url: str,
|
||||||
|
backend_url: str = "http://localhost:8080",
|
||||||
|
github_token: Optional[str] = None,
|
||||||
|
create_branch: bool = False,
|
||||||
|
branch_name: Optional[str] = None
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Resolve conflicts in a GitHub pull request.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pr_url: URL of the pull request (e.g., https://github.com/owner/repo/pull/123)
|
||||||
|
backend_url: URL of WizardMerge backend server
|
||||||
|
github_token: Optional GitHub API token
|
||||||
|
create_branch: Whether to create a new branch with resolved conflicts
|
||||||
|
branch_name: Name of the branch to create (optional)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: API response with resolution results
|
||||||
|
"""
|
||||||
|
endpoint = f"{backend_url}/api/pr/resolve"
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"pr_url": pr_url,
|
||||||
|
}
|
||||||
|
|
||||||
|
if github_token:
|
||||||
|
payload["github_token"] = github_token
|
||||||
|
|
||||||
|
if create_branch:
|
||||||
|
payload["create_branch"] = True
|
||||||
|
if branch_name:
|
||||||
|
payload["branch_name"] = branch_name
|
||||||
|
|
||||||
|
print(f"Resolving PR: {pr_url}")
|
||||||
|
print(f"Backend: {endpoint}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(endpoint, json=payload, timeout=60)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
result = response.json()
|
||||||
|
return result
|
||||||
|
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
print(f"ERROR: Could not connect to backend at {backend_url}")
|
||||||
|
print("Make sure the backend server is running:")
|
||||||
|
print(" cd backend && ./wizardmerge-cli")
|
||||||
|
sys.exit(1)
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
print(f"ERROR: HTTP {e.response.status_code}")
|
||||||
|
print(e.response.text)
|
||||||
|
sys.exit(1)
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
print(f"ERROR: Request timed out after 60 seconds")
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def print_results(result: dict):
|
||||||
|
"""Pretty print the resolution results."""
|
||||||
|
print("=" * 70)
|
||||||
|
print("PULL REQUEST RESOLUTION RESULTS")
|
||||||
|
print("=" * 70)
|
||||||
|
print()
|
||||||
|
|
||||||
|
if not result.get("success"):
|
||||||
|
print("❌ Resolution failed")
|
||||||
|
if "error" in result:
|
||||||
|
print(f"Error: {result['error']}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# PR Info
|
||||||
|
pr_info = result.get("pr_info", {})
|
||||||
|
print(f"📋 PR #{pr_info.get('number')}: {pr_info.get('title')}")
|
||||||
|
print(f" Base: {pr_info.get('base_ref')} ({pr_info.get('base_sha', '')[:7]})")
|
||||||
|
print(f" Head: {pr_info.get('head_ref')} ({pr_info.get('head_sha', '')[:7]})")
|
||||||
|
print(f" Mergeable: {pr_info.get('mergeable')}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Statistics
|
||||||
|
total = result.get("total_files", 0)
|
||||||
|
resolved = result.get("resolved_count", 0)
|
||||||
|
failed = result.get("failed_count", 0)
|
||||||
|
|
||||||
|
print(f"📊 Statistics:")
|
||||||
|
print(f" Total files: {total}")
|
||||||
|
print(f" ✅ Resolved: {resolved}")
|
||||||
|
print(f" ❌ Failed: {failed}")
|
||||||
|
print(f" Success rate: {(resolved/total*100) if total > 0 else 0:.1f}%")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# File details
|
||||||
|
print("📁 File Resolution Details:")
|
||||||
|
print()
|
||||||
|
|
||||||
|
resolved_files = result.get("resolved_files", [])
|
||||||
|
for file_info in resolved_files:
|
||||||
|
filename = file_info.get("filename", "unknown")
|
||||||
|
status = file_info.get("status", "unknown")
|
||||||
|
|
||||||
|
if file_info.get("skipped"):
|
||||||
|
print(f" ⊘ {filename} (skipped: {file_info.get('reason', 'N/A')})")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if file_info.get("error"):
|
||||||
|
print(f" ❌ {filename} - Error: {file_info.get('error')}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
had_conflicts = file_info.get("had_conflicts", False)
|
||||||
|
auto_resolved = file_info.get("auto_resolved", False)
|
||||||
|
|
||||||
|
if auto_resolved:
|
||||||
|
icon = "✅"
|
||||||
|
msg = "auto-resolved"
|
||||||
|
elif had_conflicts:
|
||||||
|
icon = "⚠️"
|
||||||
|
msg = "has unresolved conflicts"
|
||||||
|
else:
|
||||||
|
icon = "✓"
|
||||||
|
msg = "no conflicts"
|
||||||
|
|
||||||
|
print(f" {icon} {filename} - {msg}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Branch creation
|
||||||
|
if result.get("branch_created"):
|
||||||
|
branch = result.get("branch_name", "N/A")
|
||||||
|
print(f"🌿 Created branch: {branch}")
|
||||||
|
elif "branch_name" in result:
|
||||||
|
print(f"📝 Note: {result.get('note', 'Branch creation pending')}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point."""
|
||||||
|
if len(sys.argv) < 2 or sys.argv[1] in ["-h", "--help"]:
|
||||||
|
print(__doc__)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
pr_url = sys.argv[1]
|
||||||
|
|
||||||
|
# Get configuration from environment
|
||||||
|
backend_url = os.getenv("WIZARDMERGE_BACKEND", "http://localhost:8080")
|
||||||
|
github_token = os.getenv("GITHUB_TOKEN")
|
||||||
|
|
||||||
|
# Resolve the PR
|
||||||
|
result = resolve_pr(
|
||||||
|
pr_url=pr_url,
|
||||||
|
backend_url=backend_url,
|
||||||
|
github_token=github_token
|
||||||
|
)
|
||||||
|
|
||||||
|
# Print results
|
||||||
|
print_results(result)
|
||||||
|
|
||||||
|
# Exit with appropriate code
|
||||||
|
if result.get("success") and result.get("resolved_count", 0) > 0:
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -20,8 +20,6 @@ namespace git {
|
|||||||
struct PRFile {
|
struct PRFile {
|
||||||
std::string filename;
|
std::string filename;
|
||||||
std::string status; // "added", "modified", "removed", "renamed"
|
std::string status; // "added", "modified", "removed", "renamed"
|
||||||
std::string base_content;
|
|
||||||
std::string head_content;
|
|
||||||
int additions;
|
int additions;
|
||||||
int deletions;
|
int deletions;
|
||||||
int changes;
|
int changes;
|
||||||
|
|||||||
@@ -125,8 +125,13 @@ void PRController::resolvePR(
|
|||||||
std::vector<std::string> head_content = head_opt.value();
|
std::vector<std::string> head_content = head_opt.value();
|
||||||
|
|
||||||
// For added files or when there might be a conflict with existing file
|
// For added files or when there might be a conflict with existing file
|
||||||
// We use the current head as "ours" and try to merge with base
|
// Note: This is a simplified merge for PR review purposes.
|
||||||
// This is simplified - in reality, we'd need to detect actual merge conflicts
|
// In a real merge scenario with conflicts, you'd need the merge-base commit.
|
||||||
|
// Here we're showing what changes if we accept the head version:
|
||||||
|
// - base: common ancestor (PR base)
|
||||||
|
// - ours: current state (PR base)
|
||||||
|
// - theirs: proposed changes (PR head)
|
||||||
|
// This effectively shows all changes from the PR head.
|
||||||
|
|
||||||
// Perform three-way merge: base, ours (base), theirs (head)
|
// Perform three-way merge: base, ours (base), theirs (head)
|
||||||
auto merge_result = three_way_merge(base_content, base_content, head_content);
|
auto merge_result = three_way_merge(base_content, base_content, head_content);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <regex>
|
#include <regex>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include <json/json.h>
|
#include <json/json.h>
|
||||||
|
|
||||||
@@ -15,6 +16,34 @@ namespace git {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Simple base64 decoder
|
||||||
|
*/
|
||||||
|
std::string base64_decode(const std::string& encoded) {
|
||||||
|
static const std::string base64_chars =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
"abcdefghijklmnopqrstuvwxyz"
|
||||||
|
"0123456789+/";
|
||||||
|
|
||||||
|
std::string decoded;
|
||||||
|
std::vector<int> T(256, -1);
|
||||||
|
for (int i = 0; i < 64; i++) T[base64_chars[i]] = i;
|
||||||
|
|
||||||
|
int val = 0, valb = -8;
|
||||||
|
for (unsigned char c : encoded) {
|
||||||
|
if (T[c] == -1) break;
|
||||||
|
val = (val << 6) + T[c];
|
||||||
|
valb += 6;
|
||||||
|
if (valb >= 0) {
|
||||||
|
decoded.push_back(char((val >> valb) & 0xFF));
|
||||||
|
valb -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
// Callback for libcurl to write response data
|
// Callback for libcurl to write response data
|
||||||
size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
|
size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
|
||||||
((std::string*)userp)->append((char*)contents, size * nmemb);
|
((std::string*)userp)->append((char*)contents, size * nmemb);
|
||||||
@@ -228,27 +257,14 @@ std::optional<std::vector<std::string>> fetch_file_content(
|
|||||||
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());
|
||||||
|
|
||||||
// Simple base64 decode (using curl's built-in decoder)
|
// Decode base64
|
||||||
CURL* curl = curl_easy_init();
|
std::string decoded_content = base64_decode(encoded_content);
|
||||||
if (!curl) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
int outlen;
|
if (decoded_content.empty()) {
|
||||||
unsigned char* decoded = curl_easy_unescape(curl, encoded_content.c_str(), encoded_content.length(), &outlen);
|
|
||||||
|
|
||||||
if (!decoded) {
|
|
||||||
// Fallback: try manual base64 decode
|
|
||||||
// For now, return empty as we need proper base64 decoder
|
|
||||||
curl_easy_cleanup(curl);
|
|
||||||
std::cerr << "Failed to decode base64 content" << std::endl;
|
std::cerr << "Failed to decode base64 content" << std::endl;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string decoded_content(reinterpret_cast<char*>(decoded), outlen);
|
|
||||||
curl_free(decoded);
|
|
||||||
curl_easy_cleanup(curl);
|
|
||||||
|
|
||||||
// Split content into lines
|
// Split content into lines
|
||||||
return split_lines(decoded_content);
|
return split_lines(decoded_content);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user