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