]> git.lyx.org Git - lyx.git/blobdiff - src/VCBackend.cpp
FindAdv: Amend ec387b6d: Handle search for '{' and '}'
[lyx.git] / src / VCBackend.cpp
index e0d9b4cce48138ce8d7b62fecb5c81d357a33be8..b20a586090379718c4e153a69e22488cb6673b1d 100644 (file)
@@ -13,7 +13,6 @@
 
 #include "VCBackend.h"
 #include "Buffer.h"
-#include "DispatchResult.h"
 #include "LyX.h"
 #include "FuncRequest.h"
 
 #include "support/lstrings.h"
 #include "support/PathChanger.h"
 #include "support/Systemcall.h"
-#include "support/regex.h"
 #include "support/TempFile.h"
 
 #include <fstream>
+#include <iomanip>
+#include <regex>
+#include <sstream>
 
 using namespace std;
 using namespace lyx::support;
@@ -57,11 +58,15 @@ int VCS::doVCCommand(string const & cmd, FileName const & path, bool reportError
 
        if (owner_)
                owner_->setBusy(false);
-       if (ret && reportError)
+       if (ret && reportError) {
+               docstring rcsmsg;
+               if (prefixIs(cmd, "ci "))
+                       rcsmsg = "\n" + _("Perhaps the RCS package is not installed on your system?");
                frontend::Alert::error(_("Revision control error."),
                        bformat(_("Some problem occurred while running the command:\n"
-                                 "'%1$s'."),
+                                 "'%1$s'.") + rcsmsg,
                        from_utf8(cmd)));
+       }
        return ret;
 }
 
@@ -100,18 +105,18 @@ bool VCS::makeRCSRevision(string const &version, string &revis) const
 }
 
 
-bool VCS::checkparentdirs(FileName const & file, std::string const & vcsdir)
+FileName VCS::checkParentDirs(FileName const & start, std::string const & file)
 {
-       FileName dirname = file.onlyPath();
+       FileName dirname = start.onlyPath();
        do {
-               FileName tocheck = FileName(addName(dirname.absFileName(), vcsdir));
+               FileName tocheck = FileName(addPathName(dirname.absFileName(), file));
                LYXERR(Debug::LYXVC, "check file: " << tocheck.absFileName());
                if (tocheck.exists())
-                       return true;
-               //this construct because of #8295
+                       return tocheck;
+               // this construct because of #8295
                dirname = FileName(dirname.absFileName()).parentPath();
        } while (!dirname.empty());
-       return false;
+       return FileName();
 }
 
 
@@ -156,8 +161,8 @@ bool RCS::retrieve(FileName const & file)
 {
        LYXERR(Debug::LYXVC, "LyXVC::RCS: retrieve.\n\t" << file);
        // The caller ensures that file does not exist, so no need to check that.
-       return doVCCommandCall("co -q -r " + quoteName(file.toFilesystemEncoding()),
-                              FileName()) == 0;
+       int const ret = doVCCommandCall("co -q -r " + quoteName(file.toFilesystemEncoding()));
+       return ret == 0;
 }
 
 
@@ -169,6 +174,8 @@ void RCS::scanMaster()
        LYXERR(Debug::LYXVC, "LyXVC::RCS: scanMaster: " << master_);
 
        ifstream ifs(master_.toFilesystemEncoding().c_str());
+       // limit the size of strings we read to avoid memory problems
+       ifs >> setw(65636);
 
        string token;
        bool read_enough = false;
@@ -194,7 +201,7 @@ void RCS::scanMaster()
                        // get locker here
                        if (contains(token, ';')) {
                                locker_ = "Unlocked";
-                               vcstatus = UNLOCKED;
+                               vcstatus_ = UNLOCKED;
                                continue;
                        }
                        string tmpt;
@@ -208,7 +215,7 @@ void RCS::scanMaster()
                                // s2 is user, and s1 is version
                                if (s1 == version_) {
                                        locker_ = s2;
-                                       vcstatus = LOCKED;
+                                       vcstatus_ = LOCKED;
                                        break;
                                }
                        } while (!contains(tmpt, ';'));
