]> git.lyx.org Git - features.git/blob - src/VCBackend.cpp
9e0156b04e3742bf8592ba93ba7b7143ebcec6c4
[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  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "VCBackend.h"
14 #include "Buffer.h"
15 #include "LyX.h"
16 #include "FuncRequest.h"
17
18 #include "frontends/alert.h"
19 #include "frontends/Application.h"
20
21 #include "support/debug.h"
22 #include "support/filetools.h"
23 #include "support/gettext.h"
24 #include "support/lstrings.h"
25 #include "support/Path.h"
26 #include "support/Systemcall.h"
27
28 #include <boost/regex.hpp>
29
30 #include <fstream>
31
32 using namespace std;
33 using namespace lyx::support;
34
35 using boost::regex;
36 using boost::regex_match;
37 using boost::smatch;
38
39 namespace lyx {
40
41
42 int VCS::doVCCommandCall(string const & cmd, FileName const & path)
43 {
44         LYXERR(Debug::LYXVC, "doVCCommandCall: " << cmd);
45         Systemcall one;
46         support::PathChanger p(path);
47         return one.startscript(Systemcall::Wait, cmd, false);
48 }
49
50
51 int VCS::doVCCommand(string const & cmd, FileName const & path)
52 {
53         if (owner_)
54                 owner_->setBusy(true);
55
56         int const ret = doVCCommandCall(cmd, path);
57
58         if (owner_)
59                 owner_->setBusy(false);
60         if (ret)
61                 frontend::Alert::error(_("Revision control error."),
62                         bformat(_("Some problem occured while running the command:\n"
63                                   "'%1$s'."),
64                         from_utf8(cmd)));
65         return ret;
66 }
67
68
69 /////////////////////////////////////////////////////////////////////
70 //
71 // RCS
72 //
73 /////////////////////////////////////////////////////////////////////
74
75 RCS::RCS(FileName const & m)
76 {
77         master_ = m;
78         scanMaster();
79 }
80
81
82 FileName const RCS::findFile(FileName const & file)
83 {
84         // Check if *,v exists.
85         FileName tmp(file.absFilename() + ",v");
86         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp);
87         if (tmp.isReadableFile()) {
88                 LYXERR(Debug::LYXVC, "Yes, " << file << " is under rcs.");
89                 return tmp;
90         }
91
92         // Check if RCS/*,v exists.
93         tmp = FileName(addName(addPath(onlyPath(file.absFilename()), "RCS"), file.absFilename()) + ",v");
94         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp);
95         if (tmp.isReadableFile()) {
96                 LYXERR(Debug::LYXVC, "Yes, " << file << " is under rcs.");
97                 return tmp;
98         }
99
100         return FileName();
101 }
102
103
104 void RCS::retrieve(FileName const & file)
105 {
106         LYXERR(Debug::LYXVC, "LyXVC::RCS: retrieve.\n\t" << file);
107         doVCCommandCall("co -q -r " + quoteName(file.toFilesystemEncoding()),
108                          FileName());
109 }
110
111
112 void RCS::scanMaster()
113 {
114         if (master_.empty())
115                 return;
116
117         LYXERR(Debug::LYXVC, "LyXVC::RCS: scanMaster: " << master_);
118
119         ifstream ifs(master_.toFilesystemEncoding().c_str());
120
121         string token;
122         bool read_enough = false;
123
124         while (!read_enough && ifs >> token) {
125                 LYXERR(Debug::LYXVC, "LyXVC::scanMaster: current lex text: `"
126                         << token << '\'');
127
128                 if (token.empty())
129                         continue;
130                 else if (token == "head") {
131                         // get version here
132                         string tmv;
133                         ifs >> tmv;
134                         tmv = rtrim(tmv, ";");
135                         version_ = tmv;
136                         LYXERR(Debug::LYXVC, "LyXVC: version found to be " << tmv);
137                 } else if (contains(token, "access")
138                            || contains(token, "symbols")
139                            || contains(token, "strict")) {
140                         // nothing
141                 } else if (contains(token, "locks")) {
142                         // get locker here
143                         if (contains(token, ';')) {
144                                 locker_ = "Unlocked";
145                                 vcstatus = UNLOCKED;
146                                 continue;
147                         }
148                         string tmpt;
149                         string s1;
150                         string s2;
151                         do {
152                                 ifs >> tmpt;
153                                 s1 = rtrim(tmpt, ";");
154                                 // tmp is now in the format <user>:<version>
155                                 s1 = split(s1, s2, ':');
156                                 // s2 is user, and s1 is version
157                                 if (s1 == version_) {
158                                         locker_ = s2;
159                                         vcstatus = LOCKED;
160                                         break;
161                                 }
162                         } while (!contains(tmpt, ';'));
163
164                 } else if (token == "comment") {
165                         // we don't need to read any further than this.
166                         read_enough = true;
167                 } else {
168                         // unexpected
169                         LYXERR(Debug::LYXVC, "LyXVC::scanMaster(): unexpected token");
170                 }
171         }
172 }
173
174
175 void RCS::registrer(string const & msg)
176 {
177         string cmd = "ci -q -u -i -t-\"";
178         cmd += msg;
179         cmd += "\" ";
180         cmd += quoteName(onlyFilename(owner_->absFileName()));
181         doVCCommand(cmd, FileName(owner_->filePath()));
182 }
183
184
185 string RCS::checkIn(string const & msg)
186 {
187         int ret = doVCCommand("ci -q -u -m\"" + msg + "\" "
188                     + quoteName(onlyFilename(owner_->absFileName())),
189                     FileName(owner_->filePath()));
190         return ret ? string() : "RCS: Proceeded";
191 }
192
193
194 bool RCS::checkInEnabled()
195 {
196         return owner_ && !owner_->isReadonly();
197 }
198
199
200 string RCS::checkOut()
201 {
202         owner_->markClean();
203         int ret = doVCCommand("co -q -l " + quoteName(onlyFilename(owner_->absFileName())),
204                     FileName(owner_->filePath()));
205         return ret ? string() : "RCS: Proceeded";
206 }
207
208
209 bool RCS::checkOutEnabled()
210 {
211         return owner_ && owner_->isReadonly();
212 }
213
214
215 string RCS::repoUpdate()
216 {
217         lyxerr << "Sorry, not implemented." << endl;
218         return string();
219 }
220
221
222 bool RCS::repoUpdateEnabled()
223 {
224         return false;
225 }
226
227
228 string RCS::lockingToggle()
229 {
230         lyxerr << "Sorry, not implemented." << endl;
231         return string();
232 }
233
234
235 bool RCS::lockingToggleEnabled()
236 {
237         return false;
238 }
239
240
241 void RCS::revert()
242 {
243         doVCCommand("co -f -u" + version() + " "
244                     + quoteName(onlyFilename(owner_->absFileName())),
245                     FileName(owner_->filePath()));
246         // We ignore changes and just reload!
247         owner_->markClean();
248 }
249
250
251 void RCS::undoLast()
252 {
253         LYXERR(Debug::LYXVC, "LyXVC: undoLast");
254         doVCCommand("rcs -o" + version() + " "
255                     + quoteName(onlyFilename(owner_->absFileName())),
256                     FileName(owner_->filePath()));
257 }
258
259
260 bool RCS::undoLastEnabled()
261 {
262         return true;
263 }
264
265
266 void RCS::getLog(FileName const & tmpf)
267 {
268         doVCCommand("rlog " + quoteName(onlyFilename(owner_->absFileName()))
269                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
270                     FileName(owner_->filePath()));
271 }
272
273
274 bool RCS::toggleReadOnlyEnabled()
275 {
276         // This got broken somewhere along lfuns dispatch reorganization.
277         // reloadBuffer would be needed after this, but thats problematic
278         // since we are inside Buffer::dispatch.
279         // return true;
280         return false;
281 }
282
283 string RCS::revisionInfo(LyXVC::RevisionInfo const info)
284 {
285         if (info == LyXVC::File)
286                 return version_;
287         return string();
288 }
289
290
291 /////////////////////////////////////////////////////////////////////
292 //
293 // CVS
294 //
295 /////////////////////////////////////////////////////////////////////
296
297 CVS::CVS(FileName const & m, FileName const & f)
298 {
299         master_ = m;
300         file_ = f;
301         scanMaster();
302 }
303
304
305 FileName const CVS::findFile(FileName const & file)
306 {
307         // First we look for the CVS/Entries in the same dir
308         // where we have file.
309         FileName const entries(onlyPath(file.absFilename()) + "/CVS/Entries");
310         string const tmpf = '/' + onlyFilename(file.absFilename()) + '/';
311         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under cvs in `" << entries
312                              << "' for `" << tmpf << '\'');
313         if (entries.isReadableFile()) {
314                 // Ok we are at least in a CVS dir. Parse the CVS/Entries
315                 // and see if we can find this file. We do a fast and
316                 // dirty parse here.
317                 ifstream ifs(entries.toFilesystemEncoding().c_str());
318                 string line;
319                 while (getline(ifs, line)) {
320                         LYXERR(Debug::LYXVC, "\tEntries: " << line);
321                         if (contains(line, tmpf))
322                                 return entries;
323                 }
324         }
325         return FileName();
326 }
327
328
329 void CVS::scanMaster()
330 {
331         LYXERR(Debug::LYXVC, "LyXVC::CVS: scanMaster. \n     Checking: " << master_);
332         // Ok now we do the real scan...
333         ifstream ifs(master_.toFilesystemEncoding().c_str());
334         string tmpf = '/' + onlyFilename(file_.absFilename()) + '/';
335         LYXERR(Debug::LYXVC, "\tlooking for `" << tmpf << '\'');
336         string line;
337         static regex const reg("/(.*)/(.*)/(.*)/(.*)/(.*)");
338         while (getline(ifs, line)) {
339                 LYXERR(Debug::LYXVC, "\t  line: " << line);
340                 if (contains(line, tmpf)) {
341                         // Ok extract the fields.
342                         smatch sm;
343
344                         regex_match(line, sm, reg);
345
346                         //sm[0]; // whole matched string
347                         //sm[1]; // filename
348                         version_ = sm.str(2);
349                         string const file_date = sm.str(3);
350
351                         //sm[4]; // options
352                         //sm[5]; // tag or tagdate
353                         // FIXME: must double check file is stattable/existing
354                         time_t mod = file_.lastModified();
355                         string mod_date = rtrim(asctime(gmtime(&mod)), "\n");
356                         LYXERR(Debug::LYXVC, "Date in Entries: `" << file_date
357                                 << "'\nModification date of file: `" << mod_date << '\'');
358                         //FIXME this whole locking bussiness is not working under cvs and the machinery
359                         // conforms to the ci usage, not cvs.
360                         if (file_date == mod_date) {
361                                 locker_ = "Unlocked";
362                                 vcstatus = UNLOCKED;
363                         } else {
364                                 // Here we should also to some more checking
365                                 // to see if there are conflicts or not.
366                                 locker_ = "Locked";
367                                 vcstatus = LOCKED;
368                         }
369                         break;
370                 }
371         }
372 }
373
374
375 void CVS::registrer(string const & msg)
376 {
377         doVCCommand("cvs -q add -m \"" + msg + "\" "
378                     + quoteName(onlyFilename(owner_->absFileName())),
379                     FileName(owner_->filePath()));
380 }
381
382
383 string CVS::checkIn(string const & msg)
384 {
385         int ret = doVCCommand("cvs -q commit -m \"" + msg + "\" "
386                     + quoteName(onlyFilename(owner_->absFileName())),
387                     FileName(owner_->filePath()));
388         return ret ? string() : "CVS: Proceeded";
389 }
390
391
392 bool CVS::checkInEnabled()
393 {
394         return true;
395 }
396
397
398 string CVS::checkOut()
399 {
400         // cvs update or perhaps for cvs this should be a noop
401         // we need to detect conflict (eg "C" in output)
402         // before we can do this.
403         lyxerr << "Sorry, not implemented." << endl;
404         return string();
405 }
406
407
408 bool CVS::checkOutEnabled()
409 {
410         return false;
411 }
412
413
414 string CVS::repoUpdate()
415 {
416         lyxerr << "Sorry, not implemented." << endl;
417         return string();
418 }
419
420
421 bool CVS::repoUpdateEnabled()
422 {
423         return false;
424 }
425
426
427 string CVS::lockingToggle()
428 {
429         lyxerr << "Sorry, not implemented." << endl;
430         return string();
431 }
432
433
434 bool CVS::lockingToggleEnabled()
435 {
436         return false;
437 }
438
439
440 void CVS::revert()
441 {
442         // Reverts to the version in CVS repository and
443         // gets the updated version from the repository.
444         string const fil = quoteName(onlyFilename(owner_->absFileName()));
445         // This is sensitive operation, so at lest some check about
446         // existence of cvs program and its file
447         if (doVCCommand("cvs log "+ fil, FileName(owner_->filePath())))
448                 return;
449         FileName f(owner_->absFileName());
450         f.removeFile();
451         doVCCommand("cvs update " + fil,
452                     FileName(owner_->filePath()));
453         owner_->markClean();
454 }
455
456
457 void CVS::undoLast()
458 {
459         // merge the current with the previous version
460         // in a reverse patch kind of way, so that the
461         // result is to revert the last changes.
462         lyxerr << "Sorry, not implemented." << endl;
463 }
464
465
466 bool CVS::undoLastEnabled()
467 {
468         return false;
469 }
470
471
472 void CVS::getLog(FileName const & tmpf)
473 {
474         doVCCommand("cvs log " + quoteName(onlyFilename(owner_->absFileName()))
475                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
476                     FileName(owner_->filePath()));
477 }
478
479
480 bool CVS::toggleReadOnlyEnabled()
481 {
482         return false;
483 }
484
485
486 string CVS::revisionInfo(LyXVC::RevisionInfo const)
487 {
488         return string();
489 }
490
491
492 /////////////////////////////////////////////////////////////////////
493 //
494 // SVN
495 //
496 /////////////////////////////////////////////////////////////////////
497
498 SVN::SVN(FileName const & m, FileName const & f)
499 {
500         owner_ = 0;
501         master_ = m;
502         file_ = f;
503         locked_mode_ = 0;
504         scanMaster();
505 }
506
507
508 FileName const SVN::findFile(FileName const & file)
509 {
510         // First we look for the .svn/entries in the same dir
511         // where we have file.
512         FileName const entries(onlyPath(file.absFilename()) + "/.svn/entries");
513         string const tmpf = onlyFilename(file.absFilename());
514         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under svn in `" << entries
515                              << "' for `" << tmpf << '\'');
516         if (entries.isReadableFile()) {
517                 // Ok we are at least in a SVN dir. Parse the .svn/entries
518                 // and see if we can find this file. We do a fast and
519                 // dirty parse here.
520                 ifstream ifs(entries.toFilesystemEncoding().c_str());
521                 string line, oldline;
522                 while (getline(ifs, line)) {
523                         if (line == "dir" || line == "file")
524                                 LYXERR(Debug::LYXVC, "\tEntries: " << oldline);
525                         if (oldline == tmpf && line == "file")
526                                 return entries;
527                         oldline = line;
528                 }
529         }
530         return FileName();
531 }
532
533
534 void SVN::scanMaster()
535 {
536         locker_.clear();
537         // vcstatus code is somewhat superflous, until we want
538         // to implement read-only toggle for svn.
539         vcstatus = NOLOCKING;
540         if (checkLockMode()) {
541                 if (isLocked()) {
542                         locker_ = "Locked";
543                         vcstatus = LOCKED;
544                 } else {
545                         locker_ = "Unlocked";
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
901         doVCCommand("svn info --xml " + quoteName(onlyFilename(owner_->absFileName()))
902                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
903                     FileName(owner_->filePath()));
904
905         if (tmpf.empty())
906                 return false;
907
908         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
909         string line;
910         // commit log part
911         bool c = false;
912         string rev;
913
914         while (ifs) {
915                 getline(ifs, line);
916                 LYXERR(Debug::LYXVC, line);
917                 if (prefixIs(line, "<commit"))
918                         c = true;
919                 if (c && prefixIs(line, "   revision=\"") && suffixIs(line, "\">")) {
920                         string l1 = subst(line, "revision=\"", "");
921                         string l2 = trim(subst(l1, "\">", ""));
922                         if (isStrInt(l2))
923                                 rev_file_cache_ = rev = l2;
924                 }
925                 if (c && prefixIs(line, "<author>") && suffixIs(line, "</author>")) {
926                         string l1 = subst(line, "<author>", "");
927                         string l2 = subst(l1, "</author>", "");
928                         rev_author_cache_ = l2;
929                 }
930                 if (c && prefixIs(line, "<date>") && suffixIs(line, "</date>")) {
931                         string l1 = subst(line, "<date>", "");
932                         string l2 = subst(l1, "</date>", "");
933                         l2 = split(l2, l1, 'T');
934                         rev_date_cache_ = l1;
935                         l2 = split(l2, l1, '.');
936                         rev_time_cache_ = l1;
937                 }
938         }
939
940         ifs.close();
941         tmpf.erase();
942         return !rev.empty();
943 }
944
945
946 bool SVN::getTreeRevisionInfo()
947 {
948         FileName tmpf = FileName::tempName("lyxvcout");
949
950         doVCCommand("svnversion -n . > " + quoteName(tmpf.toFilesystemEncoding()),
951                     FileName(owner_->filePath()));
952
953         if (tmpf.empty())
954                 return false;
955
956         // only first line in case something bad happens.
957         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
958         string line;
959         getline(ifs, line);
960         ifs.close();
961         tmpf.erase();
962
963         rev_tree_cache_ = line;
964         return !line.empty();
965 }
966
967
968 void SVN::getLog(FileName const & tmpf)
969 {
970         doVCCommand("svn log " + quoteName(onlyFilename(owner_->absFileName()))
971                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
972                     FileName(owner_->filePath()));
973 }
974
975
976 bool SVN::toggleReadOnlyEnabled()
977 {
978         return false;
979 }
980
981
982 } // namespace lyx