]> git.lyx.org Git - lyx.git/commitdiff
* implementation of status check and use it for checkIn and revert.
authorStephan Witt <switt@lyx.org>
Mon, 25 Oct 2010 05:37:04 +0000 (05:37 +0000)
committerStephan Witt <switt@lyx.org>
Mon, 25 Oct 2010 05:37:04 +0000 (05:37 +0000)
  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
src/VCBackend.h

index 15b6033ae7cfb8738be785b0cc3325d7c6f3c1f7..208161bb24a928eed815748e483273d951c5b2ae 100644 (file)
@@ -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()
 }
 
 
+string const CVS::getTarget(OperationMode opmode) const
+{
+       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 + "\" "
-                   + quoteName(onlyFileName(owner_->absFileName())),
-                   FileName(owner_->filePath()));
+               + 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()));
 }
index a9bdb84a3353ade2fd01b21b10d15e19d0ad3b30..33aefbe968da1e36f2dec1e8e01c3f459a2d4b04 100644 (file)
@@ -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;
 };