]> git.lyx.org Git - lyx.git/blobdiff - src/VCBackend.cpp
Context menu item to add unknown branch (rest of #7643)
[lyx.git] / src / VCBackend.cpp
index 493ff91ba1355b8eaec05dc8326e1ad06a260de4..719c3c2a1f58241be6f70c50e63cffcdd5632b2e 100644 (file)
@@ -43,7 +43,7 @@ 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, false);
+       return one.startscript(Systemcall::Wait, cmd, string(), false);
 }
 
 
@@ -65,6 +65,56 @@ int VCS::doVCCommand(string const & cmd, FileName const & path, bool reportError
 }
 
 
+bool VCS::makeRCSRevision(string const &version, string &revis) const
+{
+       string rev = revis;
+       
+       if (isStrInt(rev)) {
+               int back = convert<int>(rev);
+               // if positive use as the last number in the whole revision string
+               if (back > 0) {
+                       string base;
+                       rsplit(version, base , '.' );
+                       rev = base + "." + rev;
+               }
+               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<int>(cur) + back;
+                       if (want <= 0)
+                               return false;
+                       
+                       rev = base + "." + convert<string>(want);
+               }
+       }
+
+       revis = rev;
+       return true;
+}
+
+bool VCS::checkparentdirs(FileName const & file, std::string const & pathname)
+{
+       FileName dirname = file.onlyPath();
+       FileName tocheck = FileName(addName(dirname.absFileName(),pathname));
+       LYXERR(Debug::LYXVC, "check file: " << tocheck.absFileName());
+       bool result = tocheck.exists();
+       while ( !result && !dirname.empty() ) {
+               //this construct because of #8295
+               dirname = FileName(dirname.absFileName()).parentPath();
+               LYXERR(Debug::LYXVC, "check directory: " << dirname.absFileName());
+               tocheck = FileName(addName(dirname.absFileName(),pathname));
+               result = tocheck.exists();
+       }
+       return result;
+}
+
+       
 /////////////////////////////////////////////////////////////////////
 //
 // RCS
@@ -195,9 +245,30 @@ bool RCS::checkInEnabled()
        return owner_ && !owner_->isReadonly();
 }
 
+
 bool RCS::isCheckInWithConfirmation()
 {
-       //FIXME diff
+       // FIXME one day common getDiff for all backends
+       // docstring diff;
+       // if (getDiff(file, diff) && diff.empty())
+       //      return false;
+
+       FileName tmpf = FileName::tempName("lyxvcout");
+       if (tmpf.empty()) {
+               LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
+               return true;
+       }
+
+       doVCCommandCall("rcsdiff " + quoteName(owner_->absFileName())
+                   + " > " + quoteName(tmpf.toFilesystemEncoding()),
+               FileName(owner_->filePath()));
+
+       docstring diff = tmpf.fileContents("UTF-8");
+       tmpf.erase();
+
+       if (diff.empty())
+               return false;
+
        return true;
 }
 
@@ -232,6 +303,9 @@ bool RCS::repoUpdateEnabled()
 
 string RCS::lockingToggle()
 {
+       //FIXME this might be actually possible, study rcs -U, rcs -L.
+       //State should be easy to get inside scanMaster.
+       //It would fix #4370 and make rcs/svn usage even more closer.
        lyxerr << "Sorry, not implemented." << endl;
        return string();
 }
@@ -243,13 +317,15 @@ bool RCS::lockingToggleEnabled()
 }
 
 
-void RCS::revert()
+bool RCS::revert()
 {
-       doVCCommand("co -f -u" + version_ + " "
+       if (doVCCommand("co -f -u" + version_ + " "
                    + quoteName(onlyFileName(owner_->absFileName())),
-                   FileName(owner_->filePath()));
+                   FileName(owner_->filePath())))
+               return false;
        // We ignore changes and just reload!
        owner_->markClean();
+       return true;
 }
 
 
