]> git.lyx.org Git - lyx.git/blob - src/VCBackend.cpp
listerrors.lyx : Update a link.
[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  * \author Pavel Sanda
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "VCBackend.h"
15 #include "Buffer.h"
16 #include "LyX.h"
17 #include "FuncRequest.h"
18
19 #include "frontends/alert.h"
20 #include "frontends/Application.h"
21
22 #include "support/convert.h"
23 #include "support/debug.h"
24 #include "support/filetools.h"
25 #include "support/gettext.h"
26 #include "support/lstrings.h"
27 #include "support/Path.h"
28 #include "support/Systemcall.h"
29 #include "support/regex.h"
30
31 #include <fstream>
32
33 using namespace std;
34 using namespace lyx::support;
35
36
37
38 namespace lyx {
39
40
41 int VCS::doVCCommandCall(string const & cmd, FileName const & path)
42 {
43         LYXERR(Debug::LYXVC, "doVCCommandCall: " << cmd);
44         Systemcall one;
45         support::PathChanger p(path);
46         return one.startscript(Systemcall::Wait, cmd, string(), false);
47 }
48
49
50 int VCS::doVCCommand(string const & cmd, FileName const & path, bool reportError)
51 {
52         if (owner_)
53                 owner_->setBusy(true);
54
55         int const ret = doVCCommandCall(cmd, path);
56
57         if (owner_)
58                 owner_->setBusy(false);
59         if (ret && reportError)
60                 frontend::Alert::error(_("Revision control error."),
61                         bformat(_("Some problem occured while running the command:\n"
62                                   "'%1$s'."),
63                         from_utf8(cmd)));
64         return ret;
65 }
66
67
68 bool VCS::makeRCSRevision(string const &version, string &revis) const
69 {
70         string rev = revis;
71         
72         if (isStrInt(rev)) {
73                 int back = convert<int>(rev);
74                 // if positive use as the last number in the whole revision string
75                 if (back > 0) {
76                         string base;
77                         rsplit(version, base , '.' );
78                         rev = base + "." + rev;
79                 }
80                 if (back == 0)
81                         rev = version;
82                 // we care about the last number from revision string
83                 // in case of backward indexing
84                 if (back < 0) {
85                         string cur, base;
86                         cur = rsplit(version, base , '.' );
87                         if (!isStrInt(cur))
88                                 return false;
89                         int want = convert<int>(cur) + back;
90                         if (want <= 0)
91                                 return false;
92                         
93                         rev = base + "." + convert<string>(want);
94                 }
95         }
96
97         revis = rev;
98         return true;
99 }
100         
101         
102 /////////////////////////////////////////////////////////////////////
103 //
104 // RCS
105 //
106 /////////////////////////////////////////////////////////////////////
107
108 RCS::RCS(FileName const & m)
109 {
110         master_ = m;
111         scanMaster();
112 }
113
114
115 FileName const RCS::findFile(FileName const & file)
116 {
117         // Check if *,v exists.
118         FileName tmp(file.absFileName() + ",v");
119         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp);
120         if (tmp.isReadableFile()) {
121                 LYXERR(Debug::LYXVC, "Yes, " << file << " is under rcs.");
122                 return tmp;
123         }
124
125         // Check if RCS/*,v exists.
126         tmp = FileName(addName(addPath(onlyPath(file.absFileName()), "RCS"), file.absFileName()) + ",v");
127         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp);
128         if (tmp.isReadableFile()) {
129                 LYXERR(Debug::LYXVC, "Yes, " << file << " is under rcs.");
130                 return tmp;
131         }
132
133         return FileName();
134 }
135
136
137 void RCS::retrieve(FileName const & file)
138 {
139         LYXERR(Debug::LYXVC, "LyXVC::RCS: retrieve.\n\t" << file);
140         doVCCommandCall("co -q -r " + quoteName(file.toFilesystemEncoding()),
141                          FileName());
142 }
143
144
145 void RCS::scanMaster()
146 {
147         if (master_.empty())
148                 return;
149
150         LYXERR(Debug::LYXVC, "LyXVC::RCS: scanMaster: " << master_);
151
152         ifstream ifs(master_.toFilesystemEncoding().c_str());
153
154         string token;
155         bool read_enough = false;
156
157         while (!read_enough && ifs >> token) {
158                 LYXERR(Debug::LYXVC, "LyXVC::scanMaster: current lex text: `"
159                         << token << '\'');
160
161                 if (token.empty())
162                         continue;
163                 else if (token == "head") {
164                         // get version here
165                         string tmv;
166                         ifs >> tmv;
167                         tmv = rtrim(tmv, ";");
168                         version_ = tmv;
169                         LYXERR(Debug::LYXVC, "LyXVC: version found to be " << tmv);
170                 } else if (contains(token, "access")
171                            || contains(token, "symbols")
172                            || contains(token, "strict")) {
173                         // nothing
174                 } else if (contains(token, "locks")) {
175                         // get locker here
176                         if (contains(token, ';')) {
177                                 locker_ = "Unlocked";
178                                 vcstatus = UNLOCKED;
179                                 continue;
180                         }
181                         string tmpt;
182                         string s1;
183                         string s2;
184                         do {
185                                 ifs >> tmpt;
186                                 s1 = rtrim(tmpt, ";");
187                                 // tmp is now in the format <user>:<version>
188                                 s1 = split(s1, s2, ':');
189                                 // s2 is user, and s1 is version
190                                 if (s1 == version_) {
191                                         locker_ = s2;
192                                         vcstatus = LOCKED;
193                                         break;
194                                 }
195                         } while (!contains(tmpt, ';'));
196
197                 } else if (token == "comment") {
198                         // we don't need to read any further than this.
199                         read_enough = true;
200                 } else {
201                         // unexpected
202                         LYXERR(Debug::LYXVC, "LyXVC::scanMaster(): unexpected token");
203                 }
204         }
205 }
206
207
208 void RCS::registrer(string const & msg)
209 {
210         string cmd = "ci -q -u -i -t-\"";
211         cmd += msg;
212         cmd += "\" ";
213         cmd += quoteName(onlyFileName(owner_->absFileName()));
214         doVCCommand(cmd, FileName(owner_->filePath()));
215 }
216
217
218 string RCS::checkIn(string const & msg)
219 {
220         int ret = doVCCommand("ci -q -u -m\"" + msg + "\" "
221                     + quoteName(onlyFileName(owner_->absFileName())),
222                     FileName(owner_->filePath()));
223         return ret ? string() : "RCS: Proceeded";
224 }
225
226
227 bool RCS::checkInEnabled()
228 {
229         return owner_ && !owner_->isReadonly();
230 }
231
232
233 bool RCS::isCheckInWithConfirmation()
234 {
235         // FIXME one day common getDiff for all backends
236         // docstring diff;
237         // if (getDiff(file, diff) && diff.empty())
238         //      return false;
239
240         FileName tmpf = FileName::tempName("lyxvcout");
241         if (tmpf.empty()) {
242                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
243                 return true;
244         }
245
246         doVCCommandCall("rcsdiff " + quoteName(owner_->absFileName())
247                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
248                 FileName(owner_->filePath()));
249
250         docstring diff = tmpf.fileContents("UTF-8");
251         tmpf.erase();
252
253         if (diff.empty())
254                 return false;
255
256         return true;
257 }
258
259
260 string RCS::checkOut()
261 {
262         owner_->markClean();
263         int ret = doVCCommand("co -q -l " + quoteName(onlyFileName(owner_->absFileName())),
264                     FileName(owner_->filePath()));
265         return ret ? string() : "RCS: Proceeded";
266 }
267
268
269 bool RCS::checkOutEnabled()
270 {
271         return owner_ && owner_->isReadonly();
272 }
273
274
275 string RCS::repoUpdate()
276 {
277         lyxerr << "Sorry, not implemented." << endl;
278         return string();
279 }
280
281
282 bool RCS::repoUpdateEnabled()
283 {
284         return false;
285 }
286
287
288 string RCS::lockingToggle()
289 {
290         //FIXME this might be actually possible, study rcs -U, rcs -L.
291         //State should be easy to get inside scanMaster.
292         //It would fix #4370 and make rcs/svn usage even more closer.
293         lyxerr << "Sorry, not implemented." << endl;
294         return string();
295 }
296
297
298 bool RCS::lockingToggleEnabled()
299 {
300         return false;
301 }
302
303
304 bool RCS::revert()
305 {
306         if (doVCCommand("co -f -u" + version_ + " "
307                     + quoteName(onlyFileName(owner_->absFileName())),
308                     FileName(owner_->filePath())))
309                 return false;
310         // We ignore changes and just reload!
311         owner_->markClean();
312         return true;
313 }
314
315
316 bool RCS::isRevertWithConfirmation()
317 {
318         //FIXME owner && diff ?
319         return true;
320 }
321
322
323 void RCS::undoLast()
324 {
325         LYXERR(Debug::LYXVC, "LyXVC: undoLast");
326         doVCCommand("rcs -o" + version_ + " "
327                     + quoteName(onlyFileName(owner_->absFileName())),
328                     FileName(owner_->filePath()));
329 }
330
331
332 bool RCS::undoLastEnabled()
333 {
334         return true;
335 }
336
337
338 void RCS::getLog(FileName const & tmpf)
339 {
340         doVCCommand("rlog " + quoteName(onlyFileName(owner_->absFileName()))
341                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
342                     FileName(owner_->filePath()));
343 }
344
345
346 bool RCS::toggleReadOnlyEnabled()
347 {
348         // This got broken somewhere along lfuns dispatch reorganization.
349         // reloadBuffer would be needed after this, but thats problematic
350         // since we are inside Buffer::dispatch.
351         // return true;
352         return false;
353 }
354
355
356 string RCS::revisionInfo(LyXVC::RevisionInfo const info)
357 {
358         if (info == LyXVC::File)
359                 return version_;
360         // fill the rest of the attributes for a single file
361         if (rev_date_cache_.empty())
362                 if (!getRevisionInfo())
363                         return string();
364
365         switch (info) {
366                 case LyXVC::Author:
367                         return rev_author_cache_;
368                 case LyXVC::Date:
369                         return rev_date_cache_;
370                 case LyXVC::Time:
371                         return rev_time_cache_;
372                 default: ;
373         }
374
375         return string();
376 }
377
378
379 bool RCS::getRevisionInfo()
380 {
381         FileName tmpf = FileName::tempName("lyxvcout");
382         if (tmpf.empty()) {
383                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
384                 return false;
385         }
386         doVCCommand("rlog -r " + quoteName(onlyFileName(owner_->absFileName()))
387                 + " > " + quoteName(tmpf.toFilesystemEncoding()),
388                 FileName(owner_->filePath()));
389
390         if (tmpf.empty())
391                 return false;
392
393         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
394         string line;
395
396         // we reached to the entry, i.e. after initial log message
397         bool entry=false;
398         // line with critical info, e.g:
399         //"date: 2011/07/02 11:02:54;  author: sanda;  state: Exp;  lines: +17 -2"
400         string result;
401
402         while (ifs) {
403                 getline(ifs, line);
404                 LYXERR(Debug::LYXVC, line);
405                 if (entry && prefixIs(line, "date:")) {
406                         result = line;
407                         break;
408                 }
409                 if (prefixIs(line, "revision"))
410                         entry = true;
411         }
412         if (result.empty())
413                 return false;
414
415         rev_date_cache_ = token(result, ' ', 1);
416         rev_time_cache_ = rtrim(token(result, ' ', 2), ";");
417         rev_author_cache_ = trim(token(token(result, ';', 1), ':', 1));
418
419         return !rev_author_cache_.empty();
420 }
421
422 bool RCS::prepareFileRevision(string const &revis, string & f)
423 {
424         string rev = revis;
425         if (!VCS::makeRCSRevision(version_, rev))
426                 return false;
427
428         FileName tmpf = FileName::tempName("lyxvcrev_" + rev + "_");
429         if (tmpf.empty()) {
430                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
431                 return false;
432         }
433
434         doVCCommand("co -p" + rev + " "
435                       + quoteName(onlyFileName(owner_->absFileName()))
436                       + " > " + quoteName(tmpf.toFilesystemEncoding()),
437                 FileName(owner_->filePath()));
438         if (tmpf.isFileEmpty())
439                 return false;
440
441         f = tmpf.absFileName();
442         return true;
443 }
444
445
446 bool RCS::prepareFileRevisionEnabled()
447 {
448         return true;
449 }
450
451
452 /////////////////////////////////////////////////////////////////////
453 //
454 // CVS
455 //
456 /////////////////////////////////////////////////////////////////////
457
458 CVS::CVS(FileName const & m, FileName const & f)
459 {
460         master_ = m;
461         file_ = f;
462         have_rev_info_ = false;
463         scanMaster();
464 }
465
466
467 FileName const CVS::findFile(FileName const & file)
468 {
469         // First we look for the CVS/Entries in the same dir
470         // where we have file.
471         FileName const entries(onlyPath(file.absFileName()) + "/CVS/Entries");
472         string const tmpf = '/' + onlyFileName(file.absFileName()) + '/';
473         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under cvs in `" << entries
474                              << "' for `" << tmpf << '\'');
475         if (entries.isReadableFile()) {
476                 // Ok we are at least in a CVS dir. Parse the CVS/Entries
477                 // and see if we can find this file. We do a fast and
478                 // dirty parse here.
479                 ifstream ifs(entries.toFilesystemEncoding().c_str());
480                 string line;
481                 while (getline(ifs, line)) {
482                         LYXERR(Debug::LYXVC, "\tEntries: " << line);
483                         if (contains(line, tmpf))
484                                 return entries;
485                 }
486         }
487         return FileName();
488 }
489
490
491 void CVS::scanMaster()
492 {
493         LYXERR(Debug::LYXVC, "LyXVC::CVS: scanMaster. \n     Checking: " << master_);
494         // Ok now we do the real scan...
495         ifstream ifs(master_.toFilesystemEncoding().c_str());
496         string name = onlyFileName(file_.absFileName());
497         string tmpf = '/' + name + '/';
498         LYXERR(Debug::LYXVC, "\tlooking for `" << tmpf << '\'');
499         string line;
500         static regex const reg("/(.*)/(.*)/(.*)/(.*)/(.*)");
501         while (getline(ifs, line)) {
502                 LYXERR(Debug::LYXVC, "\t  line: " << line);
503                 if (contains(line, tmpf)) {
504                         // Ok extract the fields.
505                         smatch sm;
506
507                         regex_match(line, sm, reg);
508
509                         //sm[0]; // whole matched string
510                         //sm[1]; // filename
511                         version_ = sm.str(2);
512                         string const file_date = sm.str(3);
513
514                         //sm[4]; // options
515                         //sm[5]; // tag or tagdate
516                         if (file_.isReadableFile()) {
517                                 time_t mod = file_.lastModified();
518                                 string mod_date = rtrim(asctime(gmtime(&mod)), "\n");
519                                 LYXERR(Debug::LYXVC, "Date in Entries: `" << file_date
520                                         << "'\nModification date of file: `" << mod_date << '\'');
521                                 if (file_.isReadOnly()) {
522                                         // readonly checkout is unlocked
523                                         vcstatus = UNLOCKED;
524                                 } else {
525                                         FileName bdir(addPath(master_.onlyPath().absFileName(),"Base"));
526                                         FileName base(addName(bdir.absFileName(),name));
527                                         // if base version is existent "cvs edit" was used to lock
528                                         vcstatus = base.isReadableFile() ? LOCKED : NOLOCKING;
529                                 }
530                         } else {
531                                 vcstatus = NOLOCKING;
532                         }
533                         break;
534                 }
535         }
536 }
537
538
539 string const CVS::getTarget(OperationMode opmode) const
540 {
541         switch(opmode) {
542         case Directory:
543                 // in client server mode CVS does not like full path operand for directory operation
544                 // since LyX switches to the repo dir "." is good enough as target
545                 return ".";
546         case File:
547                 return quoteName(onlyFileName(owner_->absFileName()));
548         }
549         return string();
550 }
551
552
553 docstring CVS::toString(CvsStatus status) const
554 {
555         switch (status) {
556         case UpToDate:
557                 return _("Up-to-date");
558         case LocallyModified:
559                 return _("Locally Modified");
560         case LocallyAdded:
561                 return _("Locally Added");
562         case NeedsMerge:
563                 return _("Needs Merge");
564         case NeedsCheckout:
565                 return _("Needs Checkout");
566         case NoCvsFile:
567                 return _("No CVS file");
568         case StatusError:
569                 return _("Cannot retrieve CVS status");
570         }
571         return 0;
572 }
573
574
575 int CVS::doVCCommandWithOutput(string const & cmd, FileName const & path,
576         FileName const & output, bool reportError)
577 {
578         string redirection = output.empty() ? "" : " > " + quoteName(output.toFilesystemEncoding());
579         return doVCCommand(cmd + redirection, path, reportError);
580 }
581
582
583 int CVS::doVCCommandCallWithOutput(std::string const & cmd,
584         support::FileName const & path,
585         support::FileName const & output)
586 {
587         string redirection = output.empty() ? "" : " > " + quoteName(output.toFilesystemEncoding());
588         return doVCCommandCall(cmd + redirection, path);
589 }
590
591
592 CVS::CvsStatus CVS::getStatus()
593 {
594         FileName tmpf = FileName::tempName("lyxvcout");
595         if (tmpf.empty()) {
596                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
597                 return StatusError;
598         }
599
600         if (doVCCommandCallWithOutput("cvs status " + getTarget(File),
601                 FileName(owner_->filePath()), tmpf)) {
602                 tmpf.removeFile();
603                 return StatusError;
604         }
605
606         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
607         CvsStatus status = NoCvsFile;
608
609         while (ifs) {
610                 string line;
611                 getline(ifs, line);
612                 LYXERR(Debug::LYXVC, line << "\n");
613                 if (prefixIs(line, "File:")) {
614                         if (contains(line, "Up-to-date"))
615                                 status = UpToDate;
616                         else if (contains(line, "Locally Modified"))
617                                 status = LocallyModified;
618                         else if (contains(line, "Locally Added"))
619                                 status = LocallyAdded;
620                         else if (contains(line, "Needs Merge"))
621                                 status = NeedsMerge;
622                         else if (contains(line, "Needs Checkout"))
623                                 status = NeedsCheckout;
624                 }
625         }
626         tmpf.removeFile();
627         return status;
628 }
629
630 void CVS::getRevisionInfo()
631 {
632         if (have_rev_info_)
633                 return;
634         have_rev_info_ = true;
635         FileName tmpf = FileName::tempName("lyxvcout");
636         if (tmpf.empty()) {
637                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
638                 return;
639         }
640         
641         int rc = doVCCommandCallWithOutput("cvs log -r" + version_ 
642                 + " " + getTarget(File),
643                 FileName(owner_->filePath()), tmpf);
644         if (rc) {
645                 tmpf.removeFile();
646                 LYXERR(Debug::LYXVC, "cvs log failed with exit code " << rc);
647                 return;
648         }
649         
650         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
651         static regex const reg("date: (.*) (.*) (.*);  author: (.*);  state: (.*);(.*)");
652
653         while (ifs) {
654                 string line;
655                 getline(ifs, line);
656                 LYXERR(Debug::LYXVC, line << "\n");
657                 if (prefixIs(line, "date:")) {
658                         smatch sm;
659                         regex_match(line, sm, reg);
660                         //sm[0]; // whole matched string
661                         rev_date_cache_ = sm[1];
662                         rev_time_cache_ = sm[2];
663                         //sm[3]; // GMT offset
664                         rev_author_cache_ = sm[4];
665                         break;
666                 }
667         }
668         tmpf.removeFile();
669         if (rev_author_cache_.empty())
670                 LYXERR(Debug::LYXVC,
671                    "Could not retrieve revision info for " << version_ <<
672                    " of " << getTarget(File));
673 }
674
675
676 void CVS::registrer(string const & msg)
677 {
678         doVCCommand("cvs -q add -m \"" + msg + "\" "
679                 + getTarget(File),
680                 FileName(owner_->filePath()));
681 }
682
683
684 void CVS::getDiff(OperationMode opmode, FileName const & tmpf)
685 {
686         doVCCommandWithOutput("cvs diff " + getTarget(opmode),
687                 FileName(owner_->filePath()), tmpf, false);
688 }
689
690
691 int CVS::edit()
692 {
693         vcstatus = LOCKED;
694         return doVCCommand("cvs -q edit " + getTarget(File),
695                 FileName(owner_->filePath()));
696 }
697
698
699 int CVS::unedit()
700 {
701         vcstatus = UNLOCKED;
702         return doVCCommand("cvs -q unedit " + getTarget(File),
703                 FileName(owner_->filePath()));
704 }
705
706
707 int CVS::update(OperationMode opmode, FileName const & tmpf)
708 {
709         return doVCCommandWithOutput("cvs -q update "
710                 + getTarget(opmode),
711                 FileName(owner_->filePath()), tmpf, false);
712 }
713
714
715 string CVS::scanLogFile(FileName const & f, string & status)
716 {
717         ifstream ifs(f.toFilesystemEncoding().c_str());
718
719         while (ifs) {
720                 string line;
721                 getline(ifs, line);
722                 LYXERR(Debug::LYXVC, line << "\n");
723                 if (!line.empty())
724                         status += line + "; ";
725                 if (prefixIs(line, "C ")) {
726                         ifs.close();
727                         return line;
728                 }
729         }
730         ifs.close();
731         return string();
732 }
733         
734         
735 string CVS::checkIn(string const & msg)
736 {
737         CvsStatus status = getStatus();
738         switch (status) {
739         case UpToDate:
740                 if (vcstatus != NOLOCKING)
741                         unedit();
742                 return "CVS: Proceeded";
743         case LocallyModified:
744         case LocallyAdded: {
745                 int rc = doVCCommand("cvs -q commit -m \"" + msg + "\" "
746                         + getTarget(File),
747                     FileName(owner_->filePath()));
748                 return rc ? string() : "CVS: Proceeded";
749         }
750         case NeedsMerge:
751         case NeedsCheckout:
752                 frontend::Alert::error(_("Revision control error."),
753                         _("The repository version is newer then the current check out.\n"
754                           "You have to update from repository first or revert your changes.")) ;
755                 break;
756         default:
757                 frontend::Alert::error(_("Revision control error."),
758                         bformat(_("Bad status when checking in changes.\n"
759                                           "\n'%1$s'\n\n"),
760                                 toString(status)));
761                 break;
762         }
763         return string();
764 }
765
766
767 bool CVS::isLocked() const
768 {
769         FileName fn(owner_->absFileName());
770         fn.refresh();
771         return !fn.isReadOnly();
772 }
773
774
775 bool CVS::checkInEnabled()
776 {
777         if (vcstatus != NOLOCKING)
778                 return isLocked();
779         else
780                 return true;
781 }
782
783
784 bool CVS::isCheckInWithConfirmation()
785 {
786         CvsStatus status = getStatus();
787         return status == LocallyModified || status == LocallyAdded;
788 }
789
790
791 string CVS::checkOut()
792 {
793         if (vcstatus != NOLOCKING && edit())
794                 return string();
795         FileName tmpf = FileName::tempName("lyxvcout");
796         if (tmpf.empty()) {
797                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
798                 return string();
799         }
800         
801         int rc = update(File, tmpf);
802         string log;
803         string const res = scanLogFile(tmpf, log);
804         if (!res.empty()) {
805                 frontend::Alert::error(_("Revision control error."),
806                         bformat(_("Error when updating from repository.\n"
807                                 "You have to manually resolve the conflicts NOW!\n'%1$s'.\n\n"
808                                 "After pressing OK, LyX will try to reopen the resolved document."),
809                                 from_local8bit(res)));
810                 rc = 0;
811         }
812         
813         tmpf.erase();
814         return rc ? string() : log.empty() ? "CVS: Proceeded" : "CVS: " + log;
815 }
816
817
818 bool CVS::checkOutEnabled()
819 {
820         if (vcstatus != NOLOCKING)
821                 return !isLocked();
822         else
823                 return true;
824 }
825
826
827 string CVS::repoUpdate()
828 {
829         FileName tmpf = FileName::tempName("lyxvcout");
830         if (tmpf.empty()) {
831                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
832                 return string();
833         }
834         
835         getDiff(Directory, tmpf);
836         docstring res = tmpf.fileContents("UTF-8");
837         if (!res.empty()) {
838                 LYXERR(Debug::LYXVC, "Diff detected:\n" << res);
839                 docstring const file = from_utf8(owner_->filePath());
840                 docstring text = bformat(_("There were detected changes "
841                                 "in the working directory:\n%1$s\n\n"
842                                 "Possible file conflicts must be then resolved manually "
843                                 "or you will need to revert back to the repository version."), file);
844                 int ret = frontend::Alert::prompt(_("Changes detected"),
845                                 text, 0, 1, _("&Continue"), _("&Abort"), _("View &Log ..."));
846                 if (ret == 2 ) {
847                         dispatch(FuncRequest(LFUN_DIALOG_SHOW, "file " + tmpf.absFileName()));
848                         ret = frontend::Alert::prompt(_("Changes detected"),
849                                 text, 0, 1, _("&Continue"), _("&Abort"));
850                         hideDialogs("file", 0);
851                 }
852                 if (ret == 1 ) {
853                         tmpf.removeFile();
854                         return string();
855                 }
856         }
857
858         int rc = update(Directory, tmpf);
859         res += "Update log:\n" + tmpf.fileContents("UTF-8");
860         LYXERR(Debug::LYXVC, res);
861
862         string log;
863         string sres = scanLogFile(tmpf, log);
864         if (!sres.empty()) {
865                 docstring const file = owner_->fileName().displayName(20);
866                 frontend::Alert::error(_("Revision control error."),
867                         bformat(_("Error when updating document %1$s from repository.\n"
868                                           "You have to manually resolve the conflicts NOW!\n'%2$s'.\n\n"
869                                           "After pressing OK, LyX will try to reopen the resolved document."),
870                                 file, from_local8bit(sres)));
871                 rc = 0;
872         }
873         
874         tmpf.removeFile();
875
876         return rc ? string() : log.empty() ? "CVS: Proceeded" : "CVS: " + log;
877 }
878
879
880 bool CVS::repoUpdateEnabled()
881 {
882         return true;
883 }
884
885
886 string CVS::lockingToggle()
887 {
888         lyxerr << "Sorry, not implemented." << endl;
889         return string();
890 }
891
892
893 bool CVS::lockingToggleEnabled()
894 {
895         return false;
896 }
897
898
899 bool CVS::isRevertWithConfirmation()
900 {
901         CvsStatus status = getStatus();
902         return !owner_->isClean() || status == LocallyModified || status == NeedsMerge;
903 }
904
905
906 bool CVS::revert()
907 {
908         // Reverts to the version in CVS repository and
909         // gets the updated version from the repository.
910         CvsStatus status = getStatus();
911         switch (status) {
912         case UpToDate:
913                 if (vcstatus != NOLOCKING)
914                         return 0 == unedit();
915                 break;
916         case NeedsMerge:
917         case NeedsCheckout:
918         case LocallyModified: {
919                 FileName f(owner_->absFileName());
920                 f.removeFile();
921                 update(File, FileName());
922                 owner_->markClean();
923                 break;
924         }
925         case LocallyAdded: {
926                 docstring const file = owner_->fileName().displayName(20);
927                 frontend::Alert::error(_("Revision control error."),
928                         bformat(_("The document %1$s is not in repository.\n"
929                                   "You have to check in the first revision before you can revert."),
930                                 file)) ;
931                 return false;
932         }
933         default: {
934                 docstring const file = owner_->fileName().displayName(20);
935                 frontend::Alert::error(_("Revision control error."),
936                         bformat(_("Cannot revert document %1$s to repository version.\n"
937                                   "The status '%2$s' is unexpected."),
938                                 file, toString(status)));
939                 return false;
940                 }
941         }
942         return true;
943 }
944
945
946 void CVS::undoLast()
947 {
948         // merge the current with the previous version
949         // in a reverse patch kind of way, so that the
950         // result is to revert the last changes.
951         lyxerr << "Sorry, not implemented." << endl;
952 }
953
954
955 bool CVS::undoLastEnabled()
956 {
957         return false;
958 }
959
960
961 void CVS::getLog(FileName const & tmpf)
962 {
963         doVCCommandWithOutput("cvs log " + getTarget(File),
964                 FileName(owner_->filePath()),
965                 tmpf);
966 }
967
968
969 bool CVS::toggleReadOnlyEnabled()
970 {
971         return false;
972 }
973
974
975 string CVS::revisionInfo(LyXVC::RevisionInfo const info)
976 {
977         if (!version_.empty()) {
978                 getRevisionInfo();
979                 switch (info) {
980                 case LyXVC::File:
981                         return version_;
982                 case LyXVC::Author:
983                         return rev_author_cache_;
984                 case LyXVC::Date:
985                         return rev_date_cache_;
986                 case LyXVC::Time:
987                         return rev_time_cache_;
988                 default: ;
989                 }
990         }
991         return string();
992 }
993
994
995 bool CVS::prepareFileRevision(string const & revis, string & f)
996 {
997         string rev = revis;
998         if (!VCS::makeRCSRevision(version_, rev))
999                 return false;
1000
1001         FileName tmpf = FileName::tempName("lyxvcrev_" + rev + "_");
1002         if (tmpf.empty()) {
1003                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1004                 return false;
1005         }
1006
1007         doVCCommandWithOutput("cvs update -p -r" + rev + " "
1008                 + getTarget(File),
1009                 FileName(owner_->filePath()), tmpf);
1010         if (tmpf.isFileEmpty())
1011                 return false;
1012
1013         f = tmpf.absFileName();
1014         return true;
1015 }
1016
1017
1018 bool CVS::prepareFileRevisionEnabled()
1019 {
1020         return true;
1021 }
1022
1023
1024 /////////////////////////////////////////////////////////////////////
1025 //
1026 // SVN
1027 //
1028 /////////////////////////////////////////////////////////////////////
1029
1030 SVN::SVN(FileName const & m, FileName const & f)
1031 {
1032         owner_ = 0;
1033         master_ = m;
1034         file_ = f;
1035         locked_mode_ = 0;
1036         scanMaster();
1037 }
1038
1039
1040 FileName const SVN::findFile(FileName const & file)
1041 {
1042         // First we look for the .svn/entries in the same dir
1043         // where we have file.
1044         FileName const entries(onlyPath(file.absFileName()) + "/.svn/entries");
1045         string const tmpf = onlyFileName(file.absFileName());
1046         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under svn in `" << entries
1047                              << "' for `" << tmpf << '\'');
1048         if (entries.isReadableFile()) {
1049                 // Ok we are at least in a SVN dir. Parse the .svn/entries
1050                 // and see if we can find this file. We do a fast and
1051                 // dirty parse here.
1052                 ifstream ifs(entries.toFilesystemEncoding().c_str());
1053                 string line, oldline;
1054                 while (getline(ifs, line)) {
1055                         if (line == "dir" || line == "file")
1056                                 LYXERR(Debug::LYXVC, "\tEntries: " << oldline);
1057                         if (oldline == tmpf && line == "file")
1058                                 return entries;
1059                         oldline = line;
1060                 }
1061         }
1062         return FileName();
1063 }
1064
1065
1066 void SVN::scanMaster()
1067 {
1068         // vcstatus code is somewhat superflous, until we want
1069         // to implement read-only toggle for svn.
1070         vcstatus = NOLOCKING;
1071         if (checkLockMode()) {
1072                 if (isLocked()) {
1073                         vcstatus = LOCKED;
1074                 } else {
1075                         vcstatus = UNLOCKED;
1076                 }
1077         }
1078 }
1079
1080
1081 bool SVN::checkLockMode()
1082 {
1083         FileName tmpf = FileName::tempName("lyxvcout");
1084         if (tmpf.empty()){
1085                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1086                 return false;
1087         }
1088
1089         LYXERR(Debug::LYXVC, "Detecting locking mode...");
1090         if (doVCCommandCall("svn proplist " + quoteName(file_.onlyFileName())
1091                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1092                     file_.onlyPath()))
1093                 return false;
1094
1095         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1096         string line;
1097         bool ret = false;
1098
1099         while (ifs) {
1100                 getline(ifs, line);
1101                 LYXERR(Debug::LYXVC, line);
1102                 if (contains(line, "svn:needs-lock"))
1103                         ret = true;
1104         }
1105         LYXERR(Debug::LYXVC, "Locking enabled: " << ret);
1106         ifs.close();
1107         locked_mode_ = ret;
1108         return ret;
1109
1110 }
1111
1112
1113 bool SVN::isLocked() const
1114 {
1115         file_.refresh();
1116         return !file_.isReadOnly();
1117 }
1118
1119
1120 void SVN::registrer(string const & /*msg*/)
1121 {
1122         doVCCommand("svn add -q " + quoteName(onlyFileName(owner_->absFileName())),
1123                     FileName(owner_->filePath()));
1124 }
1125
1126
1127 string SVN::checkIn(string const & msg)
1128 {
1129         FileName tmpf = FileName::tempName("lyxvcout");
1130         if (tmpf.empty()){
1131                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1132                 return N_("Error: Could not generate logfile.");
1133         }
1134
1135         doVCCommand("svn commit -m \"" + msg + "\" "
1136                     + quoteName(onlyFileName(owner_->absFileName()))
1137                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1138                     FileName(owner_->filePath()));
1139
1140         string log;
1141         string res = scanLogFile(tmpf, log);
1142         if (!res.empty())
1143                 frontend::Alert::error(_("Revision control error."),
1144                                 _("Error when committing to repository.\n"
1145                                 "You have to manually resolve the problem.\n"
1146                                 "LyX will reopen the document after you press OK."));
1147         else
1148                 fileLock(false, tmpf, log);
1149
1150         tmpf.erase();
1151         return log.empty() ? string() : "SVN: " + log;
1152 }
1153
1154
1155 bool SVN::checkInEnabled()
1156 {
1157         if (locked_mode_)
1158                 return isLocked();
1159         else
1160                 return true;
1161 }
1162
1163
1164 bool SVN::isCheckInWithConfirmation()
1165 {
1166         // FIXME one day common getDiff and perhaps OpMode for all backends
1167
1168         FileName tmpf = FileName::tempName("lyxvcout");
1169         if (tmpf.empty()) {
1170                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1171                 return true;
1172         }
1173
1174         doVCCommandCall("svn diff " + quoteName(owner_->absFileName())
1175                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1176                 FileName(owner_->filePath()));
1177
1178         docstring diff = tmpf.fileContents("UTF-8");
1179         tmpf.erase();
1180
1181         if (diff.empty())
1182                 return false;
1183
1184         return true;
1185 }
1186
1187
1188 // FIXME Correctly return code should be checked instead of this.
1189 // This would need another solution than just plain startscript.
1190 // Hint from Andre': QProcess::readAllStandardError()...
1191 string SVN::scanLogFile(FileName const & f, string & status)
1192 {
1193         ifstream ifs(f.toFilesystemEncoding().c_str());
1194         string line;
1195
1196         while (ifs) {
1197                 getline(ifs, line);
1198                 LYXERR(Debug::LYXVC, line << "\n");
1199                 if (!line.empty()) 
1200                         status += line + "; ";
1201                 if (prefixIs(line, "C ") || prefixIs(line, "CU ")
1202                                          || contains(line, "Commit failed")) {
1203                         ifs.close();
1204                         return line;
1205                 }
1206                 if (contains(line, "svn:needs-lock")) {
1207                         ifs.close();
1208                         return line;
1209                 }
1210         }
1211         ifs.close();
1212         return string();
1213 }
1214
1215
1216 void SVN::fileLock(bool lock, FileName const & tmpf, string &status)
1217 {
1218         if (!locked_mode_ || (isLocked() == lock))
1219                 return;
1220
1221         string const arg = lock ? "lock " : "unlock ";
1222         doVCCommand("svn "+ arg + quoteName(onlyFileName(owner_->absFileName()))
1223                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1224                     FileName(owner_->filePath()));
1225
1226         // Lock error messages go unfortunately on stderr and are unreachible this way.
1227         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1228         string line;
1229         while (ifs) {
1230                 getline(ifs, line);
1231                 if (!line.empty()) status += line + "; ";
1232         }
1233         ifs.close();
1234
1235         if (!isLocked() && lock)
1236                 frontend::Alert::error(_("Revision control error."),
1237                         _("Error while acquiring write lock.\n"
1238                         "Another user is most probably editing\n"
1239                         "the current document now!\n"
1240                         "Also check the access to the repository."));
1241         if (isLocked() && !lock)
1242                 frontend::Alert::error(_("Revision control error."),
1243                         _("Error while releasing write lock.\n"
1244                         "Check the access to the repository."));
1245 }
1246
1247
1248 string SVN::checkOut()
1249 {
1250         FileName tmpf = FileName::tempName("lyxvcout");
1251         if (tmpf.empty()) {
1252                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1253                 return N_("Error: Could not generate logfile.");
1254         }
1255
1256         doVCCommand("svn update --non-interactive " + quoteName(onlyFileName(owner_->absFileName()))
1257                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1258                     FileName(owner_->filePath()));
1259
1260         string log;
1261         string const res = scanLogFile(tmpf, log);
1262         if (!res.empty())
1263                 frontend::Alert::error(_("Revision control error."),
1264                         bformat(_("Error when updating from repository.\n"
1265                                 "You have to manually resolve the conflicts NOW!\n'%1$s'.\n\n"
1266                                 "After pressing OK, LyX will try to reopen the resolved document."),
1267                         from_local8bit(res)));
1268
1269         fileLock(true, tmpf, log);
1270
1271         tmpf.erase();
1272         return log.empty() ? string() : "SVN: " + log;
1273 }
1274
1275
1276 bool SVN::checkOutEnabled()
1277 {
1278         if (locked_mode_)
1279                 return !isLocked();
1280         else
1281                 return true;
1282 }
1283
1284
1285 string SVN::repoUpdate()
1286 {
1287         FileName tmpf = FileName::tempName("lyxvcout");
1288         if (tmpf.empty()) {
1289                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1290                 return N_("Error: Could not generate logfile.");
1291         }
1292
1293         doVCCommand("svn diff " + quoteName(owner_->filePath())
1294                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1295                 FileName(owner_->filePath()));
1296         docstring res = tmpf.fileContents("UTF-8");
1297         if (!res.empty()) {
1298                 LYXERR(Debug::LYXVC, "Diff detected:\n" << res);
1299                 docstring const file = from_utf8(owner_->filePath());
1300                 docstring text = bformat(_("There were detected changes "
1301                                 "in the working directory:\n%1$s\n\n"
1302                                 "In case of file conflict version of the local directory files "
1303                                 "will be preferred."
1304                                 "\n\nContinue?"), file);
1305                 int ret = frontend::Alert::prompt(_("Changes detected"),
1306                                 text, 0, 1, _("&Yes"), _("&No"), _("View &Log ..."));
1307                 if (ret == 2 ) {
1308                         dispatch(FuncRequest(LFUN_DIALOG_SHOW, "file " + tmpf.absFileName()));
1309                         ret = frontend::Alert::prompt(_("Changes detected"),
1310                                 text, 0, 1, _("&Yes"), _("&No"));
1311                         hideDialogs("file", 0);
1312                 }
1313                 if (ret == 1 ) {
1314                         tmpf.erase();
1315                         return string();
1316                 }
1317         }
1318
1319         // Reverting looks too harsh, see bug #6255.
1320         // doVCCommand("svn revert -R " + quoteName(owner_->filePath())
1321         // + " > " + quoteName(tmpf.toFilesystemEncoding()),
1322         // FileName(owner_->filePath()));
1323         // res = "Revert log:\n" + tmpf.fileContents("UTF-8");
1324         doVCCommand("svn update --accept mine-full " + quoteName(owner_->filePath())
1325                 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1326                 FileName(owner_->filePath()));
1327         res += "Update log:\n" + tmpf.fileContents("UTF-8");
1328
1329         LYXERR(Debug::LYXVC, res);
1330         tmpf.erase();
1331         return to_utf8(res);
1332 }
1333
1334
1335 bool SVN::repoUpdateEnabled()
1336 {
1337         return true;
1338 }
1339
1340
1341 string SVN::lockingToggle()
1342 {
1343         FileName tmpf = FileName::tempName("lyxvcout");
1344         if (tmpf.empty()) {
1345                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1346                 return N_("Error: Could not generate logfile.");
1347         }
1348
1349         int ret = doVCCommand("svn proplist " + quoteName(onlyFileName(owner_->absFileName()))
1350                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1351                     FileName(owner_->filePath()));
1352         if (ret)
1353                 return string();
1354
1355         string log;
1356         string res = scanLogFile(tmpf, log);
1357         bool locking = contains(res, "svn:needs-lock");
1358         if (!locking)
1359                 ret = doVCCommand("svn propset svn:needs-lock ON "
1360                     + quoteName(onlyFileName(owner_->absFileName()))
1361                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1362                     FileName(owner_->filePath()));
1363         else
1364                 ret = doVCCommand("svn propdel svn:needs-lock "
1365                     + quoteName(onlyFileName(owner_->absFileName()))
1366                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1367                     FileName(owner_->filePath()));
1368         if (ret)
1369                 return string();
1370
1371         tmpf.erase();
1372         frontend::Alert::warning(_("VCN File Locking"),
1373                 (locking ? _("Locking property unset.") : _("Locking property set.")) + "\n"
1374                 + _("Do not forget to commit the locking property into the repository."),
1375                 true);
1376
1377         return string("SVN: ") +  N_("Locking property set.");
1378 }
1379
1380
1381 bool SVN::lockingToggleEnabled()
1382 {
1383         return true;
1384 }
1385
1386
1387 bool SVN::revert()
1388 {
1389         // Reverts to the version in SVN repository and
1390         // gets the updated version from the repository.
1391         string const fil = quoteName(onlyFileName(owner_->absFileName()));
1392
1393         if (doVCCommand("svn revert -q " + fil,
1394                     FileName(owner_->filePath())))
1395                 return false;
1396         owner_->markClean();
1397         return true;
1398 }
1399
1400
1401 bool SVN::isRevertWithConfirmation()
1402 {
1403         //FIXME owner && diff
1404         return true;
1405 }
1406
1407
1408 void SVN::undoLast()
1409 {
1410         // merge the current with the previous version
1411         // in a reverse patch kind of way, so that the
1412         // result is to revert the last changes.
1413         lyxerr << "Sorry, not implemented." << endl;
1414 }
1415
1416
1417 bool SVN::undoLastEnabled()
1418 {
1419         return false;
1420 }
1421
1422
1423 string SVN::revisionInfo(LyXVC::RevisionInfo const info)
1424 {
1425         if (info == LyXVC::Tree) {
1426                         if (rev_tree_cache_.empty())
1427                                 if (!getTreeRevisionInfo())
1428                                         rev_tree_cache_ = "?";
1429                         if (rev_tree_cache_ == "?")
1430                                 return string();
1431
1432                         return rev_tree_cache_;
1433         }
1434
1435         // fill the rest of the attributes for a single file
1436         if (rev_file_cache_.empty())
1437                 if (!getFileRevisionInfo())
1438                         rev_file_cache_ = "?";
1439
1440         switch (info) {
1441                 case LyXVC::File:
1442                         if (rev_file_cache_ == "?")
1443                                 return string();
1444                         return rev_file_cache_;
1445                 case LyXVC::Author:
1446                         return rev_author_cache_;
1447                 case LyXVC::Date:
1448                         return rev_date_cache_;
1449                 case LyXVC::Time:
1450                         return rev_time_cache_;
1451                 default: ;
1452
1453         }
1454
1455         return string();
1456 }
1457
1458
1459 bool SVN::getFileRevisionInfo()
1460 {
1461         FileName tmpf = FileName::tempName("lyxvcout");
1462         if (tmpf.empty()) {
1463                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1464                 return false;
1465         }
1466
1467         doVCCommand("svn info --xml " + quoteName(onlyFileName(owner_->absFileName()))
1468                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1469                     FileName(owner_->filePath()));
1470
1471         if (tmpf.empty())
1472                 return false;
1473
1474         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1475         string line;
1476         // commit log part
1477         bool c = false;
1478         string rev;
1479
1480         while (ifs) {
1481                 getline(ifs, line);
1482                 LYXERR(Debug::LYXVC, line);
1483                 if (prefixIs(line, "<commit"))
1484                         c = true;
1485                 if (c && prefixIs(line, "   revision=\"") && suffixIs(line, "\">")) {
1486                         string l1 = subst(line, "revision=\"", "");
1487                         string l2 = trim(subst(l1, "\">", ""));
1488                         if (isStrInt(l2))
1489                                 rev_file_cache_ = rev = l2;
1490                 }
1491                 if (c && prefixIs(line, "<author>") && suffixIs(line, "</author>")) {
1492                         string l1 = subst(line, "<author>", "");
1493                         string l2 = subst(l1, "</author>", "");
1494                         rev_author_cache_ = l2;
1495                 }
1496                 if (c && prefixIs(line, "<date>") && suffixIs(line, "</date>")) {
1497                         string l1 = subst(line, "<date>", "");
1498                         string l2 = subst(l1, "</date>", "");
1499                         l2 = split(l2, l1, 'T');
1500                         rev_date_cache_ = l1;
1501                         l2 = split(l2, l1, '.');
1502                         rev_time_cache_ = l1;
1503                 }
1504         }
1505
1506         ifs.close();
1507         tmpf.erase();
1508         return !rev.empty();
1509 }
1510
1511
1512 bool SVN::getTreeRevisionInfo()
1513 {
1514         FileName tmpf = FileName::tempName("lyxvcout");
1515         if (tmpf.empty()) {
1516                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1517                 return false;
1518         }
1519
1520         doVCCommand("svnversion -n . > " + quoteName(tmpf.toFilesystemEncoding()),
1521                     FileName(owner_->filePath()));
1522
1523         if (tmpf.empty())
1524                 return false;
1525
1526         // only first line in case something bad happens.
1527         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1528         string line;
1529         getline(ifs, line);
1530         ifs.close();
1531         tmpf.erase();
1532
1533         rev_tree_cache_ = line;
1534         return !line.empty();
1535 }
1536
1537
1538 void SVN::getLog(FileName const & tmpf)
1539 {
1540         doVCCommand("svn log " + quoteName(onlyFileName(owner_->absFileName()))
1541                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1542                     FileName(owner_->filePath()));
1543 }
1544
1545
1546 bool SVN::prepareFileRevision(string const & revis, string & f)
1547 {
1548         if (!isStrInt(revis))
1549                 return false;
1550
1551         int rev = convert<int>(revis);
1552         if (rev <= 0)
1553                 if (!getFileRevisionInfo())
1554                         return false;
1555         if (rev == 0)
1556                 rev = convert<int>(rev_file_cache_);
1557         // go back for minus rev
1558         else if (rev < 0) {
1559                 rev = rev + convert<int>(rev_file_cache_);
1560                 if (rev < 1)
1561                         return false;
1562         }
1563
1564         string revname = convert<string>(rev);
1565         FileName tmpf = FileName::tempName("lyxvcrev_" + revname + "_");
1566         if (tmpf.empty()) {
1567                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1568                 return false;
1569         }
1570
1571         doVCCommand("svn cat -r " + revname + " "
1572                       + quoteName(onlyFileName(owner_->absFileName()))
1573                       + " > " + quoteName(tmpf.toFilesystemEncoding()),
1574                 FileName(owner_->filePath()));
1575         if (tmpf.isFileEmpty())
1576                 return false;
1577
1578         f = tmpf.absFileName();
1579         return true;
1580 }
1581
1582
1583 bool SVN::prepareFileRevisionEnabled()
1584 {
1585         return true;
1586 }
1587
1588
1589
1590 bool SVN::toggleReadOnlyEnabled()
1591 {
1592         return false;
1593 }
1594
1595
1596 } // namespace lyx