318 lines
10 KiB
C#
318 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Xml.Linq;
|
|
|
|
namespace EonaCat.Sync.Merge
|
|
{
|
|
/// <summary>
|
|
/// Interface for merge strategies.
|
|
/// </summary>
|
|
public interface IMergeStrategy
|
|
{
|
|
/// <summary>
|
|
/// Determines if this strategy can handle the file type.
|
|
/// </summary>
|
|
bool CanHandle(string filePath);
|
|
|
|
/// <summary>
|
|
/// Performs a 3-way merge operation.
|
|
/// </summary>
|
|
Task<MergeResult> MergeAsync(string baseFile, string sourceFile, string targetFile);
|
|
|
|
/// <summary>
|
|
/// Gets a human-readable name for the strategy.
|
|
/// </summary>
|
|
string Name { get; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Result of a merge operation.
|
|
/// </summary>
|
|
public class MergeResult
|
|
{
|
|
public bool Success { get; set; }
|
|
public string MergedContent { get; set; }
|
|
public List<MergeConflict> Conflicts { get; set; } = new List<MergeConflict>();
|
|
public int ConflictResolutionCount { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents a merge conflict.
|
|
/// </summary>
|
|
public class MergeConflict
|
|
{
|
|
public int LineNumber { get; set; }
|
|
public string BaseContent { get; set; }
|
|
public string SourceContent { get; set; }
|
|
public string TargetContent { get; set; }
|
|
public string Resolution { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Text file merge strategy with line-based 3-way merge.
|
|
/// </summary>
|
|
public class TextFileMergeStrategy : IMergeStrategy
|
|
{
|
|
public string Name { get { return "Text File Merge (Line-based)"; } }
|
|
|
|
public bool CanHandle(string filePath)
|
|
{
|
|
var extension = Path.GetExtension(filePath).ToLower();
|
|
return extension == ".txt" || extension == ".cs" || extension == ".java" ||
|
|
extension == ".cpp" || extension == ".py" || extension == ".js" ||
|
|
extension == ".config" || extension == ".properties";
|
|
}
|
|
|
|
public async Task<MergeResult> MergeAsync(string baseFile, string sourceFile, string targetFile)
|
|
{
|
|
var result = new MergeResult();
|
|
|
|
try
|
|
{
|
|
var baseLines = await ReadLinesAsync(baseFile);
|
|
var sourceLines = await ReadLinesAsync(sourceFile);
|
|
var targetLines = await ReadLinesAsync(targetFile);
|
|
|
|
var mergedLines = new List<string>();
|
|
int maxLines = Math.Max(Math.Max(baseLines.Count, sourceLines.Count), targetLines.Count);
|
|
|
|
for (int i = 0; i < maxLines; i++)
|
|
{
|
|
string baseLine = i < baseLines.Count ? baseLines[i] : string.Empty;
|
|
string sourceLine = i < sourceLines.Count ? sourceLines[i] : string.Empty;
|
|
string targetLine = i < targetLines.Count ? targetLines[i] : string.Empty;
|
|
|
|
if (baseLine == sourceLine && baseLine == targetLine)
|
|
{
|
|
mergedLines.Add(baseLine);
|
|
}
|
|
else if (baseLine == targetLine && sourceLine != baseLine)
|
|
{
|
|
mergedLines.Add(sourceLine);
|
|
}
|
|
else if (baseLine == sourceLine && targetLine != baseLine)
|
|
{
|
|
mergedLines.Add(targetLine);
|
|
}
|
|
else if (sourceLine == targetLine)
|
|
{
|
|
mergedLines.Add(sourceLine);
|
|
}
|
|
else
|
|
{
|
|
// Conflict
|
|
var resolution = sourceLine.Length >= targetLine.Length ? sourceLine : targetLine;
|
|
result.Conflicts.Add(new MergeConflict
|
|
{
|
|
LineNumber = i + 1,
|
|
BaseContent = baseLine,
|
|
SourceContent = sourceLine,
|
|
TargetContent = targetLine,
|
|
Resolution = resolution
|
|
});
|
|
mergedLines.Add(resolution);
|
|
result.ConflictResolutionCount++;
|
|
}
|
|
}
|
|
|
|
result.MergedContent = string.Join(Environment.NewLine, mergedLines);
|
|
result.Success = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.Success = false;
|
|
result.MergedContent = $"Merge failed: {ex.Message}";
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private async Task<List<string>> ReadLinesAsync(string filePath)
|
|
{
|
|
var lines = new List<string>();
|
|
|
|
if (!File.Exists(filePath))
|
|
return lines;
|
|
|
|
return await Task.Run(() =>
|
|
{
|
|
using (var reader = new StreamReader(filePath))
|
|
{
|
|
string line;
|
|
while ((line = reader.ReadLine()) != null)
|
|
{
|
|
lines.Add(line);
|
|
}
|
|
}
|
|
return lines;
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// XML file merge strategy.
|
|
/// </summary>
|
|
public class XmlFileMergeStrategy : IMergeStrategy
|
|
{
|
|
public string Name { get { return "XML File Merge"; } }
|
|
|
|
public bool CanHandle(string filePath)
|
|
{
|
|
var ext = Path.GetExtension(filePath).ToLower();
|
|
return ext == ".xml" || ext == ".config";
|
|
}
|
|
|
|
public async Task<MergeResult> MergeAsync(string baseFile, string sourceFile, string targetFile)
|
|
{
|
|
var result = new MergeResult();
|
|
|
|
try
|
|
{
|
|
var sourceXml = await ReadXmlAsync(sourceFile);
|
|
var targetXml = await ReadXmlAsync(targetFile);
|
|
|
|
var mergedXml = MergeXmlElements(sourceXml, targetXml);
|
|
result.MergedContent = mergedXml.ToString();
|
|
result.Success = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.Success = false;
|
|
result.MergedContent = $"XML merge failed: {ex.Message}";
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private async Task<XElement> ReadXmlAsync(string filePath)
|
|
{
|
|
if (!File.Exists(filePath))
|
|
return new XElement("root");
|
|
|
|
return await Task.Run(() =>
|
|
{
|
|
var content = File.ReadAllText(filePath);
|
|
return XElement.Parse(content);
|
|
});
|
|
}
|
|
|
|
private XElement MergeXmlElements(XElement sourceElem, XElement targetElem)
|
|
{
|
|
var result = new XElement(sourceElem.Name);
|
|
|
|
// Merge attributes
|
|
foreach (var attr in sourceElem.Attributes())
|
|
{
|
|
result.SetAttributeValue(attr.Name, attr.Value);
|
|
}
|
|
|
|
foreach (var attr in targetElem.Attributes())
|
|
{
|
|
if (result.Attributes().FirstOrDefault(a => a.Name == attr.Name) == null)
|
|
{
|
|
result.SetAttributeValue(attr.Name, attr.Value);
|
|
}
|
|
}
|
|
|
|
// Merge elements
|
|
foreach (var elem in sourceElem.Elements())
|
|
{
|
|
result.Add(elem);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Binary file merge strategy (simple).
|
|
/// </summary>
|
|
public class BinaryFileMergeStrategy : IMergeStrategy
|
|
{
|
|
public string Name { get { return "Binary File Merge"; } }
|
|
|
|
public bool CanHandle(string filePath)
|
|
{
|
|
return true; // Fallback for unknown types
|
|
}
|
|
|
|
public async Task<MergeResult> MergeAsync(string baseFile, string sourceFile, string targetFile)
|
|
{
|
|
var result = new MergeResult();
|
|
|
|
try
|
|
{
|
|
var sourceBytes = await ReadBytesAsync(sourceFile);
|
|
var targetBytes = await ReadBytesAsync(targetFile);
|
|
|
|
if (sourceBytes.SequenceEqual(targetBytes))
|
|
{
|
|
result.Success = true;
|
|
}
|
|
else
|
|
{
|
|
// Use larger file as resolution
|
|
result.MergedContent = "Binary conflict resolved using source file";
|
|
result.ConflictResolutionCount = 1;
|
|
result.Success = true;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.Success = false;
|
|
result.MergedContent = $"Binary merge failed: {ex.Message}";
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private async Task<byte[]> ReadBytesAsync(string filePath)
|
|
{
|
|
if (!File.Exists(filePath))
|
|
return new byte[0];
|
|
|
|
return await Task.Run(() => File.ReadAllBytes(filePath));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Merge service that selects appropriate strategy for file type.
|
|
/// </summary>
|
|
public class MergeService
|
|
{
|
|
private readonly List<IMergeStrategy> _strategies;
|
|
|
|
public MergeService()
|
|
{
|
|
_strategies = new List<IMergeStrategy>
|
|
{
|
|
new XmlFileMergeStrategy(),
|
|
new TextFileMergeStrategy(),
|
|
new BinaryFileMergeStrategy()
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Merges three files using the appropriate strategy.
|
|
/// </summary>
|
|
public async Task<MergeResult> MergeFilesAsync(string baseFile, string sourceFile, string targetFile)
|
|
{
|
|
var strategy = _strategies.FirstOrDefault(s => s.CanHandle(sourceFile)) ??
|
|
_strategies.Last();
|
|
|
|
return await strategy.MergeAsync(baseFile, sourceFile, targetFile);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers a custom merge strategy.
|
|
/// </summary>
|
|
public void RegisterStrategy(IMergeStrategy strategy)
|
|
{
|
|
_strategies.Insert(0, strategy);
|
|
}
|
|
}
|
|
}
|