ultimatepp/uppsrc/TextDiffCtrl/patch.cpp
2022-05-17 10:57:03 +02:00

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");
}
};