#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(); } ln = TrimLeft(ln); int q = ln.Find('\t'); if(q >= 0) ln.Trim(q); String fn = UnixPath(TrimLeft("\"", TrimRight("\"", ln))); Array& fp = file.GetAdd(fn); while(!ln.StartsWith("@@")) { if(pi.StepCanceled()) return false; if(in.IsEof()) return ok; ln0 = ln = in.GetLine(); } 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 == ' ') { 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& 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& 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 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& chs = file[i]; Vector 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"); } };