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