From bcbe6ae9604a907fd935e4636d3f8d910c4cfe7f Mon Sep 17 00:00:00 2001 From: Stephan Witt Date: Mon, 25 Oct 2010 05:37:04 +0000 Subject: [PATCH] * implementation of status check and use it for checkIn and revert. helps the user to avoid errors and leads to more informative messages. * implementation of diff and use it for the repoUpdate operation. * add the check for merge conflicts in checkOut. git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@35813 a592a061-630c-0410-9148-cb99ea01b6c8 --- src/VCBackend.cpp | 328 ++++++++++++++++++++++++++++++++++++++-------- src/VCBackend.h | 43 +++++- 2 files changed, 316 insertions(+), 55 deletions(-) diff --git a/src/VCBackend.cpp b/src/VCBackend.cpp index 15b6033ae7..208161bb24 100644 --- a/src/VCBackend.cpp +++ b/src/VCBackend.cpp @@ -47,7 +47,7 @@ int VCS::doVCCommandCall(string const & cmd, FileName const & path) } -int VCS::doVCCommand(string const & cmd, FileName const & path) +int VCS::doVCCommand(string const & cmd, FileName const & path, bool reportError) { if (owner_) owner_->setBusy(true); @@ -56,7 +56,7 @@ int VCS::doVCCommand(string const & cmd, FileName const & path) if (owner_) owner_->setBusy(false); - if (ret) + if (ret && reportError) frontend::Alert::error(_("Revision control error."), bformat(_("Some problem occured while running the command:\n" "'%1$s'."), @@ -383,7 +383,8 @@ void CVS::scanMaster() LYXERR(Debug::LYXVC, "LyXVC::CVS: scanMaster. \n Checking: " << master_); // Ok now we do the real scan... ifstream ifs(master_.toFilesystemEncoding().c_str()); - string tmpf = '/' + onlyFileName(file_.absFileName()) + '/'; + string name = onlyFileName(file_.absFileName()); + string tmpf = '/' + name + '/'; LYXERR(Debug::LYXVC, "\tlooking for `" << tmpf << '\''); string line; static regex const reg("/(.*)/(.*)/(.*)/(.*)/(.*)"); @@ -402,21 +403,22 @@ void CVS::scanMaster() //sm[4]; // options //sm[5]; // tag or tagdate - // FIXME: must double check file is stattable/existing - time_t mod = file_.lastModified(); - string mod_date = rtrim(asctime(gmtime(&mod)), "\n"); - LYXERR(Debug::LYXVC, "Date in Entries: `" << file_date - << "'\nModification date of file: `" << mod_date << '\''); - //FIXME this whole locking bussiness is not working under cvs and the machinery - // conforms to the ci usage, not cvs. - if (file_date == mod_date) { - locker_ = "Unlocked"; - vcstatus = UNLOCKED; + if (file_.isReadableFile()) { + time_t mod = file_.lastModified(); + string mod_date = rtrim(asctime(gmtime(&mod)), "\n"); + LYXERR(Debug::LYXVC, "Date in Entries: `" << file_date + << "'\nModification date of file: `" << mod_date << '\''); + if (file_.isReadOnly()) { + // readonly checkout is unlocked + vcstatus = UNLOCKED; + } else { + FileName bdir(addPath(master_.onlyPath().absFileName(),"Base")); + FileName base(addName(bdir.absFileName(),name)); + // if base version is existent "cvs edit" was used to lock + vcstatus = base.isReadableFile() ? LOCKED : NOLOCKING; + } } else { - // Here we should also do some more checking - // to see if there are conflicts or not. - locker_ = "Locked"; - vcstatus = LOCKED; + vcstatus = NOLOCKING; } break; } @@ -424,64 +426,269 @@ void CVS::scanMaster() } -void CVS::registrer(string const & msg) +string const CVS::getTarget(OperationMode opmode) const { - doVCCommand("cvs -q add -m \"" + msg + "\" " - + quoteName(onlyFileName(owner_->absFileName())), - FileName(owner_->filePath())); + switch(opmode) { + case Directory: + return quoteName(owner_->filePath()); + case File: + return quoteName(onlyFileName(owner_->absFileName())); + } + return string(); } +docstring CVS::toString(CvsStatus status) const +{ + switch (status) { + case UpToDate: + return _("Up-to-date"); + case LocallyModified: + return _("Locally Modified"); + case LocallyAdded: + return _("Locally Added"); + case NeedsMerge: + return _("Needs Merge"); + case NeedsCheckout: + return _("Needs Checkout"); + case NoCvsFile: + return _("No CVS file"); + case StatusError: + return _("Cannot retrieve CVS status"); + } + return 0; +} + + +CVS::CvsStatus CVS::getStatus() +{ + FileName tmpf = FileName::tempName("lyxvcout"); + if (tmpf.empty()) { + LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf); + return StatusError; + } + + if (doVCCommand("cvs status " + getTarget(File) + + " > " + quoteName(tmpf.toFilesystemEncoding()), + FileName(owner_->filePath()))) { + tmpf.removeFile(); + return StatusError; + } + + ifstream ifs(tmpf.toFilesystemEncoding().c_str()); + CvsStatus status = NoCvsFile; + + while (ifs) { + string line; + getline(ifs, line); + LYXERR(Debug::LYXVC, line << "\n"); + if (prefixIs(line, "File:")) { + if (contains(line, "Up-to-date")) + status = UpToDate; + else if (contains(line, "Locally Modified")) + status = LocallyModified; + else if (contains(line, "Locally Added")) + status = LocallyAdded; + else if (contains(line, "Needs Merge")) + status = NeedsMerge; + else if (contains(line, "Needs Checkout")) + status = NeedsCheckout; + } + } + tmpf.removeFile(); + return status; +} + + +void CVS::registrer(string const & msg) +{ + doVCCommand("cvs -q add -m \"" + msg + "\" " + + getTarget(File), + FileName(owner_->filePath())); +} + + +void CVS::getDiff(OperationMode opmode, FileName const & tmpf) +{ + doVCCommand("cvs diff " + getTarget(opmode) + + " > " + quoteName(tmpf.toFilesystemEncoding()), + FileName(owner_->filePath()), false); +} + + +int CVS::edit() +{ + vcstatus = LOCKED; + return doVCCommand("cvs -q edit " + getTarget(File), + FileName(owner_->filePath())); +} + + +int CVS::unedit() +{ + vcstatus = UNLOCKED; + return doVCCommand("cvs -q unedit " + getTarget(File), + FileName(owner_->filePath())); +} + + +int CVS::update(OperationMode opmode, FileName const & tmpf) +{ + string const redirection = tmpf.empty() ? "" + : " > " + quoteName(tmpf.toFilesystemEncoding()); + + return doVCCommand("cvs -q update " + + getTarget(opmode) + redirection, + FileName(owner_->filePath())); +} + + +string CVS::scanLogFile(FileName const & f, string & status) +{ + ifstream ifs(f.toFilesystemEncoding().c_str()); + + while (ifs) { + string line; + getline(ifs, line); + LYXERR(Debug::LYXVC, line << "\n"); + if (!line.empty()) + status += line + "; "; + if (prefixIs(line, "C ")) { + ifs.close(); + return line; + } + } + ifs.close(); + return string(); +} + + string CVS::checkIn(string const & msg) { - int ret = doVCCommand("cvs -q commit -m \"" + msg + "\" " - + quoteName(onlyFileName(owner_->absFileName())), + CvsStatus status = getStatus(); + switch (status) { + case UpToDate: + if (vcstatus != NOLOCKING) + unedit(); + return "CVS: Proceeded"; + case LocallyModified: + case LocallyAdded: { + int rc = doVCCommand("cvs -q commit -m \"" + msg + "\" " + + getTarget(File), FileName(owner_->filePath())); - return ret ? string() : "CVS: Proceeded"; + return rc ? string() : "CVS: Proceeded"; + } + case NeedsMerge: + case NeedsCheckout: + frontend::Alert::error(_("Revision control error."), + _("The repository version is newer then the current check out.\n" + "You have to update from repository first or revert your changes.")) ; + break; + default: + frontend::Alert::error(_("Revision control error."), + bformat(_("Bad status when checking in changes.\n" + "\n'%1$s'\n\n"), + toString(status))); + break; + } + return string(); +} + + +bool CVS::isLocked() const +{ + FileName fn(owner_->absFileName()); + fn.refresh(); + return !fn.isReadOnly(); } bool CVS::checkInEnabled() { - return !owner_->isReadonly(); + if (vcstatus != NOLOCKING) + return isLocked(); + else + return true; } string CVS::checkOut() { - // to be sure we test it again... - if (!checkOutEnabled()) + if (vcstatus != NOLOCKING && edit()) return string(); - - int ret = doVCCommand("cvs -q edit " - + quoteName(onlyFileName(owner_->absFileName())), - FileName(owner_->filePath())); - if (ret) + FileName tmpf = FileName::tempName("lyxvcout"); + if (tmpf.empty()) { + LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf); return string(); - - ret = doVCCommand("cvs update " - + quoteName(onlyFileName(owner_->absFileName())), - FileName(owner_->filePath())); - return ret ? string() : "CVS: Proceeded"; + } + + int rc = update(File, tmpf); + string 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 the resolved document."), + from_local8bit(res))); + + tmpf.erase(); + return rc ? string() : log.empty() ? "CVS: Proceeded" : "CVS: " + log; } bool CVS::checkOutEnabled() { - return owner_->isReadonly(); + if (vcstatus != NOLOCKING) + return !isLocked(); + else + return true; } string CVS::repoUpdate() { - lyxerr << "Sorry, not implemented." << endl; - return string(); + FileName tmpf = FileName::tempName("lyxvcout"); + if (tmpf.empty()) { + LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf); + return string(); + } + + getDiff(Directory, tmpf); + 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 you have to resolve them " + "manually or revert to repository version later."), file); + int ret = frontend::Alert::prompt(_("Changes detected"), + text, 0, 1, _("&Continue"), _("&Abort"), _("View &Log ...")); + if (ret == 2 ) { + dispatch(FuncRequest(LFUN_DIALOG_SHOW, "file " + tmpf.absFileName())); + ret = frontend::Alert::prompt(_("Changes detected"), + text, 0, 1, _("&Continue"), _("&Abort")); + hideDialogs("file", 0); + } + if (ret == 1 ) { + tmpf.removeFile(); + return string(); + } + } + + int rc = update(Directory, tmpf); + res += "Update log:\n" + tmpf.fileContents("UTF-8"); + tmpf.removeFile(); + + LYXERR(Debug::LYXVC, res); + return rc ? string() : "CVS: Proceeded" ; } bool CVS::repoUpdateEnabled() { - return false; + return true; } @@ -502,16 +709,33 @@ void CVS::revert() { // Reverts to the version in CVS repository and // gets the updated version from the repository. - string const fil = quoteName(onlyFileName(owner_->absFileName())); - // This is sensitive operation, so at lest some check about - // existence of cvs program and its file - if (doVCCommand("cvs log "+ fil, FileName(owner_->filePath()))) - return; - FileName f(owner_->absFileName()); - f.removeFile(); - doVCCommand("cvs -q update " + fil, - FileName(owner_->filePath())); - owner_->markClean(); + CvsStatus status = getStatus(); + switch (status) { + case UpToDate: + if (vcstatus != NOLOCKING) + unedit(); + break; + case NeedsMerge: + case NeedsCheckout: + case LocallyModified: { + FileName f(owner_->absFileName()); + f.removeFile(); + update(File, FileName()); + owner_->markClean(); + break; + } + case LocallyAdded: + frontend::Alert::error(_("Revision control error."), + _("The current file is not in repository.\n" + "You have to check in the first revision before you can revert.")) ; + break; + default: + frontend::Alert::error(_("Revision control error."), + bformat(_("Bad status when checking in changes.\n" + "\n'%1$s'\n\n"), + toString(status))); + break; + } } @@ -532,7 +756,7 @@ bool CVS::undoLastEnabled() void CVS::getLog(FileName const & tmpf) { - doVCCommand("cvs log " + quoteName(onlyFileName(owner_->absFileName())) + doVCCommand("cvs log " + getTarget(File) + " > " + quoteName(tmpf.toFilesystemEncoding()), FileName(owner_->filePath())); } diff --git a/src/VCBackend.h b/src/VCBackend.h index a9bdb84a33..33aefbe968 100644 --- a/src/VCBackend.h +++ b/src/VCBackend.h @@ -88,7 +88,7 @@ protected: virtual void scanMaster() = 0; // GUI container for doVCCommandCall - int doVCCommand(std::string const & cmd, support::FileName const & path); + int doVCCommand(std::string const & cmd, support::FileName const & path, bool reportError = true); /** * doVCCommandCall - call out to the version control utility * @param cmd the command to execute @@ -210,6 +210,10 @@ public: virtual void getLog(support::FileName const &); + /// Check for messages in cvs output. + /// Returns conflict line. + std::string scanLogFile(support::FileName const & f, std::string & status); + virtual std::string const versionString() const { return "CVS: " + version_; } @@ -224,13 +228,46 @@ public: protected: virtual void scanMaster(); + /// the mode of operation for some VC commands + enum OperationMode { + Directory = 0, + File = 1 + }; + /// possible status values of file + enum CvsStatus { + UpToDate = 0, + LocallyModified = 1, + LocallyAdded = 2, + NeedsMerge = 3, + NeedsCheckout = 4, + NoCvsFile = 5, + StatusError = 6 + }; private: support::FileName file_; // revision number from scanMaster std::string version_; - /// The user currently keeping the lock on the VC file. - std::string locker_; + + /// return the quoted pathname if Directory or filename if File + virtual std::string const getTarget(OperationMode opmode) const; + /// collect the diff of file or directory against repository + /// result is placed in temporary file + void getDiff(OperationMode opmode, support::FileName const & tmpf); + /// make the file ready for editing: + /// save a copy in CVS/Base and change file permissions to rw if needed + virtual int edit(); + /// revert the edit operation + virtual int unedit(); + /// retrieve repository changes into working copy + virtual int update(OperationMode opmode, support::FileName const & tmpf); + /// check readonly state for file + /// assume true when file is writable + virtual bool isLocked() const; + /// query and parse the cvs status of file + virtual CvsStatus getStatus(); + /// convert enum to string + virtual docstring toString(CvsStatus status) const; };