]> git.lyx.org Git - lyx.git/blob - src/VCBackend.cpp
Account for old versions of Pygments
[lyx.git] / src / VCBackend.cpp
1 /**
2  * \file VCBackend.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Pavel Sanda
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "VCBackend.h"
15 #include "Buffer.h"
16 #include "DispatchResult.h"
17 #include "LyX.h"
18 #include "FuncRequest.h"
19
20 #include "frontends/alert.h"
21 #include "frontends/Application.h"
22
23 #include "support/convert.h"
24 #include "support/debug.h"
25 #include "support/filetools.h"
26 #include "support/gettext.h"
27 #include "support/lstrings.h"
28 #include "support/PathChanger.h"
29 #include "support/Systemcall.h"
30 #include "support/regex.h"
31 #include "support/TempFile.h"
32
33 #include <fstream>
34 #include <iomanip>
35 #include <sstream>
36
37 using namespace std;
38 using namespace lyx::support;
39
40
41 namespace lyx {
42
43
44 int VCS::doVCCommandCall(string const & cmd, FileName const & path)
45 {
46         LYXERR(Debug::LYXVC, "doVCCommandCall: " << cmd);
47         Systemcall one;
48         support::PathChanger p(path);
49         return one.startscript(Systemcall::Wait, cmd, string(), string(), false);
50 }
51
52
53 int VCS::doVCCommand(string const & cmd, FileName const & path, bool reportError)
54 {
55         if (owner_)
56                 owner_->setBusy(true);
57
58         int const ret = doVCCommandCall(cmd, path);
59
60         if (owner_)
61                 owner_->setBusy(false);
62         if (ret && reportError)
63                 frontend::Alert::error(_("Revision control error."),
64                         bformat(_("Some problem occurred while running the command:\n"
65                                   "'%1$s'."),
66                         from_utf8(cmd)));
67         return ret;
68 }
69
70
71 bool VCS::makeRCSRevision(string const &version, string &revis) const
72 {
73         string rev = revis;
74
75         if (isStrInt(rev)) {
76                 int back = convert<int>(rev);
77                 // if positive use as the last number in the whole revision string
78                 if (back > 0) {
79                         string base;
80                         rsplit(version, base , '.');
81                         rev = base + '.' + rev;
82                 }
83                 if (back == 0)
84                         rev = version;
85                 // we care about the last number from revision string
86                 // in case of backward indexing
87                 if (back < 0) {
88                         string cur, base;
89                         cur = rsplit(version, base , '.');
90                         if (!isStrInt(cur))
91                                 return false;
92                         int want = convert<int>(cur) + back;
93                         if (want <= 0)
94                                 return false;
95
96                         rev = base + '.' + convert<string>(want);
97                 }
98         }
99
100         revis = rev;
101         return true;
102 }
103
104
105 bool VCS::checkparentdirs(FileName const & file, std::string const & vcsdir)
106 {
107         FileName dirname = file.onlyPath();
108         do {
109                 FileName tocheck = FileName(addName(dirname.absFileName(), vcsdir));
110                 LYXERR(Debug::LYXVC, "check file: " << tocheck.absFileName());
111                 if (tocheck.exists())
112                         return true;
113                 //this construct because of #8295
114                 dirname = FileName(dirname.absFileName()).parentPath();
115         } while (!dirname.empty());
116         return false;
117 }
118
119
120 /////////////////////////////////////////////////////////////////////
121 //
122 // RCS
123 //
124 /////////////////////////////////////////////////////////////////////
125
126 RCS::RCS(FileName const & m, Buffer * b) : VCS(b)
127 {
128         // Here we know that the buffer file is either already in RCS or
129         // about to be registered
130         master_ = m;
131         scanMaster();
132 }
133
134
135 FileName const RCS::findFile(FileName const & file)
136 {
137         // Check if *,v exists.
138         FileName tmp(file.absFileName() + ",v");
139         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp);
140         if (tmp.isReadableFile()) {
141                 LYXERR(Debug::LYXVC, "Yes, " << file << " is under rcs.");
142                 return tmp;
143         }
144
145         // Check if RCS/*,v exists.
146         tmp = FileName(addName(addPath(onlyPath(file.absFileName()), "RCS"), file.absFileName()) + ",v");
147         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp);
148         if (tmp.isReadableFile()) {
149                 LYXERR(Debug::LYXVC, "Yes, " << file << " is under rcs.");
150                 return tmp;
151         }
152
153         return FileName();
154 }
155
156
157 bool RCS::retrieve(FileName const & file)
158 {
159         LYXERR(Debug::LYXVC, "LyXVC::RCS: retrieve.\n\t" << file);
160         // The caller ensures that file does not exist, so no need to check that.
161         return doVCCommandCall("co -q -r " + quoteName(file.toFilesystemEncoding()),
162                                FileName()) == 0;
163 }
164
165
166 void RCS::scanMaster()
167 {
168         if (master_.empty())
169                 return;
170
171         LYXERR(Debug::LYXVC, "LyXVC::RCS: scanMaster: " << master_);
172
173         ifstream ifs(master_.toFilesystemEncoding().c_str());
174         // limit the size of strings we read to avoid memory problems
175         ifs >> setw(65636);
176
177         string token;
178         bool read_enough = false;
179
180         while (!read_enough && ifs >> token) {
181                 LYXERR(Debug::LYXVC, "LyXVC::scanMaster: current lex text: `"
182                         << token << '\'');
183
184                 if (token.empty())
185                         continue;
186                 else if (token == "head") {
187                         // get version here
188                         string tmv;
189                         ifs >> tmv;
190                         tmv = rtrim(tmv, ";");
191                         version_ = tmv;
192                         LYXERR(Debug::LYXVC, "LyXVC: version found to be " << tmv);
193                 } else if (contains(token, "access")
194                            || contains(token, "symbols")
195                            || contains(token, "strict")) {
196                         // nothing
197                 } else if (contains(token, "locks")) {
198                         // get locker here
199                         if (contains(token, ';')) {
200                                 locker_ = "Unlocked";
201                                 vcstatus = UNLOCKED;
202                                 continue;
203                         }
204                         string tmpt;
205                         string s1;
206                         string s2;
207                         do {
208                                 ifs >> tmpt;
209                                 s1 = rtrim(tmpt, ";");
210                                 // tmp is now in the format <user>:<version>
211                                 s1 = split(s1, s2, ':');
212                                 // s2 is user, and s1 is version
213                                 if (s1 == version_) {
214                                         locker_ = s2;
215                                         vcstatus = LOCKED;
216                                         break;
217                                 }
218                         } while (!contains(tmpt, ';'));
219
220                 } else if (token == "comment") {
221                         // we don't need to read any further than this.
222                         read_enough = true;
223                 } else {
224                         // unexpected
225                         LYXERR(Debug::LYXVC, "LyXVC::scanMaster(): unexpected token");
226                 }
227         }
228 }
229
230
231 void RCS::registrer(string const & msg)
232 {
233         string cmd = "ci -q -u -i -t-\"";
234         cmd += msg;
235         cmd += "\" ";
236         cmd += quoteName(onlyFileName(owner_->absFileName()));
237         doVCCommand(cmd, FileName(owner_->filePath()));
238 }
239
240
241 bool RCS::renameEnabled()
242 {
243         return false;
244 }
245
246
247 string RCS::rename(support::FileName const & /*newFile*/, string const & /*msg*/)
248 {
249         // not implemented, since a left-over file.lyx,v would be confusing.
250         return string();
251 }
252
253
254 bool RCS::copyEnabled()
255 {
256         return true;
257 }
258
259
260 string RCS::copy(support::FileName const & newFile, string const & msg)
261 {
262         // RCS has no real copy command, so we create a poor mans version
263         support::FileName const oldFile(owner_->absFileName());
264         if (!oldFile.copyTo(newFile))
265                 return string();
266         FileName path(oldFile.onlyPath());
267         string relFile(to_utf8(newFile.relPath(path.absFileName())));
268         string cmd = "ci -q -u -i -t-\"";
269         cmd += msg;
270         cmd += "\" ";
271         cmd += quoteName(relFile);
272         return doVCCommand(cmd, path) ? string() : "RCS: Proceeded";
273 }
274
275
276 LyXVC::CommandResult RCS::checkIn(string const & msg, string & log)
277 {
278         int ret = doVCCommand("ci -q -u -m\"" + msg + "\" "
279                     + quoteName(onlyFileName(owner_->absFileName())),
280                     FileName(owner_->filePath()));
281         if (ret)
282                 return LyXVC::ErrorCommand;
283         log = "RCS: Proceeded";
284         return LyXVC::VCSuccess;
285 }
286
287
288 bool RCS::checkInEnabled()
289 {
290         return owner_ && !owner_->hasReadonlyFlag();
291 }
292
293
294 bool RCS::isCheckInWithConfirmation()
295 {
296         // FIXME one day common getDiff for all backends
297         // docstring diff;
298         // if (getDiff(file, diff) && diff.empty())
299         //      return false;
300
301         TempFile tempfile("lyxvcout");
302         FileName tmpf = tempfile.name();
303         if (tmpf.empty()) {
304                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
305                 return true;
306         }
307
308         doVCCommandCall("rcsdiff " + quoteName(owner_->absFileName())
309                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
310                 FileName(owner_->filePath()));
311
312         docstring diff = tmpf.fileContents("UTF-8");
313
314         if (diff.empty())
315                 return false;
316
317         return true;
318 }
319
320
321 string RCS::checkOut()
322 {
323         owner_->markClean();
324         int ret = doVCCommand("co -q -l " + quoteName(onlyFileName(owner_->absFileName())),
325                     FileName(owner_->filePath()));
326         return ret ? string() : "RCS: Proceeded";
327 }
328
329
330 bool RCS::checkOutEnabled()
331 {
332         return owner_ && owner_->hasReadonlyFlag();
333 }
334
335
336 string RCS::repoUpdate()
337 {
338         lyxerr << "Sorry, not implemented." << endl;
339         return string();
340 }
341
342
343 bool RCS::repoUpdateEnabled()
344 {
345         return false;
346 }
347
348
349 string RCS::lockingToggle()
350 {
351         //FIXME this might be actually possible, study rcs -U, rcs -L.
352         //State should be easy to get inside scanMaster.
353         //It would fix #4370 and make rcs/svn usage even more closer.
354         lyxerr << "Sorry, not implemented." << endl;
355         return string();
356 }
357
358
359 bool RCS::lockingToggleEnabled()
360 {
361         return false;
362 }
363
364
365 bool RCS::revert()
366 {
367         if (doVCCommand("co -f -u" + version_ + ' '
368                     + quoteName(onlyFileName(owner_->absFileName())),
369                     FileName(owner_->filePath())))
370                 return false;
371         // We ignore changes and just reload!
372         owner_->markClean();
373         return true;
374 }
375
376
377 bool RCS::isRevertWithConfirmation()
378 {
379         //FIXME owner && diff ?
380         return true;
381 }
382
383
384 void RCS::undoLast()
385 {
386         LYXERR(Debug::LYXVC, "LyXVC: undoLast");
387         doVCCommand("rcs -o" + version_ + ' '
388                     + quoteName(onlyFileName(owner_->absFileName())),
389                     FileName(owner_->filePath()));
390 }
391
392
393 bool RCS::undoLastEnabled()
394 {
395         return true;
396 }
397
398
399 void RCS::getLog(FileName const & tmpf)
400 {
401         doVCCommand("rlog " + quoteName(onlyFileName(owner_->absFileName()))
402                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
403                     FileName(owner_->filePath()));
404 }
405
406
407 bool RCS::toggleReadOnlyEnabled()
408 {
409         // This got broken somewhere along lfuns dispatch reorganization.
410         // reloadBuffer would be needed after this, but thats problematic
411         // since we are inside Buffer::dispatch.
412         // return true;
413         return false;
414 }
415
416
417 string RCS::revisionInfo(LyXVC::RevisionInfo const info)
418 {
419         if (info == LyXVC::File)
420                 return version_;
421         // fill the rest of the attributes for a single file
422         if (rev_date_cache_.empty())
423                 if (!getRevisionInfo())
424                         return string();
425
426         switch (info) {
427                 case LyXVC::Author:
428                         return rev_author_cache_;
429                 case LyXVC::Date:
430                         return rev_date_cache_;
431                 case LyXVC::Time:
432                         return rev_time_cache_;
433                 default: ;
434         }
435
436         return string();
437 }
438
439
440 bool RCS::getRevisionInfo()
441 {
442         TempFile tempfile("lyxvcout");
443         FileName tmpf = tempfile.name();
444         if (tmpf.empty()) {
445                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
446                 return false;
447         }
448         doVCCommand("rlog -r " + quoteName(onlyFileName(owner_->absFileName()))
449                 + " > " + quoteName(tmpf.toFilesystemEncoding()),
450                 FileName(owner_->filePath()));
451
452         if (tmpf.empty())
453                 return false;
454
455         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
456         string line;
457
458         // we reached to the entry, i.e. after initial log message
459         bool entry=false;
460         // line with critical info, e.g:
461         //"date: 2011/07/02 11:02:54;  author: sanda;  state: Exp;  lines: +17 -2"
462         string result;
463
464         while (ifs) {
465                 getline(ifs, line);
466                 LYXERR(Debug::LYXVC, line);
467                 if (entry && prefixIs(line, "date:")) {
468                         result = line;
469                         break;
470                 }
471                 if (prefixIs(line, "revision"))
472                         entry = true;
473         }
474         if (result.empty())
475                 return false;
476
477         rev_date_cache_ = token(result, ' ', 1);
478         rev_time_cache_ = rtrim(token(result, ' ', 2), ";");
479         rev_author_cache_ = trim(token(token(result, ';', 1), ':', 1));
480
481         return !rev_author_cache_.empty();
482 }
483
484 bool RCS::prepareFileRevision(string const &revis, string & f)
485 {
486         string rev = revis;
487         if (!VCS::makeRCSRevision(version_, rev))
488                 return false;
489
490         TempFile tempfile("lyxvcrev_" + rev + '_');
491         tempfile.setAutoRemove(false);
492         FileName tmpf = tempfile.name();
493         if (tmpf.empty()) {
494                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
495                 return false;
496         }
497
498         doVCCommand("co -p" + rev + ' '
499                       + quoteName(onlyFileName(owner_->absFileName()))
500                       + " > " + quoteName(tmpf.toFilesystemEncoding()),
501                 FileName(owner_->filePath()));
502         tmpf.refresh();
503         if (tmpf.isFileEmpty())
504                 return false;
505
506         f = tmpf.absFileName();
507         return true;
508 }
509
510
511 bool RCS::prepareFileRevisionEnabled()
512 {
513         return true;
514 }
515
516
517 /////////////////////////////////////////////////////////////////////
518 //
519 // CVS
520 //
521 /////////////////////////////////////////////////////////////////////
522
523 CVS::CVS(FileName const & m, Buffer * b) : VCS(b)
524 {
525         // Here we know that the buffer file is either already in CVS or
526         // about to be registered
527         master_ = m;
528         have_rev_info_ = false;
529         scanMaster();
530 }
531
532
533 FileName const CVS::findFile(FileName const & file)
534 {
535         // First we look for the CVS/Entries in the same dir
536         // where we have file.
537         FileName const entries(onlyPath(file.absFileName()) + "/CVS/Entries");
538         string const tmpf = '/' + onlyFileName(file.absFileName()) + '/';
539         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under cvs in `" << entries
540                              << "' for `" << tmpf << '\'');
541         if (entries.isReadableFile()) {
542                 // Ok we are at least in a CVS dir. Parse the CVS/Entries
543                 // and see if we can find this file. We do a fast and
544                 // dirty parse here.
545                 ifstream ifs(entries.toFilesystemEncoding().c_str());
546                 string line;
547                 while (getline(ifs, line)) {
548                         LYXERR(Debug::LYXVC, "\tEntries: " << line);
549                         if (contains(line, tmpf))
550                                 return entries;
551                 }
552         }
553         return FileName();
554 }
555
556
557 void CVS::scanMaster()
558 {
559         LYXERR(Debug::LYXVC, "LyXVC::CVS: scanMaster. \n     Checking: " << master_);
560         // Ok now we do the real scan...
561         ifstream ifs(master_.toFilesystemEncoding().c_str());
562         string name = onlyFileName(owner_->absFileName());
563         string tmpf = '/' + name + '/';
564         LYXERR(Debug::LYXVC, "\tlooking for `" << tmpf << '\'');
565         string line;
566         static regex const reg("/(.*)/(.*)/(.*)/(.*)/(.*)");
567         while (getline(ifs, line)) {
568                 LYXERR(Debug::LYXVC, "\t  line: " << line);
569                 if (contains(line, tmpf)) {
570                         // Ok extract the fields.
571                         smatch sm;
572                         if (!regex_match(line, sm, reg)) {
573                                 LYXERR(Debug::LYXVC, "\t  Cannot parse line. Skipping.");
574                                 continue;
575                         }
576
577                         //sm[0]; // whole matched string
578                         //sm[1]; // filename
579                         version_ = sm.str(2);
580                         string const file_date = sm.str(3);
581
582                         //sm[4]; // options
583                         //sm[5]; // tag or tagdate
584                         FileName file(owner_->absFileName());
585                         if (file.isReadableFile()) {
586                                 time_t mod = file.lastModified();
587                                 string mod_date = rtrim(asctime(gmtime(&mod)), "\n");
588                                 LYXERR(Debug::LYXVC, "Date in Entries: `" << file_date
589                                         << "'\nModification date of file: `" << mod_date << '\'');
590                                 if (file.isReadOnly()) {
591                                         // readonly checkout is unlocked
592                                         vcstatus = UNLOCKED;
593                                 } else {
594                                         FileName bdir(addPath(master_.onlyPath().absFileName(),"Base"));
595                                         FileName base(addName(bdir.absFileName(),name));
596                                         // if base version is existent "cvs edit" was used to lock
597                                         vcstatus = base.isReadableFile() ? LOCKED : NOLOCKING;
598                                 }
599                         } else {
600                                 vcstatus = NOLOCKING;
601                         }
602                         break;
603                 }
604         }
605 }
606
607
608 bool CVS::retrieve(FileName const & file)
609 {
610         LYXERR(Debug::LYXVC, "LyXVC::CVS: retrieve.\n\t" << file);
611         // The caller ensures that file does not exist, so no need to check that.
612         return doVCCommandCall("cvs -q update " + quoteName(file.toFilesystemEncoding()),
613                                file.onlyPath()) == 0;
614 }
615
616
617 string const CVS::getTarget(OperationMode opmode) const
618 {
619         switch(opmode) {
620         case Directory:
621                 // in client server mode CVS does not like full path operand for directory operation
622                 // since LyX switches to the repo dir "." is good enough as target
623                 return ".";
624         case File:
625                 return quoteName(onlyFileName(owner_->absFileName()));
626         }
627         return string();
628 }
629
630
631 docstring CVS::toString(CvsStatus status) const
632 {
633         switch (status) {
634         case UpToDate:
635                 return _("Up-to-date");
636         case LocallyModified:
637                 return _("Locally Modified");
638         case LocallyAdded:
639                 return _("Locally Added");
640         case NeedsMerge:
641                 return _("Needs Merge");
642         case NeedsCheckout:
643                 return _("Needs Checkout");
644         case NoCvsFile:
645                 return _("No CVS file");
646         case StatusError:
647                 return _("Cannot retrieve CVS status");
648         }
649         return docstring();
650 }
651
652
653 int CVS::doVCCommandWithOutput(string const & cmd, FileName const & path,
654         FileName const & output, bool reportError)
655 {
656         string redirection = output.empty() ? "" : " > " + quoteName(output.toFilesystemEncoding());
657         return doVCCommand(cmd + redirection, path, reportError);
658 }
659
660
661 int CVS::doVCCommandCallWithOutput(std::string const & cmd,
662         support::FileName const & path,
663         support::FileName const & output)
664 {
665         string redirection = output.empty() ? "" : " > " + quoteName(output.toFilesystemEncoding());
666         return doVCCommandCall(cmd + redirection, path);
667 }
668
669
670 CVS::CvsStatus CVS::getStatus()
671 {
672         TempFile tempfile("lyxvout");
673         FileName tmpf = tempfile.name();
674         if (tmpf.empty()) {
675                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
676                 return StatusError;
677         }
678
679         if (doVCCommandCallWithOutput("cvs status " + getTarget(File),
680                 FileName(owner_->filePath()), tmpf)) {
681                 return StatusError;
682         }
683
684         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
685         CvsStatus status = NoCvsFile;
686
687         while (ifs) {
688                 string line;
689                 getline(ifs, line);
690                 LYXERR(Debug::LYXVC, line << '\n');
691                 if (prefixIs(line, "File:")) {
692                         if (contains(line, "Up-to-date"))
693                                 status = UpToDate;
694                         else if (contains(line, "Locally Modified"))
695                                 status = LocallyModified;
696                         else if (contains(line, "Locally Added"))
697                                 status = LocallyAdded;
698                         else if (contains(line, "Needs Merge"))
699                                 status = NeedsMerge;
700                         else if (contains(line, "Needs Checkout"))
701                                 status = NeedsCheckout;
702                 }
703         }
704         return status;
705 }
706
707 void CVS::getRevisionInfo()
708 {
709         if (have_rev_info_)
710                 return;
711         have_rev_info_ = true;
712         TempFile tempfile("lyxvout");
713         FileName tmpf = tempfile.name();
714         if (tmpf.empty()) {
715                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
716                 return;
717         }
718
719         int rc = doVCCommandCallWithOutput("cvs log -r" + version_
720                 + ' ' + getTarget(File),
721                 FileName(owner_->filePath()), tmpf);
722         if (rc) {
723                 LYXERR(Debug::LYXVC, "cvs log failed with exit code " << rc);
724                 return;
725         }
726
727         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
728         static regex const reg("date: (.*) (.*) (.*);  author: (.*);  state: (.*);(.*)");
729
730         while (ifs) {
731                 string line;
732                 getline(ifs, line);
733                 LYXERR(Debug::LYXVC, line << '\n');
734                 if (prefixIs(line, "date:")) {
735                         smatch sm;
736                         regex_match(line, sm, reg);
737                         //sm[0]; // whole matched string
738                         rev_date_cache_ = sm[1];
739                         rev_time_cache_ = sm[2];
740                         //sm[3]; // GMT offset
741                         rev_author_cache_ = sm[4];
742                         break;
743                 }
744         }
745         if (rev_author_cache_.empty())
746                 LYXERR(Debug::LYXVC,
747                    "Could not retrieve revision info for " << version_ <<
748                    " of " << getTarget(File));
749 }
750
751
752 void CVS::registrer(string const & msg)
753 {
754         doVCCommand("cvs -q add -m \"" + msg + "\" "
755                 + getTarget(File),
756                 FileName(owner_->filePath()));
757 }
758
759
760 bool CVS::renameEnabled()
761 {
762         return true;
763 }
764
765
766 string CVS::rename(support::FileName const & newFile, string const & msg)
767 {
768         // CVS has no real rename command, so we create a poor mans version
769         support::FileName const oldFile(owner_->absFileName());
770         string ret = copy(newFile, msg);
771         if (ret.empty())
772                 return ret;
773         string cmd = "cvs -q remove -m \"" + msg + "\" " +
774                 quoteName(oldFile.onlyFileName());
775         FileName path(oldFile.onlyPath());
776         return doVCCommand(cmd, path) ? string() : ret;
777 }
778
779
780 bool CVS::copyEnabled()
781 {
782         return true;
783 }
784
785
786 string CVS::copy(support::FileName const & newFile, string const & msg)
787 {
788         // CVS has no real copy command, so we create a poor mans version
789         support::FileName const oldFile(owner_->absFileName());
790         if (!oldFile.copyTo(newFile))
791                 return string();
792         FileName path(oldFile.onlyPath());
793         string relFile(to_utf8(newFile.relPath(path.absFileName())));
794         string cmd("cvs -q add -m \"" + msg + "\" " + quoteName(relFile));
795         return doVCCommand(cmd, path) ? string() : "CVS: Proceeded";
796 }
797
798
799 void CVS::getDiff(OperationMode opmode, FileName const & tmpf)
800 {
801         doVCCommandWithOutput("cvs diff " + getTarget(opmode),
802                 FileName(owner_->filePath()), tmpf, false);
803 }
804
805
806 int CVS::edit()
807 {
808         vcstatus = LOCKED;
809         return doVCCommand("cvs -q edit " + getTarget(File),
810                 FileName(owner_->filePath()));
811 }
812
813
814 int CVS::unedit()
815 {
816         vcstatus = UNLOCKED;
817         return doVCCommand("cvs -q unedit " + getTarget(File),
818                 FileName(owner_->filePath()));
819 }
820
821
822 int CVS::update(OperationMode opmode, FileName const & tmpf)
823 {
824         return doVCCommandWithOutput("cvs -q update "
825                 + getTarget(opmode),
826                 FileName(owner_->filePath()), tmpf, false);
827 }
828
829
830 string CVS::scanLogFile(FileName const & f, string & status)
831 {
832         ifstream ifs(f.toFilesystemEncoding().c_str());
833
834         while (ifs) {
835                 string line;
836                 getline(ifs, line);
837                 LYXERR(Debug::LYXVC, line << '\n');
838                 if (!line.empty())
839                         status += line + "; ";
840                 if (prefixIs(line, "C ")) {
841                         ifs.close();
842                         return line;
843                 }
844         }
845         ifs.close();
846         return string();
847 }
848
849
850 LyXVC::CommandResult CVS::checkIn(string const & msg, string & log)
851 {
852         CvsStatus status = getStatus();
853         switch (status) {
854         case UpToDate:
855                 if (vcstatus != NOLOCKING)
856                         if (unedit())
857                                 return LyXVC::ErrorCommand;
858                 log = "CVS: Proceeded";
859                 return LyXVC::VCSuccess;
860         case LocallyModified:
861         case LocallyAdded: {
862                 int rc = doVCCommand("cvs -q commit -m \"" + msg + "\" "
863                         + getTarget(File),
864                     FileName(owner_->filePath()));
865                 if (rc)
866                         return LyXVC::ErrorCommand;
867                 log = "CVS: Proceeded";
868                 return LyXVC::VCSuccess;
869         }
870         case NeedsMerge:
871         case NeedsCheckout:
872                 frontend::Alert::error(_("Revision control error."),
873                         _("The repository version is newer then the current check out.\n"
874                           "You have to update from repository first or revert your changes.")) ;
875                 break;
876         default:
877                 frontend::Alert::error(_("Revision control error."),
878                         bformat(_("Bad status when checking in changes.\n"
879                                           "\n'%1$s'\n\n"),
880                                 toString(status)));
881                 break;
882         }
883         return LyXVC::ErrorBefore;
884 }
885
886
887 bool CVS::isLocked() const
888 {
889         FileName fn(owner_->absFileName());
890         fn.refresh();
891         return !fn.isReadOnly();
892 }
893
894
895 bool CVS::checkInEnabled()
896 {
897         if (vcstatus != NOLOCKING)
898                 return isLocked();
899         else
900                 return true;
901 }
902
903
904 bool CVS::isCheckInWithConfirmation()
905 {
906         CvsStatus status = getStatus();
907         return status == LocallyModified || status == LocallyAdded;
908 }
909
910
911 string CVS::checkOut()
912 {
913         if (vcstatus != NOLOCKING && edit())
914                 return string();
915         TempFile tempfile("lyxvout");
916         FileName tmpf = tempfile.name();
917         if (tmpf.empty()) {
918                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
919                 return string();
920         }
921
922         int rc = update(File, tmpf);
923         string log;
924         string const res = scanLogFile(tmpf, log);
925         if (!res.empty()) {
926                 frontend::Alert::error(_("Revision control error."),
927                         bformat(_("Error when updating from repository.\n"
928                                 "You have to manually resolve the conflicts NOW!\n'%1$s'.\n\n"
929                                 "After pressing OK, LyX will try to reopen the resolved document."),
930                                 from_local8bit(res)));
931                 rc = 0;
932         }
933
934         return rc ? string() : log.empty() ? "CVS: Proceeded" : "CVS: " + log;
935 }
936
937
938 bool CVS::checkOutEnabled()
939 {
940         if (vcstatus != NOLOCKING)
941                 return !isLocked();
942         else
943                 return true;
944 }
945
946
947 string CVS::repoUpdate()
948 {
949         TempFile tempfile("lyxvout");
950         FileName tmpf = tempfile.name();
951         if (tmpf.empty()) {
952                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
953                 return string();
954         }
955
956         getDiff(Directory, tmpf);
957         docstring res = tmpf.fileContents("UTF-8");
958         if (!res.empty()) {
959                 LYXERR(Debug::LYXVC, "Diff detected:\n" << res);
960                 docstring const file = from_utf8(owner_->filePath());
961                 docstring text = bformat(_("There were detected changes "
962                                 "in the working directory:\n%1$s\n\n"
963                                 "Possible file conflicts must be then resolved manually "
964                                 "or you will need to revert back to the repository version."), file);
965                 int ret = frontend::Alert::prompt(_("Changes detected"),
966                                 text, 0, 1, _("&Continue"), _("&Abort"), _("View &Log ..."));
967                 if (ret == 2) {
968                         dispatch(FuncRequest(LFUN_DIALOG_SHOW, "file " + tmpf.absFileName()));
969                         ret = frontend::Alert::prompt(_("Changes detected"),
970                                 text, 0, 1, _("&Continue"), _("&Abort"));
971                         hideDialogs("file", 0);
972                 }
973                 if (ret == 1)
974                         return string();
975         }
976
977         int rc = update(Directory, tmpf);
978         res += "Update log:\n" + tmpf.fileContents("UTF-8");
979         LYXERR(Debug::LYXVC, res);
980
981         string log;
982         string sres = scanLogFile(tmpf, log);
983         if (!sres.empty()) {
984                 docstring const file = owner_->fileName().displayName(20);
985                 frontend::Alert::error(_("Revision control error."),
986                         bformat(_("Error when updating document %1$s from repository.\n"
987                                           "You have to manually resolve the conflicts NOW!\n'%2$s'.\n\n"
988                                           "After pressing OK, LyX will try to reopen the resolved document."),
989                                 file, from_local8bit(sres)));
990                 rc = 0;
991         }
992
993         return rc ? string() : log.empty() ? "CVS: Proceeded" : "CVS: " + log;
994 }
995
996
997 bool CVS::repoUpdateEnabled()
998 {
999         return true;
1000 }
1001
1002
1003 string CVS::lockingToggle()
1004 {
1005         lyxerr << "Sorry, not implemented." << endl;
1006         return string();
1007 }
1008
1009
1010 bool CVS::lockingToggleEnabled()
1011 {
1012         return false;
1013 }
1014
1015
1016 bool CVS::isRevertWithConfirmation()
1017 {
1018         CvsStatus status = getStatus();
1019         return !owner_->isClean() || status == LocallyModified || status == NeedsMerge;
1020 }
1021
1022
1023 bool CVS::revert()
1024 {
1025         // Reverts to the version in CVS repository and
1026         // gets the updated version from the repository.
1027         CvsStatus status = getStatus();
1028         switch (status) {
1029         case UpToDate:
1030                 if (vcstatus != NOLOCKING)
1031                         return 0 == unedit();
1032                 break;
1033         case NeedsMerge:
1034         case NeedsCheckout:
1035         case LocallyModified: {
1036                 FileName f(owner_->absFileName());
1037                 f.removeFile();
1038                 update(File, FileName());
1039                 owner_->markClean();
1040                 break;
1041         }
1042         case LocallyAdded: {
1043                 docstring const file = owner_->fileName().displayName(20);
1044                 frontend::Alert::error(_("Revision control error."),
1045                         bformat(_("The document %1$s is not in repository.\n"
1046                                   "You have to check in the first revision before you can revert."),
1047                                 file)) ;
1048                 return false;
1049         }
1050         default: {
1051                 docstring const file = owner_->fileName().displayName(20);
1052                 frontend::Alert::error(_("Revision control error."),
1053                         bformat(_("Cannot revert document %1$s to repository version.\n"
1054                                   "The status '%2$s' is unexpected."),
1055                                 file, toString(status)));
1056                 return false;
1057                 }
1058         }
1059         return true;
1060 }
1061
1062
1063 void CVS::undoLast()
1064 {
1065         // merge the current with the previous version
1066         // in a reverse patch kind of way, so that the
1067         // result is to revert the last changes.
1068         lyxerr << "Sorry, not implemented." << endl;
1069 }
1070
1071
1072 bool CVS::undoLastEnabled()
1073 {
1074         return false;
1075 }
1076
1077
1078 void CVS::getLog(FileName const & tmpf)
1079 {
1080         doVCCommandWithOutput("cvs log " + getTarget(File),
1081                 FileName(owner_->filePath()),
1082                 tmpf);
1083 }
1084
1085
1086 bool CVS::toggleReadOnlyEnabled()
1087 {
1088         return false;
1089 }
1090
1091
1092 string CVS::revisionInfo(LyXVC::RevisionInfo const info)
1093 {
1094         if (!version_.empty()) {
1095                 getRevisionInfo();
1096                 switch (info) {
1097                 case LyXVC::File:
1098                         return version_;
1099                 case LyXVC::Author:
1100                         return rev_author_cache_;
1101                 case LyXVC::Date:
1102                         return rev_date_cache_;
1103                 case LyXVC::Time:
1104                         return rev_time_cache_;
1105                 default: ;
1106                 }
1107         }
1108         return string();
1109 }
1110
1111
1112 bool CVS::prepareFileRevision(string const & revis, string & f)
1113 {
1114         string rev = revis;
1115         if (!VCS::makeRCSRevision(version_, rev))
1116                 return false;
1117
1118         TempFile tempfile("lyxvcrev_" + rev + '_');
1119         tempfile.setAutoRemove(false);
1120         FileName tmpf = tempfile.name();
1121         if (tmpf.empty()) {
1122                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1123                 return false;
1124         }
1125
1126         doVCCommandWithOutput("cvs update -p -r" + rev + ' '
1127                 + getTarget(File),
1128                 FileName(owner_->filePath()), tmpf);
1129         tmpf.refresh();
1130         if (tmpf.isFileEmpty())
1131                 return false;
1132
1133         f = tmpf.absFileName();
1134         return true;
1135 }
1136
1137
1138 bool CVS::prepareFileRevisionEnabled()
1139 {
1140         return true;
1141 }
1142
1143
1144 /////////////////////////////////////////////////////////////////////
1145 //
1146 // SVN
1147 //
1148 /////////////////////////////////////////////////////////////////////
1149
1150 SVN::SVN(FileName const & m, Buffer * b) : VCS(b)
1151 {
1152         // Here we know that the buffer file is either already in SVN or
1153         // about to be registered
1154         master_ = m;
1155         locked_mode_ = 0;
1156         scanMaster();
1157 }
1158
1159
1160 FileName const SVN::findFile(FileName const & file)
1161 {
1162         // First we check the existence of repository meta data.
1163         if (!VCS::checkparentdirs(file, ".svn")) {
1164                 LYXERR(Debug::LYXVC, "Cannot find SVN meta data for " << file);
1165                 return FileName();
1166         }
1167
1168         // Now we check the status of the file.
1169         TempFile tempfile("lyxvcout");
1170         FileName tmpf = tempfile.name();
1171         if (tmpf.empty()) {
1172                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1173                 return FileName();
1174         }
1175
1176         string const fname = onlyFileName(file.absFileName());
1177         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under svn control for `" << fname << '\'');
1178         bool found = 0 == doVCCommandCall("svn info " + quoteName(fname)
1179                                                 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1180                                                 file.onlyPath());
1181         LYXERR(Debug::LYXVC, "SVN control: " << (found ? "enabled" : "disabled"));
1182         return found ? file : FileName();
1183 }
1184
1185
1186 void SVN::scanMaster()
1187 {
1188         // vcstatus code is somewhat superflous,
1189         // until we want to implement read-only toggle for svn.
1190         vcstatus = NOLOCKING;
1191         if (checkLockMode()) {
1192                 if (isLocked())
1193                         vcstatus = LOCKED;
1194                 else
1195                         vcstatus = UNLOCKED;
1196         }
1197 }
1198
1199
1200 bool SVN::checkLockMode()
1201 {
1202         TempFile tempfile("lyxvcout");
1203         FileName tmpf = tempfile.name();
1204         if (tmpf.empty()){
1205                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1206                 return false;
1207         }
1208
1209         LYXERR(Debug::LYXVC, "Detecting locking mode...");
1210         if (doVCCommandCall("svn proplist " + quoteName(onlyFileName(owner_->absFileName()))
1211                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1212                     FileName(owner_->filePath())))
1213                 return false;
1214
1215         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1216         string line;
1217         bool ret = false;
1218
1219         while (ifs && !ret) {
1220                 getline(ifs, line);
1221                 LYXERR(Debug::LYXVC, line);
1222                 if (contains(line, "svn:needs-lock"))
1223                         ret = true;
1224         }
1225         LYXERR(Debug::LYXVC, "Locking enabled: " << ret);
1226         ifs.close();
1227         locked_mode_ = ret;
1228         return ret;
1229
1230 }
1231
1232
1233 bool SVN::isLocked() const
1234 {
1235         FileName file(owner_->absFileName());
1236         file.refresh();
1237         return !file.isReadOnly();
1238 }
1239
1240
1241 bool SVN::retrieve(FileName const & file)
1242 {
1243         LYXERR(Debug::LYXVC, "LyXVC::SVN: retrieve.\n\t" << file);
1244         // The caller ensures that file does not exist, so no need to check that.
1245         return doVCCommandCall("svn update -q --non-interactive " + quoteName(file.onlyFileName()),
1246                                file.onlyPath()) == 0;
1247 }
1248
1249
1250 void SVN::registrer(string const & /*msg*/)
1251 {
1252         doVCCommand("svn add -q " + quoteName(onlyFileName(owner_->absFileName())),
1253                     FileName(owner_->filePath()));
1254 }
1255
1256
1257 bool SVN::renameEnabled()
1258 {
1259         return true;
1260 }
1261
1262
1263 string SVN::rename(support::FileName const & newFile, string const & msg)
1264 {
1265         // svn move does not require a log message, since it does not commit.
1266         // In LyX we commit immediately afterwards, otherwise it could be
1267         // confusing to the user to have two uncommitted files.
1268         FileName path(owner_->filePath());
1269         string relFile(to_utf8(newFile.relPath(path.absFileName())));
1270         string cmd("svn move -q " + quoteName(onlyFileName(owner_->absFileName())) +
1271                    ' ' + quoteName(relFile));
1272         if (doVCCommand(cmd, path)) {
1273                 cmd = "svn revert -q " +
1274                         quoteName(onlyFileName(owner_->absFileName())) + ' ' +
1275                         quoteName(relFile);
1276                 doVCCommand(cmd, path);
1277                 if (newFile.exists())
1278                         newFile.removeFile();
1279                 return string();
1280         }
1281         vector<support::FileName> f;
1282         f.push_back(owner_->fileName());
1283         f.push_back(newFile);
1284         string log;
1285         if (checkIn(f, msg, log) != LyXVC::VCSuccess) {
1286                 cmd = "svn revert -q " +
1287                         quoteName(onlyFileName(owner_->absFileName())) + ' ' +
1288                         quoteName(relFile);
1289                 doVCCommand(cmd, path);
1290                 if (newFile.exists())
1291                         newFile.removeFile();
1292                 return string();
1293         }
1294         return log;
1295 }
1296
1297
1298 bool SVN::copyEnabled()
1299 {
1300         return true;
1301 }
1302
1303
1304 string SVN::copy(support::FileName const & newFile, string const & msg)
1305 {
1306         // svn copy does not require a log message, since it does not commit.
1307         // In LyX we commit immediately afterwards, otherwise it could be
1308         // confusing to the user to have an uncommitted file.
1309         FileName path(owner_->filePath());
1310         string relFile(to_utf8(newFile.relPath(path.absFileName())));
1311         string cmd("svn copy -q " + quoteName(onlyFileName(owner_->absFileName())) +
1312                    ' ' + quoteName(relFile));
1313         if (doVCCommand(cmd, path))
1314                 return string();
1315         vector<support::FileName> f(1, newFile);
1316         string log;
1317         if (checkIn(f, msg, log) == LyXVC::VCSuccess)
1318                 return log;
1319         return string();
1320 }
1321
1322
1323 LyXVC::CommandResult SVN::checkIn(string const & msg, string & log)
1324 {
1325         vector<support::FileName> f(1, owner_->fileName());
1326         return checkIn(f, msg, log);
1327 }
1328
1329
1330 LyXVC::CommandResult
1331 SVN::checkIn(vector<support::FileName> const & f, string const & msg, string & log)
1332 {
1333         TempFile tempfile("lyxvcout");
1334         FileName tmpf = tempfile.name();
1335         if (tmpf.empty()){
1336                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1337                 log = N_("Error: Could not generate logfile.");
1338                 return LyXVC::ErrorBefore;
1339         }
1340
1341         ostringstream os;
1342         os << "svn commit -m \"" << msg << '"';
1343         for (size_t i = 0; i < f.size(); ++i)
1344                 os << ' ' << quoteName(f[i].onlyFileName());
1345         os << " > " << quoteName(tmpf.toFilesystemEncoding());
1346         LyXVC::CommandResult ret =
1347                 doVCCommand(os.str(), FileName(owner_->filePath())) ?
1348                         LyXVC::ErrorCommand : LyXVC::VCSuccess;
1349
1350         string res = scanLogFile(tmpf, log);
1351         if (!res.empty()) {
1352                 frontend::Alert::error(_("Revision control error."),
1353                                 _("Error when committing to repository.\n"
1354                                 "You have to manually resolve the problem.\n"
1355                                 "LyX will reopen the document after you press OK."));
1356                 ret = LyXVC::ErrorCommand;
1357         }
1358         else
1359                 if (!fileLock(false, tmpf, log))
1360                         ret = LyXVC::ErrorCommand;
1361
1362         if (!log.empty())
1363                 log.insert(0, "SVN: ");
1364         if (ret == LyXVC::VCSuccess && log.empty())
1365                 log = "SVN: Proceeded";
1366         return ret;
1367 }
1368
1369
1370 bool SVN::checkInEnabled()
1371 {
1372         if (locked_mode_)
1373                 return isLocked();
1374         else
1375                 return true;
1376 }
1377
1378
1379 bool SVN::isCheckInWithConfirmation()
1380 {
1381         // FIXME one day common getDiff and perhaps OpMode for all backends
1382
1383         TempFile tempfile("lyxvcout");
1384         FileName tmpf = tempfile.name();
1385         if (tmpf.empty()) {
1386                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1387                 return true;
1388         }
1389
1390         doVCCommandCall("svn diff " + quoteName(owner_->absFileName())
1391                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1392                 FileName(owner_->filePath()));
1393
1394         docstring diff = tmpf.fileContents("UTF-8");
1395
1396         if (diff.empty())
1397                 return false;
1398
1399         return true;
1400 }
1401
1402
1403 // FIXME Correctly return code should be checked instead of this.
1404 // This would need another solution than just plain startscript.
1405 // Hint from Andre': QProcess::readAllStandardError()...
1406 string SVN::scanLogFile(FileName const & f, string & status)
1407 {
1408         ifstream ifs(f.toFilesystemEncoding().c_str());
1409         string line;
1410
1411         while (ifs) {
1412                 getline(ifs, line);
1413                 LYXERR(Debug::LYXVC, line << '\n');
1414                 if (!line.empty())
1415                         status += line + "; ";
1416                 if (prefixIs(line, "C ") || prefixIs(line, "CU ")
1417                                          || contains(line, "Commit failed")) {
1418                         ifs.close();
1419                         return line;
1420                 }
1421                 if (contains(line, "svn:needs-lock")) {
1422                         ifs.close();
1423                         return line;
1424                 }
1425         }
1426         ifs.close();
1427         return string();
1428 }
1429
1430
1431 bool SVN::fileLock(bool lock, FileName const & tmpf, string &status)
1432 {
1433         if (!locked_mode_ || (isLocked() == lock))
1434                 return true;
1435
1436         string const arg = lock ? "lock " : "unlock ";
1437         doVCCommand("svn "+ arg + quoteName(onlyFileName(owner_->absFileName()))
1438                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1439                     FileName(owner_->filePath()));
1440
1441         // Lock error messages go unfortunately on stderr and are unreachable this way.
1442         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1443         string line;
1444         while (ifs) {
1445                 getline(ifs, line);
1446                 if (!line.empty()) status += line + "; ";
1447         }
1448         ifs.close();
1449
1450         if (isLocked() == lock)
1451                 return true;
1452
1453         if (lock)
1454                 frontend::Alert::error(_("Revision control error."),
1455                         _("Error while acquiring write lock.\n"
1456                         "Another user is most probably editing\n"
1457                         "the current document now!\n"
1458                         "Also check the access to the repository."));
1459         else
1460                 frontend::Alert::error(_("Revision control error."),
1461                         _("Error while releasing write lock.\n"
1462                         "Check the access to the repository."));
1463         return false;
1464 }
1465
1466
1467 string SVN::checkOut()
1468 {
1469         TempFile tempfile("lyxvcout");
1470         FileName tmpf = tempfile.name();
1471         if (tmpf.empty()) {
1472                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1473                 return N_("Error: Could not generate logfile.");
1474         }
1475
1476         doVCCommand("svn update --non-interactive " + quoteName(onlyFileName(owner_->absFileName()))
1477                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1478                     FileName(owner_->filePath()));
1479
1480         string log;
1481         string const res = scanLogFile(tmpf, log);
1482         if (!res.empty())
1483                 frontend::Alert::error(_("Revision control error."),
1484                         bformat(_("Error when updating from repository.\n"
1485                                 "You have to manually resolve the conflicts NOW!\n'%1$s'.\n\n"
1486                                 "After pressing OK, LyX will try to reopen the resolved document."),
1487                         from_local8bit(res)));
1488
1489         fileLock(true, tmpf, log);
1490
1491         return log.empty() ? string() : "SVN: " + log;
1492 }
1493
1494
1495 bool SVN::checkOutEnabled()
1496 {
1497         if (locked_mode_)
1498                 return !isLocked();
1499         else
1500                 return true;
1501 }
1502
1503
1504 string SVN::repoUpdate()
1505 {
1506         TempFile tempfile("lyxvcout");
1507         FileName tmpf = tempfile.name();
1508         if (tmpf.empty()) {
1509                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1510                 return N_("Error: Could not generate logfile.");
1511         }
1512
1513         doVCCommand("svn diff " + quoteName(owner_->filePath())
1514                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1515                 FileName(owner_->filePath()));
1516         docstring res = tmpf.fileContents("UTF-8");
1517         if (!res.empty()) {
1518                 LYXERR(Debug::LYXVC, "Diff detected:\n" << res);
1519                 docstring const file = from_utf8(owner_->filePath());
1520                 docstring text = bformat(_("There were detected changes "
1521                                 "in the working directory:\n%1$s\n\n"
1522                                 "In case of file conflict version of the local directory files "
1523                                 "will be preferred."
1524                                 "\n\nContinue?"), file);
1525                 int ret = frontend::Alert::prompt(_("Changes detected"),
1526                                 text, 0, 1, _("&Yes"), _("&No"), _("View &Log ..."));
1527                 if (ret == 2) {
1528                         dispatch(FuncRequest(LFUN_DIALOG_SHOW, "file " + tmpf.absFileName()));
1529                         ret = frontend::Alert::prompt(_("Changes detected"),
1530                                 text, 0, 1, _("&Yes"), _("&No"));
1531                         hideDialogs("file", 0);
1532                 }
1533                 if (ret == 1)
1534                         return string();
1535         }
1536
1537         // Reverting looks too harsh, see bug #6255.
1538         // doVCCommand("svn revert -R " + quoteName(owner_->filePath())
1539         // + " > " + quoteName(tmpf.toFilesystemEncoding()),
1540         // FileName(owner_->filePath()));
1541         // res = "Revert log:\n" + tmpf.fileContents("UTF-8");
1542         doVCCommand("svn update --accept mine-full " + quoteName(owner_->filePath())
1543                 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1544                 FileName(owner_->filePath()));
1545         res += "Update log:\n" + tmpf.fileContents("UTF-8");
1546
1547         LYXERR(Debug::LYXVC, res);
1548         return to_utf8(res);
1549 }
1550
1551
1552 bool SVN::repoUpdateEnabled()
1553 {
1554         return true;
1555 }
1556
1557
1558 string SVN::lockingToggle()
1559 {
1560         TempFile tempfile("lyxvcout");
1561         FileName tmpf = tempfile.name();
1562         if (tmpf.empty()) {
1563                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1564                 return N_("Error: Could not generate logfile.");
1565         }
1566
1567         int ret = doVCCommand("svn proplist " + quoteName(onlyFileName(owner_->absFileName()))
1568                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1569                     FileName(owner_->filePath()));
1570         if (ret)
1571                 return string();
1572
1573         string log;
1574         string res = scanLogFile(tmpf, log);
1575         bool locking = contains(res, "svn:needs-lock");
1576         if (!locking)
1577                 ret = doVCCommand("svn propset svn:needs-lock ON "
1578                     + quoteName(onlyFileName(owner_->absFileName()))
1579                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1580                     FileName(owner_->filePath()));
1581         else
1582                 ret = doVCCommand("svn propdel svn:needs-lock "
1583                     + quoteName(onlyFileName(owner_->absFileName()))
1584                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1585                     FileName(owner_->filePath()));
1586         if (ret)
1587                 return string();
1588
1589         frontend::Alert::warning(_("SVN File Locking"),
1590                 (locking ? _("Locking property unset.") : _("Locking property set.")) + '\n'
1591                 + _("Do not forget to commit the locking property into the repository."),
1592                 true);
1593
1594         return string("SVN: ") + (locking ?
1595                 N_("Locking property unset.") : N_("Locking property set."));
1596 }
1597
1598
1599 bool SVN::lockingToggleEnabled()
1600 {
1601         return true;
1602 }
1603
1604
1605 bool SVN::revert()
1606 {
1607         // Reverts to the version in SVN repository and
1608         // gets the updated version from the repository.
1609         string const fil = quoteName(onlyFileName(owner_->absFileName()));
1610
1611         if (doVCCommand("svn revert -q " + fil,
1612                     FileName(owner_->filePath())))
1613                 return false;
1614         owner_->markClean();
1615         return true;
1616 }
1617
1618
1619 bool SVN::isRevertWithConfirmation()
1620 {
1621         //FIXME owner && diff
1622         return true;
1623 }
1624
1625
1626 void SVN::undoLast()
1627 {
1628         // merge the current with the previous version
1629         // in a reverse patch kind of way, so that the
1630         // result is to revert the last changes.
1631         lyxerr << "Sorry, not implemented." << endl;
1632 }
1633
1634
1635 bool SVN::undoLastEnabled()
1636 {
1637         return false;
1638 }
1639
1640
1641 string SVN::revisionInfo(LyXVC::RevisionInfo const info)
1642 {
1643         if (info == LyXVC::Tree) {
1644                 if (rev_tree_cache_.empty())
1645                         if (!getTreeRevisionInfo())
1646                                 rev_tree_cache_ = "?";
1647                 if (rev_tree_cache_ == "?")
1648                         return string();
1649
1650                 return rev_tree_cache_;
1651         }
1652
1653         // fill the rest of the attributes for a single file
1654         if (rev_file_cache_.empty())
1655                 if (!getFileRevisionInfo())
1656                         rev_file_cache_ = "?";
1657
1658         switch (info) {
1659                 case LyXVC::File:
1660                         if (rev_file_cache_ == "?")
1661                                 return string();
1662                         return rev_file_cache_;
1663                 case LyXVC::Author:
1664                         return rev_author_cache_;
1665                 case LyXVC::Date:
1666                         return rev_date_cache_;
1667                 case LyXVC::Time:
1668                         return rev_time_cache_;
1669                 default: ;
1670
1671         }
1672
1673         return string();
1674 }
1675
1676
1677 bool SVN::getFileRevisionInfo()
1678 {
1679         TempFile tempfile("lyxvcout");
1680         FileName tmpf = tempfile.name();
1681         if (tmpf.empty()) {
1682                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1683                 return false;
1684         }
1685
1686         doVCCommand("svn info --xml " + quoteName(onlyFileName(owner_->absFileName()))
1687                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1688                     FileName(owner_->filePath()));
1689
1690         if (tmpf.empty())
1691                 return false;
1692
1693         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1694         string line;
1695         // commit log part
1696         bool c = false;
1697         string rev;
1698
1699         while (ifs) {
1700                 getline(ifs, line);
1701                 LYXERR(Debug::LYXVC, line);
1702                 if (prefixIs(line, "<commit"))
1703                         c = true;
1704                 if (c && prefixIs(line, "   revision=\"") && suffixIs(line, "\">")) {
1705                         string l1 = subst(line, "revision=\"", "");
1706                         string l2 = trim(subst(l1, "\">", ""));
1707                         if (isStrInt(l2))
1708                                 rev_file_cache_ = rev = l2;
1709                 }
1710                 if (c && prefixIs(line, "<author>") && suffixIs(line, "</author>")) {
1711                         string l1 = subst(line, "<author>", "");
1712                         string l2 = subst(l1, "</author>", "");
1713                         rev_author_cache_ = l2;
1714                 }
1715                 if (c && prefixIs(line, "<date>") && suffixIs(line, "</date>")) {
1716                         string l1 = subst(line, "<date>", "");
1717                         string l2 = subst(l1, "</date>", "");
1718                         l2 = split(l2, l1, 'T');
1719                         rev_date_cache_ = l1;
1720                         l2 = split(l2, l1, '.');
1721                         rev_time_cache_ = l1;
1722                 }
1723         }
1724
1725         ifs.close();
1726         return !rev.empty();
1727 }
1728
1729
1730 bool SVN::getTreeRevisionInfo()
1731 {
1732         TempFile tempfile("lyxvcout");
1733         FileName tmpf = tempfile.name();
1734         if (tmpf.empty()) {
1735                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1736                 return false;
1737         }
1738
1739         doVCCommand("svnversion -n . > " + quoteName(tmpf.toFilesystemEncoding()),
1740                     FileName(owner_->filePath()));
1741
1742         if (tmpf.empty())
1743                 return false;
1744
1745         // only first line in case something bad happens.
1746         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1747         string line;
1748         getline(ifs, line);
1749         ifs.close();
1750
1751         rev_tree_cache_ = line;
1752         return !line.empty();
1753 }
1754
1755
1756 void SVN::getLog(FileName const & tmpf)
1757 {
1758         doVCCommand("svn log " + quoteName(onlyFileName(owner_->absFileName()))
1759                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
1760                     FileName(owner_->filePath()));
1761 }
1762
1763
1764 bool SVN::prepareFileRevision(string const & revis, string & f)
1765 {
1766         if (!isStrInt(revis))
1767                 return false;
1768
1769         int rev = convert<int>(revis);
1770         if (rev <= 0)
1771                 if (!getFileRevisionInfo())
1772                         return false;
1773         if (rev == 0)
1774                 rev = convert<int>(rev_file_cache_);
1775         // go back for minus rev
1776         else if (rev < 0) {
1777                 rev = rev + convert<int>(rev_file_cache_);
1778                 if (rev < 1)
1779                         return false;
1780         }
1781
1782         string revname = convert<string>(rev);
1783         TempFile tempfile("lyxvcrev_" + revname + '_');
1784         tempfile.setAutoRemove(false);
1785         FileName tmpf = tempfile.name();
1786         if (tmpf.empty()) {
1787                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1788                 return false;
1789         }
1790
1791         doVCCommand("svn cat -r " + revname + ' '
1792                       + quoteName(onlyFileName(owner_->absFileName()))
1793                       + " > " + quoteName(tmpf.toFilesystemEncoding()),
1794                 FileName(owner_->filePath()));
1795         tmpf.refresh();
1796         if (tmpf.isFileEmpty())
1797                 return false;
1798
1799         f = tmpf.absFileName();
1800         return true;
1801 }
1802
1803
1804 bool SVN::prepareFileRevisionEnabled()
1805 {
1806         return true;
1807 }
1808
1809
1810
1811 bool SVN::toggleReadOnlyEnabled()
1812 {
1813         return false;
1814 }
1815
1816
1817 /////////////////////////////////////////////////////////////////////
1818 //
1819 // GIT
1820 //
1821 /////////////////////////////////////////////////////////////////////
1822
1823 GIT::GIT(FileName const & m, Buffer * b) : VCS(b)
1824 {
1825         // Here we know that the buffer file is either already in GIT or
1826         // about to be registered
1827         master_ = m;
1828         scanMaster();
1829 }
1830
1831
1832 FileName const GIT::findFile(FileName const & file)
1833 {
1834         // First we check the existence of repository meta data.
1835         if (!VCS::checkparentdirs(file, ".git")) {
1836                 LYXERR(Debug::LYXVC, "Cannot find GIT meta data for " << file);
1837                 return FileName();
1838         }
1839
1840         // Now we check the status of the file.
1841         TempFile tempfile("lyxvcout");
1842         FileName tmpf = tempfile.name();
1843         if (tmpf.empty()) {
1844                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1845                 return FileName();
1846         }
1847
1848         string const fname = onlyFileName(file.absFileName());
1849         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under git control for `"
1850                         << fname << '\'');
1851         doVCCommandCall("git ls-files " +
1852                         quoteName(fname) + " > " +
1853                         quoteName(tmpf.toFilesystemEncoding()),
1854                         file.onlyPath());
1855         tmpf.refresh();
1856         bool found = !tmpf.isFileEmpty();
1857         LYXERR(Debug::LYXVC, "GIT control: " << (found ? "enabled" : "disabled"));
1858         return found ? file : FileName();
1859 }
1860
1861
1862 void GIT::scanMaster()
1863 {
1864         // vcstatus code is somewhat superflous,
1865         // until we want to implement read-only toggle for git.
1866         vcstatus = NOLOCKING;
1867 }
1868
1869
1870 bool GIT::retrieve(FileName const & file)
1871 {
1872         LYXERR(Debug::LYXVC, "LyXVC::GIT: retrieve.\n\t" << file);
1873         // The caller ensures that file does not exist, so no need to check that.
1874         return doVCCommandCall("git checkout -q " + quoteName(file.onlyFileName()),
1875                                file.onlyPath()) == 0;
1876 }
1877
1878
1879 void GIT::registrer(string const & /*msg*/)
1880 {
1881         doVCCommand("git add " + quoteName(onlyFileName(owner_->absFileName())),
1882                     FileName(owner_->filePath()));
1883 }
1884
1885
1886 bool GIT::renameEnabled()
1887 {
1888         return true;
1889 }
1890
1891
1892 string GIT::rename(support::FileName const & newFile, string const & msg)
1893 {
1894         // git mv does not require a log message, since it does not commit.
1895         // In LyX we commit immediately afterwards, otherwise it could be
1896         // confusing to the user to have two uncommitted files.
1897         FileName path(owner_->filePath());
1898         string relFile(to_utf8(newFile.relPath(path.absFileName())));
1899         string cmd("git mv " + quoteName(onlyFileName(owner_->absFileName())) +
1900                    ' ' + quoteName(relFile));
1901         if (doVCCommand(cmd, path)) {
1902                 cmd = "git checkout -q " +
1903                         quoteName(onlyFileName(owner_->absFileName())) + ' ' +
1904                         quoteName(relFile);
1905                 doVCCommand(cmd, path);
1906                 if (newFile.exists())
1907                         newFile.removeFile();
1908                 return string();
1909         }
1910         vector<support::FileName> f;
1911         f.push_back(owner_->fileName());
1912         f.push_back(newFile);
1913         string log;
1914         if (checkIn(f, msg, log) != LyXVC::VCSuccess) {
1915                 cmd = "git checkout -q " +
1916                         quoteName(onlyFileName(owner_->absFileName())) + ' ' +
1917                         quoteName(relFile);
1918                 doVCCommand(cmd, path);
1919                 if (newFile.exists())
1920                         newFile.removeFile();
1921                 return string();
1922         }
1923         return log;
1924 }
1925
1926
1927 bool GIT::copyEnabled()
1928 {
1929         return false;
1930 }
1931
1932
1933 string GIT::copy(support::FileName const & /*newFile*/, string const & /*msg*/)
1934 {
1935         // git does not support copy with history preservation
1936         return string();
1937 }
1938
1939
1940 LyXVC::CommandResult GIT::checkIn(string const & msg, string & log)
1941 {
1942         vector<support::FileName> f(1, owner_->fileName());
1943         return checkIn(f, msg, log);
1944 }
1945
1946
1947 LyXVC::CommandResult
1948 GIT::checkIn(vector<support::FileName> const & f, string const & msg, string & log)
1949 {
1950         TempFile tempfile("lyxvcout");
1951         FileName tmpf = tempfile.name();
1952         if (tmpf.empty()){
1953                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1954                 log = N_("Error: Could not generate logfile.");
1955                 return LyXVC::ErrorBefore;
1956         }
1957
1958         ostringstream os;
1959         os << "git commit -m \"" << msg << '"';
1960         for (size_t i = 0; i < f.size(); ++i)
1961                 os << ' ' << quoteName(f[i].onlyFileName());
1962         os << " > " << quoteName(tmpf.toFilesystemEncoding());
1963         LyXVC::CommandResult ret =
1964                 doVCCommand(os.str(), FileName(owner_->filePath())) ?
1965                         LyXVC::ErrorCommand : LyXVC::VCSuccess;
1966
1967         string res = scanLogFile(tmpf, log);
1968         if (!res.empty()) {
1969                 frontend::Alert::error(_("Revision control error."),
1970                                 _("Error when committing to repository.\n"
1971                                 "You have to manually resolve the problem.\n"
1972                                 "LyX will reopen the document after you press OK."));
1973                 ret = LyXVC::ErrorCommand;
1974         }
1975
1976         if (!log.empty())
1977                 log.insert(0, "GIT: ");
1978         if (ret == LyXVC::VCSuccess && log.empty())
1979                 log = "GIT: Proceeded";
1980         return ret;
1981 }
1982
1983
1984 bool GIT::checkInEnabled()
1985 {
1986         return true;
1987 }
1988
1989
1990 bool GIT::isCheckInWithConfirmation()
1991 {
1992         // FIXME one day common getDiff and perhaps OpMode for all backends
1993
1994         TempFile tempfile("lyxvcout");
1995         FileName tmpf = tempfile.name();
1996         if (tmpf.empty()) {
1997                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1998                 return true;
1999         }
2000
2001         doVCCommandCall("git diff " + quoteName(owner_->absFileName())
2002                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
2003                 FileName(owner_->filePath()));
2004
2005         docstring diff = tmpf.fileContents("UTF-8");
2006
2007         if (diff.empty())
2008                 return false;
2009
2010         return true;
2011 }
2012
2013
2014 // FIXME Correctly return code should be checked instead of this.
2015 // This would need another solution than just plain startscript.
2016 // Hint from Andre': QProcess::readAllStandardError()...
2017 string GIT::scanLogFile(FileName const & f, string & status)
2018 {
2019         ifstream ifs(f.toFilesystemEncoding().c_str());
2020         string line;
2021
2022         while (ifs) {
2023                 getline(ifs, line);
2024                 LYXERR(Debug::LYXVC, line << "\n");
2025                 if (!line.empty())
2026                         status += line + "; ";
2027                 if (prefixIs(line, "C ") || prefixIs(line, "CU ")
2028                                          || contains(line, "Commit failed")) {
2029                         ifs.close();
2030                         return line;
2031                 }
2032         }
2033         ifs.close();
2034         return string();
2035 }
2036
2037
2038 string GIT::checkOut()
2039 {
2040         return string();
2041 }
2042
2043
2044 bool GIT::checkOutEnabled()
2045 {
2046         return false;
2047 }
2048
2049
2050 string GIT::repoUpdate()
2051 {
2052         return string();
2053 }
2054
2055
2056 bool GIT::repoUpdateEnabled()
2057 {
2058         return false;
2059 }
2060
2061
2062 string GIT::lockingToggle()
2063 {
2064         return string();
2065 }
2066
2067
2068 bool GIT::lockingToggleEnabled()
2069 {
2070         return false;
2071 }
2072
2073
2074 bool GIT::revert()
2075 {
2076         // Reverts to the version in GIT repository and
2077         // gets the updated version from the repository.
2078         string const fil = quoteName(onlyFileName(owner_->absFileName()));
2079
2080         if (doVCCommand("git checkout -q " + fil,
2081                     FileName(owner_->filePath())))
2082                 return false;
2083         owner_->markClean();
2084         return true;
2085 }
2086
2087
2088 bool GIT::isRevertWithConfirmation()
2089 {
2090         //FIXME owner && diff
2091         return true;
2092 }
2093
2094
2095 void GIT::undoLast()
2096 {
2097         // merge the current with the previous version
2098         // in a reverse patch kind of way, so that the
2099         // result is to revert the last changes.
2100         lyxerr << "Sorry, not implemented." << endl;
2101 }
2102
2103
2104 bool GIT::undoLastEnabled()
2105 {
2106         return false;
2107 }
2108
2109
2110 string GIT::revisionInfo(LyXVC::RevisionInfo const info)
2111 {
2112         if (info == LyXVC::Tree) {
2113                 if (rev_tree_cache_.empty())
2114                         if (!getTreeRevisionInfo())
2115                                 rev_tree_cache_ = "?";
2116                 if (rev_tree_cache_ == "?")
2117                         return string();
2118
2119                 return rev_tree_cache_;
2120         }
2121
2122         // fill the rest of the attributes for a single file
2123         if (rev_file_cache_.empty())
2124                 if (!getFileRevisionInfo())
2125                         rev_file_cache_ = "?";
2126
2127         switch (info) {
2128                 case LyXVC::File:
2129                         if (rev_file_cache_ == "?")
2130                                 return string();
2131                         return rev_file_cache_;
2132                 case LyXVC::Author:
2133                         return rev_author_cache_;
2134                 case LyXVC::Date:
2135                         return rev_date_cache_;
2136                 case LyXVC::Time:
2137                         return rev_time_cache_;
2138                 default: ;
2139
2140         }
2141
2142         return string();
2143 }
2144
2145
2146 bool GIT::getFileRevisionInfo()
2147 {
2148         TempFile tempfile("lyxvcout");
2149         FileName tmpf = tempfile.name();
2150         if (tmpf.empty()) {
2151                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
2152                 return false;
2153         }
2154
2155         doVCCommand("git log -n 1 --pretty=format:%H%n%an%n%ai " + quoteName(onlyFileName(owner_->absFileName()))
2156                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
2157                     FileName(owner_->filePath()));
2158
2159         if (tmpf.empty())
2160                 return false;
2161
2162         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
2163
2164         if (ifs)
2165                 getline(ifs, rev_file_cache_);
2166         if (ifs)
2167                 getline(ifs, rev_author_cache_);
2168         if (ifs) {
2169                 string line;
2170                 getline(ifs, line);
2171                 rev_time_cache_ = split(line, rev_date_cache_, ' ');
2172         }
2173
2174         ifs.close();
2175         return !rev_file_cache_.empty();
2176 }
2177
2178
2179 bool GIT::getTreeRevisionInfo()
2180 {
2181         TempFile tempfile("lyxvcout");
2182         FileName tmpf = tempfile.name();
2183         if (tmpf.empty()) {
2184                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
2185                 return false;
2186         }
2187
2188         doVCCommand("git describe --abbrev --dirty --long > " + quoteName(tmpf.toFilesystemEncoding()),
2189                     FileName(owner_->filePath()));
2190
2191         if (tmpf.empty())
2192                 return false;
2193
2194         // only first line in case something bad happens.
2195         ifstream ifs(tmpf.toFilesystemEncoding().c_str());
2196         getline(ifs, rev_tree_cache_);
2197         ifs.close();
2198
2199         return !rev_tree_cache_.empty();
2200 }
2201
2202
2203 void GIT::getLog(FileName const & tmpf)
2204 {
2205         doVCCommand("git log " + quoteName(onlyFileName(owner_->absFileName()))
2206                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
2207                     FileName(owner_->filePath()));
2208 }
2209
2210
2211 //at this moment we don't accept revision SHA, but just number of revision steps back
2212 //GUI and infrastucture needs to be changed first
2213 bool GIT::prepareFileRevision(string const & revis, string & f)
2214 {
2215         // anything positive means we got hash, not "0" or minus revision
2216         int rev = 1;
2217
2218         // hash is rarely number and should be long
2219         if (isStrInt(revis) && revis.length()<20)
2220                 rev = convert<int>(revis);
2221
2222         // revision and filename
2223         string pointer;
2224
2225         // go back for "minus" revisions
2226         if (rev <= 0)
2227                 pointer = "HEAD~" + convert<string>(-rev);
2228         // normal hash
2229         else
2230                 pointer = revis;
2231
2232         pointer += ':';
2233
2234         TempFile tempfile("lyxvcrev_" + revis + '_');
2235         tempfile.setAutoRemove(false);
2236         FileName tmpf = tempfile.name();
2237         if (tmpf.empty()) {
2238                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
2239                 return false;
2240         }
2241
2242         doVCCommand("git show " + pointer + "./"
2243                       + quoteName(onlyFileName(owner_->absFileName()))
2244                       + " > " + quoteName(tmpf.toFilesystemEncoding()),
2245                 FileName(owner_->filePath()));
2246         tmpf.refresh();
2247         if (tmpf.isFileEmpty())
2248                 return false;
2249
2250         f = tmpf.absFileName();
2251         return true;
2252 }
2253
2254
2255 bool GIT::prepareFileRevisionEnabled()
2256 {
2257         return true;
2258 }
2259
2260
2261 bool GIT::toggleReadOnlyEnabled()
2262 {
2263         return true;
2264 }
2265
2266
2267 } // namespace lyx