Merge pull request #13 from johndoe6345789/copilot/convert-backend-to-drogon

Convert backend from CLI to Drogon HTTP API server
This commit is contained in:
2025-12-26 03:59:38 +00:00
committed by GitHub
17 changed files with 1208 additions and 116 deletions

View File

@@ -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

214
DROGON_CONVERSION.md Normal file
View File

@@ -0,0 +1,214 @@
# Backend Conversion to Drogon - Implementation Summary
## Overview
The WizardMerge C++ backend has been successfully converted from a command-line interface (CLI) application to a modern HTTP API server using the Drogon web framework.
## Changes Made
### 1. Core Architecture Changes
#### HTTP Server Implementation
- **New**: `src/main.cpp` - Now runs as an HTTP server using Drogon
- **Previous**: CLI tool that processed files directly
- **Current**: HTTP API server that accepts JSON requests
#### API Controller
- **Added**: `src/controllers/MergeController.h` - HTTP controller interface
- **Added**: `src/controllers/MergeController.cc` - Controller implementation
- **Endpoint**: POST /api/merge - Performs three-way merge operations
### 2. Build System Updates
#### Dependencies
- **Added**: Drogon framework (via Conan)
- **Updated**: `conanfile.py` - Added Drogon 1.9.3 dependency
- **Updated**: `CMakeLists.txt` - Integrated Drogon, made it optional
#### Build Flexibility
The build system now supports multiple installation methods:
1. **Conan** - Automatic dependency management
2. **Docker** - Containerized deployment
3. **Manual** - Direct system installation of Drogon
4. **Library-only** - Build without Drogon (library remains functional)
### 3. Configuration
#### Server Configuration
- **Added**: `config.json` - Drogon server configuration
- Port: 8080 (default)
- Threads: 4
- Logging: INFO level to ./logs
- Max body size: 10MB
### 4. Deployment Support
#### Docker
- **Added**: `Dockerfile` - Multi-stage build for production deployment
- **Added**: `docker-compose.yml` - Easy orchestration
#### Installation Script
- **Added**: `install_drogon.sh` - Automated Drogon installation from source
#### Build Script Enhancement
- **Updated**: `build.sh` - Now supports multiple build paths with fallbacks
### 5. Documentation
#### API Documentation
- **Updated**: `backend/README.md` - Comprehensive API documentation
- **Added**: `examples/README.md` - API usage examples
- **Updated**: `BUILD.md` - Updated build instructions
- **Updated**: Main `README.md` - Architecture overview
#### Examples
- **Added**: `examples/api_client.py` - Python client example
- **Added**: `examples/test_api.sh` - curl-based test scripts
## API Specification
### Endpoint: POST /api/merge
**Request Format:**
```json
{
"base": ["line1", "line2", "..."],
"ours": ["line1", "line2", "..."],
"theirs": ["line1", "line2", "..."]
}
```
**Response Format:**
```json
{
"merged": ["merged_line1", "merged_line2", "..."],
"conflicts": [
{
"start_line": 0,
"end_line": 5,
"base_lines": ["..."],
"our_lines": ["..."],
"their_lines": ["..."]
}
],
"has_conflicts": false
}
```
**Error Responses:**
- 400 Bad Request - Invalid JSON or missing fields
- 500 Internal Server Error - Processing error
## Deployment Options
### 1. Docker (Recommended for Production)
```bash
docker-compose up -d
```
### 2. Direct Installation
```bash
./install_drogon.sh
./build.sh
cd build && ./wizardmerge-cli
```
### 3. Development
```bash
./build.sh
cd build && ./wizardmerge-cli config.json
```
## Backward Compatibility
### Library Compatibility
- The core merge library (`libwizardmerge.a`) remains unchanged
- All existing merge algorithms work identically
- Unit tests continue to pass without modification
### Breaking Changes
- CLI interface removed (replaced with HTTP API)
- Direct file I/O removed (now uses JSON over HTTP)
### Migration Path
For users needing CLI functionality:
1. Use the HTTP API with curl/scripts
2. Write a thin CLI wrapper around the HTTP API
3. Link against `libwizardmerge.a` directly in custom applications
## Testing
### Unit Tests
- All existing unit tests pass
- Tests use the library directly, unaffected by HTTP layer
### Integration Testing
- Python client example demonstrates API usage
- Shell script examples for curl-based testing
- Docker-based deployment testing
## Security
### Security Analysis
- CodeQL scan: No vulnerabilities found
- Code review: All issues addressed
### Security Features
- JSON validation on all inputs
- Request body size limits (10MB default)
- Type-safe JSON conversions
- Exception handling for all endpoints
## Performance Considerations
### Drogon Benefits
- High-concurrency, non-blocking I/O
- 150k+ requests/sec capability
- Multi-threaded request processing (4 threads default)
- HTTP/1.1 support with keep-alive
### Resource Usage
- Minimal memory footprint
- Efficient JSON parsing with JsonCpp
- Static library linking for core algorithms
## Future Enhancements
### Potential Improvements
1. WebSocket support for real-time conflict resolution
2. Additional endpoints for batch processing
3. File upload support for direct file merging
4. Authentication/authorization layer
5. Rate limiting for production use
6. Metrics and monitoring endpoints
### Scalability
- Horizontal scaling via load balancer
- Stateless design allows multiple instances
- Docker Swarm or Kubernetes ready
## Files Changed/Added
### Modified Files
- `backend/CMakeLists.txt` - Build configuration
- `backend/conanfile.py` - Dependencies
- `backend/src/main.cpp` - Server implementation
- `backend/build.sh` - Build script
- `backend/README.md` - Documentation
- `BUILD.md` - Build guide
- `README.md` - Main documentation
### Added Files
- `backend/config.json` - Server configuration
- `backend/Dockerfile` - Container definition
- `backend/docker-compose.yml` - Orchestration
- `backend/install_drogon.sh` - Installation script
- `backend/src/controllers/MergeController.h` - Controller header
- `backend/src/controllers/MergeController.cc` - Controller implementation
- `backend/examples/README.md` - Examples documentation
- `backend/examples/api_client.py` - Python client
- `backend/examples/test_api.sh` - Shell test scripts
## Conclusion
The conversion to Drogon provides a modern, scalable HTTP API while maintaining the core merge algorithm functionality. The implementation includes comprehensive documentation, multiple deployment options, and maintains backward compatibility at the library level. The flexible build system ensures developers can work in various environments, from containerized deployments to manual installations.

