]> git.lyx.org Git - lyx.git/blob - src/VCBackend.cpp
Workaround for #6865: smarter FontList::setMisspelled implementation
[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, false);
47 }
48
49
50 int VCS::doVCCommand(string const & cmd, FileName const & path)
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)
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 /////////////////////////////////////////////////////////////////////
69 //
70 // RCS
71 //
72 /////////////////////////////////////////////////////////////////////
73
74 RCS::RCS(FileName const & m)
75 {
76         master_ = m;
77         scanMaster();
78 }
79
80
81 FileName const RCS::findFile(FileName const & file)
82 {
83         // Check if *,v exists.
84         FileName tmp(file.absFileName() + ",v");
85         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp);
86         if (tmp.isReadableFile()) {
87                 LYXERR(Debug::LYXVC, "Yes, " << file << " is under rcs.");
88                 return tmp;
89         }
90
91         // Check if RCS/*,v exists.
92         tmp = FileName(addName(addPath(onlyPath(file.absFileName()), "RCS"), file.absFileName()) + ",v");
93         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp);
94         if (tmp.isReadableFile()) {
95                 LYXERR(Debug::LYXVC, "Yes, " << file << " is under rcs.");
96                 return tmp;
97         }
98
99         return FileName();
100 }
101
102
103 void RCS::retrieve(FileName const & file)
104 {
105         LYXERR(Debug::LYXVC, "LyXVC::RCS: retrieve.\n\t" << file);
106         doVCCommandCall("co -q -r " + quoteName(file.toFilesystemEncoding()),
107                          FileName());
108 }
109
110
111 void RCS::scanMaster()
112 {
113         if (master_.empty())
114                 return;
115
116         LYXERR(Debug::LYXVC, "LyXVC::RCS: scanMaster: " << master_);
117
118         ifstream ifs(master_.toFilesystemEncoding().c_str());
119
120         string token;
121         bool read_enough = false;
122
123         while (!read_enough && ifs >> token) {
124                 LYXERR(Debug::LYXVC, "LyXVC::scanMaster: current lex text: `"
125                         << token << '\'');
126
127                 if (token.empty())
128                         continue;
129                 else if (token == "head") {
130                         // get version here
131                         string tmv;
132                         ifs >> tmv;
133                         tmv = rtrim(tmv, ";");
134                         version_ = tmv;
135                         LYXERR(Debug::LYXVC, "LyXVC: version found to be " << tmv);
136                 } else if (contains(token, "access")
137                            || contains(token, "symbols")
138                            || contains(token, "strict")) {
139                         // nothing
140                 } else if (contains(token, "locks")) {
141                         // get locker here
142                         if (contains(token, ';')) {
143                                 locker_ = "Unlocked";
144                                 vcstatus = UNLOCKED;
145                                 continue;
146                         }
147                         string tmpt;
148                         string s1;
149                         string s2;
150                         do {
151                                 ifs >> tmpt;
152                                 s1 = rtrim(tmpt, ";");
153                                 // tmp is now in the format <user>:<version>
154                                 s1 = split(s1, s2, ':');
155                                 // s2 is user, and s1 is version
156                                 if (s1 == version_) {
157                                         locker_ = s2;
158                                         vcstatus = LOCKED;
159                                         break;
160                                 }
161                         } while (!contains(tmpt, ';'));
162
163                 } else if (token == "comment") {
164                         // we don't need to read any further than this.
165                         read_enough = true;
166                 } else {
167                         // unexpected
168                         LYXERR(Debug::LYXVC, "LyXVC::scanMaster(): unexpected token");
169                 }
170         }
171 }
172
173
174 void RCS::registrer(string const & msg)
175 {
176         string cmd = "ci -q -u -i -t-\"";
177         cmd += msg;
178         cmd += "\" ";
179         cmd += quoteName(onlyFileName(owner_->absFileName()));
180         doVCCommand(cmd, FileName(owner_->filePath()));
181 }
182
183
184 string RCS::checkIn(string const & msg)
185 {
186         int ret = doVCCommand("ci -q -u -m\"" + msg + "\" "
187                     + quoteName(onlyFileName(owner_->absFileName())),
188                     FileName(owner_->filePath()));
189         return ret ? string() : "RCS: Proceeded";
190 }
191
192
193 bool RCS::checkInEnabled()
194 {
195         return owner_ && !owner_->isReadonly();
196 }
197
198
199 string RCS::checkOut()
200 {
201         owner_->markClean();
202         int ret = doVCCommand("co -q -l " + quoteName(onlyFileName(owner_->absFileName())),
203                     FileName(owner_->filePath()));
204         return ret ? string() : "RCS: Proceeded";
205 }
206
207
208 bool RCS::checkOutEnabled()
209 {
210         return owner_ && owner_->isReadonly();
211 }
212
213
214 string RCS::repoUpdate()
215 {
216         lyxerr << "Sorry, not implemented." << endl;
217         return string();
218 }
219
220
221 bool RCS::repoUpdateEnabled()
222 {
223         return false;
224 }
225
226
227 string RCS::lockingToggle()
228 {
229         lyxerr << "Sorry, not implemented." << endl;
230         return string();
231 }
232
233
234 bool RCS::lockingToggleEnabled()
235 {
236         return false;
237 }
238
239
240 void RCS::revert()
241 {
242         doVCCommand("co -f -u" + version_ + " "
243                     + quoteName(onlyFileName(owner_->absFileName())),
244                     FileName(owner_->filePath()));
245         // We ignore changes and just reload!
246         owner_->markClean();
247 }
248
249
250 void RCS::undoLast()
251 {
252         LYXERR(Debug::LYXVC, "LyXVC: undoLast");
253         doVCCommand("rcs -o" + version_ + " "
254                     + quoteName(onlyFileName(owner_->absFileName())),
255                     FileName(owner_->filePath()));
256 }
257
258
259 bool RCS::undoLastEnabled()
260 {
261         return true;
262 }
263
264
265 void RCS::getLog(FileName const & tmpf)
266 {
267         doVCCommand("rlog " + quoteName(onlyFileName(owner_->absFileName()))
268                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
269                     FileName(owner_->filePath()));
270 }
271
272
273 bool RCS::toggleReadOnlyEnabled()
274 {
275         // This got broken somewhere along lfuns dispatch reorganization.
276         // reloadBuffer would be needed after this, but thats problematic
277         // since we are inside Buffer::dispatch.
278         // return true;
279         return false;
280 }
281
282 string RCS::revisionInfo(LyXVC::RevisionInfo const info)
283 {
284         if (info == LyXVC::File)
285                 return version_;
286         return string();
287 }
288
289
290 bool RCS::prepareFileRevision(string const &revis, string & f)
291 {
292         string rev = revis;
293
294         if (isStrInt(rev)) {
295                 int back = convert<int>(rev);
296                 if (back > 0)
297                         return false;
298                 if (back == 0)
299                         rev = version_;
300                 // we care about the last number from revision string
301                 // in case of backward indexing
302                 if (back < 0) {
303                         string cur, base;
304                         cur = rsplit(version_, base , '.' );
305                         if (!isStrInt(cur))
306                                 return false;
307                         int want = convert<int>(cur) + back;
308                         if (want <= 0)
309                                 return false;
310
311                         rev = base + "." + convert<string>(want);
312                 }
313         }
314
315         FileName tmpf = FileName::tempName("lyxvcrev");
316         if (tmpf.empty()) {
317                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
318                 return N_("Error: Could not generate logfile.");
319         }
320
321         doVCCommand("co -p" + rev + " "
322                       + quoteName(onlyFileName(owner_->absFileName()))
323                       + " > " + quoteName(tmpf.toFilesystemEncoding()),
324                 FileName(owner_->filePath()));
325         if (tmpf.isFileEmpty())
326                 return false;
327
328         f = tmpf.absFileName();
329         return true;
330 }
331
332
333 bool RCS::prepareFileRevisionEnabled()
334 {
335         return true;
336 }
337
338
339 /////////////////////////////////////////////////////////////////////
340 //
341 // CVS
342 //
343 /////////////////////////////////////////////////////////////////////
344
345 CVS::CVS(FileName const & m, FileName const & f)
346 {
347         master_ = m;
348         file_ = f;
349         scanMaster();
350 }
351
352
353 FileName const CVS::findFile(FileName const & file)
354 {
355         // First we look for the CVS/Entries in the same dir
356         // where we have file.
357         FileName const entries(onlyPath(file.absFileName()) + "/CVS/Entries");
358         string const tmpf = '/' + onlyFileName(file.absFileName()) + '/';
359         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under cvs in `" << entries
360                              << "' for `" << tmpf << '\'');
361         if (entries.isReadableFile()) {
362                 // Ok we are at least in a CVS dir. Parse the CVS/Entries
363                 // and see if we can find this file. We do a fast and
364                 // dirty parse here.
365                 ifstream ifs(entries.toFilesystemEncoding().c_str());
366                 string line;
367                 while (getline(ifs, line)) {
368                         LYXERR(Debug::LYXVC, "\tEntries: " << line);
369                         if (contains(line, tmpf))
370                                 return entries;
371                 }
372         }
373         return FileName();
374 }
375
376
377 void CVS::scanMaster()
378 {
379         LYXERR(Debug::LYXVC, "LyXVC::CVS: scanMaster. \n     Checking: " << master_);
380         // Ok now we do the real scan...
381         ifstream ifs(master_.toFilesystemEncoding().c_str());
382         string tmpf = '/' + onlyFileName(file_.absFileName()) + '/';
383         LYXERR(Debug::LYXVC, "\tlooking for `" << tmpf << '\'');
384         string line;
385         static regex const reg("/(.*)/(.*)/(.*)/(.*)/(.*)");
386         while (getline(ifs, line)) {
387                 LYXERR(Debug::LYXVC, "\t  line: " << line);
388                 if (contains(line, tmpf)) {
389                         // Ok extract the fields.
390                         smatch sm;
391
392                         regex_match(line, sm, reg);
393
394                         //sm[0]; // whole matched string
395                         //sm[1]; // filename
396                         version_ = sm.str(2);
397                         string const file_date = sm.str(3);
398
399                         //sm[4]; // options
400                         //sm[5]; // tag or tagdate
401                         // FIXME: must double check file is stattable/existing
402                         time_t mod = file_.lastModified();
403                         string mod_date = rtrim(asctime(gmtime(&mod)), "\n");
404                         LYXERR(Debug::LYXVC, "Date in Entries: `" << file_date
405                                 << "'\nModification date of file: `" << mod_date << '\'');
406                         //FIXME this whole locking bussiness is not working under cvs and the machinery
407                         // conforms to the ci usage, not cvs.
408                         if (file_date == mod_date) {
409                                 locker_ = "Unlocked";
410                                 vcstatus = UNLOCKED;
411                         } else {
412                                 // Here we should also do some more checking
413                                 // to see if there are conflicts or not.
414                                 locker_ = "Locked";
415                                 vcstatus = LOCKED;
416                         }
417                         break;
418                 }
419         }
420 }
421
422
423 void CVS::registrer(string const & msg)
424 {
425         doVCCommand("cvs -q add -m \"" + msg + "\" "
426                     + quoteName(onlyFileName(owner_->absFileName())),
427                     FileName(owner_->filePath()));
428 }
429
430
431 string CVS::checkIn(string const & msg)
432 {
433         int ret = doVCCommand("cvs -q commit -m \"" + msg + "\" "
434                     + quoteName(onlyFileName(owner_->absFileName())),
435                     FileName(owner_->filePath()));
436         return ret ? string() : "CVS: Proceeded";
437 }
438
439
440 bool CVS::checkInEnabled()
441 {
442         return true;
443 }
444
445
446 string CVS::checkOut()
447 {
448         // cvs update or perhaps for cvs this should be a noop
449         // we need to detect conflict (eg "C" in output)
450         // before we can do this.
451         lyxerr << "Sorry, not implemented." << endl;
452         return string();
453 }
454
455
456 bool CVS::checkOutEnabled()
457 {
458         return false;
459 }
460
461
462 string CVS::repoUpdate()
463 {
464         lyxerr << "Sorry, not implemented." << endl;
465         return string();
466 }
467
468
469 bool CVS::repoUpdateEnabled()
470 {
471         return false;
472 }
473
474
475 string CVS::lockingToggle()
476 {
477         lyxerr << "Sorry, not implemented." << endl;
478         return string();
479 }
480
481
482 bool CVS::lockingToggleEnabled()
483 {
484         return false;
485 }
486
487
488 void CVS::revert()
489 {
490         // Reverts to the version in CVS repository and
491         // gets the updated version from the repository.
492         string const fil = quoteName(onlyFileName(owner_->absFileName()));
493         // This is sensitive operation, so at lest some check about
494         // existence of cvs program and its file
495         if (doVCCommand("cvs log "+ fil, FileName(owner_->filePath())))
496                 return;
497         FileName f(owner_->absFileName());
498         f.removeFile();
499         doVCCommand("cvs update " + fil,
500                     FileName(owner_->filePath()));
501         owner_->markClean();
502 }
503
504
505 void CVS::undoLast()
506 {
507         // merge the current with the previous version
508         // in a reverse patch kind of way, so that the
509         // result is to revert the last changes.
510         lyxerr << "Sorry, not implemented." << endl;
511 }
512
513
514 bool CVS::undoLastEnabled()
515 {
516         return false;
517 }
518
519
520 void CVS::getLog(FileName const & tmpf)
521 {
522         doVCCommand("cvs log " + quoteName(onlyFileName(owner_->absFileName()))
523                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
524                     FileName(owner_->filePath()));
525 }
526
527
528 bool CVS::toggleReadOnlyEnabled()
529 {
530         return false;
531 }
532
533
534 string CVS::revisionInfo(LyXVC::RevisionInfo const info)
535 {
536         if (info == LyXVC::File)
537                 return version_;
538         return string();
539 }
540
541
542 bool CVS::prepareFileRevision(string const &, string &)
543 {
544         return false;
545 }
546
547
548 bool CVS::prepareFileRevisionEnabled()
549 {
550         return false;
551 }
552
553
554 /////////////////////////////////////////////////////////////////////
555 //
556 // SVN
557 //
558 /////////////////////////////////////////////////////////////////////
559
560 SVN::SVN(FileName const & m, FileName const & f)
561 {
562         owner_ = 0;
563         master_ = m;
564         file_ = f;
565         locked_mode_ = 0;
566         scanMaster();
567 }
568
569
570 FileName const SVN::findFile(FileName const & file)
571 {
572         // First we look for the .svn/entries in the same dir
573         // where we have file.
574         FileName const entries(onlyPath(file.absFileName()) + "/.svn/entries");
575         string const tmpf = onlyFileName(file.absFileName());
576         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under svn in `" << entries
577                              << "' for `" << tmpf << '\'');
578         if (entries.isReadableFile()) {
579                 // Ok we are at least in a SVN dir. Parse the .svn/entries
580                 // and see if we can find this file. We do a fast and
581                 // dirty parse here.
582                 ifstream ifs(entries.toFilesystemEncoding().c_str());
583                 string line, oldline;
584                 while (getline(ifs, line)) {
585                         if (line == "dir" || line == "file")
586                                 LYXERR(Debug::LYXVC, "\tEntries: " << oldline);
587                         if (oldline == tmpf && line == "file")
588                                 return entries;
589                         oldline = line;
590                 }
591         }
592         return FileName();
593 }
594
595
596 void SVN::scanMaster()
597 {
598         // vcstatus code is somewhat superflous, until we want
599         // to implement read-only toggle for svn.
600         vcstatus = NOLOCKING;
601         if (checkLockMode()) {
602                 if (isLocked()) {
603                         vcstatus = LOCKED;
604                 } else {
605                         vcstatus = UNLOCKED;
606                 }
607         }
608 }
609
610
611 bool SVN::checkLockMode()
612 {
613         FileName tmpf = FileName::tempName("lyxvcout");
614         if (tmpf.empty()){
615                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
616                 return N_("Error: Could not generate logfile.");
617         }
618
619         LYXERR(Debug::LYXVC, "Detecting locking mode...");
620         if (doVCCommandCall("svn proplist " + quoteName(file_.onlyFileName())
621                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
622                     file_.onlyPath()))
623                 return false;
624
625         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
626         string line;
627         bool ret = false;
628
629         while (ifs) {
630                 getline(ifs, line);
631                 LYXERR(Debug::LYXVC, line);
632                 if (contains(line, "svn:needs-lock"))
633                         ret = true;
634         }
635         LYXERR(Debug::LYXVC, "Locking enabled: " << ret);
636         ifs.close();
637         locked_mode_ = ret;
638         return ret;
639
640 }
641
642
643 bool SVN::isLocked() const
644 {
645         file_.refresh();
646         return !file_.isReadOnly();
647 }
648
649
650 void SVN::registrer(string const & /*msg*/)
651 {
652         doVCCommand("svn add -q " + quoteName(onlyFileName(owner_->absFileName())),
653                     FileName(owner_->filePath()));
654 }
655
656
657 string SVN::checkIn(string const & msg)
658 {
659         FileName tmpf = FileName::tempName("lyxvcout");
660         if (tmpf.empty()){
661                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
662                 return N_("Error: Could not generate logfile.");
663         }
664
665         doVCCommand("svn commit -m \"" + msg + "\" "
666                     + quoteName(onlyFileName(owner_->absFileName()))
667                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
668                     FileName(owner_->filePath()));
669
670         string log;
671         string res = scanLogFile(tmpf, log);
672         if (!res.empty())
673                 frontend::Alert::error(_("Revision control error."),
674                                 _("Error when committing to repository.\n"
675                                 "You have to manually resolve the problem.\n"
676                                 "LyX will reopen the document after you press OK."));
677         else
678                 fileLock(false, tmpf, log);
679
680         tmpf.erase();
681         return log.empty() ? string() : "SVN: " + log;
682 }
683
684
685 bool SVN::checkInEnabled()
686 {
687         if (locked_mode_)
688                 return isLocked();
689         else
690                 return true;
691 }
692
693
694 // FIXME Correctly return code should be checked instead of this.
695 // This would need another solution than just plain startscript.
696 // Hint from Andre': QProcess::readAllStandardError()...
697 string SVN::scanLogFile(FileName const & f, string & status)
698 {
699         ifstream ifs(f.toFilesystemEncoding().c_str());
700         string line;
701
702         while (ifs) {
703                 getline(ifs, line);
704                 LYXERR(Debug::LYXVC, line << "\n");
705                 if (!line.empty()) 
706                         status += line + "; ";
707                 if (prefixIs(line, "C ") || prefixIs(line, "CU ")
708                                          || contains(line, "Commit failed")) {
709                         ifs.close();
710                         return line;
711                 }
712                 if (contains(line, "svn:needs-lock")) {
713                         ifs.close();
714                         return line;
715                 }
716         }
717         ifs.close();
718         return string();
719 }
720
721
722 void SVN::fileLock(bool lock, FileName const & tmpf, string &status)
723 {
724         if (!locked_mode_ || (isLocked() == lock))
725                 return;
726
727         string const arg = lock ? "lock " : "unlock ";
728         doVCCommand("svn "+ arg + quoteName(onlyFileName(owner_->absFileName()))
729                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
730                     FileName(owner_->filePath()));
731
732         // Lock error messages go unfortunately on stderr and are unreachible this way.
733         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
734         string line;
735         while (ifs) {
736                 getline(ifs, line);
737                 if (!line.empty()) status += line + "; ";
738         }
739         ifs.close();
740
741         if (!isLocked() && lock)
742                 frontend::Alert::error(_("Revision control error."),
743                         _("Error while acquiring write lock.\n"
744                         "Another user is most probably editing\n"
745                         "the current document now!\n"
746                         "Also check the access to the repository."));
747         if (isLocked() && !lock)
748                 frontend::Alert::error(_("Revision control error."),
749                         _("Error while releasing write lock.\n"
750                         "Check the access to the repository."));
751 }
752
753
754 string SVN::checkOut()
755 {
756         FileName tmpf = FileName::tempName("lyxvcout");
757         if (tmpf.empty()) {
758                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
759                 return N_("Error: Could not generate logfile.");
760         }
761
762         doVCCommand("svn update --non-interactive " + quoteName(onlyFileName(owner_->absFileName()))
763                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
764                     FileName(owner_->filePath()));
765
766         string log;
767         string const res = scanLogFile(tmpf, log);
768         if (!res.empty())
769                 frontend::Alert::error(_("Revision control error."),
770                         bformat(_("Error when updating from repository.\n"
771                                 "You have to manually resolve the conflicts NOW!\n'%1$s'.\n\n"
772                                 "After pressing OK, LyX will try to reopen the resolved document."),
773                         from_local8bit(res)));
774
775         fileLock(true, tmpf, log);
776
777         tmpf.erase();
778         return log.empty() ? string() : "SVN: " + log;
779 }
780
781
782 bool SVN::checkOutEnabled()
783 {
784         if (locked_mode_)
785                 return !isLocked();
786         else
787                 return true;
788 }
789
790
791 string SVN::repoUpdate()
792 {
793         FileName tmpf = FileName::tempName("lyxvcout");
794         if (tmpf.empty()) {
795                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
796                 return N_("Error: Could not generate logfile.");
797         }
798
799         doVCCommand("svn diff " + quoteName(owner_->filePath())
800                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
801                 FileName(owner_->filePath()));
802         docstring res = tmpf.fileContents("UTF-8");
803         if (!res.empty()) {
804                 LYXERR(Debug::LYXVC, "Diff detected:\n" << res);
805                 docstring const file = from_utf8(owner_->filePath());
806                 docstring text = bformat(_("There were detected changes "
807                                 "in the working directory:\n%1$s\n\n"
808                                 "In case of file conflict version of the local directory files "
809                                 "will be preferred."
810                                 "\n\nContinue?"), file);
811                 int ret = frontend::Alert::prompt(_("Changes detected"),
812                                 text, 0, 1, _("&Yes"), _("&No"), _("View &Log ..."));
813                 if (ret == 2 ) {
814                         dispatch(FuncRequest(LFUN_DIALOG_SHOW, "file " + tmpf.absFileName()));
815                         ret = frontend::Alert::prompt(_("Changes detected"),
816                                 text, 0, 1, _("&Yes"), _("&No"));
817                         hideDialogs("file", 0);
818                 }
819                 if (ret == 1 ) {
820                         tmpf.erase();
821                         return string();
822                 }
823         }
824
825         // Reverting looks too harsh, see bug #6255.
826         // doVCCommand("svn revert -R " + quoteName(owner_->filePath())
827         // + " > " + quoteName(tmpf.toFilesystemEncoding()),
828         // FileName(owner_->filePath()));
829         // res = "Revert log:\n" + tmpf.fileContents("UTF-8");
830         doVCCommand("svn update --accept mine-full " + quoteName(owner_->filePath())
831                 + " > " + quoteName(tmpf.toFilesystemEncoding()),
832         FileName(owner_->filePath()));
833         res += "Update log:\n" + tmpf.fileContents("UTF-8");
834
835         LYXERR(Debug::LYXVC, res);
836         tmpf.erase();
837         return to_utf8(res);
838 }
839
840
841 bool SVN::repoUpdateEnabled()
842 {
843         return true;
844 }
845
846
847 string SVN::lockingToggle()
848 {
849         FileName tmpf = FileName::tempName("lyxvcout");
850         if (tmpf.empty()) {
851                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
852                 return N_("Error: Could not generate logfile.");
853         }
854
855         int ret = doVCCommand("svn proplist " + quoteName(onlyFileName(owner_->absFileName()))
856                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
857                     FileName(owner_->filePath()));
858         if (ret)
859                 return string();
860
861         string log;
862         string res = scanLogFile(tmpf, log);
863         bool locking = contains(res, "svn:needs-lock");
864         if (!locking)
865                 ret = doVCCommand("svn propset svn:needs-lock ON "
866                     + quoteName(onlyFileName(owner_->absFileName()))
867                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
868                     FileName(owner_->filePath()));
869         else
870                 ret = doVCCommand("svn propdel svn:needs-lock "
871                     + quoteName(onlyFileName(owner_->absFileName()))
872                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
873                     FileName(owner_->filePath()));
874         if (ret)
875                 return string();
876
877         tmpf.erase();
878         frontend::Alert::warning(_("VCN File Locking"),
879                 (locking ? _("Locking property unset.") : _("Locking property set.")) + "\n"
880                 + _("Do not forget to commit the locking property into the repository."),
881                 true);
882
883         return string("SVN: ") +  N_("Locking property set.");
884 }
885
886
887 bool SVN::lockingToggleEnabled()
888 {
889         return true;
890 }
891
892
893 void SVN::revert()
894 {
895         // Reverts to the version in CVS repository and
896         // gets the updated version from the repository.
897         string const fil = quoteName(onlyFileName(owner_->absFileName()));
898
899         doVCCommand("svn revert -q " + fil,
900                     FileName(owner_->filePath()));
901         owner_->markClean();
902 }
903
904
905 void SVN::undoLast()
906 {
907         // merge the current with the previous version
908         // in a reverse patch kind of way, so that the
909         // result is to revert the last changes.
910         lyxerr << "Sorry, not implemented." << endl;
911 }
912
913
914 bool SVN::undoLastEnabled()
915 {
916         return false;
917 }
918
919
920 string SVN::revisionInfo(LyXVC::RevisionInfo const info)
921 {
922         if (info == LyXVC::Tree) {
923                         if (rev_tree_cache_.empty())
924                                 if (!getTreeRevisionInfo())
925                                         rev_tree_cache_ = "?";
926                         if (rev_tree_cache_ == "?")
927                                 return string();
928
929                         return rev_tree_cache_;
930         }
931
932         // fill the rest of the attributes for a single file
933         if (rev_file_cache_.empty())
934                 if (!getFileRevisionInfo())
935                         rev_file_cache_ = "?";
936
937         switch (info) {
938                 case LyXVC::File:
939                         if (rev_file_cache_ == "?")
940                                 return string();
941                         return rev_file_cache_;
942                 case LyXVC::Author:
943                         return rev_author_cache_;
944                 case LyXVC::Date:
945                         return rev_date_cache_;
946                 case LyXVC::Time:
947                         return rev_time_cache_;
948                 default: ;
949
950         }
951
952         return string();
953 }
954
955
956 bool SVN::getFileRevisionInfo()
957 {
958         FileName tmpf = FileName::tempName("lyxvcout");
959         if (tmpf.empty()) {
960                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
961                 return N_("Error: Could not generate logfile.");
962         }
963
964         doVCCommand("svn info --xml " + quoteName(onlyFileName(owner_->absFileName()))
965                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
966                     FileName(owner_->filePath()));
967
968         if (tmpf.empty())
969                 return false;
970
971         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
972         string line;
973         // commit log part
974         bool c = false;
975         string rev;
976
977         while (ifs) {
978                 getline(ifs, line);
979                 LYXERR(Debug::LYXVC, line);
980                 if (prefixIs(line, "<commit"))
981                         c = true;
982                 if (c && prefixIs(line, "   revision=\"") && suffixIs(line, "\">")) {
983                         string l1 = subst(line, "revision=\"", "");
984                         string l2 = trim(subst(l1, "\">", ""));
985                         if (isStrInt(l2))
986                                 rev_file_cache_ = rev = l2;
987                 }
988                 if (c && prefixIs(line, "<author>") && suffixIs(line, "</author>")) {
989                         string l1 = subst(line, "<author>", "");
990                         string l2 = subst(l1, "</author>", "");
991                         rev_author_cache_ = l2;
992                 }
993                 if (c && prefixIs(line, "<date>") && suffixIs(line, "</date>")) {
994                         string l1 = subst(line, "<date>", "");
995                         string l2 = subst(l1, "</date>", "");
996                         l2 = split(l2, l1, 'T');
997                         rev_date_cache_ = l1;
998                         l2 = split(l2, l1, '.');
999                         rev_time_cache_ = l1;
1000                 }
1001         }
1002
1003         ifs.close();
1004         tmpf.erase();
1005         return !rev.empty();
1006 }
1007
1008
1009 bool SVN::getTreeRevisionInfo()
1010 {
1011         FileName tmpf = FileName::tempName("lyxvcout");
1012         if (tmpf.empty()) {
1013                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1014                 return N_("Error: Could not generate logfile.");
1015         }
1016
1017         doVCCommand("svnversion -n . > " + quoteName(tmpf.toFilesystemEncoding()),
1018                     FileName(owner_->filePath()));
1019
1020         if (tmpf.empty())
1021                 return false;
1022
1023         // only first line in case something bad happens.
1024         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1025         string line;
1026         getline(ifs, line);
1027         ifs.close();
1028         tmpf.erase();
1029
1030         rev_tree_cache_ = line;
1031         return !line.empty();
1032 }
1033
1034
1035 void SVN::getLog(FileName const & tmpf)
1036 {
1037         doVCCommand("svn log " + quoteName(onlyFileName(owner_->absFileName()))
1038                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1039                     FileName(owner_->filePath()));
1040 }
1041
1042
1043 bool SVN::prepareFileRevision(string const & revis, string & f)
1044 {
1045         if (!isStrInt(revis))
1046                 return false;
1047
1048         int rev = convert<int>(revis);
1049         if (rev <= 0)
1050                 if (!getFileRevisionInfo())
1051                         return false;
1052         if (rev == 0)
1053                 rev = convert<int>(rev_file_cache_);
1054         // go back for minus rev
1055         else if (rev < 0) {
1056                 rev = rev + convert<int>(rev_file_cache_);
1057                 if (rev < 1)
1058                         return false;
1059         }
1060
1061         FileName tmpf = FileName::tempName("lyxvcrev");
1062         if (tmpf.empty()) {
1063                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1064                 return N_("Error: Could not generate logfile.");
1065         }
1066
1067         doVCCommand("svn cat -r " + convert<string>(rev) + " "
1068                       + quoteName(onlyFileName(owner_->absFileName()))
1069                       + " > " + quoteName(tmpf.toFilesystemEncoding()),
1070                 FileName(owner_->filePath()));
1071         if (tmpf.isFileEmpty())
1072                 return false;
1073
1074         f = tmpf.absFileName();
1075         return true;
1076 }
1077
1078
1079 bool SVN::prepareFileRevisionEnabled()
1080 {
1081         return true;
1082 }
1083
1084
1085
1086 bool SVN::toggleReadOnlyEnabled()
1087 {
1088         return false;
1089 }
1090
1091
1092 } // namespace lyx