@@ -283,7 +290,7 @@ LyXVC::CommandResult RCS::checkIn(string const & msg, string & log)
 
 bool RCS::checkInEnabled()
 {
-       return owner_ && !owner_->isReadonly();
+       return owner_ && !owner_->hasReadonlyFlag();
 }
 
 
@@ -325,7 +332,7 @@ string RCS::checkOut()
 
 bool RCS::checkOutEnabled()
 {
-       return owner_ && owner_->isReadonly();
+       return owner_ && owner_->hasReadonlyFlag();
 }
 
 
@@ -388,7 +395,7 @@ void RCS::undoLast()
 
 bool RCS::undoLastEnabled()
 {
-       return true;
+       return owner_->hasReadonlyFlag();
 }
 
 
@@ -426,7 +433,8 @@ string RCS::revisionInfo(LyXVC::RevisionInfo const info)
                        return rev_date_cache_;
                case LyXVC::Time:
                        return rev_time_cache_;
-               default: ;
+               default:
+                       break;
        }
 
        return string();
@@ -528,23 +536,19 @@ CVS::CVS(FileName const & m, Buffer * b) : VCS(b)
 
 FileName const CVS::findFile(FileName const & file)
 {
-       // First we look for the CVS/Entries in the same dir
-       // where we have file.
+       LYXERR(Debug::LYXVC, "LyXVC: Checking if "
+                  << onlyFileName(file.absFileName()) << "is under cvs");
+       // First we look for the CVS/Entries in the same dir where we have file.
+       // Note that it is not necessary to search parent directories, since
+       // there will be a CVS/Entries file in every subdirectory.
        FileName const entries(onlyPath(file.absFileName()) + "/CVS/Entries");
-       string const tmpf = '/' + onlyFileName(file.absFileName()) + '/';
-       LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under cvs in `" << entries
-                            << "' for `" << tmpf << '\'');
        if (entries.isReadableFile()) {
-               // Ok we are at least in a CVS dir. Parse the CVS/Entries
-               // and see if we can find this file. We do a fast and
-               // dirty parse here.
-               ifstream ifs(entries.toFilesystemEncoding().c_str());
-               string line;
-               while (getline(ifs, line)) {
-                       LYXERR(Debug::LYXVC, "\tEntries: " << line);
-                       if (contains(line, tmpf))
-                               return entries;
-               }
+               // We are in a CVS-managed directory
+               // See if the file is known to CVS
+               string const cmd = "cvs log " + quoteName(file.toFilesystemEncoding());
+               int const ret = doVCCommandCall(cmd, file.onlyPath());
+               if (ret == 0)
+                       return entries;
        }
        return FileName();
 }
@@ -555,8 +559,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 name = onlyFileName(owner_->absFileName());
-       string tmpf = '/' + name + '/';
+       string const name = onlyFileName(owner_->absFileName());
+       string const tmpf = '/' + name + '/';
        LYXERR(Debug::LYXVC, "\tlooking for `" << tmpf << '\'');
        string line;
        static regex const reg("/(.*)/(.*)/(.*)/(.*)/(.*)");
@@ -565,8 +569,10 @@ void CVS::scanMaster()
                if (contains(line, tmpf)) {
                        // Ok extract the fields.
                        smatch sm;
-
-                       regex_match(line, sm, reg);
+                       if (!regex_match(line, sm, reg)) {
+                               LYXERR(Debug::LYXVC, "\t  Cannot parse line. Skipping.");
+                               continue;
+                       }
 
                        //sm[0]; // whole matched string
                        //sm[1]; // filename
@@ -577,21 +583,21 @@ void CVS::scanMaster()
                        //sm[5]; // tag or tagdate
                        FileName file(owner_->absFileName());
                        if (file.isReadableFile()) {
-                               time_t mod = file.lastModified();
-                               string mod_date = rtrim(asctime(gmtime(&mod)), "\n");
+                               time_t const mod = file.lastModified();
+                               string const 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;
+                                       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;
+                                       vcstatus_ = base.isReadableFile() ? LOCKED : NOLOCKING;
                                }
                        } else {
-                               vcstatus = NOLOCKING;
+                               vcstatus_ = NOLOCKING;
                        }
                        break;
                }
