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