@@ -292,47 +368,83 @@ bool RCS::toggleReadOnlyEnabled()
        return false;
 }
 
+
 string RCS::revisionInfo(LyXVC::RevisionInfo const info)
 {
        if (info == LyXVC::File)
                return version_;
+       // fill the rest of the attributes for a single file
+       if (rev_date_cache_.empty())
+               if (!getRevisionInfo())
+                       return string();
+
+       switch (info) {
+               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 RCS::prepareFileRevision(string const &revis, string & f)
+bool RCS::getRevisionInfo()
 {
-       string rev = revis;
+       FileName tmpf = FileName::tempName("lyxvcout");
+       if (tmpf.empty()) {
+               LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
+               return false;
+       }
+       doVCCommand("rlog -r " + quoteName(onlyFileName(owner_->absFileName()))
+               + " > " + quoteName(tmpf.toFilesystemEncoding()),
+               FileName(owner_->filePath()));
 
-       if (isStrInt(rev)) {
-               int back = convert<int>(rev);
-               // if positive use as the last number in the whole revision string
-               if (back > 0) {
-                       string base;
-                       rsplit(version_, base , '.' );
-                       rev = base + "." + rev;
-               }
-               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<int>(cur) + back;
-                       if (want <= 0)
-                               return false;
+       if (tmpf.empty())
+               return false;
 
-                       rev = base + "." + convert<string>(want);
+       ifstream ifs(tmpf.toFilesystemEncoding().c_str());
+       string line;
+
+       // we reached to the entry, i.e. after initial log message
+       bool entry=false;
+       // line with critical info, e.g:
+       //"date: 2011/07/02 11:02:54;  author: sanda;  state: Exp;  lines: +17 -2"
+       string result;
+
+       while (ifs) {
+               getline(ifs, line);
+               LYXERR(Debug::LYXVC, line);
+               if (entry && prefixIs(line, "date:")) {
+                       result = line;
+                       break;
                }
+               if (prefixIs(line, "revision"))
+                       entry = true;
        }
+       if (result.empty())
+               return false;
+
+       rev_date_cache_ = token(result, ' ', 1);
+       rev_time_cache_ = rtrim(token(result, ' ', 2), ";");
+       rev_author_cache_ = trim(token(token(result, ';', 1), ':', 1));
+
+       return !rev_author_cache_.empty();
+}
+
+bool RCS::prepareFileRevision(string const &revis, string & f)
+{
+       string rev = revis;
+       if (!VCS::makeRCSRevision(version_, rev))
+               return false;
 
        FileName tmpf = FileName::tempName("lyxvcrev_" + rev + "_");
        if (tmpf.empty()) {
                LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
-               return N_("Error: Could not generate logfile.");
+               return false;
        }
 
        doVCCommand("co -p" + rev + " "
@@ -363,6 +475,7 @@ CVS::CVS(FileName const & m, FileName const & f)
 {
        master_ = m;
        file_ = f;
+       have_rev_info_ = false;
        scanMaster();
 }
 
@@ -443,7 +556,9 @@ string const CVS::getTarget(OperationMode opmode) const
 {
        switch(opmode) {
        case Directory:
-               return quoteName(owner_->filePath());
+               // in client server mode CVS does not like full path operand for directory operation
+               // since LyX switches to the repo dir "." is good enough as target
+               return ".";
        case File:
                return quoteName(onlyFileName(owner_->absFileName()));
        }
@@ -473,6 +588,23 @@ docstring CVS::toString(CvsStatus status) const
 }
 
 
+int CVS::doVCCommandWithOutput(string const & cmd, FileName const & path,
+       FileName const & output, bool reportError)
+{
+       string redirection = output.empty() ? "" : " > " + quoteName(output.toFilesystemEncoding());
+       return doVCCommand(cmd + redirection, path, reportError);
+}
+
+
+int CVS::doVCCommandCallWithOutput(std::string const & cmd,
+       support::FileName const & path,
+       support::FileName const & output)
+{
+       string redirection = output.empty() ? "" : " > " + quoteName(output.toFilesystemEncoding());
+       return doVCCommandCall(cmd + redirection, path);
+}
+
+
 CVS::CvsStatus CVS::getStatus()
 {
        FileName tmpf = FileName::tempName("lyxvcout");
@@ -481,9 +613,8 @@ CVS::CvsStatus CVS::getStatus()
                return StatusError;
        }
 
-       if (doVCCommand("cvs status " + getTarget(File)
-               + " > " + quoteName(tmpf.toFilesystemEncoding()),
-               FileName(owner_->filePath()))) {
+       if (doVCCommandCallWithOutput("cvs status " + getTarget(File),
+               FileName(owner_->filePath()), tmpf)) {
                tmpf.removeFile();
                return StatusError;
        }
@@ -512,6 +643,51 @@ CVS::CvsStatus CVS::getStatus()
        return status;
 }
 
+void CVS::getRevisionInfo()
+{
+       if (have_rev_info_)
+               return;
+       have_rev_info_ = true;
+       FileName tmpf = FileName::tempName("lyxvcout");
+       if (tmpf.empty()) {
+               LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
+               return;
+       }
+       
+       int rc = doVCCommandCallWithOutput("cvs log -r" + version_ 
+               + " " + getTarget(File),
+               FileName(owner_->filePath()), tmpf);
+       if (rc) {
+               tmpf.removeFile();
+               LYXERR(Debug::LYXVC, "cvs log failed with exit code " << rc);
+               return;
+       }
+       
+       ifstream ifs(tmpf.toFilesystemEncoding().c_str());
+       static regex const reg("date: (.*) (.*) (.*);  author: (.*);  state: (.*);(.*)");
+
+       while (ifs) {
+               string line;
+               getline(ifs, line);
+               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];
+                       break;
+               }
+       }
+       tmpf.removeFile();
+       if (rev_author_cache_.empty())
+               LYXERR(Debug::LYXVC,
+                  "Could not retrieve revision info for " << version_ <<
+                  " of " << getTarget(File));
+}
+
 
 void CVS::registrer(string const & msg)
 {
@@ -523,9 +699,8 @@ void CVS::registrer(string const & msg)
 
 void CVS::getDiff(OperationMode opmode, FileName const & tmpf)
 {
-       doVCCommand("cvs diff " + getTarget(opmode)
-               + " > " + quoteName(tmpf.toFilesystemEncoding()),
-               FileName(owner_->filePath()), false);
+       doVCCommandWithOutput("cvs diff " + getTarget(opmode),
+               FileName(owner_->filePath()), tmpf, false);
 }
 
 
@@ -547,12 +722,9 @@ int CVS::unedit()
 
 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()));
+       return doVCCommandWithOutput("cvs -q update "
+               + getTarget(opmode),
+               FileName(owner_->filePath()), tmpf, false);
 }
 
 
@@ -645,12 +817,14 @@ string CVS::checkOut()
        int rc = update(File, tmpf);
        string log;
        string const res = scanLogFile(tmpf, log);
-       if (!res.empty())
+       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)));
+               rc = 0;
+       }
        
        tmpf.erase();
        return rc ? string() : log.empty() ? "CVS: Proceeded" : "CVS: " + log;
