X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FVCBackend.cpp;h=5bbc1746b417ae0dd2720dbb4c575a4d8076e637;hb=b8550d11e836e857967a31250e6ca248b2d43f82;hp=664e86c16d40e628e69fd8e490a34d1f91c91875;hpb=1f94bc302aaa6cf9c81febb71785a3c940c9739c;p=lyx.git diff --git a/src/VCBackend.cpp b/src/VCBackend.cpp index 664e86c16d..5bbc1746b4 100644 --- a/src/VCBackend.cpp +++ b/src/VCBackend.cpp @@ -4,6 +4,7 @@ * Licence details can be found in the file COPYING. * * \author Lars Gullik Bjønnes + * \author Pavel Sanda * * Full author contact details are available in file CREDITS. */ @@ -12,9 +13,13 @@ #include "VCBackend.h" #include "Buffer.h" +#include "LyX.h" +#include "FuncRequest.h" #include "frontends/alert.h" +#include "frontends/Application.h" +#include "support/convert.h" #include "support/debug.h" #include "support/filetools.h" #include "support/gettext.h" @@ -36,18 +41,24 @@ using boost::smatch; namespace lyx { -int VCS::doVCCommandCall(string const & cmd, FileName const & path){ +int VCS::doVCCommandCall(string const & cmd, FileName const & path) +{ LYXERR(Debug::LYXVC, "doVCCommandCall: " << cmd); Systemcall one; support::PathChanger p(path); - return one.startscript(Systemcall::Wait, cmd); + return one.startscript(Systemcall::Wait, cmd, false); } + int VCS::doVCCommand(string const & cmd, FileName const & path) { - owner_->setBusy(true); + if (owner_) + owner_->setBusy(true); + int const ret = doVCCommandCall(cmd, path); - owner_->setBusy(false); + + if (owner_) + owner_->setBusy(false); if (ret) frontend::Alert::error(_("Revision control error."), bformat(_("Some problem occured while running the command:\n" @@ -76,7 +87,7 @@ FileName const RCS::findFile(FileName const & file) FileName tmp(file.absFilename() + ",v"); LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp); if (tmp.isReadableFile()) { - LYXERR(Debug::LYXVC, "Yes " << file << " is under rcs."); + LYXERR(Debug::LYXVC, "Yes, " << file << " is under rcs."); return tmp; } @@ -84,7 +95,7 @@ FileName const RCS::findFile(FileName const & file) tmp = FileName(addName(addPath(onlyPath(file.absFilename()), "RCS"), file.absFilename()) + ",v"); LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp); if (tmp.isReadableFile()) { - LYXERR(Debug::LYXVC, "Yes " << file << " it is under rcs."); + LYXERR(Debug::LYXVC, "Yes, " << file << " is under rcs."); return tmp; } @@ -181,11 +192,13 @@ string RCS::checkIn(string const & msg) return ret ? string() : "RCS: Proceeded"; } + bool RCS::checkInEnabled() { return owner_ && !owner_->isReadonly(); } + string RCS::checkOut() { owner_->markClean(); @@ -201,9 +214,35 @@ bool RCS::checkOutEnabled() } +string RCS::repoUpdate() +{ + lyxerr << "Sorry, not implemented." << endl; + return string(); +} + + +bool RCS::repoUpdateEnabled() +{ + return false; +} + + +string RCS::lockingToggle() +{ + lyxerr << "Sorry, not implemented." << endl; + return string(); +} + + +bool RCS::lockingToggleEnabled() +{ + return false; +} + + void RCS::revert() { - doVCCommand("co -f -u" + version() + " " + doVCCommand("co -f -u" + version_ + " " + quoteName(onlyFilename(owner_->absFileName())), FileName(owner_->filePath())); // We ignore changes and just reload! @@ -214,7 +253,7 @@ void RCS::revert() void RCS::undoLast() { LYXERR(Debug::LYXVC, "LyXVC: undoLast"); - doVCCommand("rcs -o" + version() + " " + doVCCommand("rcs -o" + version_ + " " + quoteName(onlyFilename(owner_->absFileName())), FileName(owner_->filePath())); } @@ -235,6 +274,66 @@ void RCS::getLog(FileName const & tmpf) bool RCS::toggleReadOnlyEnabled() +{ + // This got broken somewhere along lfuns dispatch reorganization. + // reloadBuffer would be needed after this, but thats problematic + // since we are inside Buffer::dispatch. + // return true; + return false; +} + +string RCS::revisionInfo(LyXVC::RevisionInfo const info) +{ + if (info == LyXVC::File) + return version_; + return string(); +} + + +bool RCS::prepareFileRevision(string const &revis, string & f) +{ + string rev = revis; + + if (isStrInt(rev)) { + int back = convert(rev); + if (back > 0) + return false; + if (back == 0) + rev = version_; + // we care about the last number from revision string + // in case of backward indexing + if (back < 0) { + string cur, base; + cur = rsplit(version_, base , '.' ); + if (!isStrInt(cur)) + return false; + int want = convert(cur) + back; + if (want <= 0) + return false; + + rev = base + "." + convert(want); + } + } + + FileName tmpf = FileName::tempName("lyxvcrev"); + if (tmpf.empty()) { + LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf); + return N_("Error: Could not generate logfile."); + } + + doVCCommand("co -p" + rev + " " + + quoteName(onlyFilename(owner_->absFileName())) + + " > " + quoteName(tmpf.toFilesystemEncoding()), + FileName(owner_->filePath())); + if (tmpf.isFileEmpty()) + return false; + + f = tmpf.absFilename(); + return true; +} + + +bool RCS::prepareFileRevisionEnabled() { return true; } @@ -313,7 +412,7 @@ void CVS::scanMaster() locker_ = "Unlocked"; vcstatus = UNLOCKED; } else { - // Here we should also to some more checking + // Here we should also do some more checking // to see if there are conflicts or not. locker_ = "Locked"; vcstatus = LOCKED; @@ -352,7 +451,7 @@ string CVS::checkOut() // cvs update or perhaps for cvs this should be a noop // we need to detect conflict (eg "C" in output) // before we can do this. - lyxerr << "Sorry not implemented." << endl; + lyxerr << "Sorry, not implemented." << endl; return string(); } @@ -363,6 +462,32 @@ bool CVS::checkOutEnabled() } +string CVS::repoUpdate() +{ + lyxerr << "Sorry, not implemented." << endl; + return string(); +} + + +bool CVS::repoUpdateEnabled() +{ + return false; +} + + +string CVS::lockingToggle() +{ + lyxerr << "Sorry, not implemented." << endl; + return string(); +} + + +bool CVS::lockingToggleEnabled() +{ + return false; +} + + void CVS::revert() { // Reverts to the version in CVS repository and @@ -385,7 +510,7 @@ void CVS::undoLast() // merge the current with the previous version // in a reverse patch kind of way, so that the // result is to revert the last changes. - lyxerr << "Sorry not implemented." << endl; + lyxerr << "Sorry, not implemented." << endl; } @@ -402,11 +527,33 @@ void CVS::getLog(FileName const & tmpf) FileName(owner_->filePath())); } + bool CVS::toggleReadOnlyEnabled() { return false; } + +string CVS::revisionInfo(LyXVC::RevisionInfo const info) +{ + if (info == LyXVC::File) + return version_; + return string(); +} + + +bool CVS::prepareFileRevision(string const &, string &) +{ + return false; +} + + +bool CVS::prepareFileRevisionEnabled() +{ + return false; +} + + ///////////////////////////////////////////////////////////////////// // // SVN @@ -415,8 +562,10 @@ bool CVS::toggleReadOnlyEnabled() SVN::SVN(FileName const & m, FileName const & f) { + owner_ = 0; master_ = m; file_ = f; + locked_mode_ = 0; scanMaster(); } @@ -449,10 +598,55 @@ FileName const SVN::findFile(FileName const & file) void SVN::scanMaster() { - // if we want some locking under svn - // we need different infrastructure around - locker_ = "Unlocked"; - vcstatus = UNLOCKED; + // vcstatus code is somewhat superflous, until we want + // to implement read-only toggle for svn. + vcstatus = NOLOCKING; + if (checkLockMode()) { + if (isLocked()) { + vcstatus = LOCKED; + } else { + vcstatus = UNLOCKED; + } + } +} + + +bool SVN::checkLockMode() +{ + FileName tmpf = FileName::tempName("lyxvcout"); + if (tmpf.empty()){ + LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf); + return N_("Error: Could not generate logfile."); + } + + LYXERR(Debug::LYXVC, "Detecting locking mode..."); + if (doVCCommandCall("svn proplist " + quoteName(file_.onlyFileName()) + + " > " + quoteName(tmpf.toFilesystemEncoding()), + file_.onlyPath())) + return false; + + ifstream ifs(tmpf.toFilesystemEncoding().c_str()); + string line; + bool ret = false; + + while (ifs) { + getline(ifs, line); + LYXERR(Debug::LYXVC, line); + if (contains(line, "svn:needs-lock")) + ret = true; + } + LYXERR(Debug::LYXVC, "Locking enabled: " << ret); + ifs.close(); + locked_mode_ = ret; + return ret; + +} + + +bool SVN::isLocked() const +{ + file_.refresh(); + return !file_.isReadOnly(); } @@ -480,19 +674,26 @@ string SVN::checkIn(string const & msg) string res = scanLogFile(tmpf, log); if (!res.empty()) frontend::Alert::error(_("Revision control error."), - _("Error when commiting to repository.\n" + _("Error when committing to repository.\n" "You have to manually resolve the problem.\n" - "After pressing OK, LyX will reopen the document.")); + "LyX will reopen the document after you press OK.")); + else + fileLock(false, tmpf, log); + tmpf.erase(); - return "SVN: " + log; + return log.empty() ? string() : "SVN: " + log; } bool SVN::checkInEnabled() { - return true; + if (locked_mode_) + return isLocked(); + else + return true; } + // FIXME Correctly return code should be checked instead of this. // This would need another solution than just plain startscript. // Hint from Andre': QProcess::readAllStandardError()... @@ -503,9 +704,15 @@ string SVN::scanLogFile(FileName const & f, string & status) while (ifs) { getline(ifs, line); - lyxerr << line << "\n"; - if (!line.empty()) status += line + "; "; - if (prefixIs(line, "C ") || contains(line, "Commit failed")) { + LYXERR(Debug::LYXVC, line << "\n"); + if (!line.empty()) + status += line + "; "; + if (prefixIs(line, "C ") || prefixIs(line, "CU ") + || contains(line, "Commit failed")) { + ifs.close(); + return line; + } + if (contains(line, "svn:needs-lock")) { ifs.close(); return line; } @@ -515,6 +722,38 @@ string SVN::scanLogFile(FileName const & f, string & status) } +void SVN::fileLock(bool lock, FileName const & tmpf, string &status) +{ + if (!locked_mode_ || (isLocked() == lock)) + return; + + string const arg = lock ? "lock " : "unlock "; + doVCCommand("svn "+ arg + quoteName(onlyFilename(owner_->absFileName())) + + " > " + quoteName(tmpf.toFilesystemEncoding()), + FileName(owner_->filePath())); + + // Lock error messages go unfortunately on stderr and are unreachible this way. + ifstream ifs(tmpf.toFilesystemEncoding().c_str()); + string line; + while (ifs) { + getline(ifs, line); + if (!line.empty()) status += line + "; "; + } + ifs.close(); + + if (!isLocked() && lock) + frontend::Alert::error(_("Revision control error."), + _("Error while acquiring write lock.\n" + "Another user is most probably editing\n" + "the current document now!\n" + "Also check the access to the repository.")); + if (isLocked() && !lock) + frontend::Alert::error(_("Revision control error."), + _("Error while releasing write lock.\n" + "Check the access to the repository.")); +} + + string SVN::checkOut() { FileName tmpf = FileName::tempName("lyxvcout"); @@ -523,24 +762,132 @@ string SVN::checkOut() return N_("Error: Could not generate logfile."); } - doVCCommand("svn update " + quoteName(onlyFilename(owner_->absFileName())) + doVCCommand("svn update --non-interactive " + quoteName(onlyFilename(owner_->absFileName())) + " > " + quoteName(tmpf.toFilesystemEncoding()), FileName(owner_->filePath())); string log; - string res = scanLogFile(tmpf, log); + string const res = scanLogFile(tmpf, log); if (!res.empty()) frontend::Alert::error(_("Revision control error."), bformat(_("Error when updating from repository.\n" "You have to manually resolve the conflicts NOW!\n'%1$s'.\n\n" - "After pressing OK, LyX will try to reopen resolved document."), + "After pressing OK, LyX will try to reopen the resolved document."), from_local8bit(res))); + + fileLock(true, tmpf, log); + tmpf.erase(); - return "SVN: " + log; + return log.empty() ? string() : "SVN: " + log; } bool SVN::checkOutEnabled() +{ + if (locked_mode_) + return !isLocked(); + else + return true; +} + + +string SVN::repoUpdate() +{ + FileName tmpf = FileName::tempName("lyxvcout"); + if (tmpf.empty()) { + LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf); + return N_("Error: Could not generate logfile."); + } + + doVCCommand("svn diff " + quoteName(owner_->filePath()) + + " > " + quoteName(tmpf.toFilesystemEncoding()), + FileName(owner_->filePath())); + docstring res = tmpf.fileContents("UTF-8"); + if (!res.empty()) { + LYXERR(Debug::LYXVC, "Diff detected:\n" << res); + docstring const file = from_utf8(owner_->filePath()); + docstring text = bformat(_("There were detected changes " + "in the working directory:\n%1$s\n\n" + "In case of file conflict version of the local directory files " + "will be preferred." + "\n\nContinue?"), file); + int ret = frontend::Alert::prompt(_("Changes detected"), + text, 0, 1, _("&Yes"), _("&No"), _("View &Log ...")); + if (ret == 2 ) { + dispatch(FuncRequest(LFUN_DIALOG_SHOW, "file " + tmpf.absFilename())); + ret = frontend::Alert::prompt(_("Changes detected"), + text, 0, 1, _("&Yes"), _("&No")); + hideDialogs("file", 0); + } + if (ret == 1 ) { + tmpf.erase(); + return string(); + } + } + + // Reverting looks too harsh, see bug #6255. + // doVCCommand("svn revert -R " + quoteName(owner_->filePath()) + // + " > " + quoteName(tmpf.toFilesystemEncoding()), + // FileName(owner_->filePath())); + // res = "Revert log:\n" + tmpf.fileContents("UTF-8"); + doVCCommand("svn update --accept mine-full " + quoteName(owner_->filePath()) + + " > " + quoteName(tmpf.toFilesystemEncoding()), + FileName(owner_->filePath())); + res += "Update log:\n" + tmpf.fileContents("UTF-8"); + + LYXERR(Debug::LYXVC, res); + tmpf.erase(); + return to_utf8(res); +} + + +bool SVN::repoUpdateEnabled() +{ + return true; +} + + +string SVN::lockingToggle() +{ + FileName tmpf = FileName::tempName("lyxvcout"); + if (tmpf.empty()) { + LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf); + return N_("Error: Could not generate logfile."); + } + + int ret = doVCCommand("svn proplist " + quoteName(onlyFilename(owner_->absFileName())) + + " > " + quoteName(tmpf.toFilesystemEncoding()), + FileName(owner_->filePath())); + if (ret) + return string(); + + string log; + string res = scanLogFile(tmpf, log); + bool locking = contains(res, "svn:needs-lock"); + if (!locking) + ret = doVCCommand("svn propset svn:needs-lock ON " + + quoteName(onlyFilename(owner_->absFileName())) + + " > " + quoteName(tmpf.toFilesystemEncoding()), + FileName(owner_->filePath())); + else + ret = doVCCommand("svn propdel svn:needs-lock " + + quoteName(onlyFilename(owner_->absFileName())) + + " > " + quoteName(tmpf.toFilesystemEncoding()), + FileName(owner_->filePath())); + if (ret) + return string(); + + tmpf.erase(); + frontend::Alert::warning(_("VCN File Locking"), + (locking ? _("Locking property unset.") : _("Locking property set.")) + "\n" + + _("Do not forget to commit the locking property into the repository."), + true); + + return string("SVN: ") + N_("Locking property set."); +} + + +bool SVN::lockingToggleEnabled() { return true; } @@ -563,7 +910,7 @@ void SVN::undoLast() // merge the current with the previous version // in a reverse patch kind of way, so that the // result is to revert the last changes. - lyxerr << "Sorry not implemented." << endl; + lyxerr << "Sorry, not implemented." << endl; } @@ -573,6 +920,121 @@ bool SVN::undoLastEnabled() } +string SVN::revisionInfo(LyXVC::RevisionInfo const info) +{ + if (info == LyXVC::Tree) { + if (rev_tree_cache_.empty()) + if (!getTreeRevisionInfo()) + rev_tree_cache_ = "?"; + if (rev_tree_cache_ == "?") + return string(); + + return rev_tree_cache_; + } + + // fill the rest of the attributes for a single file + if (rev_file_cache_.empty()) + if (!getFileRevisionInfo()) + rev_file_cache_ = "?"; + + switch (info) { + case LyXVC::File: + if (rev_file_cache_ == "?") + return string(); + return rev_file_cache_; + case LyXVC::Author: + return rev_author_cache_; + case LyXVC::Date: + return rev_date_cache_; + case LyXVC::Time: + return rev_time_cache_; + default: ; + + } + + return string(); +} + + +bool SVN::getFileRevisionInfo() +{ + FileName tmpf = FileName::tempName("lyxvcout"); + if (tmpf.empty()) { + LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf); + return N_("Error: Could not generate logfile."); + } + + doVCCommand("svn info --xml " + quoteName(onlyFilename(owner_->absFileName())) + + " > " + quoteName(tmpf.toFilesystemEncoding()), + FileName(owner_->filePath())); + + if (tmpf.empty()) + return false; + + ifstream ifs(tmpf.toFilesystemEncoding().c_str()); + string line; + // commit log part + bool c = false; + string rev; + + while (ifs) { + getline(ifs, line); + LYXERR(Debug::LYXVC, line); + if (prefixIs(line, "")) { + string l1 = subst(line, "revision=\"", ""); + string l2 = trim(subst(l1, "\">", "")); + if (isStrInt(l2)) + rev_file_cache_ = rev = l2; + } + if (c && prefixIs(line, "") && suffixIs(line, "")) { + string l1 = subst(line, "", ""); + string l2 = subst(l1, "", ""); + rev_author_cache_ = l2; + } + if (c && prefixIs(line, "") && suffixIs(line, "")) { + string l1 = subst(line, "", ""); + string l2 = subst(l1, "", ""); + l2 = split(l2, l1, 'T'); + rev_date_cache_ = l1; + l2 = split(l2, l1, '.'); + rev_time_cache_ = l1; + } + } + + ifs.close(); + tmpf.erase(); + return !rev.empty(); +} + + +bool SVN::getTreeRevisionInfo() +{ + FileName tmpf = FileName::tempName("lyxvcout"); + if (tmpf.empty()) { + LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf); + return N_("Error: Could not generate logfile."); + } + + doVCCommand("svnversion -n . > " + quoteName(tmpf.toFilesystemEncoding()), + FileName(owner_->filePath())); + + if (tmpf.empty()) + return false; + + // only first line in case something bad happens. + ifstream ifs(tmpf.toFilesystemEncoding().c_str()); + string line; + getline(ifs, line); + ifs.close(); + tmpf.erase(); + + rev_tree_cache_ = line; + return !line.empty(); +} + + void SVN::getLog(FileName const & tmpf) { doVCCommand("svn log " + quoteName(onlyFilename(owner_->absFileName())) @@ -581,6 +1043,49 @@ void SVN::getLog(FileName const & tmpf) } +bool SVN::prepareFileRevision(string const & revis, string & f) +{ + if (!isStrInt(revis)) + return false; + + int rev = convert(revis); + if (rev <= 0) + if (!getFileRevisionInfo()) + return false; + if (rev == 0) + rev = convert(rev_file_cache_); + // go back for minus rev + else if (rev < 0) { + rev = rev + convert(rev_file_cache_); + if (rev < 1) + return false; + } + + FileName tmpf = FileName::tempName("lyxvcrev"); + if (tmpf.empty()) { + LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf); + return N_("Error: Could not generate logfile."); + } + + doVCCommand("svn cat -r " + convert(rev) + " " + + quoteName(onlyFilename(owner_->absFileName())) + + " > " + quoteName(tmpf.toFilesystemEncoding()), + FileName(owner_->filePath())); + if (tmpf.isFileEmpty()) + return false; + + f = tmpf.absFilename(); + return true; +} + + +bool SVN::prepareFileRevisionEnabled() +{ + return true; +} + + + bool SVN::toggleReadOnlyEnabled() { return false;