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