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