@@ -727,12 +733,14 @@ void CVS::getRevisionInfo()
                LYXERR(Debug::LYXVC, line << '\n');
                if (prefixIs(line, "date:")) {
                        smatch sm;
-                       regex_match(line, sm, reg);
-                       //sm[0]; // whole matched string
-                       rev_date_cache_ = sm[1];
-                       rev_time_cache_ = sm[2];
-                       //sm[3]; // GMT offset
-                       rev_author_cache_ = sm[4];
+                       if (regex_match(line, sm, reg)) {
+                         //sm[0]; // whole matched string
+                         rev_date_cache_ = sm[1];
+                         rev_time_cache_ = sm[2];
+                         //sm[3]; // GMT offset
+                         rev_author_cache_ = sm[4];
+                       } else
+                         LYXERR(Debug::LYXVC, "\tCannot parse line. Skipping."); 
                        break;
                }
        }
@@ -799,7 +807,7 @@ void CVS::getDiff(OperationMode opmode, FileName const & tmpf)
 
 int CVS::edit()
 {
-       vcstatus = LOCKED;
+       vcstatus_ = LOCKED;
        return doVCCommand("cvs -q edit " + getTarget(File),
                FileName(owner_->filePath()));
 }
@@ -807,7 +815,7 @@ int CVS::edit()
 
 int CVS::unedit()
 {
-       vcstatus = UNLOCKED;
+       vcstatus_ = UNLOCKED;
        return doVCCommand("cvs -q unedit " + getTarget(File),
                FileName(owner_->filePath()));
 }
