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::repoSynchro()
214 lyxerr
<< "Sorry, not implemented." << endl
;
219 bool RCS::repoSynchroEnabled()
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::repoSynchro()
402 lyxerr
<< "Sorry, not implemented." << endl
;
407 bool CVS::repoSynchroEnabled()
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::repoSynchro()
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"
724 "Synchronizing with repository will discard "
725 "any uncommitted changes in the directory:\n%1$s"
726 "\n\nContinue?"), file
);
727 int const ret
= frontend::Alert::prompt(_("Changes detected"),
728 text
, 0, 1, _("&Yes"), _("&No"));
735 doVCCommand("svn revert -R " + quoteName(owner_
->filePath())
736 + " > " + quoteName(tmpf
.toFilesystemEncoding()),
737 FileName(owner_
->filePath()));
738 res
= "Revert log:\n" + tmpf
.fileContents("UTF-8");
739 doVCCommand("svn update " + quoteName(owner_
->filePath())
740 + " > " + quoteName(tmpf
.toFilesystemEncoding()),
741 FileName(owner_
->filePath()));
742 res
+= "Update log:\n" + tmpf
.fileContents("UTF-8");
744 LYXERR(Debug::LYXVC
, res
);
750 bool SVN::repoSynchroEnabled()
756 string
SVN::lockingToggle()
758 FileName tmpf
= FileName::tempName("lyxvcout");
760 LYXERR(Debug::LYXVC
, "Could not generate logfile " << tmpf
);
761 return N_("Error: Could not generate logfile.");
764 int ret
= doVCCommand("svn proplist " + quoteName(onlyFilename(owner_
->absFileName()))
765 + " > " + quoteName(tmpf
.toFilesystemEncoding()),
766 FileName(owner_
->filePath()));
771 string res
= scanLogFile(tmpf
, log
);
772 bool locking
= contains(res
, "svn:needs-lock");
774 ret
= doVCCommand("svn propset svn:needs-lock ON "
775 + quoteName(onlyFilename(owner_
->absFileName()))
776 + " > " + quoteName(tmpf
.toFilesystemEncoding()),
777 FileName(owner_
->filePath()));
779 ret
= doVCCommand("svn propdel svn:needs-lock "
780 + quoteName(onlyFilename(owner_
->absFileName()))
781 + " > " + quoteName(tmpf
.toFilesystemEncoding()),
782 FileName(owner_
->filePath()));
787 frontend::Alert::warning(_("VCN File Locking"),
788 (locking
? _("Locking property unset.") : _("Locking property set.")) + "\n"
789 + _("Do not forget to commit the locking property into the repository."),
792 return string("SVN: ") + N_("Locking property set.");
796 bool SVN::lockingToggleEnabled()
804 // Reverts to the version in CVS repository and
805 // gets the updated version from the repository.
806 string
const fil
= quoteName(onlyFilename(owner_
->absFileName()));
808 doVCCommand("svn revert -q " + fil
,
809 FileName(owner_
->filePath()));
816 // merge the current with the previous version
817 // in a reverse patch kind of way, so that the
818 // result is to revert the last changes.
819 lyxerr
<< "Sorry, not implemented." << endl
;
823 bool SVN::undoLastEnabled()
829 void SVN::getLog(FileName
const & tmpf
)
831 doVCCommand("svn log " + quoteName(onlyFilename(owner_
->absFileName()))
832 + " > " + quoteName(tmpf
.toFilesystemEncoding()),
833 FileName(owner_
->filePath()));
837 bool SVN::toggleReadOnlyEnabled()