NumeRe v1.1.4
NumeRe: Framework für Numerische Rechnungen
filerevisions.cpp
Go to the documentation of this file.
1/*****************************************************************************
2 NumeRe: Framework fuer Numerische Rechnungen
3 Copyright (C) 2019 Erik Haenel et al.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17******************************************************************************/
18
19#include "filerevisions.hpp"
20#include "dtl/dtl.hpp"
21#include <wx/zipstrm.h>
22#include <wx/txtstrm.h>
23#include <wx/wfstream.h>
24#include <memory>
25#include <fstream>
26#include <sstream>
27#include <wx/encconv.h>
28
29#define COMPRESSIONLEVEL 6
30
31#include "../kernel/core/utils/stringtools.hpp"
32
39FileRevisions::FileRevisions(const wxString& revisionPath) : m_revisionPath(revisionPath)
40{
41 if (!m_revisionPath.Exists())
42 wxFileName::Mkdir(m_revisionPath.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL);
43}
44
45
55std::vector<wxString> FileRevisions::vectorize(wxString fileContent)
56{
57 std::vector<wxString> vVectorizedFile;
58
59 // As long as there are contents left in the
60 // current file
61 while (fileContent.length())
62 {
63 // Push the current line to the vector
64 vVectorizedFile.push_back(fileContent.substr(0, fileContent.find('\n')));
65
66 // Erase the current line from the file
67 if (fileContent.find('\n') != std::string::npos)
68 fileContent.erase(0, fileContent.find('\n')+1);
69 else
70 break;
71 }
72
73 // Return the vectorized file
74 return vVectorizedFile;
75}
76
77
78
89wxString FileRevisions::convertLineEndings(const wxString& content)
90{
91 wxString target = content;
92
93 while (target.find("\r\n") != std::string::npos)
94 target.erase(target.find("\r\n"), 1);
95
96 return target;
97}
98
99
111wxString FileRevisions::readRevision(const wxString& revString)
112{
113 wxFFileInputStream in(m_revisionPath.GetFullPath());
114 wxZipInputStream zip(in);
115 wxTextInputStream txt(zip, " \t", wxConvUTF8); // Convert from UTF-8 to Unicode on-the-fly
116
117 std::unique_ptr<wxZipEntry> entry;
118
119 // Search for the entry with the correct name
120 // in the file
121 while (entry.reset(zip.GetNextEntry()), entry.get() != nullptr)
122 {
123 // Found it?
124 if (entry->GetName() == revString)
125 {
126 wxString revision;
127
128 // Read the whole entry and append windows line endings. The
129 // necessary conversion from UTF-8 is done by the text input
130 // stream on-the-fly
131 while (!zip.Eof())
132 revision += wxString(txt.ReadLine()) + "\r\n";
133
134 // Remove the last line's endings
135 revision.erase(revision.length()-2);
136
137 // If the ZIP comment equals "DIFF", we need to merge
138 // the changes in this revision into the current
139 // revision root
140 if (entry->GetComment() == "DIFF")
141 revision = createMerge(revision);
142
143 return revision;
144 }
145 }
146
147 return "";
148}
149
150
161wxString FileRevisions::getLastContentModification(const wxString& revString)
162{
163 // Get the revision list
164 wxArrayString revisionList = getRevisionList();
165 bool revFound = false;
166
167 // Go reversely through the revision list and try to find
168 // the revision tag, where the file was modified lastly
169 for (int i = revisionList.size()-1; i >= 0; i--)
170 {
171 // Found the user selected revision?
172 if (!revFound && revisionList[i].substr(0, revisionList[i].find('\t')) == revString)
173 revFound = true;
174
175 // Does the comment section contain one of the ignored
176 // tags?
177 if (revFound
178 && revisionList[i].find("\tMOVE:") == std::string::npos
179 && revisionList[i].find("\tRENAME:") == std::string::npos
180 && revisionList[i].find("\tTAG:") == std::string::npos)
181 return revisionList[i].substr(0, revisionList[i].find('\t'));
182 }
183
184 return revString;
185}
186
187
202wxString FileRevisions::getLastRevisionRoot(const wxString& revString)
203{
204 // Get the revision list
205 wxArrayString revisionList = getRevisionList();
206 bool revFound = false;
207
208 // Go reversely through the revision list and try to find
209 // the revision tag, where the file was modified lastly
210 for (int i = revisionList.size()-1; i >= 0; i--)
211 {
212 // Found the user selected revision?
213 if (!revFound && revisionList[i].substr(0, revisionList[i].find('\t')) == revString)
214 revFound = true;
215
216 // Is there an empty comment or the initial revision?
217 if (revFound
218 && (revisionList[i].find("\tInitial revision") != std::string::npos
219 || revisionList[i].substr(revisionList[i].length()-1) == "\t"))
220 return revisionList[i].substr(0, revisionList[i].find('\t'));
221 }
222
223 return revString;
224}
225
226
241wxString FileRevisions::diff(const wxString& revision1, const wxString& revisionID1, const wxString& revision2, const wxString& revisionID2)
242{
243 // Vectorize the revision root and the current file
244 std::vector<wxString> vVectorizedRoot = vectorize(convertLineEndings(revision1));
245 std::vector<wxString> vVectorizedFile = vectorize(convertLineEndings(revision2));
246
247 // Calculate the differences between both files
248 dtl::Diff<wxString> diffFile(vVectorizedRoot, vVectorizedFile);
249 diffFile.compose();
250
251 // Convert the differences into unified form
252 diffFile.composeUnifiedHunks();
253
254 // Print the differences to a string stream
255 std::ostringstream uniDiff;
256
257 // Get revision identifier
258 if (revisionID1.length() && revisionID2.length())
259 uniDiff << "--- " << revisionID1 << "\n+++ " << revisionID2 << "\n";
260
261 diffFile.printUnifiedFormat(uniDiff);
262
263 // Return the contents of the stream
264 return uniDiff.str();
265}
266
267
277wxString FileRevisions::createDiff(const wxString& revisionContent)
278{
279 // Find the revision root
280 wxString revisionRoot = getRevision(getLastRevisionRoot(getCurrentRevision()));
281
282 // Calculate the differences of both files
283 return diff(revisionRoot, getLastRevisionRoot(getCurrentRevision()), revisionContent, "rev"+toString(getRevisionCount()));
284}
285
286
298wxString FileRevisions::createMerge(const wxString& diffFile)
299{
300 // Vectorizes the passed diff file and the current revision root
301 std::vector<wxString> vVectorizedDiff = vectorize(convertLineEndings(diffFile));
302 std::vector<wxString> vVectorizedRoot = vectorize(convertLineEndings(getRevision(vVectorizedDiff.front().substr(4))));
303
304 int nCurrentInputLine = 0;
305
306 // Go through the lines of the diff file. Ignore the first
307 // two, because they only contain the revision identifiers.
308 for (size_t i = 2; i < vVectorizedDiff.size(); i++)
309 {
310 // The start of a change section. Extract the target input
311 // line in the current revision (the second number set starting
312 // with a "+")
313 if (vVectorizedDiff[i].substr(0, 4) == "@@ -")
314 {
315 nCurrentInputLine = atoi(vVectorizedDiff[i].substr(vVectorizedDiff[i].find('+')+1, vVectorizedDiff[i].find(',', vVectorizedDiff[i].find('+') - vVectorizedDiff[i].find('+')-1)).c_str())-1;
316 continue;
317 }
318
319 // A deletion. This requires that we decrement the current
320 // input line
321 if (vVectorizedDiff[i][0] == '-')
322 {
323 vVectorizedRoot.erase(vVectorizedRoot.begin()+nCurrentInputLine);
324 nCurrentInputLine--;
325 }
326
327 // An addition
328 if (vVectorizedDiff[i][0] == '+')
329 vVectorizedRoot.insert(vVectorizedRoot.begin()+nCurrentInputLine, vVectorizedDiff[i].substr(1));
330
331 // Always increment the current input line
332 nCurrentInputLine++;
333 }
334
335 wxString mergedFile;
336
337 // Merge the vectorized file into a single-string file
338 for (size_t i = 0; i < vVectorizedRoot.size(); i++)
339 mergedFile += vVectorizedRoot[i] + "\r\n";
340
341 // Return the merged file with exception of the trailing
342 // line break characters
343 return mergedFile.substr(0, mergedFile.length()-2);
344}
345
346
357wxString FileRevisions::readExternalFile(const wxString& filePath)
358{
359 std::ifstream file_in;
360 wxString sFileContents;
361 std::string sLine;
362 wxMBConvUTF8 conv;
363
364 file_in.open(filePath.ToStdString().c_str());
365
366 while (file_in.good() && !file_in.eof())
367 {
368 std::getline(file_in, sLine);
369 sFileContents += sLine + "\r\n";
370 }
371
372 sFileContents.erase(sFileContents.length()-2);
373
374 return sFileContents;
375}
376
377
389size_t FileRevisions::createNewRevision(const wxString& revContent, const wxString& comment)
390{
391 size_t revisionNo = getRevisionCount();
392
393 std::unique_ptr<wxFFileInputStream> in(new wxFFileInputStream(m_revisionPath.GetFullPath()));
394 wxTempFileOutputStream out(m_revisionPath.GetFullPath());
395
396 wxZipInputStream inzip(*in);
397 wxZipOutputStream outzip(out, COMPRESSIONLEVEL);
398 wxTextOutputStream txt(outzip);
399
400 std::unique_ptr<wxZipEntry> entry;
401
402 outzip.CopyArchiveMetaData(inzip);
403
404 // Copy all available entries to the new file
405 while (entry.reset(inzip.GetNextEntry()), entry.get() != nullptr)
406 {
407 outzip.CopyEntry(entry.release(), inzip);
408 }
409
410 // Create the new revision
411 wxZipEntry* currentRev = new wxZipEntry("rev" + toString(revisionNo));
412 currentRev->SetComment(comment);
413
414 outzip.PutNextEntry(currentRev);
415 txt << revContent;
416 outzip.CloseEntry();
417
418 // Close the temporary file and commit it,
419 // so that it may override the previous file
420 // safely.
421 in.reset();
422 outzip.Close();
423 out.Commit();
424
425 return revisionNo;
426}
427
428
440size_t FileRevisions::createNewTag(const wxString& revString, const wxString& comment)
441{
442 wxString revision = "TAG FOR " + revString;
443 size_t revisionNo = getRevisionCount();
444 wxZipEntry* taggedRevision = new wxZipEntry("tag" + revString.substr(3) + "-rev" + toString(revisionNo));
445 taggedRevision->SetComment("TAG: " + comment);
446
447 std::unique_ptr<wxFFileInputStream> in(new wxFFileInputStream(m_revisionPath.GetFullPath()));
448 wxTempFileOutputStream out(m_revisionPath.GetFullPath());
449
450 wxZipInputStream inzip(*in);
451 wxZipOutputStream outzip(out, COMPRESSIONLEVEL);
452 wxTextOutputStream txt(outzip);
453
454 std::unique_ptr<wxZipEntry> entry;
455
456 outzip.CopyArchiveMetaData(inzip);
457
458 // Copy the contents to the new file
459 while (entry.reset(inzip.GetNextEntry()), entry.get() != nullptr)
460 {
461 // is this the selected revision?
462 if (entry->GetName() == revString)
463 {
464 outzip.CopyEntry(entry.release(), inzip);
465
466 // Append the tag directly after the
467 // selected revision
468 outzip.PutNextEntry(taggedRevision);
469 txt << revision;
470 outzip.CloseEntry();
471 }
472 else
473 outzip.CopyEntry(entry.release(), inzip);
474 }
475
476 // Close the temporary file and commit it,
477 // so that it may override the previous file
478 // safely.
479 in.reset();
480 outzip.Close();
481 out.Commit();
482
483 return revisionNo;
484}
485
486
497void FileRevisions::fileMove(const wxString& newRevPath, const wxString& comment)
498{
499 wxString revContent = "FILEMOVE OPERATION ON " + getCurrentRevision();
500 createNewRevision(revContent, comment);
501
502 wxFileName newPath(newRevPath);
503
504 if (!newPath.DirExists())
505 wxFileName::Mkdir(newPath.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL);
506
507 wxRenameFile(m_revisionPath.GetFullPath(), newRevPath);
508 m_revisionPath.Assign(newRevPath);
509}
510
511
519size_t FileRevisions::getMaxDiffFileSize(size_t nFileSize)
520{
521 const size_t MINFILESIZE = 1024;
522 double dOffset = nFileSize > MINFILESIZE ? ((double)MINFILESIZE / (double)nFileSize) : 0.5;
523 size_t nTargetFileSize = ((1.0 - dOffset) * 0.8 * exp(-(double)getRevisionCount() / 40.0) + dOffset) * nFileSize;
524 return nTargetFileSize > (MINFILESIZE / 2) ? nTargetFileSize : (MINFILESIZE / 2);
525}
526
527
535{
536 if (!m_revisionPath.Exists())
537 return 0;
538
539 wxFFileInputStream in(m_revisionPath.GetFullPath());
540 wxZipInputStream zip(in);
541
542 return zip.GetTotalEntries();
543}
544
545
558{
559 wxArrayString stringArray;
560
561 if (m_revisionPath.Exists())
562 {
563 wxFFileInputStream in(m_revisionPath.GetFullPath());
564 wxZipInputStream zip(in);
565
566 std::unique_ptr<wxZipEntry> entry;
567
568 // Get the names, the time and the comments for each entry
569 // in the ZIP file
570 while (entry.reset(zip.GetNextEntry()), entry.get() != nullptr)
571 {
572 stringArray.Add(entry->GetName() + "\t" + entry->GetDateTime().FormatISOCombined(' ') + "\t" + entry->GetComment());
573 }
574 }
575
576 return stringArray;
577}
578
579
589{
590 wxArrayString revList = getRevisionList();
591
592 for (int i = revList.size()-1; i >= 0; i--)
593 {
594 if (revList[i].substr(0, 3) == "rev")
595 return revList[i].substr(0, revList[i].find('\t'));
596 }
597
598 return "";
599}
600
601
612wxString FileRevisions::getRevision(size_t nRevision)
613{
614 if (!m_revisionPath.Exists())
615 return "";
616
617 return getRevision("rev" + toString(nRevision));
618}
619
620
631wxString FileRevisions::getRevision(wxString revString)
632{
633 if (!m_revisionPath.Exists())
634 return "";
635
636 if (revString.substr(0, 3) == "tag")
637 {
638 revString.replace(0, 3, "rev");
639 revString.erase(revString.find('-'));
640 }
641
642 revString = getLastContentModification(revString);
643
644 return readRevision(revString);
645}
646
647
658wxString FileRevisions::compareRevisions(const wxString& rev1, const wxString& rev2)
659{
660 return diff(getRevision(rev1), rev1, getRevision(rev2), rev2);
661}
662
663
674void FileRevisions::restoreRevision(size_t nRevision, const wxString& targetFile)
675{
676 wxString revision = getRevision(nRevision);
677
678 wxFile restoredFile;
679 restoredFile.Open(targetFile, wxFile::write);
680 restoredFile.Write(revision);
681
682 createNewRevision(convertLineEndings(revision), "RESTORE: rev" + toString(nRevision));
683}
684
685
696void FileRevisions::restoreRevision(const wxString& revString, const wxString& targetFile)
697{
698 wxString revision = getRevision(revString);
699
700 wxFile restoredFile;
701 restoredFile.Open(targetFile, wxFile::write);
702 restoredFile.Write(revision);
703
704 createNewRevision(convertLineEndings(revision), "RESTORE: " + revString);
705}
706
707
716size_t FileRevisions::tagRevision(size_t nRevision, const wxString& tagComment)
717{
718 // Will probably fail, because the selected revision is not
719 // stored as "revXYZ" but as "tagABC-revXYZ"
720 return createNewTag("rev" + toString(nRevision), tagComment);
721}
722
723
732size_t FileRevisions::tagRevision(const wxString& revString, const wxString& tagComment)
733{
734 return createNewTag(revString, tagComment);
735}
736
737
748size_t FileRevisions::addRevision(const wxString& revisionContent)
749{
750 wxString revContent = convertLineEndings(revisionContent);
751
752 if (!m_revisionPath.Exists())
753 {
754 wxFFileOutputStream out(m_revisionPath.GetFullPath());
755 wxZipOutputStream zip(out, COMPRESSIONLEVEL);
756 wxTextOutputStream txt(zip);
757
758 wxZipEntry* rev0 = new wxZipEntry("rev0");
759 rev0->SetComment("Initial revision");
760
761 zip.PutNextEntry(rev0);
762 txt << revContent;
763 zip.CloseEntry();
764
765 return 0;
766 }
767 else
768 {
769 // Only add a new revision if the contents differ
770 if (revisionContent == getRevision(getCurrentRevision()))
771 return -1;
772
773 wxString diffFile = createDiff(revisionContent);
774
775 if (diffFile.length() < getMaxDiffFileSize(revContent.length()))
776 return createNewRevision(diffFile, "DIFF");
777 else
778 return createNewRevision(revContent, "");
779 }
780}
781
782
795size_t FileRevisions::addExternalRevision(const wxString& filePath)
796{
797 wxString revContent = readExternalFile(filePath);
798
799 if (!revContent.length())
800 revContent = "Other error";
801
802 // Only add the external revision, if it actually
803 // modified the file
804 wxString currRev = getRevision(getCurrentRevision());
805 if (currRev == revContent)
806 return -1;
807
808 // Ensure that the diff is not actually empty
809 if (!diff(currRev, "", revContent, "").length())
810 return -1;
811
812 return createNewRevision(convertLineEndings(revContent), "External modification");
813}
814
815
823{
824 if (m_revisionPath.Exists())
825 {
826 size_t revisionNo = getRevisionCount();
827
828 if (revisionNo > 1)
829 {
830 std::unique_ptr<wxFFileInputStream> in(new wxFFileInputStream(m_revisionPath.GetFullPath()));
831 wxTempFileOutputStream out(m_revisionPath.GetFullPath());
832
833 wxZipInputStream inzip(*in);
834 wxZipOutputStream outzip(out, COMPRESSIONLEVEL);
835 wxTextOutputStream txt(outzip);
836
837 std::unique_ptr<wxZipEntry> entry;
838
839 outzip.CopyArchiveMetaData(inzip);
840
841 while (entry.reset(inzip.GetNextEntry()), entry.get() != nullptr)
842 {
843 if (entry->GetName() != "rev" + wxString(toString(revisionNo-1)))
844 outzip.CopyEntry(entry.release(), inzip);
845 }
846
847 in.reset();
848 outzip.Close();
849 out.Commit();
850 }
851 else
852 wxRemoveFile(m_revisionPath.GetFullPath());
853 }
854}
855
856
868void FileRevisions::renameFile(const wxString& oldName, const wxString& newName, const wxString& newRevPath)
869{
870 fileMove(newRevPath, "RENAME: " + oldName + " -> " + newName);
871}
872
873
885void FileRevisions::moveFile(const wxString& oldPath, const wxString& newPath, const wxString& newRevPath)
886{
887 fileMove(newRevPath, "MOVE: " + oldPath + " -> " + newPath);
888}
889
890
891
wxString getRevision(size_t nRevision)
Returns the contents of the selected revision.
size_t addRevision(const wxString &revisionContent)
This method adds a new revision.
void renameFile(const wxString &oldName, const wxString &newName, const wxString &newRevPath)
This method handles renames of the corresponding file.
size_t getRevisionCount()
Returns the number of available revisions.
void restoreRevision(size_t nRevision, const wxString &targetFile)
This method will restore the contents of the selected revision.
wxArrayString getRevisionList()
This method returns a log-like list of revisions.
wxString createMerge(const wxString &diffFile)
This method merges the diff file into the current revision root.
FileRevisions(const wxString &revisionPath)
Constructor. Will try to create the missing folders on-the-fly.
size_t createNewTag(const wxString &revString, const wxString &comment)
This method creates a new tag for the passed revision.
wxString convertLineEndings(const wxString &content)
This method converts line end characters.
size_t createNewRevision(const wxString &revContent, const wxString &comment)
This method creates a new revision.
wxFileName m_revisionPath
size_t tagRevision(size_t nRevision, const wxString &tagComment)
Allows the user to tag a selected revision with the passed comment.
wxString diff(const wxString &revision1, const wxString &revisionID1, const wxString &revision2, const wxString &revisionID2)
This method calculates the differences between two files.
wxString readRevision(const wxString &revString)
This method returns the contents of the selected revision.
wxString getLastContentModification(const wxString &revString)
This method returns the last modification revision identifier.
void undoRevision()
This method removes the last added revision.
wxString compareRevisions(const wxString &rev1, const wxString &rev2)
This member function compares two revisions with each other and returns the differnces as unified dif...
wxString getLastRevisionRoot(const wxString &revString)
This method returns the revision identifier of the last revision root.
void moveFile(const wxString &oldPath, const wxString &newPath, const wxString &newRevPath)
This method handles moves of the corresponding file.
wxString readExternalFile(const wxString &filePath)
This method reads an external file into a string.
std::vector< wxString > vectorize(wxString fileContent)
Converts a single-string file into a vector of strings.
void fileMove(const wxString &newRevPath, const wxString &comment)
This method handles all file move operations.
size_t addExternalRevision(const wxString &filePath)
This method adds an external modification as new revision.
wxString createDiff(const wxString &revisionContent)
This method creates the file differences between the file contents and the current revision root.
wxString getCurrentRevision()
This method returns the revision identifier of the current revision.
size_t getMaxDiffFileSize(size_t nFileSize)
This member function returns the maximal Diff file size.
void printUnifiedFormat(stream &out) const
Definition: Diff.hpp:348
void composeUnifiedHunks()
Definition: Diff.hpp:371
void compose()
Definition: Diff.hpp:258
#define COMPRESSIONLEVEL
std::string toString(int)
Converts an integer to a string without the Settings bloat.