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