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