mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 14:16:07 -06:00
222 lines
5.1 KiB
C++
222 lines
5.1 KiB
C++
#include "TextDiffCtrl.h"
|
|
|
|
namespace Upp {
|
|
|
|
bool Patch::Load0(Stream& in, Progress& pi)
|
|
{
|
|
pi.SetText("Loading patch file");
|
|
target_dir.Clear();
|
|
file.Clear();
|
|
String ln0;
|
|
String ln = in.GetLine();
|
|
bool ok = false; // at least one hunk was loaded
|
|
for(;;) {
|
|
while(!ln.TrimStart("--- ")) {
|
|
if(pi.StepCanceled())
|
|
return false;
|
|
if(in.IsEof())
|
|
return ok;
|
|
ln = in.GetLine();
|
|
if(ln.StartsWith("-- ")) // git format-patch "EOF"
|
|
return ok;
|
|
}
|
|
ln = TrimLeft(ln);
|
|
int q = ln.Find('\t');
|
|
if(q >= 0)
|
|
ln.Trim(q);
|
|
String fn = UnixPath(TrimLeft("\"", TrimRight("\"", ln)));
|
|
Array<Chunk>& fp = file.GetAdd(fn);
|
|
while(!ln.StartsWith("@@")) {
|
|
if(pi.StepCanceled())
|
|
return false;
|
|
if(in.IsEof())
|
|
return ok;
|
|
ln0 = ln = in.GetLine();
|
|
if(ln.StartsWith("-- ")) // git format-patch "EOF"
|
|
return ok;
|
|
}
|
|
while(ln.TrimStart("@@")) {
|
|
if(pi.StepCanceled())
|
|
return false;
|
|
int line = atoi(ln);
|
|
if(line <= 0) {
|
|
Chunk& ch = fp.Add();
|
|
ch.src << ln0 << "\n";
|
|
ch.line = max(-line - 1, 0);
|
|
for(;;) {
|
|
if(pi.StepCanceled())
|
|
return false;
|
|
if(in.IsEof())
|
|
return ok;
|
|
ln0 = ln = in.GetLine();
|
|
if(ln.StartsWith("-- ")) // git format-patch "EOF"
|
|
return ok;
|
|
if(ln.StartsWith("--- "))
|
|
break;
|
|
if(*ln == ' ') {
|
|
ch.orig.Add(ln.Mid(1));
|
|
ch.patch.Add(ln.Mid(1));
|
|
}
|
|
else
|
|
if(*ln == '+')
|
|
ch.patch.Add(ln.Mid(1));
|
|
else
|
|
if(*ln == '-')
|
|
ch.orig.Add(ln.Mid(1));
|
|
else
|
|
break;
|
|
ok = true;
|
|
ch.src << ln << "\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
bool Patch::Load(Stream& in, Progress& pi)
|
|
{
|
|
common_path.Clear();
|
|
if(!Load0(in, pi))
|
|
return false;
|
|
const Vector<String>& h = file.GetKeys();
|
|
if(h.GetCount() == 0)
|
|
return false;
|
|
common_path = GetFileFolder(h[0]);
|
|
for(int i = 1; i < h.GetCount(); i++)
|
|
common_path.Trim(MatchLen(common_path, h[i]));
|
|
common_path.TrimEnd("/");
|
|
common_path.TrimStart("/");
|
|
if(common_path.GetCount())
|
|
for(int i = 0; i < h.GetCount(); i++)
|
|
file.SetKey(i, h[i].Mid(common_path.GetCount() + 1));
|
|
return true;
|
|
}
|
|
|
|
int Patch::MatchLen(const String& a, const String& b)
|
|
{
|
|
int n = min(a.GetLength(), b.GetLength());
|
|
int q = 0;
|
|
for(int i = 0; i < n; i++) {
|
|
if(a[i] != b[i])
|
|
return q;
|
|
if(a[i] == '/')
|
|
q = i;
|
|
}
|
|
return a[n] || b[n] ? b[n] == '/' || a[n] == '/' ? n : q : n;
|
|
}
|
|
|
|
int Patch::MatchCount(const char *dir)
|
|
{
|
|
int count = 0;
|
|
for(const String& p : file.GetKeys()) {
|
|
if(FileExists(AppendFileName(dir, p)))
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
bool Patch::MatchFiles(const Vector<String>& dir, Progress& pi)
|
|
{
|
|
pi.SetText("Matching directories");
|
|
int best = 0;
|
|
String com_path = common_path;
|
|
com_path.Replace("\\", "/");
|
|
if(dir.GetCount())
|
|
for(int pass = 0; pass < 2; pass++) {
|
|
for(;;) {
|
|
for(String d : dir) {
|
|
while(d.GetCount() > 3) {
|
|
if(pi.StepCanceled())
|
|
return false;
|
|
String dir = AppendFileName(d, com_path);
|
|
int n = MatchCount(dir);
|
|
if(n > best) {
|
|
best = n;
|
|
target_dir = dir;
|
|
}
|
|
d = GetFileFolder(d);
|
|
}
|
|
}
|
|
int q = com_path.Find('/');
|
|
if(q >= 0)
|
|
com_path = com_path.Mid(q + 1);
|
|
else
|
|
break;
|
|
}
|
|
com_path = GetFileFolder(dir[0]);
|
|
}
|
|
return best;
|
|
}
|
|
|
|
bool Patch::Load(const char *fn, Progress& pi)
|
|
{
|
|
FileIn in(fn);
|
|
return Load(in, pi);
|
|
}
|
|
|
|
String Patch::GetPatch(int i) const
|
|
{
|
|
String r;
|
|
for(const Chunk& ch : file[i])
|
|
r << ch.src;
|
|
return r;
|
|
}
|
|
|
|
String Patch::GetPatchedFile(int i, const String& text) const
|
|
{
|
|
if(GetFileLength(GetPath(i)) > 4 * 1024 * 1024)
|
|
return String::GetVoid();
|
|
Vector<String> lines;
|
|
bool crlf = true;
|
|
String path = GetPath(i);
|
|
if(FileExists(path) || IsNull(text)) {
|
|
String s = Nvl(text, LoadFile(GetPath(i)));
|
|
crlf = s.Find('\r') >= 0;
|
|
lines = Split(Filter(s, [](int c) { return c == '\r' ? 0 : c; }), '\n', false);
|
|
}
|
|
|
|
const Array<Chunk>& chs = file[i];
|
|
Vector<int> ch_pos;
|
|
|
|
for(int i = 0; i < chs.GetCount(); i++) {
|
|
const Chunk& ch = chs[i];
|
|
int pos = -1;
|
|
for(int d = 0; d < 200; d++) { // 200 lines of tolerance in both directions when matching the patch
|
|
auto Match = [&](int li) {
|
|
if(li < 0)
|
|
return false;
|
|
for(int i = 0; i < ch.orig.GetCount(); i++)
|
|
if(li + i >= lines.GetCount() || ch.orig[i] != lines[li + i])
|
|
return false;
|
|
return true;
|
|
};
|
|
if(Match(ch.line + d)) {
|
|
pos = ch.line + d;
|
|
break;
|
|
}
|
|
if(d && Match(ch.line - d)) {
|
|
pos = ch.line - d;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(pos < 0 || ch_pos.GetCount() && ch_pos.Top() > pos)
|
|
return String::GetVoid();
|
|
ch_pos.Add(pos);
|
|
}
|
|
|
|
for(int i = 0; i < ch_pos.GetCount(); i++) {
|
|
const Chunk& ch = chs[i];
|
|
lines.Remove(ch_pos[i], ch.orig.GetCount());
|
|
lines.Insert(ch_pos[i], ch.patch);
|
|
int added = ch.patch.GetCount() - ch.orig.GetCount(); // negative if removed
|
|
for(int j = i + 1; j < ch_pos.GetCount(); j++)
|
|
ch_pos[j] += added;
|
|
}
|
|
|
|
return Join(lines, crlf ? "\r\n" : "\n");
|
|
}
|
|
|
|
};
|