@@ -846,7 +854,7 @@ LyXVC::CommandResult CVS::checkIn(string const & msg, string & log)
        CvsStatus status = getStatus();
        switch (status) {
        case UpToDate:
-               if (vcstatus != NOLOCKING)
+               if (vcstatus_ != NOLOCKING)
                        if (unedit())
                                return LyXVC::ErrorCommand;
                log = "CVS: Proceeded";
@@ -888,7 +896,7 @@ bool CVS::isLocked() const
 
 bool CVS::checkInEnabled()
 {
-       if (vcstatus != NOLOCKING)
+       if (vcstatus_ != NOLOCKING)
                return isLocked();
        else
                return true;
@@ -904,7 +912,7 @@ bool CVS::isCheckInWithConfirmation()
 
 string CVS::checkOut()
 {
-       if (vcstatus != NOLOCKING && edit())
+       if (vcstatus_ != NOLOCKING && edit())
                return string();
        TempFile tempfile("lyxvout");
        FileName tmpf = tempfile.name();
@@ -931,7 +939,7 @@ string CVS::checkOut()
 
 bool CVS::checkOutEnabled()
 {
-       if (vcstatus != NOLOCKING)
+       if (vcstatus_ != NOLOCKING)
                return !isLocked();
        else
                return true;
@@ -962,7 +970,7 @@ string CVS::repoUpdate()
                        dispatch(FuncRequest(LFUN_DIALOG_SHOW, "file " + tmpf.absFileName()));
                        ret = frontend::Alert::prompt(_("Changes detected"),
                                text, 0, 1, _("&Continue"), _("&Abort"));
-                       hideDialogs("file", 0);
+                       hideDialogs("file", nullptr);
                }
                if (ret == 1)
                        return string();
@@ -1021,7 +1029,7 @@ bool CVS::revert()
        CvsStatus status = getStatus();
        switch (status) {
        case UpToDate:
-               if (vcstatus != NOLOCKING)
+               if (vcstatus_ != NOLOCKING)
                        return 0 == unedit();
                break;
        case NeedsMerge:
@@ -1096,7 +1104,8 @@ string CVS::revisionInfo(LyXVC::RevisionInfo const info)
                        return rev_date_cache_;
                case LyXVC::Time:
                        return rev_time_cache_;
-               default: ;
+               default:
+                       break;
                }
        }
        return string();
@@ -1141,39 +1150,30 @@ bool CVS::prepareFileRevisionEnabled()
 //
 /////////////////////////////////////////////////////////////////////
 
-SVN::SVN(FileName const & m, Buffer * b) : VCS(b)
+SVN::SVN(Buffer * b) : VCS(b)
 {
        // Here we know that the buffer file is either already in SVN or
        // about to be registered
-       master_ = m;
-       locked_mode_ = 0;
+       locked_mode_ = false;
        scanMaster();
 }
 
 
-FileName const SVN::findFile(FileName const & file)
+bool SVN::findFile(FileName const & file)
 {
        // First we check the existence of repository meta data.
-       if (!VCS::checkparentdirs(file, ".svn")) {
+       if (VCS::checkParentDirs(file, ".svn").empty()) {
                LYXERR(Debug::LYXVC, "Cannot find SVN meta data for " << file);
-               return FileName();
+               return false;
        }
 
        // Now we check the status of the file.
-       TempFile tempfile("lyxvcout");
-       FileName tmpf = tempfile.name();
-       if (tmpf.empty()) {
-               LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
-               return FileName();
-       }
-
        string const fname = onlyFileName(file.absFileName());
        LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under svn control for `" << fname << '\'');
-       bool found = 0 == doVCCommandCall("svn info " + quoteName(fname)
-                                               + " > " + quoteName(tmpf.toFilesystemEncoding()),
+       bool found = 0 == doVCCommandCall("svn info " + quoteName(fname),
                                                file.onlyPath());
        LYXERR(Debug::LYXVC, "SVN control: " << (found ? "enabled" : "disabled"));
-       return found ? file : FileName();
+       return found;
 }
 
 
@@ -1181,13 +1181,9 @@ void SVN::scanMaster()
 {
        // 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;
-       }
+       vcstatus_ = NOLOCKING;
+       if (checkLockMode())
+               vcstatus_ = isLocked() ? LOCKED : UNLOCKED;
 }
 
 
@@ -1243,7 +1239,7 @@ bool SVN::retrieve(FileName const & file)
 
 void SVN::registrer(string const & /*msg*/)
 {
-       doVCCommand("svn add -q " + quoteName(onlyFileName(owner_->absFileName())),
+       doVCCommand("svn add -q --parents " + quoteName(onlyFileName(owner_->absFileName())),
                    FileName(owner_->filePath()));
 }
 
@@ -1432,7 +1428,7 @@ bool SVN::fileLock(bool lock, FileName const & tmpf, string &status)
                    + " > " + quoteName(tmpf.toFilesystemEncoding()),
                    FileName(owner_->filePath()));
 
-       // Lock error messages go unfortunately on stderr and are unreachible this way.
+       // Lock error messages go unfortunately on stderr and are unreachable this way.
        ifstream ifs(tmpf.toFilesystemEncoding().c_str());
        string line;
        while (ifs) {
@@ -1522,7 +1518,7 @@ string SVN::repoUpdate()
                        dispatch(FuncRequest(LFUN_DIALOG_SHOW, "file " + tmpf.absFileName()));
                        ret = frontend::Alert::prompt(_("Changes detected"),
                                text, 0, 1, _("&Yes"), _("&No"));
-                       hideDialogs("file", 0);
+                       hideDialogs("file", nullptr);
                }
                if (ret == 1)
                        return string();
@@ -1660,8 +1656,8 @@ string SVN::revisionInfo(LyXVC::RevisionInfo const info)
                        return rev_date_cache_;
                case LyXVC::Time:
                        return rev_time_cache_;
-               default: ;
-
+               default:
+                       break;
        }
 
        return string();
@@ -1814,42 +1810,31 @@ bool SVN::toggleReadOnlyEnabled()
 //
 /////////////////////////////////////////////////////////////////////
 
-GIT::GIT(FileName const & m, Buffer * b) : VCS(b)
+GIT::GIT(Buffer * b) : VCS(b)
 {
        // Here we know that the buffer file is either already in GIT or
        // about to be registered
-       master_ = m;
        scanMaster();
 }
 
 
-FileName const GIT::findFile(FileName const & file)
+bool GIT::findFile(FileName const & file)
 {
        // First we check the existence of repository meta data.
-       if (!VCS::checkparentdirs(file, ".git")) {
+       if (VCS::checkParentDirs(file, ".git").empty()) {
                LYXERR(Debug::LYXVC, "Cannot find GIT meta data for " << file);
-               return FileName();
-       }
-
-       // Now we check the status of the file.
-       TempFile tempfile("lyxvcout");
-       FileName tmpf = tempfile.name();
-       if (tmpf.empty()) {
-               LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
-               return FileName();
+               return false;
        }
 
+       // Now we check if the file is known to git.
        string const fname = onlyFileName(file.absFileName());
        LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under git control for `"
                        << fname << '\'');
-       doVCCommandCall("git ls-files " +
-                       quoteName(fname) + " > " +
-                       quoteName(tmpf.toFilesystemEncoding()),
+       int const ret = doVCCommandCall("git log -n 0 " + quoteName(fname),
                        file.onlyPath());
-       tmpf.refresh();
-       bool found = !tmpf.isFileEmpty();
+       bool const found = (ret == 0);
        LYXERR(Debug::LYXVC, "GIT control: " << (found ? "enabled" : "disabled"));
-       return found ? file : FileName();
+       return found;
 }
 
 
@@ -1857,7 +1842,7 @@ void GIT::scanMaster()
 {
        // vcstatus code is somewhat superflous,
        // until we want to implement read-only toggle for git.
-       vcstatus = NOLOCKING;
+       vcstatus_ = NOLOCKING;
 }
 
 
@@ -2115,22 +2100,28 @@ string GIT::revisionInfo(LyXVC::RevisionInfo const info)
 
        // fill the rest of the attributes for a single file
        if (rev_file_cache_.empty())
-               if (!getFileRevisionInfo())
+               if (!getFileRevisionInfo()) {
                        rev_file_cache_ = "?";
+                       rev_file_abbrev_cache_ = "?";
+    }
 
        switch (info) {
                case LyXVC::File:
                        if (rev_file_cache_ == "?")
                                return string();
                        return rev_file_cache_;
+               case LyXVC::FileAbbrev:
+                       if (rev_file_abbrev_cache_ == "?")
+                               return string();
+                       return rev_file_abbrev_cache_;
                case LyXVC::Author:
                        return rev_author_cache_;
                case LyXVC::Date:
                        return rev_date_cache_;
                case LyXVC::Time:
                        return rev_time_cache_;
-               default: ;
-
+               default:
+                       break;
        }
 
        return string();
@@ -2146,7 +2137,7 @@ bool GIT::getFileRevisionInfo()
                return false;
        }
 
-       doVCCommand("git log -n 1 --pretty=format:%H%n%an%n%ai " + quoteName(onlyFileName(owner_->absFileName()))
+       doVCCommand("git log -n 1 --pretty=format:%H%n%h%n%an%n%ai " + quoteName(onlyFileName(owner_->absFileName()))
                    + " > " + quoteName(tmpf.toFilesystemEncoding()),
                    FileName(owner_->filePath()));
 
@@ -2157,6 +2148,8 @@ bool GIT::getFileRevisionInfo()
 
        if (ifs)
                getline(ifs, rev_file_cache_);
+       if (ifs)
+               getline(ifs, rev_file_abbrev_cache_);
        if (ifs)
                getline(ifs, rev_author_cache_);
        if (ifs) {
@@ -2180,7 +2173,8 @@ bool GIT::getTreeRevisionInfo()
        }
 
        doVCCommand("git describe --abbrev --dirty --long > " + quoteName(tmpf.toFilesystemEncoding()),
-                   FileName(owner_->filePath()));
+                   FileName(owner_->filePath()),
+                   false); //git describe returns $?=128 when no tag found (but git repo still exists)
 
        if (tmpf.empty())
                return false;