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