mirror of
https://github.com/johndoe6345789/WizardMerge.git
synced 2026-04-24 13:44:55 +00:00
Implement Phase 1.1: Delete Python skeleton and create C++/TypeScript architecture
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
49
backend/CMakeLists.txt
Normal file
49
backend/CMakeLists.txt
Normal file
@@ -0,0 +1,49 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
project(WizardMerge VERSION 0.1.0 LANGUAGES CXX)
|
||||
|
||||
# C++ standard
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# Find dependencies via Conan
|
||||
find_package(GTest QUIET)
|
||||
|
||||
# Library sources
|
||||
add_library(wizardmerge
|
||||
src/merge/three_way_merge.cpp
|
||||
)
|
||||
|
||||
target_include_directories(wizardmerge
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
|
||||
# Executable
|
||||
add_executable(wizardmerge-cli
|
||||
src/main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(wizardmerge-cli PRIVATE wizardmerge)
|
||||
|
||||
# Tests (if GTest is available)
|
||||
if(GTest_FOUND)
|
||||
enable_testing()
|
||||
add_executable(wizardmerge-tests
|
||||
tests/test_three_way_merge.cpp
|
||||
)
|
||||
target_link_libraries(wizardmerge-tests PRIVATE wizardmerge GTest::gtest_main)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(wizardmerge-tests)
|
||||
endif()
|
||||
|
||||
# Install targets
|
||||
install(TARGETS wizardmerge wizardmerge-cli
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
RUNTIME DESTINATION bin
|
||||
)
|
||||
|
||||
install(DIRECTORY include/ DESTINATION include)
|
||||
84
backend/README.md
Normal file
84
backend/README.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# WizardMerge C++ Backend
|
||||
|
||||
This is the C++ backend for WizardMerge implementing the core merge algorithms.
|
||||
|
||||
## Build System
|
||||
|
||||
- **Build Tool**: Ninja
|
||||
- **Package Manager**: Conan
|
||||
- **CMake**: Version 3.15+
|
||||
- **C++ Standard**: C++17
|
||||
|
||||
## Building
|
||||
|
||||
### Prerequisites
|
||||
|
||||
```sh
|
||||
# Install Conan
|
||||
pip install conan
|
||||
|
||||
# Install CMake and Ninja
|
||||
# On Ubuntu/Debian:
|
||||
sudo apt-get install cmake ninja-build
|
||||
|
||||
# On macOS:
|
||||
brew install cmake ninja
|
||||
```
|
||||
|
||||
### Build Steps
|
||||
|
||||
```sh
|
||||
# Configure with Conan
|
||||
conan install . --output-folder=build --build=missing
|
||||
|
||||
# Build with CMake and Ninja
|
||||
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
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```sh
|
||||
# Run tests (if GTest is available)
|
||||
ninja test
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
backend/
|
||||
├── CMakeLists.txt # CMake build configuration
|
||||
├── conanfile.py # Conan package definition
|
||||
├── include/ # Public headers
|
||||
│ └── wizardmerge/
|
||||
│ └── merge/
|
||||
│ └── three_way_merge.h
|
||||
├── src/ # Implementation files
|
||||
│ ├── main.cpp
|
||||
│ └── merge/
|
||||
│ └── three_way_merge.cpp
|
||||
└── tests/ # Unit tests
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Three-way merge algorithm (Phase 1.1 from ROADMAP)
|
||||
- Conflict detection and marking
|
||||
- Auto-resolution of common patterns
|
||||
- Command-line interface
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
wizardmerge-cli <base> <ours> <theirs> <output>
|
||||
```
|
||||
|
||||
Arguments:
|
||||
- `base`: Common ancestor version
|
||||
- `ours`: Current branch version
|
||||
- `theirs`: Branch being merged
|
||||
- `output`: Output file for merged result
|
||||
32
backend/build.sh
Executable file
32
backend/build.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
# Build script for WizardMerge C++ backend using Conan and Ninja
|
||||
|
||||
set -e
|
||||
|
||||
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; }
|
||||
|
||||
# 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
|
||||
|
||||
# Build with Ninja
|
||||
echo "Building with Ninja..."
|
||||
ninja
|
||||
|
||||
echo
|
||||
echo "=== Build Complete ==="
|
||||
echo "Binary: build/wizardmerge-cli"
|
||||
47
backend/conanfile.py
Normal file
47
backend/conanfile.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""Conan package configuration for WizardMerge backend."""
|
||||
from conan import ConanFile
|
||||
from conan.tools.cmake import CMake, cmake_layout
|
||||
|
||||
|
||||
class WizardMergeConan(ConanFile):
|
||||
"""WizardMerge C++ backend package."""
|
||||
|
||||
name = "wizardmerge"
|
||||
version = "0.1.0"
|
||||
|
||||
# Binary configuration
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
options = {"shared": [True, False], "fPIC": [True, False]}
|
||||
default_options = {"shared": False, "fPIC": True}
|
||||
|
||||
# Sources
|
||||
exports_sources = "CMakeLists.txt", "src/*", "include/*"
|
||||
|
||||
# Dependencies
|
||||
requires = []
|
||||
|
||||
generators = "CMakeDeps", "CMakeToolchain"
|
||||
|
||||
def config_options(self):
|
||||
"""Configure platform-specific options."""
|
||||
if self.settings.os == "Windows":
|
||||
del self.options.fPIC
|
||||
|
||||
def layout(self):
|
||||
"""Define project layout."""
|
||||
cmake_layout(self)
|
||||
|
||||
def build(self):
|
||||
"""Build the project using CMake."""
|
||||
cmake = CMake(self)
|
||||
cmake.configure()
|
||||
cmake.build()
|
||||
|
||||
def package(self):
|
||||
"""Package the built artifacts."""
|
||||
cmake = CMake(self)
|
||||
cmake.install()
|
||||
|
||||
def package_info(self):
|
||||
"""Define package information for consumers."""
|
||||
self.cpp_info.libs = ["wizardmerge"]
|
||||
82
backend/include/wizardmerge/merge/three_way_merge.h
Normal file
82
backend/include/wizardmerge/merge/three_way_merge.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @file three_way_merge.h
|
||||
* @brief Three-way merge algorithm for WizardMerge
|
||||
*
|
||||
* Implements the core three-way merge algorithm based on the paper from
|
||||
* The University of Hong Kong. This algorithm uses dependency analysis
|
||||
* at both text and LLVM-IR levels to provide intelligent merge suggestions.
|
||||
*/
|
||||
|
||||
#ifndef WIZARDMERGE_MERGE_THREE_WAY_MERGE_H
|
||||
#define WIZARDMERGE_MERGE_THREE_WAY_MERGE_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wizardmerge {
|
||||
namespace merge {
|
||||
|
||||
/**
|
||||
* @brief Represents a single line in a file with its origin.
|
||||
*/
|
||||
struct Line {
|
||||
std::string content;
|
||||
enum Origin { BASE, OURS, THEIRS, MERGED } origin;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Represents a conflict region in the merge result.
|
||||
*/
|
||||
struct Conflict {
|
||||
size_t start_line;
|
||||
size_t end_line;
|
||||
std::vector<Line> base_lines;
|
||||
std::vector<Line> our_lines;
|
||||
std::vector<Line> their_lines;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Result of a three-way merge operation.
|
||||
*/
|
||||
struct MergeResult {
|
||||
std::vector<Line> merged_lines;
|
||||
std::vector<Conflict> conflicts;
|
||||
bool has_conflicts() const { return !conflicts.empty(); }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Performs a three-way merge on three versions of content.
|
||||
*
|
||||
* This function implements the three-way merge algorithm that compares
|
||||
* the base version with two variants (ours and theirs) to produce a
|
||||
* merged result with conflict markers where automatic resolution is
|
||||
* not possible.
|
||||
*
|
||||
* @param base The common ancestor version
|
||||
* @param ours Our version (current branch)
|
||||
* @param theirs Their version (branch being merged)
|
||||
* @return MergeResult containing the merged content and any conflicts
|
||||
*/
|
||||
MergeResult three_way_merge(
|
||||
const std::vector<std::string>& base,
|
||||
const std::vector<std::string>& ours,
|
||||
const std::vector<std::string>& theirs
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Auto-resolves simple non-conflicting patterns.
|
||||
*
|
||||
* Handles common auto-resolvable patterns:
|
||||
* - Non-overlapping changes
|
||||
* - Identical changes from both sides
|
||||
* - Whitespace-only differences
|
||||
*
|
||||
* @param result The merge result to auto-resolve
|
||||
* @return Updated merge result with resolved conflicts
|
||||
*/
|
||||
MergeResult auto_resolve(const MergeResult& result);
|
||||
|
||||
} // namespace merge
|
||||
} // namespace wizardmerge
|
||||
|
||||
#endif // WIZARDMERGE_MERGE_THREE_WAY_MERGE_H
|
||||
83
backend/src/main.cpp
Normal file
83
backend/src/main.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* @file main.cpp
|
||||
* @brief Command-line interface for WizardMerge
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include "wizardmerge/merge/three_way_merge.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';
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
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;
|
||||
}
|
||||
}
|
||||
117
backend/src/merge/three_way_merge.cpp
Normal file
117
backend/src/merge/three_way_merge.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* @file three_way_merge.cpp
|
||||
* @brief Implementation of three-way merge algorithm
|
||||
*/
|
||||
|
||||
#include "wizardmerge/merge/three_way_merge.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace wizardmerge {
|
||||
namespace merge {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief Check if two lines are effectively equal (ignoring whitespace).
|
||||
*/
|
||||
bool lines_equal_ignore_whitespace(const std::string& a, const std::string& b) {
|
||||
auto trim = [](const std::string& s) {
|
||||
size_t start = s.find_first_not_of(" \t\n\r");
|
||||
size_t end = s.find_last_not_of(" \t\n\r");
|
||||
if (start == std::string::npos) return std::string();
|
||||
return s.substr(start, end - start + 1);
|
||||
};
|
||||
return trim(a) == trim(b);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MergeResult three_way_merge(
|
||||
const std::vector<std::string>& base,
|
||||
const std::vector<std::string>& ours,
|
||||
const std::vector<std::string>& theirs
|
||||
) {
|
||||
MergeResult result;
|
||||
|
||||
// Simple line-by-line comparison for initial implementation
|
||||
// This is a placeholder - full algorithm will use dependency analysis
|
||||
|
||||
size_t max_len = std::max({base.size(), ours.size(), theirs.size()});
|
||||
|
||||
for (size_t i = 0; i < max_len; ++i) {
|
||||
std::string base_line = (i < base.size()) ? base[i] : "";
|
||||
std::string our_line = (i < ours.size()) ? ours[i] : "";
|
||||
std::string their_line = (i < theirs.size()) ? theirs[i] : "";
|
||||
|
||||
// Case 1: All three are the same - use as-is
|
||||
if (base_line == our_line && base_line == their_line) {
|
||||
result.merged_lines.push_back({base_line, Line::BASE});
|
||||
}
|
||||
// Case 2: Base == Ours, but Theirs changed - use theirs
|
||||
else if (base_line == our_line && base_line != their_line) {
|
||||
result.merged_lines.push_back({their_line, Line::THEIRS});
|
||||
}
|
||||
// Case 3: Base == Theirs, but Ours changed - use ours
|
||||
else if (base_line == their_line && base_line != our_line) {
|
||||
result.merged_lines.push_back({our_line, Line::OURS});
|
||||
}
|
||||
// Case 4: Ours == Theirs, but different from Base - use the common change
|
||||
else if (our_line == their_line && our_line != base_line) {
|
||||
result.merged_lines.push_back({our_line, Line::MERGED});
|
||||
}
|
||||
// Case 5: All different - conflict
|
||||
else {
|
||||
Conflict conflict;
|
||||
conflict.start_line = result.merged_lines.size();
|
||||
conflict.base_lines.push_back({base_line, Line::BASE});
|
||||
conflict.our_lines.push_back({our_line, Line::OURS});
|
||||
conflict.their_lines.push_back({their_line, Line::THEIRS});
|
||||
conflict.end_line = result.merged_lines.size();
|
||||
|
||||
result.conflicts.push_back(conflict);
|
||||
|
||||
// Add conflict markers
|
||||
result.merged_lines.push_back({"<<<<<<< OURS", Line::MERGED});
|
||||
result.merged_lines.push_back({our_line, Line::OURS});
|
||||
result.merged_lines.push_back({"=======", Line::MERGED});
|
||||
result.merged_lines.push_back({their_line, Line::THEIRS});
|
||||
result.merged_lines.push_back({">>>>>>> THEIRS", Line::MERGED});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
MergeResult auto_resolve(const MergeResult& result) {
|
||||
MergeResult resolved = result;
|
||||
|
||||
// Auto-resolve whitespace-only differences
|
||||
std::vector<Conflict> remaining_conflicts;
|
||||
|
||||
for (const auto& conflict : result.conflicts) {
|
||||
bool can_resolve = false;
|
||||
|
||||
// Check if differences are whitespace-only
|
||||
if (conflict.our_lines.size() == conflict.their_lines.size()) {
|
||||
can_resolve = true;
|
||||
for (size_t i = 0; i < conflict.our_lines.size(); ++i) {
|
||||
if (!lines_equal_ignore_whitespace(
|
||||
conflict.our_lines[i].content,
|
||||
conflict.their_lines[i].content)) {
|
||||
can_resolve = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!can_resolve) {
|
||||
remaining_conflicts.push_back(conflict);
|
||||
}
|
||||
}
|
||||
|
||||
resolved.conflicts = remaining_conflicts;
|
||||
return resolved;
|
||||
}
|
||||
|
||||
} // namespace merge
|
||||
} // namespace wizardmerge
|
||||
Reference in New Issue
Block a user