View File

@@ -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/`

View File

@@ -7,6 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Find dependencies via Conan
find_package(Drogon CONFIG QUIET)
find_package(GTest QUIET)
# Library sources
@@ -20,12 +21,23 @@ target_include_directories(wizardmerge
$<INSTALL_INTERFACE:include>
)
# Executable
add_executable(wizardmerge-cli
src/main.cpp
)
# Executable (only if Drogon is found)
if(Drogon_FOUND)
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)
install(TARGETS wizardmerge-cli
RUNTIME DESTINATION bin
)
message(STATUS "Drogon found - building HTTP server")
else()
message(WARNING "Drogon not found - skipping HTTP server build. Install Drogon to build the server.")
endif()
# Tests (if GTest is available)
if(GTest_FOUND)
@@ -40,10 +52,9 @@ if(GTest_FOUND)
endif()
# Install targets
install(TARGETS wizardmerge wizardmerge-cli
install(TARGETS wizardmerge
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
)
install(DIRECTORY include/ DESTINATION include)

46
backend/Dockerfile Normal file
View File

@@ -0,0 +1,46 @@
# Dockerfile for WizardMerge Backend with Drogon
FROM ubuntu:22.04
# Install dependencies
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y \
git \
gcc \
g++ \
cmake \
ninja-build \
libjsoncpp-dev \
uuid-dev \
zlib1g-dev \
libssl-dev \
&& rm -rf /var/lib/apt/lists/*
# Install Drogon framework
WORKDIR /tmp
RUN git clone https://github.com/drogonframework/drogon.git && \
cd drogon && \
git submodule update --init && \
mkdir build && cd build && \
cmake .. -DCMAKE_BUILD_TYPE=Release && \
make -j$(nproc) && \
make install && \
cd /tmp && rm -rf drogon
# Set up work directory
WORKDIR /app
COPY . .
# Build WizardMerge
RUN mkdir -p build && cd build && \
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release && \
ninja
# Expose port
EXPOSE 8080
# Copy config to build directory
RUN cp config.json build/
# Run the server
WORKDIR /app/build
CMD ["./wizardmerge-cli"]

View File

@@ -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,36 +8,85 @@ 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
### Prerequisites
**Required:**
- C++17 compiler (GCC 7+, Clang 6+, MSVC 2017+)
- CMake 3.15+
- Ninja build tool
**For HTTP Server:**
- Drogon framework (see installation methods below)
### Installation Methods
#### Method 1: Using Installer Script (Recommended)
```sh
# Install Drogon from source
./install_drogon.sh
# Build WizardMerge
./build.sh
```
#### Method 2: Using Docker (Easiest)
```sh
# Build and run with Docker Compose
docker-compose up --build
# Or use Docker directly
docker build -t wizardmerge-backend .
docker run -p 8080:8080 wizardmerge-backend
```
#### Method 3: Using Conan
```sh
# Install Conan
pip install conan
# Install CMake and Ninja
# On Ubuntu/Debian:
sudo apt-get install cmake ninja-build
# Build with Conan
./build.sh
```
# On macOS:
brew install cmake ninja
Note: Conan requires internet access to download Drogon.
#### Method 4: Manual CMake Build
If you have Drogon already installed system-wide:
```sh
mkdir build && cd build
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release
ninja
```
### Build Steps
The build script automatically handles dependencies and provides multiple build options:
```sh
# Configure with Conan
conan install . --output-folder=build --build=missing
# Automatic build (tries Conan, falls back to direct CMake)
./build.sh
```
# Build with CMake and Ninja
cd build
cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release
If Drogon is not found, the library will still build but the HTTP server will be skipped.
### Running Without Drogon
If you only need the merge library (not the HTTP server):
```sh
mkdir build && cd build
cmake .. -G Ninja
ninja
# Run the executable
./wizardmerge-cli base.txt ours.txt theirs.txt output.txt
# This builds libwizardmerge.a which can be linked into other applications
```
## Testing
@@ -53,12 +102,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 +122,114 @@ 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"]
}'
```
## Deployment
### Production Deployment with Docker
The recommended way to deploy in production:
```sh
# Using Docker Compose
docker-compose up -d
# Check logs
docker-compose logs -f
# Stop the server
docker-compose down
```
### Configuration
Edit `config.json` to customize server settings:
- `listeners[].port`: Change server port (default: 8080)
- `app.threads_num`: Number of worker threads (default: 4)
- `app.log.log_level`: Logging level (DEBUG, INFO, WARN, ERROR)
- `app.client_max_body_size`: Maximum request body size
### Monitoring
Logs are written to `./logs/` directory by default. Monitor with:
```sh
tail -f logs/wizardmerge.log
```
## Development
### Architecture
The backend is now structured as a Drogon HTTP API server:
- **Core Library** (`libwizardmerge.a`): Contains the merge algorithms
- **HTTP Server** (`wizardmerge-cli`): Drogon-based API server
- **Controllers** (`src/controllers/`): HTTP request handlers
- **Configuration** (`config.json`): Server settings
### Adding New Endpoints
1. Create a new controller in `src/controllers/`
2. Implement the controller methods
3. Add the controller source to CMakeLists.txt
4. Rebuild the project
Example controller structure:
```cpp
class MyController : public HttpController<MyController> {
public:
METHOD_LIST_BEGIN
ADD_METHOD_TO(MyController::myMethod, "/api/mypath", Post);
METHOD_LIST_END
void myMethod(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
};
```

