mirror of
https://github.com/johndoe6345789/WizardMerge.git
synced 2026-04-24 13:44:55 +00:00
Convert backend to Drogon HTTP API server
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
8
BUILD.md
8
BUILD.md
@@ -19,7 +19,7 @@ WizardMerge/
|
||||
|
||||
### C++ Backend
|
||||
|
||||
The backend implements the core three-way merge algorithm.
|
||||
The backend implements the core three-way merge algorithm with an HTTP API server using Drogon.
|
||||
|
||||
**Prerequisites:**
|
||||
- C++17 compiler (GCC 7+, Clang 6+, MSVC 2017+)
|
||||
@@ -33,11 +33,13 @@ cd backend
|
||||
./build.sh
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
**Run the server:**
|
||||
```bash
|
||||
./build/wizardmerge-cli base.txt ours.txt theirs.txt output.txt
|
||||
./build/wizardmerge-cli
|
||||
```
|
||||
|
||||
The HTTP server will start on port 8080. Use the POST /api/merge endpoint to perform merges.
|
||||
|
||||
See [backend/README.md](backend/README.md) for details.
|
||||
|
||||
### TypeScript Frontend
|
||||
|
||||
@@ -14,7 +14,8 @@ WizardMerge uses a multi-frontend architecture with a high-performance C++ backe
|
||||
- **Location**: `backend/`
|
||||
- **Build System**: CMake + Ninja
|
||||
- **Package Manager**: Conan
|
||||
- **Features**: Three-way merge algorithm, conflict detection, auto-resolution
|
||||
- **Web Framework**: Drogon
|
||||
- **Features**: Three-way merge algorithm, conflict detection, auto-resolution, HTTP API
|
||||
|
||||
### Frontend (TypeScript/Next.js)
|
||||
- **Location**: `frontend/`
|
||||
|
||||
@@ -7,6 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# Find dependencies via Conan
|
||||
find_package(Drogon CONFIG REQUIRED)
|
||||
find_package(GTest QUIET)
|
||||
|
||||
# Library sources
|
||||
@@ -23,9 +24,10 @@ target_include_directories(wizardmerge
|
||||
# Executable
|
||||
add_executable(wizardmerge-cli
|
||||
src/main.cpp
|
||||
src/controllers/MergeController.cc
|
||||
)
|
||||
|
||||
target_link_libraries(wizardmerge-cli PRIVATE wizardmerge)
|
||||
target_link_libraries(wizardmerge-cli PRIVATE wizardmerge Drogon::Drogon)
|
||||
|
||||
# Tests (if GTest is available)
|
||||
if(GTest_FOUND)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# WizardMerge C++ Backend
|
||||
|
||||
This is the C++ backend for WizardMerge implementing the core merge algorithms.
|
||||
This is the C++ backend for WizardMerge implementing the core merge algorithms with a Drogon-based HTTP API.
|
||||
|
||||
## Build System
|
||||
|
||||
@@ -8,6 +8,7 @@ This is the C++ backend for WizardMerge implementing the core merge algorithms.
|
||||
- **Package Manager**: Conan
|
||||
- **CMake**: Version 3.15+
|
||||
- **C++ Standard**: C++17
|
||||
- **Web Framework**: Drogon
|
||||
|
||||
## Building
|
||||
|
||||
@@ -36,8 +37,8 @@ cd build
|
||||
cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release
|
||||
ninja
|
||||
|
||||
# Run the executable
|
||||
./wizardmerge-cli base.txt ours.txt theirs.txt output.txt
|
||||
# Run the HTTP server
|
||||
./wizardmerge-cli
|
||||
```
|
||||
|
||||
## Testing
|
||||
@@ -53,12 +54,16 @@ ninja test
|
||||
backend/
|
||||
├── CMakeLists.txt # CMake build configuration
|
||||
├── conanfile.py # Conan package definition
|
||||
├── config.json # Drogon server configuration
|
||||
├── include/ # Public headers
|
||||
│ └── wizardmerge/
|
||||
│ └── merge/
|
||||
│ └── three_way_merge.h
|
||||
├── src/ # Implementation files
|
||||
│ ├── main.cpp
|
||||
│ ├── controllers/
|
||||
│ │ ├── MergeController.h
|
||||
│ │ └── MergeController.cc
|
||||
│ └── merge/
|
||||
│ └── three_way_merge.cpp
|
||||
└── tests/ # Unit tests
|
||||
@@ -69,16 +74,48 @@ backend/
|
||||
- Three-way merge algorithm (Phase 1.1 from ROADMAP)
|
||||
- Conflict detection and marking
|
||||
- Auto-resolution of common patterns
|
||||
- Command-line interface
|
||||
- HTTP API server using Drogon framework
|
||||
- JSON-based request/response
|
||||
|
||||
## Usage
|
||||
## API Usage
|
||||
|
||||
### Start the server
|
||||
|
||||
```sh
|
||||
wizardmerge-cli <base> <ours> <theirs> <output>
|
||||
./wizardmerge-cli [config.json]
|
||||
```
|
||||
|
||||
Arguments:
|
||||
- `base`: Common ancestor version
|
||||
- `ours`: Current branch version
|
||||
- `theirs`: Branch being merged
|
||||
- `output`: Output file for merged result
|
||||
The server will start on port 8080 by default (configurable in config.json).
|
||||
|
||||
### POST /api/merge
|
||||
|
||||
Perform a three-way merge operation.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"base": ["line1", "line2", "line3"],
|
||||
"ours": ["line1", "line2_modified", "line3"],
|
||||
"theirs": ["line1", "line2", "line3_modified"]
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"merged": ["line1", "line2_modified", "line3_modified"],
|
||||
"conflicts": [],
|
||||
"has_conflicts": false
|
||||
}
|
||||
```
|
||||
|
||||
**Example with curl:**
|
||||
```sh
|
||||
curl -X POST http://localhost:8080/api/merge \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"base": ["line1", "line2", "line3"],
|
||||
"ours": ["line1", "line2_ours", "line3"],
|
||||
"theirs": ["line1", "line2_theirs", "line3"]
|
||||
}'
|
||||
```
|
||||
|
||||
@@ -18,7 +18,7 @@ class WizardMergeConan(ConanFile):
|
||||
exports_sources = "CMakeLists.txt", "src/*", "include/*"
|
||||
|
||||
# Dependencies
|
||||
requires = []
|
||||
requires = ["drogon/1.9.3"]
|
||||
|
||||
generators = "CMakeDeps", "CMakeToolchain"
|
||||
|
||||
|
||||
43
backend/config.json
Normal file
43
backend/config.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"app": {
|
||||
"threads_num": 4,
|
||||
"enable_session": false,
|
||||
"session_timeout": 0,
|
||||
"document_root": "",
|
||||
"upload_path": "./uploads",
|
||||
"file_types": [
|
||||
"json"
|
||||
],
|
||||
"max_connections": 100000,
|
||||
"max_connections_per_ip": 0,
|
||||
"load_libs": [],
|
||||
"log": {
|
||||
"log_path": "./logs",
|
||||
"logfile_base_name": "wizardmerge",
|
||||
"log_size_limit": 100000000,
|
||||
"log_level": "INFO"
|
||||
},
|
||||
"run_as_daemon": false,
|
||||
"relaunch_on_error": false,
|
||||
"use_sendfile": true,
|
||||
"use_gzip": true,
|
||||
"use_brotli": false,
|
||||
"static_files_cache_time": 0,
|
||||
"simple_controllers_map": [],
|
||||
"idle_connection_timeout": 60,
|
||||
"server_header_field": "WizardMerge/0.1.0",
|
||||
"enable_server_header": true,
|
||||
"keepalive_requests": 0,
|
||||
"pipelining_requests": 0,
|
||||
"client_max_body_size": "10M",
|
||||
"client_max_memory_body_size": "10M",
|
||||
"client_max_websocket_message_size": "128K"
|
||||
},
|
||||
"listeners": [
|
||||
{
|
||||
"address": "0.0.0.0",
|
||||
"port": 8080,
|
||||
"https": false
|
||||
}
|
||||
]
|
||||
}
|
||||
112
backend/src/controllers/MergeController.cc
Normal file
112
backend/src/controllers/MergeController.cc
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* @file MergeController.cc
|
||||
* @brief Implementation of HTTP controller for merge operations
|
||||
*/
|
||||
|
||||
#include "MergeController.h"
|
||||
#include "wizardmerge/merge/three_way_merge.h"
|
||||
#include <json/json.h>
|
||||
|
||||
using namespace wizardmerge::controllers;
|
||||
using namespace wizardmerge::merge;
|
||||
|
||||
void MergeController::merge(
|
||||
const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback) {
|
||||
|
||||
// Parse request JSON
|
||||
auto jsonPtr = req->getJsonObject();
|
||||
if (!jsonPtr) {
|
||||
Json::Value error;
|
||||
error["error"] = "Invalid JSON in request body";
|
||||
auto resp = HttpResponse::newHttpJsonResponse(error);
|
||||
resp->setStatusCode(k400BadRequest);
|
||||
callback(resp);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &json = *jsonPtr;
|
||||
|
||||
// Validate required fields
|
||||
if (!json.isMember("base") || !json.isMember("ours") || !json.isMember("theirs")) {
|
||||
Json::Value error;
|
||||
error["error"] = "Missing required fields: base, ours, theirs";
|
||||
auto resp = HttpResponse::newHttpJsonResponse(error);
|
||||
resp->setStatusCode(k400BadRequest);
|
||||
callback(resp);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert JSON arrays to std::vector<std::string>
|
||||
std::vector<std::string> base;
|
||||
std::vector<std::string> ours;
|
||||
std::vector<std::string> theirs;
|
||||
|
||||
try {
|
||||
for (const auto &line : json["base"]) {
|
||||
base.push_back(line.asString());
|
||||
}
|
||||
for (const auto &line : json["ours"]) {
|
||||
ours.push_back(line.asString());
|
||||
}
|
||||
for (const auto &line : json["theirs"]) {
|
||||
theirs.push_back(line.asString());
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
Json::Value error;
|
||||
error["error"] = "Invalid array format in request";
|
||||
auto resp = HttpResponse::newHttpJsonResponse(error);
|
||||
resp->setStatusCode(k400BadRequest);
|
||||
callback(resp);
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform merge
|
||||
auto result = three_way_merge(base, ours, theirs);
|
||||
|
||||
// Auto-resolve simple conflicts
|
||||
result = auto_resolve(result);
|
||||
|
||||
// Build response JSON
|
||||
Json::Value response;
|
||||
Json::Value mergedArray(Json::arrayValue);
|
||||
for (const auto &line : result.merged_lines) {
|
||||
mergedArray.append(line.content);
|
||||
}
|
||||
response["merged"] = mergedArray;
|
||||
|
||||
// Add conflicts information
|
||||
Json::Value conflictsArray(Json::arrayValue);
|
||||
for (const auto &conflict : result.conflicts) {
|
||||
Json::Value conflictObj;
|
||||
conflictObj["start_line"] = Json::Value::UInt64(conflict.start_line);
|
||||
conflictObj["end_line"] = Json::Value::UInt64(conflict.end_line);
|
||||
|
||||
Json::Value baseLines(Json::arrayValue);
|
||||
for (const auto &line : conflict.base_lines) {
|
||||
baseLines.append(line.content);
|
||||
}
|
||||
conflictObj["base_lines"] = baseLines;
|
||||
|
||||
Json::Value ourLines(Json::arrayValue);
|
||||
for (const auto &line : conflict.our_lines) {
|
||||
ourLines.append(line.content);
|
||||
}
|
||||
conflictObj["our_lines"] = ourLines;
|
||||
|
||||
Json::Value theirLines(Json::arrayValue);
|
||||
for (const auto &line : conflict.their_lines) {
|
||||
theirLines.append(line.content);
|
||||
}
|
||||
conflictObj["their_lines"] = theirLines;
|
||||
|
||||
conflictsArray.append(conflictObj);
|
||||
}
|
||||
response["conflicts"] = conflictsArray;
|
||||
response["has_conflicts"] = result.has_conflicts();
|
||||
|
||||
// Return successful response
|
||||
auto resp = HttpResponse::newHttpJsonResponse(response);
|
||||
resp->setStatusCode(k200OK);
|
||||
callback(resp);
|
||||
}
|
||||
49
backend/src/controllers/MergeController.h
Normal file
49
backend/src/controllers/MergeController.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @file MergeController.h
|
||||
* @brief HTTP controller for merge operations
|
||||
*/
|
||||
|
||||
#ifndef WIZARDMERGE_CONTROLLERS_MERGE_CONTROLLER_H
|
||||
#define WIZARDMERGE_CONTROLLERS_MERGE_CONTROLLER_H
|
||||
|
||||
#include <drogon/HttpController.h>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
namespace wizardmerge {
|
||||
namespace controllers {
|
||||
|
||||
/**
|
||||
* @brief HTTP controller for three-way merge API
|
||||
*/
|
||||
class MergeController : public HttpController<MergeController> {
|
||||
public:
|
||||
METHOD_LIST_BEGIN
|
||||
// POST /api/merge - Perform three-way merge
|
||||
ADD_METHOD_TO(MergeController::merge, "/api/merge", Post);
|
||||
METHOD_LIST_END
|
||||
|
||||
/**
|
||||
* @brief Perform three-way merge operation
|
||||
*
|
||||
* Request body should be JSON:
|
||||
* {
|
||||
* "base": ["line1", "line2", ...],
|
||||
* "ours": ["line1", "line2", ...],
|
||||
* "theirs": ["line1", "line2", ...]
|
||||
* }
|
||||
*
|
||||
* Response:
|
||||
* {
|
||||
* "merged": ["line1", "line2", ...],
|
||||
* "conflicts": [...]
|
||||
* }
|
||||
*/
|
||||
void merge(const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback);
|
||||
};
|
||||
|
||||
} // namespace controllers
|
||||
} // namespace wizardmerge
|
||||
|
||||
#endif // WIZARDMERGE_CONTROLLERS_MERGE_CONTROLLER_H
|
||||
@@ -1,83 +1,43 @@
|
||||
/**
|
||||
* @file main.cpp
|
||||
* @brief Command-line interface for WizardMerge
|
||||
* @brief HTTP API server for WizardMerge using Drogon framework
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include "wizardmerge/merge/three_way_merge.h"
|
||||
#include <drogon/drogon.h>
|
||||
#include "controllers/MergeController.h"
|
||||
|
||||
using namespace wizardmerge::merge;
|
||||
|
||||
/**
|
||||
* @brief Read lines from a file
|
||||
*/
|
||||
std::vector<std::string> read_file(const std::string& filename) {
|
||||
std::vector<std::string> lines;
|
||||
std::ifstream file(filename);
|
||||
std::string line;
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
lines.push_back(line);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write lines to a file
|
||||
*/
|
||||
void write_file(const std::string& filename, const std::vector<Line>& lines) {
|
||||
std::ofstream file(filename);
|
||||
for (const auto& line : lines) {
|
||||
file << line.content << '\n';
|
||||
}
|
||||
}
|
||||
using namespace drogon;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc != 5) {
|
||||
std::cerr << "Usage: " << argv[0] << " <base> <ours> <theirs> <output>\n";
|
||||
std::cerr << "Performs three-way merge on three file versions.\n";
|
||||
std::cout << "WizardMerge - Intelligent Merge Conflict Resolution API\n";
|
||||
std::cout << "======================================================\n";
|
||||
std::cout << "Starting HTTP server...\n\n";
|
||||
|
||||
// Load configuration from file
|
||||
std::string config_file = "config.json";
|
||||
if (argc > 1) {
|
||||
config_file = argv[1];
|
||||
}
|
||||
|
||||
try {
|
||||
// Load configuration and start server
|
||||
app().loadConfigFile(config_file);
|
||||
|
||||
std::cout << "Server will listen on port "
|
||||
<< app().getListeners()[0].toPort << "\n";
|
||||
std::cout << "Available endpoints:\n";
|
||||
std::cout << " POST /api/merge - Three-way merge API\n";
|
||||
std::cout << "\nPress Ctrl+C to stop the server.\n\n";
|
||||
|
||||
// Run the application
|
||||
app().run();
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << '\n';
|
||||
std::cerr << "Failed to load config file: " << config_file << '\n';
|
||||
std::cerr << "Usage: " << argv[0] << " [config.json]\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string base_file = argv[1];
|
||||
std::string ours_file = argv[2];
|
||||
std::string theirs_file = argv[3];
|
||||
std::string output_file = argv[4];
|
||||
|
||||
std::cout << "WizardMerge - Intelligent Merge Conflict Resolution\n";
|
||||
std::cout << "===================================================\n";
|
||||
std::cout << "Base: " << base_file << '\n';
|
||||
std::cout << "Ours: " << ours_file << '\n';
|
||||
std::cout << "Theirs: " << theirs_file << '\n';
|
||||
std::cout << "Output: " << output_file << '\n';
|
||||
std::cout << '\n';
|
||||
|
||||
// Read input files
|
||||
auto base = read_file(base_file);
|
||||
auto ours = read_file(ours_file);
|
||||
auto theirs = read_file(theirs_file);
|
||||
|
||||
// Perform merge
|
||||
auto result = three_way_merge(base, ours, theirs);
|
||||
|
||||
// Auto-resolve simple conflicts
|
||||
result = auto_resolve(result);
|
||||
|
||||
// Write output
|
||||
write_file(output_file, result.merged_lines);
|
||||
|
||||
// Report results
|
||||
if (result.has_conflicts()) {
|
||||
std::cout << "Merge completed with " << result.conflicts.size()
|
||||
<< " conflict(s).\n";
|
||||
std::cout << "Please review and resolve conflicts in: " << output_file << '\n';
|
||||
return 1;
|
||||
} else {
|
||||
std::cout << "Merge completed successfully with no conflicts.\n";
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user