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