@@ -681,8 +855,8 @@ string CVS::repoUpdate()
                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);
+                               "Possible file conflicts must be then resolved manually "
+                               "or you will need to revert back to the repository version."), file);
                int ret = frontend::Alert::prompt(_("Changes detected"),
                                text, 0, 1, _("&Continue"), _("&Abort"), _("View &Log ..."));
                if (ret == 2 ) {
@@ -699,10 +873,23 @@ string CVS::repoUpdate()
 
        int rc = update(Directory, tmpf);
        res += "Update log:\n" + tmpf.fileContents("UTF-8");
+       LYXERR(Debug::LYXVC, res);
+
+       string log;
+       string sres = scanLogFile(tmpf, log);
+       if (!sres.empty()) {
+               docstring const file = owner_->fileName().displayName(20);
+               frontend::Alert::error(_("Revision control error."),
+                       bformat(_("Error when updating document %1$s from repository.\n"
+                                         "You have to manually resolve the conflicts NOW!\n'%2$s'.\n\n"
+                                         "After pressing OK, LyX will try to reopen the resolved document."),
+                               file, from_local8bit(sres)));
+               rc = 0;
+       }
+       
        tmpf.removeFile();
 
-       LYXERR(Debug::LYXVC, res);
-       return rc ? string() : "CVS: Proceeded" ;
+       return rc ? string() : log.empty() ? "CVS: Proceeded" : "CVS: " + log;
 }
 
 
