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