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