@@ -732,7 +919,7 @@ bool CVS::isRevertWithConfirmation()
 }
 
 
-void CVS::revert()
+bool CVS::revert()
 {
        // Reverts to the version in CVS repository and
        // gets the updated version from the repository.
@@ -740,7 +927,7 @@ void CVS::revert()
        switch (status) {
        case UpToDate:
                if (vcstatus != NOLOCKING)
-                       unedit();
+                       return 0 == unedit();
                break;
        case NeedsMerge:
        case NeedsCheckout:
@@ -757,7 +944,7 @@ void CVS::revert()
                        bformat(_("The document %1$s is not in repository.\n"
                                  "You have to check in the first revision before you can revert."),
                                file)) ;
-               break;
+               return false;
        }
        default: {
                docstring const file = owner_->fileName().displayName(20);
@@ -765,9 +952,10 @@ void CVS::revert()
                        bformat(_("Cannot revert document %1$s to repository version.\n"
                                  "The status '%2$s' is unexpected."),
                                file, toString(status)));
-               break;
+               return false;
                }
        }
+       return true;
 }
 
 
@@ -788,9 +976,9 @@ bool CVS::undoLastEnabled()
 
 void CVS::getLog(FileName const & tmpf)
 {
-       doVCCommand("cvs log " + getTarget(File)
-                   + " > " + quoteName(tmpf.toFilesystemEncoding()),
-                   FileName(owner_->filePath()));
+       doVCCommandWithOutput("cvs log " + getTarget(File),
+               FileName(owner_->filePath()),
+               tmpf);
 }
 
 
