3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * Full author contact details are available in file CREDITS.
13 #include "VCBackend.h"
16 #include "frontends/alert.h"
18 #include "support/debug.h"
19 #include "support/filetools.h"
20 #include "support/gettext.h"
21 #include "support/lstrings.h"
22 #include "support/Path.h"
23 #include "support/Systemcall.h"
25 #include <boost/regex.hpp>
30 using namespace lyx::support;
33 using boost::regex_match;
39 int VCS::doVCCommandCall(string const & cmd, FileName const & path)
41 LYXERR(Debug::LYXVC, "doVCCommandCall: " << cmd);
43 support::PathChanger p(path);
44 return one.startscript(Systemcall::Wait, cmd);
48 int VCS::doVCCommand(string const & cmd, FileName const & path)
51 owner_->setBusy(true);
53 int const ret = doVCCommandCall(cmd, path);
56 owner_->setBusy(false);
58 frontend::Alert::error(_("Revision control error."),
59 bformat(_("Some problem occured while running the command:\n"
66 /////////////////////////////////////////////////////////////////////
70 /////////////////////////////////////////////////////////////////////
72 RCS::RCS(FileName const & m)
79 FileName const RCS::findFile(FileName const & file)
81 // Check if *,v exists.
82 FileName tmp(file.absFilename() + ",v");
83 LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp);
84 if (tmp.isReadableFile()) {
85 LYXERR(Debug::LYXVC, "Yes, " << file << " is under rcs.");
89 // Check if RCS/*,v exists.
90 tmp = FileName(addName(addPath(onlyPath(file.absFilename()), "RCS"), file.absFilename()) + ",v");
91 LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp);
92 if (tmp.isReadableFile()) {
93 LYXERR(Debug::LYXVC, "Yes, " << file << " is under rcs.");
101 void RCS::retrieve(FileName const & file)
103 LYXERR(Debug::LYXVC, "LyXVC::RCS: retrieve.\n\t" << file);
104 doVCCommandCall("co -q -r " + quoteName(file.toFilesystemEncoding()),
109 void RCS::scanMaster()
114 LYXERR(Debug::LYXVC, "LyXVC::RCS: scanMaster: " << master_);
116 ifstream ifs(master_.toFilesystemEncoding().c_str());
119 bool read_enough = false;
121 while (!read_enough && ifs >> token) {
122 LYXERR(Debug::LYXVC, "LyXVC::scanMaster: current lex text: `"
127 else if (token == "head") {
131 tmv = rtrim(tmv, ";");
133 LYXERR(Debug::LYXVC, "LyXVC: version found to be " << tmv);
134 } else if (contains(token, "access")
135 || contains(token, "symbols")
136 || contains(token, "strict")) {
138 } else if (contains(token, "locks")) {
140 if (contains(token, ';')) {
141 locker_ = "Unlocked";
150 s1 = rtrim(tmpt, ";");
151 // tmp is now in the format <user>:<version>
152 s1 = split(s1, s2, ':');
153 // s2 is user, and s1 is version
154 if (s1 == version_) {
159 } while (!contains(tmpt, ';'));
161 } else if (token == "comment") {
162 // we don't need to read any further than this.
166 LYXERR(Debug::LYXVC, "LyXVC::scanMaster(): unexpected token");
172 void RCS::registrer(string const & msg)
174 string cmd = "ci -q -u -i -t-\"";
177 cmd += quoteName(onlyFilename(owner_->absFileName()));
178 doVCCommand(cmd, FileName(owner_->filePath()));
182 string RCS::checkIn(string const & msg)
184 int ret = doVCCommand("ci -q -u -m\"" + msg + "\" "
185 + quoteName(onlyFilename(owner_->absFileName())),
186 FileName(owner_->filePath()));
187 return ret ? string() : "RCS: Proceeded";
191 bool RCS::checkInEnabled()
193 return owner_ && !owner_->isReadonly();
197 string RCS::checkOut()
200 int ret = doVCCommand("co -q -l " + quoteName(onlyFilename(owner_->absFileName())),
201 FileName(owner_->filePath()));
202 return ret ? string() : "RCS: Proceeded";
206 bool RCS::checkOutEnabled()
208 return owner_ && owner_->isReadonly();
212 string RCS::repoUpdate()
214 lyxerr << "Sorry, not implemented." << endl;
219 bool RCS::repoUpdateEnabled()
225 string RCS::lockingToggle()
227 lyxerr << "Sorry, not implemented." << endl;
232 bool RCS::lockingToggleEnabled()
240 doVCCommand("co -f -u" + version() + " "
241 + quoteName(onlyFilename(owner_->absFileName())),
242 FileName(owner_->filePath()));
243 // We ignore changes and just reload!
250 LYXERR(Debug::LYXVC, "LyXVC: undoLast");
251 doVCCommand("rcs -o" + version() + " "
252 + quoteName(onlyFilename(owner_->absFileName())),
253 FileName(owner_->filePath()));
257 bool RCS::undoLastEnabled()
263 void RCS::getLog(FileName const & tmpf)
265 doVCCommand("rlog " + quoteName(onlyFilename(owner_->absFileName()))
266 + " > " + quoteName(tmpf.toFilesystemEncoding()),
267 FileName(owner_->filePath()));
271 bool RCS::toggleReadOnlyEnabled()
277 /////////////////////////////////////////////////////////////////////
281 /////////////////////////////////////////////////////////////////////
283 CVS::CVS(FileName const & m, FileName const & f)
291 FileName const CVS::findFile(FileName const & file)
293 // First we look for the CVS/Entries in the same dir
294 // where we have file.
295 FileName const entries(onlyPath(file.absFilename()) + "/CVS/Entries");
296 string const tmpf = '/' + onlyFilename(file.absFilename()) + '/';
297 LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under cvs in `" << entries
298 << "' for `" << tmpf << '\'');
299 if (entries.isReadableFile()) {
300 // Ok we are at least in a CVS dir. Parse the CVS/Entries
301 // and see if we can find this file. We do a fast and
303 ifstream ifs(entries.toFilesystemEncoding().c_str());
305 while (getline(ifs, line)) {
306 LYXERR(Debug::LYXVC, "\tEntries: " << line);
307 if (contains(line, tmpf))
315 void CVS::scanMaster()
317 LYXERR(Debug::LYXVC, "LyXVC::CVS: scanMaster. \n Checking: " << master_);
318 // Ok now we do the real scan...
319 ifstream ifs(master_.toFilesystemEncoding().c_str());
320 string tmpf = '/' + onlyFilename(file_.absFilename()) + '/';
321 LYXERR(Debug::LYXVC, "\tlooking for `" << tmpf << '\'');
323 static regex const reg("/(.*)/(.*)/(.*)/(.*)/(.*)");
324 while (getline(ifs, line)) {
325 LYXERR(Debug::LYXVC, "\t line: " << line);
326 if (contains(line, tmpf)) {
327 // Ok extract the fields.
330 regex_match(line, sm, reg);
332 //sm[0]; // whole matched string
334 version_ = sm.str(2);
335 string const file_date = sm.str(3);
338 //sm[5]; // tag or tagdate
339 // FIXME: must double check file is stattable/existing
340 time_t mod = file_.lastModified();
341 string mod_date = rtrim(asctime(gmtime(&mod)), "\n");
342 LYXERR(Debug::LYXVC, "Date in Entries: `" << file_date
343 << "'\nModification date of file: `" << mod_date << '\'');
344 //FIXME this whole locking bussiness is not working under cvs and the machinery
345 // conforms to the ci usage, not cvs.
346 if (file_date == mod_date) {
347 locker_ = "Unlocked";
350 // Here we should also to some more checking
351 // to see if there are conflicts or not.
361 void CVS::registrer(string const & msg)
363 doVCCommand("cvs -q add -m \"" + msg + "\" "
364 + quoteName(onlyFilename(owner_->absFileName())),
365 FileName(owner_->filePath()));
369 string CVS::checkIn(string const & msg)
371 int ret = doVCCommand("cvs -q commit -m \"" + msg + "\" "
372 + quoteName(onlyFilename(owner_->absFileName())),
373 FileName(owner_->filePath()));
374 return ret ? string() : "CVS: Proceeded";
378 bool CVS::checkInEnabled()
384 string CVS::checkOut()
386 // cvs update or perhaps for cvs this should be a noop
387 // we need to detect conflict (eg "C" in output)
388 // before we can do this.
389 lyxerr << "Sorry, not implemented." << endl;
394 bool CVS::checkOutEnabled()
400 string CVS::repoUpdate()
402 lyxerr << "Sorry, not implemented." << endl;
407 bool CVS::repoUpdateEnabled()
413 string CVS::lockingToggle()
415 lyxerr << "Sorry, not implemented." << endl;
420 bool CVS::lockingToggleEnabled()
428 // Reverts to the version in CVS repository and
429 // gets the updated version from the repository.
430 string const fil = quoteName(onlyFilename(owner_->absFileName()));
431 // This is sensitive operation, so at lest some check about
432 // existence of cvs program and its file
433 if (doVCCommand("cvs log "+ fil, FileName(owner_->filePath())))
435 FileName f(owner_->absFileName());
437 doVCCommand("cvs update " + fil,
438 FileName(owner_->filePath()));
445 // merge the current with the previous version
446 // in a reverse patch kind of way, so that the
447 // result is to revert the last changes.
448 lyxerr << "Sorry, not implemented." << endl;
452 bool CVS::undoLastEnabled()
458 void CVS::getLog(FileName const & tmpf)
460 doVCCommand("cvs log " + quoteName(onlyFilename(owner_->absFileName()))
461 + " > " + quoteName(tmpf.toFilesystemEncoding()),
462 FileName(owner_->filePath()));
466 bool CVS::toggleReadOnlyEnabled()
471 /////////////////////////////////////////////////////////////////////
475 /////////////////////////////////////////////////////////////////////
477 SVN::SVN(FileName const & m, FileName const & f)
487 FileName const SVN::findFile(FileName const & file)
489 // First we look for the .svn/entries in the same dir
490 // where we have file.
491 FileName const entries(onlyPath(file.absFilename()) + "/.svn/entries");
492 string const tmpf = onlyFilename(file.absFilename());
493 LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under svn in `" << entries
494 << "' for `" << tmpf << '\'');
495 if (entries.isReadableFile()) {
496 // Ok we are at least in a SVN dir. Parse the .svn/entries
497 // and see if we can find this file. We do a fast and
499 ifstream ifs(entries.toFilesystemEncoding().c_str());
500 string line, oldline;
501 while (getline(ifs, line)) {
502 if (line == "dir" || line == "file")
503 LYXERR(Debug::LYXVC, "\tEntries: " << oldline);
504 if (oldline == tmpf && line == "file")
513 void SVN::scanMaster()
516 vcstatus = NOLOCKING;
517 if (checkLockMode()) {
522 locker_ = "Unlocked";
529 bool SVN::checkLockMode()
531 FileName tmpf = FileName::tempName("lyxvcout");
533 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
534 return N_("Error: Could not generate logfile.");
537 LYXERR(Debug::LYXVC, "Detecting locking mode...");
538 if (doVCCommandCall("svn proplist " + quoteName(file_.onlyFileName())
539 + " > " + quoteName(tmpf.toFilesystemEncoding()),
543 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
549 LYXERR(Debug::LYXVC, line);
550 if (contains(line, "svn:needs-lock"))
553 LYXERR(Debug::LYXVC, "Locking enabled: " << ret);
561 bool SVN::isLocked() const
564 FileName file(file_.absFilename());
565 return !file.isReadOnly();
569 void SVN::registrer(string const & /*msg*/)
571 doVCCommand("svn add -q " + quoteName(onlyFilename(owner_->absFileName())),
572 FileName(owner_->filePath()));
576 string SVN::checkIn(string const & msg)
578 FileName tmpf = FileName::tempName("lyxvcout");
580 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
581 return N_("Error: Could not generate logfile.");
584 doVCCommand("svn commit -m \"" + msg + "\" "
585 + quoteName(onlyFilename(owner_->absFileName()))
586 + " > " + quoteName(tmpf.toFilesystemEncoding()),
587 FileName(owner_->filePath()));
590 string res = scanLogFile(tmpf, log);
592 frontend::Alert::error(_("Revision control error."),
593 _("Error when committing to repository.\n"
594 "You have to manually resolve the problem.\n"
595 "After pressing OK, LyX will reopen the document."));
597 fileLock(false, tmpf, log);
600 return "SVN: " + log;
604 bool SVN::checkInEnabled()
613 // FIXME Correctly return code should be checked instead of this.
614 // This would need another solution than just plain startscript.
615 // Hint from Andre': QProcess::readAllStandardError()...
616 string SVN::scanLogFile(FileName const & f, string & status)
618 ifstream ifs(f.toFilesystemEncoding().c_str());
623 lyxerr << line << "\n";
624 if (!line.empty()) status += line + "; ";
625 if (prefixIs(line, "C ") || contains(line, "Commit failed")) {
629 if (contains(line, "svn:needs-lock")) {
639 void SVN::fileLock(bool lock, FileName const & tmpf, string &status)
641 if (!locked_mode_ || (isLocked() == lock))
644 string arg = lock ? "lock " : "unlock ";
645 doVCCommand("svn "+ arg + quoteName(onlyFilename(owner_->absFileName()))
646 + " > " + quoteName(tmpf.toFilesystemEncoding()),
647 FileName(owner_->filePath()));
649 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
653 if (!line.empty()) status += line + "; ";
657 if (!isLocked() && lock)
658 frontend::Alert::error(_("Revision control error."),
659 _("Error when acquiring write lock.\n"
660 "Most probably another user is editing\n"
661 "the current document now!\n"
662 "Also check the access to the repository."));
663 if (isLocked() && !lock)
664 frontend::Alert::error(_("Revision control error."),
665 _("Error when releasing write lock.\n"
666 "Check the access to the repository."));
670 string SVN::checkOut()
672 FileName tmpf = FileName::tempName("lyxvcout");
674 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
675 return N_("Error: Could not generate logfile.");
678 doVCCommand("svn update " + quoteName(onlyFilename(owner_->absFileName()))
679 + " > " + quoteName(tmpf.toFilesystemEncoding()),
680 FileName(owner_->filePath()));
683 string res = scanLogFile(tmpf, log);
685 frontend::Alert::error(_("Revision control error."),
686 bformat(_("Error when updating from repository.\n"
687 "You have to manually resolve the conflicts NOW!\n'%1$s'.\n\n"
688 "After pressing OK, LyX will try to reopen resolved document."),
689 from_local8bit(res)));
691 fileLock(true, tmpf, log);
694 return "SVN: " + log;
698 bool SVN::checkOutEnabled()
707 string SVN::repoUpdate()
709 FileName tmpf = FileName::tempName("lyxvcout");
711 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
712 return N_("Error: Could not generate logfile.");
715 doVCCommand("svn diff " + quoteName(owner_->filePath())
716 + " > " + quoteName(tmpf.toFilesystemEncoding()),
717 FileName(owner_->filePath()));
718 docstring res = tmpf.fileContents("UTF-8");
720 LYXERR(Debug::LYXVC, "Diff detected:\n" << res);
721 docstring const file = from_utf8(owner_->filePath());
722 docstring text = bformat(_("There were detected changes "
723 "in the working directory:\n%1$s\n\n"
724 "In case of file conflict version of the local directory files "
726 "\n\nContinue?"), file);
727 int const ret = frontend::Alert::prompt(_("Changes detected"),
728 text, 0, 1, _("&Yes"), _("&No"));
735 // Reverting looks too harsh, see bug #6255.
736 // doVCCommand("svn revert -R " + quoteName(owner_->filePath())
737 // + " > " + quoteName(tmpf.toFilesystemEncoding()),
738 // FileName(owner_->filePath()));
739 // res = "Revert log:\n" + tmpf.fileContents("UTF-8");
740 doVCCommand("svn update --accept mine-full " + quoteName(owner_->filePath())
741 + " > " + quoteName(tmpf.toFilesystemEncoding()),
742 FileName(owner_->filePath()));
743 res += "Update log:\n" + tmpf.fileContents("UTF-8");
745 LYXERR(Debug::LYXVC, res);
751 bool SVN::repoUpdateEnabled()
757 string SVN::lockingToggle()
759 FileName tmpf = FileName::tempName("lyxvcout");
761 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
762 return N_("Error: Could not generate logfile.");
765 int ret = doVCCommand("svn proplist " + quoteName(onlyFilename(owner_->absFileName()))
766 + " > " + quoteName(tmpf.toFilesystemEncoding()),
767 FileName(owner_->filePath()));
772 string res = scanLogFile(tmpf, log);
773 bool locking = contains(res, "svn:needs-lock");
775 ret = doVCCommand("svn propset svn:needs-lock ON "
776 + quoteName(onlyFilename(owner_->absFileName()))
777 + " > " + quoteName(tmpf.toFilesystemEncoding()),
778 FileName(owner_->filePath()));
780 ret = doVCCommand("svn propdel svn:needs-lock "
781 + quoteName(onlyFilename(owner_->absFileName()))
782 + " > " + quoteName(tmpf.toFilesystemEncoding()),
783 FileName(owner_->filePath()));
788 frontend::Alert::warning(_("VCN File Locking"),
789 (locking ? _("Locking property unset.") : _("Locking property set.")) + "\n"
790 + _("Do not forget to commit the locking property into the repository."),
793 return string("SVN: ") + N_("Locking property set.");
797 bool SVN::lockingToggleEnabled()
805 // Reverts to the version in CVS repository and
806 // gets the updated version from the repository.
807 string const fil = quoteName(onlyFilename(owner_->absFileName()));
809 doVCCommand("svn revert -q " + fil,
810 FileName(owner_->filePath()));
817 // merge the current with the previous version
818 // in a reverse patch kind of way, so that the
819 // result is to revert the last changes.
820 lyxerr << "Sorry, not implemented." << endl;
824 bool SVN::undoLastEnabled()
830 void SVN::getLog(FileName const & tmpf)
832 doVCCommand("svn log " + quoteName(onlyFilename(owner_->absFileName()))
833 + " > " + quoteName(tmpf.toFilesystemEncoding()),
834 FileName(owner_->filePath()));
838 bool SVN::toggleReadOnlyEnabled()