View File

@@ -1,5 +1,5 @@
#!/bin/bash
# Build script for WizardMerge C++ backend using Conan and Ninja
# Build script for WizardMerge C++ backend with Drogon support
set -e
@@ -7,21 +7,48 @@ echo "=== WizardMerge C++ Backend Build ==="
echo
# Check for required tools
command -v conan >/dev/null 2>&1 || { echo "Error: conan not found. Install with: pip install conan"; exit 1; }
command -v ninja >/dev/null 2>&1 || { echo "Error: ninja not found. Install with: apt-get install ninja-build / brew install ninja"; exit 1; }
command -v cmake >/dev/null 2>&1 || { echo "Error: cmake not found."; exit 1; }
command -v ninja >/dev/null 2>&1 || { echo "Error: ninja not found. Install with: apt-get install ninja-build / brew install ninja"; exit 1; }
# Check if Drogon is installed
if ! pkg-config --exists drogon 2>/dev/null && ! ldconfig -p 2>/dev/null | grep -q libdrogon; then
echo "WARNING: Drogon framework not found."
echo "The library will be built, but the HTTP server will be skipped."
echo
echo "To build the HTTP server, install Drogon first:"
echo " Option 1: Run ./install_drogon.sh"
echo " Option 2: Use Docker: docker-compose up --build"
echo " Option 3: Use Conan: conan install . --output-folder=build --build=missing"
echo
read -p "Continue building without Drogon? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
# Create build directory
mkdir -p build
cd build
# Install dependencies with Conan
echo "Installing dependencies with Conan..."
conan install .. --output-folder=. --build=missing
# Configure with CMake
echo "Configuring with CMake..."
cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release
# Check if we should use Conan
if command -v conan >/dev/null 2>&1 && [ -f ../conanfile.py ]; then
echo "Installing dependencies with Conan..."
conan install .. --output-folder=. --build=missing 2>/dev/null && CONAN_SUCCESS=true || CONAN_SUCCESS=false
if [ "$CONAN_SUCCESS" = true ]; then
echo "Configuring with CMake (Conan toolchain)..."
cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release
else
echo "Conan installation failed, trying without Conan..."
echo "Configuring with CMake..."
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release
fi
else
# Configure with CMake (without Conan)
echo "Configuring with CMake..."
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release
fi
# Build with Ninja
echo "Building with Ninja..."
@@ -29,4 +56,14 @@ ninja
echo
echo "=== Build Complete ==="
echo "Binary: build/wizardmerge-cli"
if [ -f wizardmerge-cli ]; then
echo "HTTP Server: build/wizardmerge-cli"
echo "Run with: cd build && ./wizardmerge-cli"
else
echo "Library: build/libwizardmerge.a"
echo "HTTP server not built (Drogon not found)"
fi
if [ -f wizardmerge-tests ]; then
echo "Tests: build/wizardmerge-tests"
echo "Run with: cd build && ./wizardmerge-tests"
fi