@@ -802,21 +990,50 @@ bool CVS::toggleReadOnlyEnabled()
 
 string CVS::revisionInfo(LyXVC::RevisionInfo const info)
 {
-       if (info == LyXVC::File)
-               return version_;
+       if (!version_.empty()) {
+               getRevisionInfo();
+               switch (info) {
+               case LyXVC::File:
+                       return version_;
+               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 CVS::prepareFileRevision(string const &, string &)
+bool CVS::prepareFileRevision(string const & revis, string & f)
 {
-       return false;
+       string rev = revis;
+       if (!VCS::makeRCSRevision(version_, rev))
+               return false;
+
+       FileName tmpf = FileName::tempName("lyxvcrev_" + rev + "_");
+       if (tmpf.empty()) {
+               LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
+               return false;
+       }
+
+       doVCCommandWithOutput("cvs update -p -r" + rev + " "
+               + getTarget(File),
+               FileName(owner_->filePath()), tmpf);
+       if (tmpf.isFileEmpty())
+               return false;
+
+       f = tmpf.absFileName();
+       return true;
 }
 
 
 bool CVS::prepareFileRevisionEnabled()
 {
-       return false;
+       return true;
 }
 
 
@@ -838,27 +1055,26 @@ SVN::SVN(FileName const & m, FileName const & f)
 
 FileName const SVN::findFile(FileName const & file)
 {
-       // First we look for the .svn/entries in the same dir
-       // where we have file.
-       FileName const entries(onlyPath(file.absFileName()) + "/.svn/entries");
-       string const tmpf = onlyFileName(file.absFileName());
-       LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under svn in `" << entries
-                            << "' for `" << tmpf << '\'');
-       if (entries.isReadableFile()) {
-               // Ok we are at least in a SVN dir. Parse the .svn/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, oldline;
-               while (getline(ifs, line)) {
-                       if (line == "dir" || line == "file")
-                               LYXERR(Debug::LYXVC, "\tEntries: " << oldline);
-                       if (oldline == tmpf && line == "file")
-                               return entries;
-                       oldline = line;
-               }
+       // First we check the existence of repository meta data.
+       if (!VCS::checkparentdirs(file, ".svn")) {
+               LYXERR(Debug::LYXVC, "Cannot find SVN meta data for " << file);
+               return FileName();
        }
-       return FileName();
+
+       // Now we check the status of the file.
+       FileName tmpf = FileName::tempName("lyxvcout");
+       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()),
+                                               file.onlyPath());
+       LYXERR(Debug::LYXVC, "SVN control: " << (found ? "enabled" : "disabled"));
+       return found ? file : FileName();
 }
 
 
@@ -882,7 +1098,7 @@ 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.");
+               return false;
        }
 
        LYXERR(Debug::LYXVC, "Detecting locking mode...");
@@ -895,7 +1111,7 @@ bool SVN::checkLockMode()
        string line;
        bool ret = false;
 
-       while (ifs) {
+       while (ifs && !ret) {
                getline(ifs, line);
                LYXERR(Debug::LYXVC, line);
                if (contains(line, "svn:needs-lock"))
@@ -962,7 +1178,24 @@ bool SVN::checkInEnabled()
 
 bool SVN::isCheckInWithConfirmation()
 {
-       //FIXME diff
+       // FIXME one day common getDiff and perhaps OpMode for all backends
+
+       FileName tmpf = FileName::tempName("lyxvcout");
+       if (tmpf.empty()) {
+               LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
+               return true;
+       }
+
+       doVCCommandCall("svn diff " + quoteName(owner_->absFileName())
+                   + " > " + quoteName(tmpf.toFilesystemEncoding()),
+               FileName(owner_->filePath()));
+
+       docstring diff = tmpf.fileContents("UTF-8");
+       tmpf.erase();
+
+       if (diff.empty())
+               return false;
+
        return true;
 }
 
@@ -1166,15 +1399,17 @@ bool SVN::lockingToggleEnabled()
 }
 
 
-void SVN::revert()
+bool SVN::revert()
 {
        // Reverts to the version in SVN repository and
        // gets the updated version from the repository.
        string const fil = quoteName(onlyFileName(owner_->absFileName()));
 
-       doVCCommand("svn revert -q " + fil,
-                   FileName(owner_->filePath()));
+       if (doVCCommand("svn revert -q " + fil,
+                   FileName(owner_->filePath())))
+               return false;
        owner_->markClean();
+       return true;
 }
 
 
@@ -1241,7 +1476,7 @@ 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.");
+               return false;
        }
 
        doVCCommand("svn info --xml " + quoteName(onlyFileName(owner_->absFileName()))
@@ -1294,7 +1529,7 @@ 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.");
+               return false;
        }
 
        doVCCommand("svnversion -n . > " + quoteName(tmpf.toFilesystemEncoding()),
@@ -1345,7 +1580,7 @@ bool SVN::prepareFileRevision(string const & revis, string & f)
        FileName tmpf = FileName::tempName("lyxvcrev_" + revname + "_");
        if (tmpf.empty()) {
                LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
-               return N_("Error: Could not generate logfile.");
+               return false;
        }
 
        doVCCommand("svn cat -r " + revname + " "