]> git.lyx.org Git - lyx.git/blob - src/VCBackend.cpp
6f6b60590ec746c0a4d972fe9e3554dd95ca8c00
[lyx.git] / src / VCBackend.cpp
1 /**
2  * \file VCBackend.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "VCBackend.h"
14 #include "Buffer.h"
15
16 #include "frontends/alert.h"
17
18 #include "support/debug.h"
19 #include "support/filetools.h"
20 #include "support/gettext.h"
21 #include "support/lstrings.h"
22 #include "support/Path.h"
23 #include "support/Systemcall.h"
24
25 #include <boost/regex.hpp>
26
27 #include <fstream>
28
29 using namespace std;
30 using namespace lyx::support;
31
32 using boost::regex;
33 using boost::regex_match;
34 using boost::smatch;
35
36 namespace lyx {
37
38
39 int VCS::doVCCommandCall(string const & cmd, FileName const & path){
40         LYXERR(Debug::LYXVC, "doVCCommandCall: " << cmd);
41         Systemcall one;
42         support::PathChanger p(path);
43         return one.startscript(Systemcall::Wait, cmd);
44 }
45
46 int VCS::doVCCommand(string const & cmd, FileName const & path)
47 {
48         if (owner_)
49                 owner_->setBusy(true);
50
51         int const ret = doVCCommandCall(cmd, path);
52
53         if (owner_)
54                 owner_->setBusy(false);
55         if (ret)
56                 frontend::Alert::error(_("Revision control error."),
57                         bformat(_("Some problem occured while running the command:\n"
58                                   "'%1$s'."),
59                         from_utf8(cmd)));
60         return ret;
61 }
62
63
64 /////////////////////////////////////////////////////////////////////
65 //
66 // RCS
67 //
68 /////////////////////////////////////////////////////////////////////
69
70 RCS::RCS(FileName const & m)
71 {
72         master_ = m;
73         scanMaster();
74 }
75
76
77 FileName const RCS::findFile(FileName const & file)
78 {
79         // Check if *,v exists.
80         FileName tmp(file.absFilename() + ",v");
81         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp);
82         if (tmp.isReadableFile()) {
83                 LYXERR(Debug::LYXVC, "Yes, " << file << " is under rcs.");
84                 return tmp;
85         }
86
87         // Check if RCS/*,v exists.
88         tmp = FileName(addName(addPath(onlyPath(file.absFilename()), "RCS"), file.absFilename()) + ",v");
89         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp);
90         if (tmp.isReadableFile()) {
91                 LYXERR(Debug::LYXVC, "Yes, " << file << " is under rcs.");
92                 return tmp;
93         }
94
95         return FileName();
96 }
97
98
99 void RCS::retrieve(FileName const & file)
100 {
101         LYXERR(Debug::LYXVC, "LyXVC::RCS: retrieve.\n\t" << file);
102         doVCCommandCall("co -q -r " + quoteName(file.toFilesystemEncoding()),
103                          FileName());
104 }
105
106
107 void RCS::scanMaster()
108 {
109         if (master_.empty())
110                 return;
111
112         LYXERR(Debug::LYXVC, "LyXVC::RCS: scanMaster: " << master_);
113
114         ifstream ifs(master_.toFilesystemEncoding().c_str());
115
116         string token;
117         bool read_enough = false;
118
119         while (!read_enough && ifs >> token) {
120                 LYXERR(Debug::LYXVC, "LyXVC::scanMaster: current lex text: `"
121                         << token << '\'');
122
123                 if (token.empty())
124                         continue;
125                 else if (token == "head") {
126                         // get version here
127                         string tmv;
128                         ifs >> tmv;
129                         tmv = rtrim(tmv, ";");
130                         version_ = tmv;
131                         LYXERR(Debug::LYXVC, "LyXVC: version found to be " << tmv);
132                 } else if (contains(token, "access")
133                            || contains(token, "symbols")
134                            || contains(token, "strict")) {
135                         // nothing
136                 } else if (contains(token, "locks")) {
137                         // get locker here
138                         if (contains(token, ';')) {
139                                 locker_ = "Unlocked";
140                                 vcstatus = UNLOCKED;
141                                 continue;
142                         }
143                         string tmpt;
144                         string s1;
145                         string s2;
146                         do {
147                                 ifs >> tmpt;
148                                 s1 = rtrim(tmpt, ";");
149                                 // tmp is now in the format <user>:<version>
150                                 s1 = split(s1, s2, ':');
151                                 // s2 is user, and s1 is version
152                                 if (s1 == version_) {
153                                         locker_ = s2;
154                                         vcstatus = LOCKED;
155                                         break;
156                                 }
157                         } while (!contains(tmpt, ';'));
158
159                 } else if (token == "comment") {
160                         // we don't need to read any further than this.
161                         read_enough = true;
162                 } else {
163                         // unexpected
164                         LYXERR(Debug::LYXVC, "LyXVC::scanMaster(): unexpected token");
165                 }
166         }
167 }
168
169
170 void RCS::registrer(string const & msg)
171 {
172         string cmd = "ci -q -u -i -t-\"";
173         cmd += msg;
174         cmd += "\" ";
175         cmd += quoteName(onlyFilename(owner_->absFileName()));
176         doVCCommand(cmd, FileName(owner_->filePath()));
177 }
178
179
180 string RCS::checkIn(string const & msg)
181 {
182         int ret = doVCCommand("ci -q -u -m\"" + msg + "\" "
183                     + quoteName(onlyFilename(owner_->absFileName())),
184                     FileName(owner_->filePath()));
185         return ret ? string() : "RCS: Proceeded";
186 }
187
188 bool RCS::checkInEnabled()
189 {
190         return owner_ && !owner_->isReadonly();
191 }
192
193 string RCS::checkOut()
194 {
195         owner_->markClean();
196         int ret = doVCCommand("co -q -l " + quoteName(onlyFilename(owner_->absFileName())),
197                     FileName(owner_->filePath()));
198         return ret ? string() : "RCS: Proceeded";
199 }
200
201
202 bool RCS::checkOutEnabled()
203 {
204         return owner_ && owner_->isReadonly();
205 }
206
207
208 string RCS::repoSynchro()
209 {
210         lyxerr << "Sorry, not implemented." << endl;
211         return string();
212 }
213
214
215 bool RCS::repoSynchroEnabled()
216 {
217         return false;
218 }
219
220
221 string RCS::lockingToggle()
222 {
223         lyxerr << "Sorry, not implemented." << endl;
224         return string();
225 }
226
227
228 bool RCS::lockingToggleEnabled()
229 {
230         return false;
231 }
232
233
234 void RCS::revert()
235 {
236         doVCCommand("co -f -u" + version() + " "
237                     + quoteName(onlyFilename(owner_->absFileName())),
238                     FileName(owner_->filePath()));
239         // We ignore changes and just reload!
240         owner_->markClean();
241 }
242
243
244 void RCS::undoLast()
245 {
246         LYXERR(Debug::LYXVC, "LyXVC: undoLast");
247         doVCCommand("rcs -o" + version() + " "
248                     + quoteName(onlyFilename(owner_->absFileName())),
249                     FileName(owner_->filePath()));
250 }
251
252
253 bool RCS::undoLastEnabled()
254 {
255         return true;
256 }
257
258
259 void RCS::getLog(FileName const & tmpf)
260 {
261         doVCCommand("rlog " + quoteName(onlyFilename(owner_->absFileName()))
262                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
263                     FileName(owner_->filePath()));
264 }
265
266
267 bool RCS::toggleReadOnlyEnabled()
268 {
269         return true;
270 }
271
272
273 /////////////////////////////////////////////////////////////////////
274 //
275 // CVS
276 //
277 /////////////////////////////////////////////////////////////////////
278
279 CVS::CVS(FileName const & m, FileName const & f)
280 {
281         master_ = m;
282         file_ = f;
283         scanMaster();
284 }
285
286
287 FileName const CVS::findFile(FileName const & file)
288 {
289         // First we look for the CVS/Entries in the same dir
290         // where we have file.
291         FileName const entries(onlyPath(file.absFilename()) + "/CVS/Entries");
292         string const tmpf = '/' + onlyFilename(file.absFilename()) + '/';
293         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under cvs in `" << entries
294                              << "' for `" << tmpf << '\'');
295         if (entries.isReadableFile()) {
296                 // Ok we are at least in a CVS dir. Parse the CVS/Entries
297                 // and see if we can find this file. We do a fast and
298                 // dirty parse here.
299                 ifstream ifs(entries.toFilesystemEncoding().c_str());
300                 string line;
301                 while (getline(ifs, line)) {
302                         LYXERR(Debug::LYXVC, "\tEntries: " << line);
303                         if (contains(line, tmpf))
304                                 return entries;
305                 }
306         }
307         return FileName();
308 }
309
310
311 void CVS::scanMaster()
312 {
313         LYXERR(Debug::LYXVC, "LyXVC::CVS: scanMaster. \n     Checking: " << master_);
314         // Ok now we do the real scan...
315         ifstream ifs(master_.toFilesystemEncoding().c_str());
316         string tmpf = '/' + onlyFilename(file_.absFilename()) + '/';
317         LYXERR(Debug::LYXVC, "\tlooking for `" << tmpf << '\'');
318         string line;
319         static regex const reg("/(.*)/(.*)/(.*)/(.*)/(.*)");
320         while (getline(ifs, line)) {
321                 LYXERR(Debug::LYXVC, "\t  line: " << line);
322                 if (contains(line, tmpf)) {
323                         // Ok extract the fields.
324                         smatch sm;
325
326                         regex_match(line, sm, reg);
327
328                         //sm[0]; // whole matched string
329                         //sm[1]; // filename
330                         version_ = sm.str(2);
331                         string const file_date = sm.str(3);
332
333                         //sm[4]; // options
334                         //sm[5]; // tag or tagdate
335                         // FIXME: must double check file is stattable/existing
336                         time_t mod = file_.lastModified();
337                         string mod_date = rtrim(asctime(gmtime(&mod)), "\n");
338                         LYXERR(Debug::LYXVC, "Date in Entries: `" << file_date
339                                 << "'\nModification date of file: `" << mod_date << '\'');
340                         //FIXME this whole locking bussiness is not working under cvs and the machinery
341                         // conforms to the ci usage, not cvs.
342                         if (file_date == mod_date) {
343                                 locker_ = "Unlocked";
344                                 vcstatus = UNLOCKED;
345                         } else {
346                                 // Here we should also to some more checking
347                                 // to see if there are conflicts or not.
348                                 locker_ = "Locked";
349                                 vcstatus = LOCKED;
350                         }
351                         break;
352                 }
353         }
354 }
355
356
357 void CVS::registrer(string const & msg)
358 {
359         doVCCommand("cvs -q add -m \"" + msg + "\" "
360                     + quoteName(onlyFilename(owner_->absFileName())),
361                     FileName(owner_->filePath()));
362 }
363
364
365 string CVS::checkIn(string const & msg)
366 {
367         int ret = doVCCommand("cvs -q commit -m \"" + msg + "\" "
368                     + quoteName(onlyFilename(owner_->absFileName())),
369                     FileName(owner_->filePath()));
370         return ret ? string() : "CVS: Proceeded";
371 }
372
373
374 bool CVS::checkInEnabled()
375 {
376         return true;
377 }
378
379
380 string CVS::checkOut()
381 {
382         // cvs update or perhaps for cvs this should be a noop
383         // we need to detect conflict (eg "C" in output)
384         // before we can do this.
385         lyxerr << "Sorry, not implemented." << endl;
386         return string();
387 }
388
389
390 bool CVS::checkOutEnabled()
391 {
392         return false;
393 }
394
395
396 string CVS::repoSynchro()
397 {
398         lyxerr << "Sorry, not implemented." << endl;
399         return string();
400 }
401
402
403 bool CVS::repoSynchroEnabled()
404 {
405         return false;
406 }
407
408
409 string CVS::lockingToggle()
410 {
411         lyxerr << "Sorry, not implemented." << endl;
412         return string();
413 }
414
415
416 bool CVS::lockingToggleEnabled()
417 {
418         return false;
419 }
420
421
422 void CVS::revert()
423 {
424         // Reverts to the version in CVS repository and
425         // gets the updated version from the repository.
426         string const fil = quoteName(onlyFilename(owner_->absFileName()));
427         // This is sensitive operation, so at lest some check about
428         // existence of cvs program and its file
429         if (doVCCommand("cvs log "+ fil, FileName(owner_->filePath())))
430                 return;
431         FileName f(owner_->absFileName());
432         f.removeFile();
433         doVCCommand("cvs update " + fil,
434                     FileName(owner_->filePath()));
435         owner_->markClean();
436 }
437
438
439 void CVS::undoLast()
440 {
441         // merge the current with the previous version
442         // in a reverse patch kind of way, so that the
443         // result is to revert the last changes.
444         lyxerr << "Sorry, not implemented." << endl;
445 }
446
447
448 bool CVS::undoLastEnabled()
449 {
450         return false;
451 }
452
453
454 void CVS::getLog(FileName const & tmpf)
455 {
456         doVCCommand("cvs log " + quoteName(onlyFilename(owner_->absFileName()))
457                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
458                     FileName(owner_->filePath()));
459 }
460
461 bool CVS::toggleReadOnlyEnabled()
462 {
463         return false;
464 }
465
466 /////////////////////////////////////////////////////////////////////
467 //
468 // SVN
469 //
470 /////////////////////////////////////////////////////////////////////
471
472 SVN::SVN(FileName const & m, FileName const & f)
473 {
474         owner_ = 0;
475         master_ = m;
476         file_ = f;
477         locked_mode_ = 0;
478         scanMaster();
479 }
480
481
482 FileName const SVN::findFile(FileName const & file)
483 {
484         // First we look for the .svn/entries in the same dir
485         // where we have file.
486         FileName const entries(onlyPath(file.absFilename()) + "/.svn/entries");
487         string const tmpf = onlyFilename(file.absFilename());
488         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under svn in `" << entries
489                              << "' for `" << tmpf << '\'');
490         if (entries.isReadableFile()) {
491                 // Ok we are at least in a SVN dir. Parse the .svn/entries
492                 // and see if we can find this file. We do a fast and
493                 // dirty parse here.
494                 ifstream ifs(entries.toFilesystemEncoding().c_str());
495                 string line, oldline;
496                 while (getline(ifs, line)) {
497                         if (line == "dir" || line == "file")
498                                 LYXERR(Debug::LYXVC, "\tEntries: " << oldline);
499                         if (oldline == tmpf && line == "file")
500                                 return entries;
501                         oldline = line;
502                 }
503         }
504         return FileName();
505 }
506
507
508 void SVN::scanMaster()
509 {
510         locker_.clear();
511         vcstatus = NOLOCKING;
512         if (checkLockMode()) {
513                 if (isLocked()) {
514                         locker_ = "Locked";
515                         vcstatus = LOCKED;
516                 } else {
517                         locker_ = "Unlocked";
518                         vcstatus = LOCKED;
519                 }
520         }
521 }
522
523
524 bool SVN::checkLockMode()
525 {
526         FileName tmpf = FileName::tempName("lyxvcout");
527         if (tmpf.empty()){
528                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
529                 return N_("Error: Could not generate logfile.");
530         }
531
532         LYXERR(Debug::LYXVC, "Detecting locking mode...");
533         if (doVCCommandCall("svn proplist " + quoteName(file_.onlyFileName())
534                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
535                     file_.onlyPath()))
536                 return false;
537
538         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
539         string line;
540         bool ret = false;
541
542         while (ifs) {
543                 getline(ifs, line);
544                 LYXERR(Debug::LYXVC, line);
545                 if (contains(line, "svn:needs-lock"))
546                         ret = true;
547         }
548         LYXERR(Debug::LYXVC, "Locking enabled: " << ret);
549         ifs.close();
550         locked_mode_ = ret;
551         return ret;
552
553 }
554
555
556 bool SVN::isLocked() const
557 {
558         //refresh file info
559         FileName file(file_.absFilename());
560         return !file.isReadOnly();
561 }
562
563
564 void SVN::registrer(string const & /*msg*/)
565 {
566         doVCCommand("svn add -q " + quoteName(onlyFilename(owner_->absFileName())),
567                     FileName(owner_->filePath()));
568 }
569
570
571 string SVN::checkIn(string const & msg)
572 {
573         FileName tmpf = FileName::tempName("lyxvcout");
574         if (tmpf.empty()){
575                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
576                 return N_("Error: Could not generate logfile.");
577         }
578
579         doVCCommand("svn commit -m \"" + msg + "\" "
580                     + quoteName(onlyFilename(owner_->absFileName()))
581                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
582                     FileName(owner_->filePath()));
583
584         string log;
585         string res = scanLogFile(tmpf, log);
586         if (!res.empty())
587                 frontend::Alert::error(_("Revision control error."),
588                                 _("Error when committing to repository.\n"
589                                 "You have to manually resolve the problem.\n"
590                                 "After pressing OK, LyX will reopen the document."));
591         else
592                 fileLock(false, tmpf, log);
593
594         tmpf.erase();
595         return "SVN: " + log;
596 }
597
598
599 bool SVN::checkInEnabled()
600 {
601         if (locked_mode_)
602                 return isLocked();
603         else
604                 return true;
605 }
606
607
608 // FIXME Correctly return code should be checked instead of this.
609 // This would need another solution than just plain startscript.
610 // Hint from Andre': QProcess::readAllStandardError()...
611 string SVN::scanLogFile(FileName const & f, string & status)
612 {
613         ifstream ifs(f.toFilesystemEncoding().c_str());
614         string line;
615
616         while (ifs) {
617                 getline(ifs, line);
618                 lyxerr << line << "\n";
619                 if (!line.empty()) status += line + "; ";
620                 if (prefixIs(line, "C ") || contains(line, "Commit failed")) {
621                         ifs.close();
622                         return line;
623                 }
624                 if (contains(line, "svn:needs-lock")) {
625                         ifs.close();
626                         return line;
627                 }
628         }
629         ifs.close();
630         return string();
631 }
632
633
634 void SVN::fileLock(bool lock, FileName const & tmpf, string &status)
635 {
636         if (!locked_mode_ || (isLocked() == lock))
637                 return;
638
639         string arg = lock ? "lock " : "unlock ";
640         doVCCommand("svn "+ arg + quoteName(onlyFilename(owner_->absFileName()))
641                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
642                     FileName(owner_->filePath()));
643
644         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
645         string line;
646         while (ifs) {
647                 getline(ifs, line);
648                 if (!line.empty()) status += line + "; ";
649         }
650         ifs.close();
651
652         if (!isLocked() && lock)
653                 frontend::Alert::error(_("Revision control error."),
654                         _("Error when acquiring write lock.\n"
655                         "Most probably another user is editing\n"
656                         "the current document now!\n"
657                         "Also check the access to the repository."));
658         if (isLocked() && !lock)
659                 frontend::Alert::error(_("Revision control error."),
660                         _("Error when releasing write lock.\n"
661                         "Check the access to the repository."));
662 }
663
664
665 string SVN::checkOut()
666 {
667         FileName tmpf = FileName::tempName("lyxvcout");
668         if (tmpf.empty()) {
669                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
670                 return N_("Error: Could not generate logfile.");
671         }
672
673         doVCCommand("svn update " + quoteName(onlyFilename(owner_->absFileName()))
674                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
675                     FileName(owner_->filePath()));
676
677         string log;
678         string res = scanLogFile(tmpf, log);
679         if (!res.empty())
680                 frontend::Alert::error(_("Revision control error."),
681                         bformat(_("Error when updating from repository.\n"
682                                 "You have to manually resolve the conflicts NOW!\n'%1$s'.\n\n"
683                                 "After pressing OK, LyX will try to reopen resolved document."),
684                         from_local8bit(res)));
685
686         fileLock(true, tmpf, log);
687
688         tmpf.erase();
689         return "SVN: " + log;
690 }
691
692
693 bool SVN::checkOutEnabled()
694 {
695         if (locked_mode_)
696                 return !isLocked();
697         else
698                 return true;
699 }
700
701
702 string SVN::repoSynchro()
703 {
704         FileName tmpf = FileName::tempName("lyxvcout");
705         if (tmpf.empty()) {
706                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
707                 return N_("Error: Could not generate logfile.");
708         }
709
710         doVCCommand("svn diff " + quoteName(owner_->filePath())
711         + " > " + quoteName(tmpf.toFilesystemEncoding()),
712         FileName(owner_->filePath()));
713         docstring res = tmpf.fileContents("UTF-8");
714         if (!res.empty()) {
715                 LYXERR(Debug::LYXVC, "Diff detected:\n" << res);
716                 docstring const file = from_utf8(owner_->filePath());
717                 docstring text = bformat(_("There were detected changes"
718                                 "in the working directory.\n"
719                                 "Synchronizing with repository will discard "
720                                 "any uncommitted changes in the directory:\n%1$s"
721                                 "\n\nContinue?"), file);
722                 int const ret = frontend::Alert::prompt(_("Changes detected"),
723                                 text, 0, 1, _("&Yes"), _("&No"));
724                 if (ret) {
725                         tmpf.erase();
726                         return string();
727                 }
728         }
729
730         doVCCommand("svn revert -R " + quoteName(owner_->filePath())
731         + " > " + quoteName(tmpf.toFilesystemEncoding()),
732         FileName(owner_->filePath()));
733         res = "Revert log:\n" + tmpf.fileContents("UTF-8");
734         doVCCommand("svn update " + quoteName(owner_->filePath())
735         + " > " + quoteName(tmpf.toFilesystemEncoding()),
736         FileName(owner_->filePath()));
737         res += "Update log:\n" + tmpf.fileContents("UTF-8");
738
739         LYXERR(Debug::LYXVC, res);
740         tmpf.erase();
741         return to_utf8(res);
742 }
743
744
745 bool SVN::repoSynchroEnabled()
746 {
747         return true;
748 }
749
750
751 string SVN::lockingToggle()
752 {
753         FileName tmpf = FileName::tempName("lyxvcout");
754         if (tmpf.empty()) {
755                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
756                 return N_("Error: Could not generate logfile.");
757         }
758
759         int ret = doVCCommand("svn proplist " + quoteName(onlyFilename(owner_->absFileName()))
760                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
761                     FileName(owner_->filePath()));
762         if (ret)
763                 return string();
764
765         string log;
766         string res = scanLogFile(tmpf, log);
767         bool locking = contains(res, "svn:needs-lock");
768         if (!locking)
769                 ret = doVCCommand("svn propset svn:needs-lock ON "
770                     + quoteName(onlyFilename(owner_->absFileName()))
771                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
772                     FileName(owner_->filePath()));
773         else
774                 ret = doVCCommand("svn propdel svn:needs-lock "
775                     + quoteName(onlyFilename(owner_->absFileName()))
776                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
777                     FileName(owner_->filePath()));
778         if (ret)
779                 return string();
780
781         tmpf.erase();
782         frontend::Alert::warning(_("VCN File Locking"),
783                 (locking ? _("Locking property unset.") : _("Locking property set.")) + "\n"
784                 + _("Do not forget to commit the locking property into the repository."),
785                 true);
786
787         return string("SVN: ") +  N_("Locking property set.");
788 }
789
790
791 bool SVN::lockingToggleEnabled()
792 {
793         return true;
794 }
795
796 void SVN::revert()
797 {
798         // Reverts to the version in CVS repository and
799         // gets the updated version from the repository.
800         string const fil = quoteName(onlyFilename(owner_->absFileName()));
801
802         doVCCommand("svn revert -q " + fil,
803                     FileName(owner_->filePath()));
804         owner_->markClean();
805 }
806
807
808 void SVN::undoLast()
809 {
810         // merge the current with the previous version
811         // in a reverse patch kind of way, so that the
812         // result is to revert the last changes.
813         lyxerr << "Sorry, not implemented." << endl;
814 }
815
816
817 bool SVN::undoLastEnabled()
818 {
819         return false;
820 }
821
822
823 void SVN::getLog(FileName const & tmpf)
824 {
825         doVCCommand("svn log " + quoteName(onlyFilename(owner_->absFileName()))
826                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
827                     FileName(owner_->filePath()));
828 }
829
830
831 bool SVN::toggleReadOnlyEnabled()
832 {
833         return false;
834 }
835
836
837 } // namespace lyx