View File

@@ -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
View 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
}
]
}

View File

@@ -0,0 +1,14 @@
version: '3.8'
services:
wizardmerge-backend:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
volumes:
- ./logs:/app/build/logs
restart: unless-stopped
environment:
- LOG_LEVEL=INFO

221
backend/examples/README.md Normal file
View File

@@ -0,0 +1,221 @@
# WizardMerge API Examples
This directory contains example clients for the WizardMerge HTTP API.
## Prerequisites
Make sure the WizardMerge HTTP server is running:
```sh
cd ..
./build.sh
cd build
./wizardmerge-cli
```
The server should be running on http://localhost:8080
## Examples
### Python Client (`api_client.py`)
Demonstrates API usage with the requests library.
**Requirements:**
```sh
pip install requests
```
**Run:**
```sh
./api_client.py
```
### Curl Examples (`test_api.sh`)
Shell script with curl commands for testing the API.
**Requirements:**
- curl
- jq (optional, for pretty JSON output)
**Run:**
```sh
./test_api.sh
```
## API Endpoint
### POST /api/merge
Performs a three-way merge operation.
**Request Body:**
```json
{
"base": ["line1", "line2", "line3"],
"ours": ["line1", "line2_modified", "line3"],
"theirs": ["line1", "line2", "line3_modified"]
}
```
**Response (Success):**
```json
{
"merged": ["line1", "line2_modified", "line3_modified"],
"conflicts": [],
"has_conflicts": false
}
```
**Response (With Conflicts):**
```json
{
"merged": [
"line1",
"<<<<<<< OURS",
"line2_ours",
"=======",
"line2_theirs",
">>>>>>> THEIRS",
"line3"
],
"conflicts": [
{
"start_line": 1,
"end_line": 1,
"base_lines": ["line2"],
"our_lines": ["line2_ours"],
"their_lines": ["line2_theirs"]
}
],
"has_conflicts": true
}
```
**Error Response (400 Bad Request):**
```json
{
"error": "Missing required fields: base, ours, theirs"
}
```
## Manual Testing with curl
Basic example:
```sh
curl -X POST http://localhost:8080/api/merge \
-H "Content-Type: application/json" \
-d '{
"base": ["hello", "world"],
"ours": ["hello", "beautiful world"],
"theirs": ["goodbye", "world"]
}'
```
## Integration Examples
### JavaScript/Node.js
```javascript
const axios = require('axios');
async function merge(base, ours, theirs) {
const response = await axios.post('http://localhost:8080/api/merge', {
base, ours, theirs
});
return response.data;
}
// Usage
merge(
['line1', 'line2'],
['line1', 'line2_modified'],
['line1', 'line2']
).then(result => {
console.log('Merged:', result.merged);
console.log('Has conflicts:', result.has_conflicts);
});
```
### Python
```python
import requests
def merge(base, ours, theirs, server_url="http://localhost:8080"):
response = requests.post(
f"{server_url}/api/merge",
json={"base": base, "ours": ours, "theirs": theirs}
)
return response.json()
# Usage
result = merge(
base=["line1", "line2"],
ours=["line1", "line2_modified"],
theirs=["line1", "line2"]
)
print(f"Merged: {result['merged']}")
print(f"Has conflicts: {result['has_conflicts']}")
```
### Go
```go
package main
import (
"bytes"
"encoding/json"
"net/http"
)
type MergeRequest struct {
Base []string `json:"base"`
Ours []string `json:"ours"`
Theirs []string `json:"theirs"`
}
type MergeResponse struct {
Merged []string `json:"merged"`
Conflicts []Conflict `json:"conflicts"`
HasConflicts bool `json:"has_conflicts"`
}
func merge(base, ours, theirs []string) (*MergeResponse, error) {
req := MergeRequest{Base: base, Ours: ours, Theirs: theirs}
jsonData, _ := json.Marshal(req)
resp, err := http.Post(
"http://localhost:8080/api/merge",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result MergeResponse
json.NewDecoder(resp.Body).Decode(&result)
return &result, nil
}
```
## Docker Usage
If running the server in Docker:
```sh
# Start server
docker-compose up -d
# Test API (from host)
curl -X POST http://localhost:8080/api/merge \
-H "Content-Type: application/json" \
-d '{"base":["a"],"ours":["b"],"theirs":["c"]}'
# Check logs
docker-compose logs -f
```

