Another minor change, but this should almost get us to the point that we
[lyx.git] / src / VCBackend.cpp
blob19dbb9638547653fadbf4b15c0a0bebb7efcdb89
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.
6 * \author Lars Gullik Bjønnes
8 * Full author contact details are available in file CREDITS.
9 */
11 #include <config.h>
13 #include "VCBackend.h"
14 #include "Buffer.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>
27 #include <fstream>
29 using namespace std;
30 using namespace lyx::support;
32 using boost::regex;
33 using boost::regex_match;
34 using boost::smatch;
36 namespace lyx {
39 int VCS::doVCCommandCall(string const & cmd, FileName const & path)
41 LYXERR(Debug::LYXVC, "doVCCommandCall: " << cmd);
42 Systemcall one;
43 support::PathChanger p(path);
44 return one.startscript(Systemcall::Wait, cmd);
48 int VCS::doVCCommand(string const & cmd, FileName const & path)
50 if (owner_)
51 owner_->setBusy(true);
53 int const ret = doVCCommandCall(cmd, path);
55 if (owner_)
56 owner_->setBusy(false);
57 if (ret)
58 frontend::Alert::error(_("Revision control error."),
59 bformat(_("Some problem occured while running the command:\n"
60 "'%1$s'."),
61 from_utf8(cmd)));
62 return ret;
66 /////////////////////////////////////////////////////////////////////
68 // RCS
70 /////////////////////////////////////////////////////////////////////
72 RCS::RCS(FileName const & m)
74 master_ = m;
75 scanMaster();
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.");
86 return tmp;
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.");
94 return tmp;
97 return FileName();
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()),
105 FileName());
109 void RCS::scanMaster()
111 if (master_.empty())
112 return;
114 LYXERR(Debug::LYXVC, "LyXVC::RCS: scanMaster: " << master_);
116 ifstream ifs(master_.toFilesystemEncoding().c_str());
118 string token;
119 bool read_enough = false;
121 while (!read_enough && ifs >> token) {
122 LYXERR(Debug::LYXVC, "LyXVC::scanMaster: current lex text: `"
123 << token << '\'');
125 if (token.empty())
126 continue;
127 else if (token == "head") {
128 // get version here
129 string tmv;
130 ifs >> tmv;
131 tmv = rtrim(tmv, ";");
132 version_ = 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")) {
137 // nothing
138 } else if (contains(token, "locks")) {
139 // get locker here
140 if (contains(token, ';')) {
141 locker_ = "Unlocked";
142 vcstatus = UNLOCKED;
143 continue;
145 string tmpt;
146 string s1;
147 string s2;
148 do {
149 ifs >> tmpt;
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_) {
155 locker_ = s2;
156 vcstatus = LOCKED;
157 break;
159 } while (!contains(tmpt, ';'));
161 } else if (token == "comment") {
162 // we don't need to read any further than this.
163 read_enough = true;
164 } else {
165 // unexpected
166 LYXERR(Debug::LYXVC, "LyXVC::scanMaster(): unexpected token");
172 void RCS::registrer(string const & msg)
174 string cmd = "ci -q -u -i -t-\"";
175 cmd += msg;
176 cmd += "\" ";
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()
199 owner_->markClean();
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;
215 return string();
219 bool RCS::repoUpdateEnabled()
221 return false;
225 string RCS::lockingToggle()
227 lyxerr << "Sorry, not implemented." << endl;
228 return string();
232 bool RCS::lockingToggleEnabled()
234 return false;
238 void RCS::revert()
240 doVCCommand("co -f -u" + version() + " "
241 + quoteName(onlyFilename(owner_->absFileName())),
242 FileName(owner_->filePath()));
243 // We ignore changes and just reload!
244 owner_->markClean();
248 void RCS::undoLast()
250 LYXERR(Debug::LYXVC, "LyXVC: undoLast");
251 doVCCommand("rcs -o" + version() + " "
252 + quoteName(onlyFilename(owner_->absFileName())),
253 FileName(owner_->filePath()));
257 bool RCS::undoLastEnabled()
259 return true;
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()
273 return true;
277 /////////////////////////////////////////////////////////////////////
279 // CVS
281 /////////////////////////////////////////////////////////////////////
283 CVS::CVS(FileName const & m, FileName const & f)
285 master_ = m;
286 file_ = f;
287 scanMaster();
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
302 // dirty parse here.
303 ifstream ifs(entries.toFilesystemEncoding().c_str());
304 string line;
305 while (getline(ifs, line)) {
306 LYXERR(Debug::LYXVC, "\tEntries: " << line);
307 if (contains(line, tmpf))
308 return entries;
311 return FileName();
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 << '\'');
322 string line;
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.
328 smatch sm;
330 regex_match(line, sm, reg);
332 //sm[0]; // whole matched string
333 //sm[1]; // filename
334 version_ = sm.str(2);
335 string const file_date = sm.str(3);
337 //sm[4]; // options
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";
348 vcstatus = UNLOCKED;
349 } else {
350 // Here we should also to some more checking
351 // to see if there are conflicts or not.
352 locker_ = "Locked";
353 vcstatus = LOCKED;
355 break;
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()
380 return true;
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;
390 return string();
394 bool CVS::checkOutEnabled()
396 return false;
400 string CVS::repoUpdate()
402 lyxerr << "Sorry, not implemented." << endl;
403 return string();
407 bool CVS::repoUpdateEnabled()
409 return false;
413 string CVS::lockingToggle()
415 lyxerr << "Sorry, not implemented." << endl;
416 return string();
420 bool CVS::lockingToggleEnabled()
422 return false;
426 void CVS::revert()
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())))
434 return;
435 FileName f(owner_->absFileName());
436 f.removeFile();
437 doVCCommand("cvs update " + fil,
438 FileName(owner_->filePath()));
439 owner_->markClean();
443 void CVS::undoLast()
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()
454 return false;
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()
468 return false;
471 /////////////////////////////////////////////////////////////////////
473 // SVN
475 /////////////////////////////////////////////////////////////////////
477 SVN::SVN(FileName const & m, FileName const & f)
479 owner_ = 0;
480 master_ = m;
481 file_ = f;
482 locked_mode_ = 0;
483 scanMaster();
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
498 // dirty parse here.
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")
505 return entries;
506 oldline = line;
509 return FileName();
513 void SVN::scanMaster()
515 locker_.clear();
516 vcstatus = NOLOCKING;
517 if (checkLockMode()) {
518 if (isLocked()) {
519 locker_ = "Locked";
520 vcstatus = LOCKED;
521 } else {
522 locker_ = "Unlocked";
523 vcstatus = LOCKED;
529 bool SVN::checkLockMode()
531 FileName tmpf = FileName::tempName("lyxvcout");
532 if (tmpf.empty()){
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()),
540 file_.onlyPath()))
541 return false;
543 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
544 string line;
545 bool ret = false;
547 while (ifs) {
548 getline(ifs, line);
549 LYXERR(Debug::LYXVC, line);
550 if (contains(line, "svn:needs-lock"))
551 ret = true;
553 LYXERR(Debug::LYXVC, "Locking enabled: " << ret);
554 ifs.close();
555 locked_mode_ = ret;
556 return ret;
561 bool SVN::isLocked() const
563 //refresh file info
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");
579 if (tmpf.empty()){
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()));
589 string log;
590 string res = scanLogFile(tmpf, log);
591 if (!res.empty())
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."));
596 else
597 fileLock(false, tmpf, log);
599 tmpf.erase();
600 return "SVN: " + log;
604 bool SVN::checkInEnabled()
606 if (locked_mode_)
607 return isLocked();
608 else
609 return true;
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());
619 string line;
621 while (ifs) {
622 getline(ifs, line);
623 lyxerr << line << "\n";
624 if (!line.empty()) status += line + "; ";
625 if (prefixIs(line, "C ") || contains(line, "Commit failed")) {
626 ifs.close();
627 return line;
629 if (contains(line, "svn:needs-lock")) {
630 ifs.close();
631 return line;
634 ifs.close();
635 return string();
639 void SVN::fileLock(bool lock, FileName const & tmpf, string &status)
641 if (!locked_mode_ || (isLocked() == lock))
642 return;
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());
650 string line;
651 while (ifs) {
652 getline(ifs, line);
653 if (!line.empty()) status += line + "; ";
655 ifs.close();
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");
673 if (tmpf.empty()) {
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()));
682 string log;
683 string res = scanLogFile(tmpf, log);
684 if (!res.empty())
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);
693 tmpf.erase();
694 return "SVN: " + log;
698 bool SVN::checkOutEnabled()
700 if (locked_mode_)
701 return !isLocked();
702 else
703 return true;
707 string SVN::repoUpdate()
709 FileName tmpf = FileName::tempName("lyxvcout");
710 if (tmpf.empty()) {
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");
719 if (!res.empty()) {
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 "
725 "will be preferred."
726 "\n\nContinue?"), file);
727 int const ret = frontend::Alert::prompt(_("Changes detected"),
728 text, 0, 1, _("&Yes"), _("&No"));
729 if (ret) {
730 tmpf.erase();
731 return string();
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);
746 tmpf.erase();
747 return to_utf8(res);
751 bool SVN::repoUpdateEnabled()
753 return true;
757 string SVN::lockingToggle()
759 FileName tmpf = FileName::tempName("lyxvcout");
760 if (tmpf.empty()) {
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()));
768 if (ret)
769 return string();
771 string log;
772 string res = scanLogFile(tmpf, log);
773 bool locking = contains(res, "svn:needs-lock");
774 if (!locking)
775 ret = doVCCommand("svn propset svn:needs-lock ON "
776 + quoteName(onlyFilename(owner_->absFileName()))
777 + " > " + quoteName(tmpf.toFilesystemEncoding()),
778 FileName(owner_->filePath()));
779 else
780 ret = doVCCommand("svn propdel svn:needs-lock "
781 + quoteName(onlyFilename(owner_->absFileName()))
782 + " > " + quoteName(tmpf.toFilesystemEncoding()),
783 FileName(owner_->filePath()));
784 if (ret)
785 return string();
787 tmpf.erase();
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."),
791 true);
793 return string("SVN: ") + N_("Locking property set.");
797 bool SVN::lockingToggleEnabled()
799 return true;
803 void SVN::revert()
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()));
811 owner_->markClean();
815 void SVN::undoLast()
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()
826 return false;
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()
840 return false;
844 } // namespace lyx