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