98
backend/examples/api_client.py Executable file
View File

@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""
Example client for WizardMerge HTTP API
Demonstrates how to use the POST /api/merge endpoint
"""
import requests
import json
import sys
def test_merge(base_lines, ours_lines, theirs_lines, server_url="http://localhost:8080"):
"""
Test the merge API with given inputs
"""
endpoint = f"{server_url}/api/merge"
payload = {
"base": base_lines,
"ours": ours_lines,
"theirs": theirs_lines
}
print(f"Sending merge request to {endpoint}")
print(f"Base: {base_lines}")
print(f"Ours: {ours_lines}")
print(f"Theirs: {theirs_lines}")
print()
try:
response = requests.post(endpoint, json=payload)
response.raise_for_status()
result = response.json()
print("=== Merge Result ===")
print(f"Merged lines: {result['merged']}")
print(f"Has conflicts: {result['has_conflicts']}")
if result['conflicts']:
print(f"Number of conflicts: {len(result['conflicts'])}")
for i, conflict in enumerate(result['conflicts']):
print(f"\nConflict {i+1}:")
print(f" Lines: {conflict['start_line']}-{conflict['end_line']}")
print(f" Base: {conflict['base_lines']}")
print(f" Ours: {conflict['our_lines']}")
print(f" Theirs: {conflict['their_lines']}")
return result
except requests.exceptions.ConnectionError:
print(f"ERROR: Could not connect to server at {server_url}")
print("Make sure the server is running with: ./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 Exception as e:
print(f"ERROR: {e}")
sys.exit(1)
def main():
print("WizardMerge API Client - Example Usage")
print("=" * 50)
print()
# Test 1: No conflicts
print("Test 1: No conflicts (non-overlapping changes)")
print("-" * 50)
test_merge(
base_lines=["line1", "line2", "line3"],
ours_lines=["line1", "line2_modified", "line3"],
theirs_lines=["line1", "line2", "line3_modified"]
)
print()
# Test 2: With conflicts
print("\nTest 2: With conflicts (overlapping changes)")
print("-" * 50)
test_merge(
base_lines=["line1", "line2", "line3"],
ours_lines=["line1", "line2_ours", "line3"],
theirs_lines=["line1", "line2_theirs", "line3"]
)
print()
# Test 3: Identical changes
print("\nTest 3: Identical changes (auto-resolved)")
print("-" * 50)
test_merge(
base_lines=["line1", "line2", "line3"],
ours_lines=["line1", "line2_same", "line3"],
theirs_lines=["line1", "line2_same", "line3"]
)
print()
if __name__ == "__main__":
main()

70
backend/examples/test_api.sh Executable file
View File

@@ -0,0 +1,70 @@
#!/bin/bash
# Example API calls using curl
SERVER_URL="http://localhost:8080"
echo "WizardMerge API - Example curl Commands"
echo "========================================"
echo
# Test 1: No conflicts
echo "Test 1: No conflicts (non-overlapping changes)"
echo "-----------------------------------------------"
curl -X POST "${SERVER_URL}/api/merge" \
-H "Content-Type: application/json" \
-d '{
"base": ["line1", "line2", "line3"],
"ours": ["line1", "line2_modified", "line3"],
"theirs": ["line1", "line2", "line3_modified"]
}' | jq '.'
echo
echo
# Test 2: With conflicts
echo "Test 2: With conflicts (overlapping changes)"
echo "---------------------------------------------"
curl -X POST "${SERVER_URL}/api/merge" \
-H "Content-Type: application/json" \
-d '{
"base": ["line1", "line2", "line3"],
"ours": ["line1", "line2_ours", "line3"],
"theirs": ["line1", "line2_theirs", "line3"]
}' | jq '.'
echo
echo
# Test 3: Identical changes
echo "Test 3: Identical changes (auto-resolved)"
echo "------------------------------------------"
curl -X POST "${SERVER_URL}/api/merge" \
-H "Content-Type: application/json" \
-d '{
"base": ["line1", "line2", "line3"],
"ours": ["line1", "line2_same", "line3"],
"theirs": ["line1", "line2_same", "line3"]
}' | jq '.'
echo
echo
# Test 4: Error handling - Missing field
echo "Test 4: Error handling - Missing required field"
echo "------------------------------------------------"
curl -X POST "${SERVER_URL}/api/merge" \
-H "Content-Type: application/json" \
-d '{
"base": ["line1", "line2"],
"ours": ["line1", "line2_modified"]
}' | jq '.'
echo
echo
# Test 5: Error handling - Invalid JSON
echo "Test 5: Error handling - Invalid JSON"
echo "--------------------------------------"
curl -X POST "${SERVER_URL}/api/merge" \
-H "Content-Type: application/json" \
-d 'not json'
echo
echo
echo "Done!"

51
backend/install_drogon.sh Executable file
View File

@@ -0,0 +1,51 @@
#!/bin/bash
# Script to install Drogon framework from source
# Run this script before building WizardMerge if Drogon is not installed
set -e
echo "=== Installing Drogon Framework from Source ==="
echo
# Check for required tools
command -v git >/dev/null 2>&1 || { echo "Error: git not found."; exit 1; }
command -v cmake >/dev/null 2>&1 || { echo "Error: cmake not found."; exit 1; }
command -v make >/dev/null 2>&1 || { echo "Error: make not found."; exit 1; }
# Install system dependencies (Ubuntu/Debian)
if command -v apt-get >/dev/null 2>&1; then
echo "Installing system dependencies..."
sudo apt-get update
sudo apt-get install -y \
libjsoncpp-dev \
uuid-dev \
zlib1g-dev \
libssl-dev
fi
# Clone Drogon
TEMP_DIR=$(mktemp -d)
cd "$TEMP_DIR"
echo "Cloning Drogon from GitHub..."
git clone https://github.com/drogonframework/drogon.git
cd drogon
git submodule update --init
# Build and install
echo "Building Drogon..."
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
echo "Installing Drogon..."
sudo make install
# Cleanup
cd /
rm -rf "$TEMP_DIR"
echo
echo "=== Drogon Installation Complete ==="
echo "You can now build WizardMerge with: ./build.sh"

View File

@@ -0,0 +1,113 @@
/**
* @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;
// Note: start_line and end_line are size_t (always non-negative)
conflictObj["start_line"] = static_cast<Json::UInt64>(conflict.start_line);
conflictObj["end_line"] = static_cast<Json::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);
}

View 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

View File

@@ -1,83 +1,54 @@
/**
* @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);
// Display listener information if available
auto listeners = app().getListeners();
if (!listeners.empty()) {
try {
std::cout << "Server will listen on port "
<< listeners[0].toPort << "\n";
} catch (...) {
std::cout << "Server listener configured\n";
}
} else {
std::cout << "Server configuration loaded\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;
}