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