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