3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
9 * Full author contact details are available in file CREDITS.
14 #include "VCBackend.h"
17 #include "FuncRequest.h"
19 #include "frontends/alert.h"
20 #include "frontends/Application.h"
22 #include "support/convert.h"
23 #include "support/debug.h"
24 #include "support/filetools.h"
25 #include "support/gettext.h"
26 #include "support/lstrings.h"
27 #include "support/PathChanger.h"
28 #include "support/Systemcall.h"
29 #include "support/regex.h"
30 #include "support/TempFile.h"
37 using namespace lyx::support;
43 int VCS::doVCCommandCall(string const & cmd, FileName const & path)
45 LYXERR(Debug::LYXVC, "doVCCommandCall: " << cmd);
47 support::PathChanger p(path);
48 return one.startscript(Systemcall::Wait, cmd, string(), string(), false);
52 int VCS::doVCCommand(string const & cmd, FileName const & path, bool reportError)
55 owner_->setBusy(true);
57 int const ret = doVCCommandCall(cmd, path);
60 owner_->setBusy(false);
61 if (ret && reportError)
62 frontend::Alert::error(_("Revision control error."),
63 bformat(_("Some problem occurred while running the command:\n"
70 bool VCS::makeRCSRevision(string const &version, string &revis) const
75 int back = convert<int>(rev);
76 // if positive use as the last number in the whole revision string
79 rsplit(version, base , '.');
80 rev = base + '.' + rev;
84 // we care about the last number from revision string
85 // in case of backward indexing
88 cur = rsplit(version, base , '.');
91 int want = convert<int>(cur) + back;
95 rev = base + '.' + convert<string>(want);
104 bool VCS::checkparentdirs(FileName const & file, std::string const & vcsdir)
106 FileName dirname = file.onlyPath();
108 FileName tocheck = FileName(addName(dirname.absFileName(), vcsdir));
109 LYXERR(Debug::LYXVC, "check file: " << tocheck.absFileName());
110 if (tocheck.exists())
112 //this construct because of #8295
113 dirname = FileName(dirname.absFileName()).parentPath();
114 } while (!dirname.empty());
119 /////////////////////////////////////////////////////////////////////
123 /////////////////////////////////////////////////////////////////////
125 RCS::RCS(FileName const & m, Buffer * b) : VCS(b)
127 // Here we know that the buffer file is either already in RCS or
128 // about to be registered
134 FileName const RCS::findFile(FileName const & file)
136 // Check if *,v exists.
137 FileName tmp(file.absFileName() + ",v");
138 LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp);
139 if (tmp.isReadableFile()) {
140 LYXERR(Debug::LYXVC, "Yes, " << file << " is under rcs.");
144 // Check if RCS/*,v exists.
145 tmp = FileName(addName(addPath(onlyPath(file.absFileName()), "RCS"), file.absFileName()) + ",v");
146 LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp);
147 if (tmp.isReadableFile()) {
148 LYXERR(Debug::LYXVC, "Yes, " << file << " is under rcs.");
156 bool RCS::retrieve(FileName const & file)
158 LYXERR(Debug::LYXVC, "LyXVC::RCS: retrieve.\n\t" << file);
159 // The caller ensures that file does not exist, so no need to check that.
160 return doVCCommandCall("co -q -r " + quoteName(file.toFilesystemEncoding()),
165 void RCS::scanMaster()
170 LYXERR(Debug::LYXVC, "LyXVC::RCS: scanMaster: " << master_);
172 ifstream ifs(master_.toFilesystemEncoding().c_str());
173 // limit the size of strings we read to avoid memory problems
177 bool read_enough = false;
179 while (!read_enough && ifs >> token) {
180 LYXERR(Debug::LYXVC, "LyXVC::scanMaster: current lex text: `"
185 else if (token == "head") {
189 tmv = rtrim(tmv, ";");
191 LYXERR(Debug::LYXVC, "LyXVC: version found to be " << tmv);
192 } else if (contains(token, "access")
193 || contains(token, "symbols")
194 || contains(token, "strict")) {
196 } else if (contains(token, "locks")) {
198 if (contains(token, ';')) {
199 locker_ = "Unlocked";
208 s1 = rtrim(tmpt, ";");
209 // tmp is now in the format <user>:<version>
210 s1 = split(s1, s2, ':');
211 // s2 is user, and s1 is version
212 if (s1 == version_) {
217 } while (!contains(tmpt, ';'));
219 } else if (token == "comment") {
220 // we don't need to read any further than this.
224 LYXERR(Debug::LYXVC, "LyXVC::scanMaster(): unexpected token");
230 void RCS::registrer(string const & msg)
232 string cmd = "ci -q -u -i -t-\"";
235 cmd += quoteName(onlyFileName(owner_->absFileName()));
236 doVCCommand(cmd, FileName(owner_->filePath()));
240 bool RCS::renameEnabled()
246 string RCS::rename(support::FileName const & /*newFile*/, string const & /*msg*/)
248 // not implemented, since a left-over file.lyx,v would be confusing.
253 bool RCS::copyEnabled()
259 string RCS::copy(support::FileName const & newFile, string const & msg)
261 // RCS has no real copy command, so we create a poor mans version
262 support::FileName const oldFile(owner_->absFileName());
263 if (!oldFile.copyTo(newFile))
265 FileName path(oldFile.onlyPath());
266 string relFile(to_utf8(newFile.relPath(path.absFileName())));
267 string cmd = "ci -q -u -i -t-\"";
270 cmd += quoteName(relFile);
271 return doVCCommand(cmd, path) ? string() : "RCS: Proceeded";
275 LyXVC::CommandResult RCS::checkIn(string const & msg, string & log)
277 int ret = doVCCommand("ci -q -u -m\"" + msg + "\" "
278 + quoteName(onlyFileName(owner_->absFileName())),
279 FileName(owner_->filePath()));
281 return LyXVC::ErrorCommand;
282 log = "RCS: Proceeded";
283 return LyXVC::VCSuccess;
287 bool RCS::checkInEnabled()
289 return owner_ && !owner_->hasReadonlyFlag();
293 bool RCS::isCheckInWithConfirmation()
295 // FIXME one day common getDiff for all backends
297 // if (getDiff(file, diff) && diff.empty())
300 TempFile tempfile("lyxvcout");
301 FileName tmpf = tempfile.name();
303 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
307 doVCCommandCall("rcsdiff " + quoteName(owner_->absFileName())
308 + " > " + quoteName(tmpf.toFilesystemEncoding()),
309 FileName(owner_->filePath()));
311 docstring diff = tmpf.fileContents("UTF-8");
320 string RCS::checkOut()
323 int ret = doVCCommand("co -q -l " + quoteName(onlyFileName(owner_->absFileName())),
324 FileName(owner_->filePath()));
325 return ret ? string() : "RCS: Proceeded";
329 bool RCS::checkOutEnabled()
331 return owner_ && owner_->hasReadonlyFlag();
335 string RCS::repoUpdate()
337 lyxerr << "Sorry, not implemented." << endl;
342 bool RCS::repoUpdateEnabled()
348 string RCS::lockingToggle()
350 //FIXME this might be actually possible, study rcs -U, rcs -L.
351 //State should be easy to get inside scanMaster.
352 //It would fix #4370 and make rcs/svn usage even more closer.
353 lyxerr << "Sorry, not implemented." << endl;
358 bool RCS::lockingToggleEnabled()
366 if (doVCCommand("co -f -u" + version_ + ' '
367 + quoteName(onlyFileName(owner_->absFileName())),
368 FileName(owner_->filePath())))
370 // We ignore changes and just reload!
376 bool RCS::isRevertWithConfirmation()
378 //FIXME owner && diff ?
385 LYXERR(Debug::LYXVC, "LyXVC: undoLast");
386 doVCCommand("rcs -o" + version_ + ' '
387 + quoteName(onlyFileName(owner_->absFileName())),
388 FileName(owner_->filePath()));
392 bool RCS::undoLastEnabled()
394 return owner_->hasReadonlyFlag();
398 void RCS::getLog(FileName const & tmpf)
400 doVCCommand("rlog " + quoteName(onlyFileName(owner_->absFileName()))
401 + " > " + quoteName(tmpf.toFilesystemEncoding()),
402 FileName(owner_->filePath()));
406 bool RCS::toggleReadOnlyEnabled()
408 // This got broken somewhere along lfuns dispatch reorganization.
409 // reloadBuffer would be needed after this, but thats problematic
410 // since we are inside Buffer::dispatch.
416 string RCS::revisionInfo(LyXVC::RevisionInfo const info)
418 if (info == LyXVC::File)
420 // fill the rest of the attributes for a single file
421 if (rev_date_cache_.empty())
422 if (!getRevisionInfo())
427 return rev_author_cache_;
429 return rev_date_cache_;
431 return rev_time_cache_;
440 bool RCS::getRevisionInfo()
442 TempFile tempfile("lyxvcout");
443 FileName tmpf = tempfile.name();
445 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
448 doVCCommand("rlog -r " + quoteName(onlyFileName(owner_->absFileName()))
449 + " > " + quoteName(tmpf.toFilesystemEncoding()),
450 FileName(owner_->filePath()));
455 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
458 // we reached to the entry, i.e. after initial log message
460 // line with critical info, e.g:
461 //"date: 2011/07/02 11:02:54; author: sanda; state: Exp; lines: +17 -2"
466 LYXERR(Debug::LYXVC, line);
467 if (entry && prefixIs(line, "date:")) {
471 if (prefixIs(line, "revision"))
477 rev_date_cache_ = token(result, ' ', 1);
478 rev_time_cache_ = rtrim(token(result, ' ', 2), ";");
479 rev_author_cache_ = trim(token(token(result, ';', 1), ':', 1));
481 return !rev_author_cache_.empty();
484 bool RCS::prepareFileRevision(string const &revis, string & f)
487 if (!VCS::makeRCSRevision(version_, rev))
490 TempFile tempfile("lyxvcrev_" + rev + '_');
491 tempfile.setAutoRemove(false);
492 FileName tmpf = tempfile.name();
494 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
498 doVCCommand("co -p" + rev + ' '
499 + quoteName(onlyFileName(owner_->absFileName()))
500 + " > " + quoteName(tmpf.toFilesystemEncoding()),
501 FileName(owner_->filePath()));
503 if (tmpf.isFileEmpty())
506 f = tmpf.absFileName();
511 bool RCS::prepareFileRevisionEnabled()
517 /////////////////////////////////////////////////////////////////////
521 /////////////////////////////////////////////////////////////////////
523 CVS::CVS(FileName const & m, Buffer * b) : VCS(b)
525 // Here we know that the buffer file is either already in CVS or
526 // about to be registered
528 have_rev_info_ = false;
533 FileName const CVS::findFile(FileName const & file)
535 // First we look for the CVS/Entries in the same dir
536 // where we have file.
537 FileName const entries(onlyPath(file.absFileName()) + "/CVS/Entries");
538 string const tmpf = '/' + onlyFileName(file.absFileName()) + '/';
539 LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under cvs in `" << entries
540 << "' for `" << tmpf << '\'');
541 if (entries.isReadableFile()) {
542 // Ok we are at least in a CVS dir. Parse the CVS/Entries
543 // and see if we can find this file. We do a fast and
545 ifstream ifs(entries.toFilesystemEncoding().c_str());
547 while (getline(ifs, line)) {
548 LYXERR(Debug::LYXVC, "\tEntries: " << line);
549 if (contains(line, tmpf))
557 void CVS::scanMaster()
559 LYXERR(Debug::LYXVC, "LyXVC::CVS: scanMaster. \n Checking: " << master_);
560 // Ok now we do the real scan...
561 ifstream ifs(master_.toFilesystemEncoding().c_str());
562 string name = onlyFileName(owner_->absFileName());
563 string tmpf = '/' + name + '/';
564 LYXERR(Debug::LYXVC, "\tlooking for `" << tmpf << '\'');
566 static regex const reg("/(.*)/(.*)/(.*)/(.*)/(.*)");
567 while (getline(ifs, line)) {
568 LYXERR(Debug::LYXVC, "\t line: " << line);
569 if (contains(line, tmpf)) {
570 // Ok extract the fields.
572 if (!regex_match(line, sm, reg)) {
573 LYXERR(Debug::LYXVC, "\t Cannot parse line. Skipping.");
577 //sm[0]; // whole matched string
579 version_ = sm.str(2);
580 string const file_date = sm.str(3);
583 //sm[5]; // tag or tagdate
584 FileName file(owner_->absFileName());
585 if (file.isReadableFile()) {
586 time_t mod = file.lastModified();
587 string mod_date = rtrim(asctime(gmtime(&mod)), "\n");
588 LYXERR(Debug::LYXVC, "Date in Entries: `" << file_date
589 << "'\nModification date of file: `" << mod_date << '\'');
590 if (file.isReadOnly()) {
591 // readonly checkout is unlocked
594 FileName bdir(addPath(master_.onlyPath().absFileName(),"Base"));
595 FileName base(addName(bdir.absFileName(),name));
596 // if base version is existent "cvs edit" was used to lock
597 vcstatus = base.isReadableFile() ? LOCKED : NOLOCKING;
600 vcstatus = NOLOCKING;
608 bool CVS::retrieve(FileName const & file)
610 LYXERR(Debug::LYXVC, "LyXVC::CVS: retrieve.\n\t" << file);
611 // The caller ensures that file does not exist, so no need to check that.
612 return doVCCommandCall("cvs -q update " + quoteName(file.toFilesystemEncoding()),
613 file.onlyPath()) == 0;
617 string const CVS::getTarget(OperationMode opmode) const
621 // in client server mode CVS does not like full path operand for directory operation
622 // since LyX switches to the repo dir "." is good enough as target
625 return quoteName(onlyFileName(owner_->absFileName()));
631 docstring CVS::toString(CvsStatus status) const
635 return _("Up-to-date");
636 case LocallyModified:
637 return _("Locally Modified");
639 return _("Locally Added");
641 return _("Needs Merge");
643 return _("Needs Checkout");
645 return _("No CVS file");
647 return _("Cannot retrieve CVS status");
653 int CVS::doVCCommandWithOutput(string const & cmd, FileName const & path,
654 FileName const & output, bool reportError)
656 string redirection = output.empty() ? "" : " > " + quoteName(output.toFilesystemEncoding());
657 return doVCCommand(cmd + redirection, path, reportError);
661 int CVS::doVCCommandCallWithOutput(std::string const & cmd,
662 support::FileName const & path,
663 support::FileName const & output)
665 string redirection = output.empty() ? "" : " > " + quoteName(output.toFilesystemEncoding());
666 return doVCCommandCall(cmd + redirection, path);
670 CVS::CvsStatus CVS::getStatus()
672 TempFile tempfile("lyxvout");
673 FileName tmpf = tempfile.name();
675 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
679 if (doVCCommandCallWithOutput("cvs status " + getTarget(File),
680 FileName(owner_->filePath()), tmpf)) {
684 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
685 CvsStatus status = NoCvsFile;
690 LYXERR(Debug::LYXVC, line << '\n');
691 if (prefixIs(line, "File:")) {
692 if (contains(line, "Up-to-date"))
694 else if (contains(line, "Locally Modified"))
695 status = LocallyModified;
696 else if (contains(line, "Locally Added"))
697 status = LocallyAdded;
698 else if (contains(line, "Needs Merge"))
700 else if (contains(line, "Needs Checkout"))
701 status = NeedsCheckout;
707 void CVS::getRevisionInfo()
711 have_rev_info_ = true;
712 TempFile tempfile("lyxvout");
713 FileName tmpf = tempfile.name();
715 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
719 int rc = doVCCommandCallWithOutput("cvs log -r" + version_
720 + ' ' + getTarget(File),
721 FileName(owner_->filePath()), tmpf);
723 LYXERR(Debug::LYXVC, "cvs log failed with exit code " << rc);
727 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
728 static regex const reg("date: (.*) (.*) (.*); author: (.*); state: (.*);(.*)");
733 LYXERR(Debug::LYXVC, line << '\n');
734 if (prefixIs(line, "date:")) {
736 if (regex_match(line, sm, reg)) {
737 //sm[0]; // whole matched string
738 rev_date_cache_ = sm[1];
739 rev_time_cache_ = sm[2];
740 //sm[3]; // GMT offset
741 rev_author_cache_ = sm[4];
743 LYXERR(Debug::LYXVC, "\tCannot parse line. Skipping.");
747 if (rev_author_cache_.empty())
749 "Could not retrieve revision info for " << version_ <<
750 " of " << getTarget(File));
754 void CVS::registrer(string const & msg)
756 doVCCommand("cvs -q add -m \"" + msg + "\" "
758 FileName(owner_->filePath()));
762 bool CVS::renameEnabled()
768 string CVS::rename(support::FileName const & newFile, string const & msg)
770 // CVS has no real rename command, so we create a poor mans version
771 support::FileName const oldFile(owner_->absFileName());
772 string ret = copy(newFile, msg);
775 string cmd = "cvs -q remove -m \"" + msg + "\" " +
776 quoteName(oldFile.onlyFileName());
777 FileName path(oldFile.onlyPath());
778 return doVCCommand(cmd, path) ? string() : ret;
782 bool CVS::copyEnabled()
788 string CVS::copy(support::FileName const & newFile, string const & msg)
790 // CVS has no real copy command, so we create a poor mans version
791 support::FileName const oldFile(owner_->absFileName());
792 if (!oldFile.copyTo(newFile))
794 FileName path(oldFile.onlyPath());
795 string relFile(to_utf8(newFile.relPath(path.absFileName())));
796 string cmd("cvs -q add -m \"" + msg + "\" " + quoteName(relFile));
797 return doVCCommand(cmd, path) ? string() : "CVS: Proceeded";
801 void CVS::getDiff(OperationMode opmode, FileName const & tmpf)
803 doVCCommandWithOutput("cvs diff " + getTarget(opmode),
804 FileName(owner_->filePath()), tmpf, false);
811 return doVCCommand("cvs -q edit " + getTarget(File),
812 FileName(owner_->filePath()));
819 return doVCCommand("cvs -q unedit " + getTarget(File),
820 FileName(owner_->filePath()));
824 int CVS::update(OperationMode opmode, FileName const & tmpf)
826 return doVCCommandWithOutput("cvs -q update "
828 FileName(owner_->filePath()), tmpf, false);
832 string CVS::scanLogFile(FileName const & f, string & status)
834 ifstream ifs(f.toFilesystemEncoding().c_str());
839 LYXERR(Debug::LYXVC, line << '\n');
841 status += line + "; ";
842 if (prefixIs(line, "C ")) {
852 LyXVC::CommandResult CVS::checkIn(string const & msg, string & log)
854 CvsStatus status = getStatus();
857 if (vcstatus != NOLOCKING)
859 return LyXVC::ErrorCommand;
860 log = "CVS: Proceeded";
861 return LyXVC::VCSuccess;
862 case LocallyModified:
864 int rc = doVCCommand("cvs -q commit -m \"" + msg + "\" "
866 FileName(owner_->filePath()));
868 return LyXVC::ErrorCommand;
869 log = "CVS: Proceeded";
870 return LyXVC::VCSuccess;
874 frontend::Alert::error(_("Revision control error."),
875 _("The repository version is newer then the current check out.\n"
876 "You have to update from repository first or revert your changes.")) ;
879 frontend::Alert::error(_("Revision control error."),
880 bformat(_("Bad status when checking in changes.\n"
885 return LyXVC::ErrorBefore;
889 bool CVS::isLocked() const
891 FileName fn(owner_->absFileName());
893 return !fn.isReadOnly();
897 bool CVS::checkInEnabled()
899 if (vcstatus != NOLOCKING)
906 bool CVS::isCheckInWithConfirmation()
908 CvsStatus status = getStatus();
909 return status == LocallyModified || status == LocallyAdded;
913 string CVS::checkOut()
915 if (vcstatus != NOLOCKING && edit())
917 TempFile tempfile("lyxvout");
918 FileName tmpf = tempfile.name();
920 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
924 int rc = update(File, tmpf);
926 string const res = scanLogFile(tmpf, log);
928 frontend::Alert::error(_("Revision control error."),
929 bformat(_("Error when updating from repository.\n"
930 "You have to manually resolve the conflicts NOW!\n'%1$s'.\n\n"
931 "After pressing OK, LyX will try to reopen the resolved document."),
932 from_local8bit(res)));
936 return rc ? string() : log.empty() ? "CVS: Proceeded" : "CVS: " + log;
940 bool CVS::checkOutEnabled()
942 if (vcstatus != NOLOCKING)
949 string CVS::repoUpdate()
951 TempFile tempfile("lyxvout");
952 FileName tmpf = tempfile.name();
954 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
958 getDiff(Directory, tmpf);
959 docstring res = tmpf.fileContents("UTF-8");
961 LYXERR(Debug::LYXVC, "Diff detected:\n" << res);
962 docstring const file = from_utf8(owner_->filePath());
963 docstring text = bformat(_("There were detected changes "
964 "in the working directory:\n%1$s\n\n"
965 "Possible file conflicts must be then resolved manually "
966 "or you will need to revert back to the repository version."), file);
967 int ret = frontend::Alert::prompt(_("Changes detected"),
968 text, 0, 1, _("&Continue"), _("&Abort"), _("View &Log ..."));
970 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "file " + tmpf.absFileName()));
971 ret = frontend::Alert::prompt(_("Changes detected"),
972 text, 0, 1, _("&Continue"), _("&Abort"));
973 hideDialogs("file", nullptr);
979 int rc = update(Directory, tmpf);
980 res += "Update log:\n" + tmpf.fileContents("UTF-8");
981 LYXERR(Debug::LYXVC, res);
984 string sres = scanLogFile(tmpf, log);
986 docstring const file = owner_->fileName().displayName(20);
987 frontend::Alert::error(_("Revision control error."),
988 bformat(_("Error when updating document %1$s from repository.\n"
989 "You have to manually resolve the conflicts NOW!\n'%2$s'.\n\n"
990 "After pressing OK, LyX will try to reopen the resolved document."),
991 file, from_local8bit(sres)));
995 return rc ? string() : log.empty() ? "CVS: Proceeded" : "CVS: " + log;
999 bool CVS::repoUpdateEnabled()
1005 string CVS::lockingToggle()
1007 lyxerr << "Sorry, not implemented." << endl;
1012 bool CVS::lockingToggleEnabled()
1018 bool CVS::isRevertWithConfirmation()
1020 CvsStatus status = getStatus();
1021 return !owner_->isClean() || status == LocallyModified || status == NeedsMerge;
1027 // Reverts to the version in CVS repository and
1028 // gets the updated version from the repository.
1029 CvsStatus status = getStatus();
1032 if (vcstatus != NOLOCKING)
1033 return 0 == unedit();
1037 case LocallyModified: {
1038 FileName f(owner_->absFileName());
1040 update(File, FileName());
1041 owner_->markClean();
1044 case LocallyAdded: {
1045 docstring const file = owner_->fileName().displayName(20);
1046 frontend::Alert::error(_("Revision control error."),
1047 bformat(_("The document %1$s is not in repository.\n"
1048 "You have to check in the first revision before you can revert."),
1053 docstring const file = owner_->fileName().displayName(20);
1054 frontend::Alert::error(_("Revision control error."),
1055 bformat(_("Cannot revert document %1$s to repository version.\n"
1056 "The status '%2$s' is unexpected."),
1057 file, toString(status)));
1065 void CVS::undoLast()
1067 // merge the current with the previous version
1068 // in a reverse patch kind of way, so that the
1069 // result is to revert the last changes.
1070 lyxerr << "Sorry, not implemented." << endl;
1074 bool CVS::undoLastEnabled()
1080 void CVS::getLog(FileName const & tmpf)
1082 doVCCommandWithOutput("cvs log " + getTarget(File),
1083 FileName(owner_->filePath()),
1088 bool CVS::toggleReadOnlyEnabled()
1094 string CVS::revisionInfo(LyXVC::RevisionInfo const info)
1096 if (!version_.empty()) {
1102 return rev_author_cache_;
1104 return rev_date_cache_;
1106 return rev_time_cache_;
1115 bool CVS::prepareFileRevision(string const & revis, string & f)
1118 if (!VCS::makeRCSRevision(version_, rev))
1121 TempFile tempfile("lyxvcrev_" + rev + '_');
1122 tempfile.setAutoRemove(false);
1123 FileName tmpf = tempfile.name();
1125 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1129 doVCCommandWithOutput("cvs update -p -r" + rev + ' '
1131 FileName(owner_->filePath()), tmpf);
1133 if (tmpf.isFileEmpty())
1136 f = tmpf.absFileName();
1141 bool CVS::prepareFileRevisionEnabled()
1147 /////////////////////////////////////////////////////////////////////
1151 /////////////////////////////////////////////////////////////////////
1153 SVN::SVN(FileName const & m, Buffer * b) : VCS(b)
1155 // Here we know that the buffer file is either already in SVN or
1156 // about to be registered
1158 locked_mode_ = false;
1163 FileName const SVN::findFile(FileName const & file)
1165 // First we check the existence of repository meta data.
1166 if (!VCS::checkparentdirs(file, ".svn")) {
1167 LYXERR(Debug::LYXVC, "Cannot find SVN meta data for " << file);
1171 // Now we check the status of the file.
1172 TempFile tempfile("lyxvcout");
1173 FileName tmpf = tempfile.name();
1175 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1179 string const fname = onlyFileName(file.absFileName());
1180 LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under svn control for `" << fname << '\'');
1181 bool found = 0 == doVCCommandCall("svn info " + quoteName(fname)
1182 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1184 LYXERR(Debug::LYXVC, "SVN control: " << (found ? "enabled" : "disabled"));
1185 return found ? file : FileName();
1189 void SVN::scanMaster()
1191 // vcstatus code is somewhat superflous,
1192 // until we want to implement read-only toggle for svn.
1193 vcstatus = NOLOCKING;
1194 if (checkLockMode()) {
1198 vcstatus = UNLOCKED;
1203 bool SVN::checkLockMode()
1205 TempFile tempfile("lyxvcout");
1206 FileName tmpf = tempfile.name();
1208 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1212 LYXERR(Debug::LYXVC, "Detecting locking mode...");
1213 if (doVCCommandCall("svn proplist " + quoteName(onlyFileName(owner_->absFileName()))
1214 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1215 FileName(owner_->filePath())))
1218 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1222 while (ifs && !ret) {
1224 LYXERR(Debug::LYXVC, line);
1225 if (contains(line, "svn:needs-lock"))
1228 LYXERR(Debug::LYXVC, "Locking enabled: " << ret);
1236 bool SVN::isLocked() const
1238 FileName file(owner_->absFileName());
1240 return !file.isReadOnly();
1244 bool SVN::retrieve(FileName const & file)
1246 LYXERR(Debug::LYXVC, "LyXVC::SVN: retrieve.\n\t" << file);
1247 // The caller ensures that file does not exist, so no need to check that.
1248 return doVCCommandCall("svn update -q --non-interactive " + quoteName(file.onlyFileName()),
1249 file.onlyPath()) == 0;
1253 void SVN::registrer(string const & /*msg*/)
1255 doVCCommand("svn add -q " + quoteName(onlyFileName(owner_->absFileName())),
1256 FileName(owner_->filePath()));
1260 bool SVN::renameEnabled()
1266 string SVN::rename(support::FileName const & newFile, string const & msg)
1268 // svn move does not require a log message, since it does not commit.
1269 // In LyX we commit immediately afterwards, otherwise it could be
1270 // confusing to the user to have two uncommitted files.
1271 FileName path(owner_->filePath());
1272 string relFile(to_utf8(newFile.relPath(path.absFileName())));
1273 string cmd("svn move -q " + quoteName(onlyFileName(owner_->absFileName())) +
1274 ' ' + quoteName(relFile));
1275 if (doVCCommand(cmd, path)) {
1276 cmd = "svn revert -q " +
1277 quoteName(onlyFileName(owner_->absFileName())) + ' ' +
1279 doVCCommand(cmd, path);
1280 if (newFile.exists())
1281 newFile.removeFile();
1284 vector<support::FileName> f;
1285 f.push_back(owner_->fileName());
1286 f.push_back(newFile);
1288 if (checkIn(f, msg, log) != LyXVC::VCSuccess) {
1289 cmd = "svn revert -q " +
1290 quoteName(onlyFileName(owner_->absFileName())) + ' ' +
1292 doVCCommand(cmd, path);
1293 if (newFile.exists())
1294 newFile.removeFile();
1301 bool SVN::copyEnabled()
1307 string SVN::copy(support::FileName const & newFile, string const & msg)
1309 // svn copy does not require a log message, since it does not commit.
1310 // In LyX we commit immediately afterwards, otherwise it could be
1311 // confusing to the user to have an uncommitted file.
1312 FileName path(owner_->filePath());
1313 string relFile(to_utf8(newFile.relPath(path.absFileName())));
1314 string cmd("svn copy -q " + quoteName(onlyFileName(owner_->absFileName())) +
1315 ' ' + quoteName(relFile));
1316 if (doVCCommand(cmd, path))
1318 vector<support::FileName> f(1, newFile);
1320 if (checkIn(f, msg, log) == LyXVC::VCSuccess)
1326 LyXVC::CommandResult SVN::checkIn(string const & msg, string & log)
1328 vector<support::FileName> f(1, owner_->fileName());
1329 return checkIn(f, msg, log);
1333 LyXVC::CommandResult
1334 SVN::checkIn(vector<support::FileName> const & f, string const & msg, string & log)
1336 TempFile tempfile("lyxvcout");
1337 FileName tmpf = tempfile.name();
1339 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1340 log = N_("Error: Could not generate logfile.");
1341 return LyXVC::ErrorBefore;
1345 os << "svn commit -m \"" << msg << '"';
1346 for (size_t i = 0; i < f.size(); ++i)
1347 os << ' ' << quoteName(f[i].onlyFileName());
1348 os << " > " << quoteName(tmpf.toFilesystemEncoding());
1349 LyXVC::CommandResult ret =
1350 doVCCommand(os.str(), FileName(owner_->filePath())) ?
1351 LyXVC::ErrorCommand : LyXVC::VCSuccess;
1353 string res = scanLogFile(tmpf, log);
1355 frontend::Alert::error(_("Revision control error."),
1356 _("Error when committing to repository.\n"
1357 "You have to manually resolve the problem.\n"
1358 "LyX will reopen the document after you press OK."));
1359 ret = LyXVC::ErrorCommand;
1362 if (!fileLock(false, tmpf, log))
1363 ret = LyXVC::ErrorCommand;
1366 log.insert(0, "SVN: ");
1367 if (ret == LyXVC::VCSuccess && log.empty())
1368 log = "SVN: Proceeded";
1373 bool SVN::checkInEnabled()
1382 bool SVN::isCheckInWithConfirmation()
1384 // FIXME one day common getDiff and perhaps OpMode for all backends
1386 TempFile tempfile("lyxvcout");
1387 FileName tmpf = tempfile.name();
1389 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1393 doVCCommandCall("svn diff " + quoteName(owner_->absFileName())
1394 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1395 FileName(owner_->filePath()));
1397 docstring diff = tmpf.fileContents("UTF-8");
1406 // FIXME Correctly return code should be checked instead of this.
1407 // This would need another solution than just plain startscript.
1408 // Hint from Andre': QProcess::readAllStandardError()...
1409 string SVN::scanLogFile(FileName const & f, string & status)
1411 ifstream ifs(f.toFilesystemEncoding().c_str());
1416 LYXERR(Debug::LYXVC, line << '\n');
1418 status += line + "; ";
1419 if (prefixIs(line, "C ") || prefixIs(line, "CU ")
1420 || contains(line, "Commit failed")) {
1424 if (contains(line, "svn:needs-lock")) {
1434 bool SVN::fileLock(bool lock, FileName const & tmpf, string &status)
1436 if (!locked_mode_ || (isLocked() == lock))
1439 string const arg = lock ? "lock " : "unlock ";
1440 doVCCommand("svn "+ arg + quoteName(onlyFileName(owner_->absFileName()))
1441 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1442 FileName(owner_->filePath()));
1444 // Lock error messages go unfortunately on stderr and are unreachable this way.
1445 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1449 if (!line.empty()) status += line + "; ";
1453 if (isLocked() == lock)
1457 frontend::Alert::error(_("Revision control error."),
1458 _("Error while acquiring write lock.\n"
1459 "Another user is most probably editing\n"
1460 "the current document now!\n"
1461 "Also check the access to the repository."));
1463 frontend::Alert::error(_("Revision control error."),
1464 _("Error while releasing write lock.\n"
1465 "Check the access to the repository."));
1470 string SVN::checkOut()
1472 TempFile tempfile("lyxvcout");
1473 FileName tmpf = tempfile.name();
1475 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1476 return N_("Error: Could not generate logfile.");
1479 doVCCommand("svn update --non-interactive " + quoteName(onlyFileName(owner_->absFileName()))
1480 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1481 FileName(owner_->filePath()));
1484 string const res = scanLogFile(tmpf, log);
1486 frontend::Alert::error(_("Revision control error."),
1487 bformat(_("Error when updating from repository.\n"
1488 "You have to manually resolve the conflicts NOW!\n'%1$s'.\n\n"
1489 "After pressing OK, LyX will try to reopen the resolved document."),
1490 from_local8bit(res)));
1492 fileLock(true, tmpf, log);
1494 return log.empty() ? string() : "SVN: " + log;
1498 bool SVN::checkOutEnabled()
1507 string SVN::repoUpdate()
1509 TempFile tempfile("lyxvcout");
1510 FileName tmpf = tempfile.name();
1512 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1513 return N_("Error: Could not generate logfile.");
1516 doVCCommand("svn diff " + quoteName(owner_->filePath())
1517 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1518 FileName(owner_->filePath()));
1519 docstring res = tmpf.fileContents("UTF-8");
1521 LYXERR(Debug::LYXVC, "Diff detected:\n" << res);
1522 docstring const file = from_utf8(owner_->filePath());
1523 docstring text = bformat(_("There were detected changes "
1524 "in the working directory:\n%1$s\n\n"
1525 "In case of file conflict version of the local directory files "
1526 "will be preferred."
1527 "\n\nContinue?"), file);
1528 int ret = frontend::Alert::prompt(_("Changes detected"),
1529 text, 0, 1, _("&Yes"), _("&No"), _("View &Log ..."));
1531 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "file " + tmpf.absFileName()));
1532 ret = frontend::Alert::prompt(_("Changes detected"),
1533 text, 0, 1, _("&Yes"), _("&No"));
1534 hideDialogs("file", nullptr);
1540 // Reverting looks too harsh, see bug #6255.
1541 // doVCCommand("svn revert -R " + quoteName(owner_->filePath())
1542 // + " > " + quoteName(tmpf.toFilesystemEncoding()),
1543 // FileName(owner_->filePath()));
1544 // res = "Revert log:\n" + tmpf.fileContents("UTF-8");
1545 doVCCommand("svn update --accept mine-full " + quoteName(owner_->filePath())
1546 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1547 FileName(owner_->filePath()));
1548 res += "Update log:\n" + tmpf.fileContents("UTF-8");
1550 LYXERR(Debug::LYXVC, res);
1551 return to_utf8(res);
1555 bool SVN::repoUpdateEnabled()
1561 string SVN::lockingToggle()
1563 TempFile tempfile("lyxvcout");
1564 FileName tmpf = tempfile.name();
1566 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1567 return N_("Error: Could not generate logfile.");
1570 int ret = doVCCommand("svn proplist " + quoteName(onlyFileName(owner_->absFileName()))
1571 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1572 FileName(owner_->filePath()));
1577 string res = scanLogFile(tmpf, log);
1578 bool locking = contains(res, "svn:needs-lock");
1580 ret = doVCCommand("svn propset svn:needs-lock ON "
1581 + quoteName(onlyFileName(owner_->absFileName()))
1582 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1583 FileName(owner_->filePath()));
1585 ret = doVCCommand("svn propdel svn:needs-lock "
1586 + quoteName(onlyFileName(owner_->absFileName()))
1587 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1588 FileName(owner_->filePath()));
1592 frontend::Alert::warning(_("SVN File Locking"),
1593 (locking ? _("Locking property unset.") : _("Locking property set.")) + '\n'
1594 + _("Do not forget to commit the locking property into the repository."),
1597 return string("SVN: ") + (locking ?
1598 N_("Locking property unset.") : N_("Locking property set."));
1602 bool SVN::lockingToggleEnabled()
1610 // Reverts to the version in SVN repository and
1611 // gets the updated version from the repository.
1612 string const fil = quoteName(onlyFileName(owner_->absFileName()));
1614 if (doVCCommand("svn revert -q " + fil,
1615 FileName(owner_->filePath())))
1617 owner_->markClean();
1622 bool SVN::isRevertWithConfirmation()
1624 //FIXME owner && diff
1629 void SVN::undoLast()
1631 // merge the current with the previous version
1632 // in a reverse patch kind of way, so that the
1633 // result is to revert the last changes.
1634 lyxerr << "Sorry, not implemented." << endl;
1638 bool SVN::undoLastEnabled()
1644 string SVN::revisionInfo(LyXVC::RevisionInfo const info)
1646 if (info == LyXVC::Tree) {
1647 if (rev_tree_cache_.empty())
1648 if (!getTreeRevisionInfo())
1649 rev_tree_cache_ = "?";
1650 if (rev_tree_cache_ == "?")
1653 return rev_tree_cache_;
1656 // fill the rest of the attributes for a single file
1657 if (rev_file_cache_.empty())
1658 if (!getFileRevisionInfo())
1659 rev_file_cache_ = "?";
1663 if (rev_file_cache_ == "?")
1665 return rev_file_cache_;
1667 return rev_author_cache_;
1669 return rev_date_cache_;
1671 return rev_time_cache_;
1680 bool SVN::getFileRevisionInfo()
1682 TempFile tempfile("lyxvcout");
1683 FileName tmpf = tempfile.name();
1685 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1689 doVCCommand("svn info --xml " + quoteName(onlyFileName(owner_->absFileName()))
1690 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1691 FileName(owner_->filePath()));
1696 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1704 LYXERR(Debug::LYXVC, line);
1705 if (prefixIs(line, "<commit"))
1707 if (c && prefixIs(line, " revision=\"") && suffixIs(line, "\">")) {
1708 string l1 = subst(line, "revision=\"", "");
1709 string l2 = trim(subst(l1, "\">", ""));
1711 rev_file_cache_ = rev = l2;
1713 if (c && prefixIs(line, "<author>") && suffixIs(line, "</author>")) {
1714 string l1 = subst(line, "<author>", "");
1715 string l2 = subst(l1, "</author>", "");
1716 rev_author_cache_ = l2;
1718 if (c && prefixIs(line, "<date>") && suffixIs(line, "</date>")) {
1719 string l1 = subst(line, "<date>", "");
1720 string l2 = subst(l1, "</date>", "");
1721 l2 = split(l2, l1, 'T');
1722 rev_date_cache_ = l1;
1723 l2 = split(l2, l1, '.');
1724 rev_time_cache_ = l1;
1729 return !rev.empty();
1733 bool SVN::getTreeRevisionInfo()
1735 TempFile tempfile("lyxvcout");
1736 FileName tmpf = tempfile.name();
1738 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1742 doVCCommand("svnversion -n . > " + quoteName(tmpf.toFilesystemEncoding()),
1743 FileName(owner_->filePath()));
1748 // only first line in case something bad happens.
1749 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1754 rev_tree_cache_ = line;
1755 return !line.empty();
1759 void SVN::getLog(FileName const & tmpf)
1761 doVCCommand("svn log " + quoteName(onlyFileName(owner_->absFileName()))
1762 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1763 FileName(owner_->filePath()));
1767 bool SVN::prepareFileRevision(string const & revis, string & f)
1769 if (!isStrInt(revis))
1772 int rev = convert<int>(revis);
1774 if (!getFileRevisionInfo())
1777 rev = convert<int>(rev_file_cache_);
1778 // go back for minus rev
1780 rev = rev + convert<int>(rev_file_cache_);
1785 string revname = convert<string>(rev);
1786 TempFile tempfile("lyxvcrev_" + revname + '_');
1787 tempfile.setAutoRemove(false);
1788 FileName tmpf = tempfile.name();
1790 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1794 doVCCommand("svn cat -r " + revname + ' '
1795 + quoteName(onlyFileName(owner_->absFileName()))
1796 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1797 FileName(owner_->filePath()));
1799 if (tmpf.isFileEmpty())
1802 f = tmpf.absFileName();
1807 bool SVN::prepareFileRevisionEnabled()
1814 bool SVN::toggleReadOnlyEnabled()
1820 /////////////////////////////////////////////////////////////////////
1824 /////////////////////////////////////////////////////////////////////
1826 GIT::GIT(FileName const & m, Buffer * b) : VCS(b)
1828 // Here we know that the buffer file is either already in GIT or
1829 // about to be registered
1835 FileName const GIT::findFile(FileName const & file)
1837 // First we check the existence of repository meta data.
1838 if (!VCS::checkparentdirs(file, ".git")) {
1839 LYXERR(Debug::LYXVC, "Cannot find GIT meta data for " << file);
1843 // Now we check the status of the file.
1844 TempFile tempfile("lyxvcout");
1845 FileName tmpf = tempfile.name();
1847 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1851 string const fname = onlyFileName(file.absFileName());
1852 LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under git control for `"
1854 doVCCommandCall("git ls-files " +
1855 quoteName(fname) + " > " +
1856 quoteName(tmpf.toFilesystemEncoding()),
1859 bool found = !tmpf.isFileEmpty();
1860 LYXERR(Debug::LYXVC, "GIT control: " << (found ? "enabled" : "disabled"));
1861 return found ? file : FileName();
1865 void GIT::scanMaster()
1867 // vcstatus code is somewhat superflous,
1868 // until we want to implement read-only toggle for git.
1869 vcstatus = NOLOCKING;
1873 bool GIT::retrieve(FileName const & file)
1875 LYXERR(Debug::LYXVC, "LyXVC::GIT: retrieve.\n\t" << file);
1876 // The caller ensures that file does not exist, so no need to check that.
1877 return doVCCommandCall("git checkout -q " + quoteName(file.onlyFileName()),
1878 file.onlyPath()) == 0;
1882 void GIT::registrer(string const & /*msg*/)
1884 doVCCommand("git add " + quoteName(onlyFileName(owner_->absFileName())),
1885 FileName(owner_->filePath()));
1889 bool GIT::renameEnabled()
1895 string GIT::rename(support::FileName const & newFile, string const & msg)
1897 // git mv does not require a log message, since it does not commit.
1898 // In LyX we commit immediately afterwards, otherwise it could be
1899 // confusing to the user to have two uncommitted files.
1900 FileName path(owner_->filePath());
1901 string relFile(to_utf8(newFile.relPath(path.absFileName())));
1902 string cmd("git mv " + quoteName(onlyFileName(owner_->absFileName())) +
1903 ' ' + quoteName(relFile));
1904 if (doVCCommand(cmd, path)) {
1905 cmd = "git checkout -q " +
1906 quoteName(onlyFileName(owner_->absFileName())) + ' ' +
1908 doVCCommand(cmd, path);
1909 if (newFile.exists())
1910 newFile.removeFile();
1913 vector<support::FileName> f;
1914 f.push_back(owner_->fileName());
1915 f.push_back(newFile);
1917 if (checkIn(f, msg, log) != LyXVC::VCSuccess) {
1918 cmd = "git checkout -q " +
1919 quoteName(onlyFileName(owner_->absFileName())) + ' ' +
1921 doVCCommand(cmd, path);
1922 if (newFile.exists())
1923 newFile.removeFile();
1930 bool GIT::copyEnabled()
1936 string GIT::copy(support::FileName const & /*newFile*/, string const & /*msg*/)
1938 // git does not support copy with history preservation
1943 LyXVC::CommandResult GIT::checkIn(string const & msg, string & log)
1945 vector<support::FileName> f(1, owner_->fileName());
1946 return checkIn(f, msg, log);
1950 LyXVC::CommandResult
1951 GIT::checkIn(vector<support::FileName> const & f, string const & msg, string & log)
1953 TempFile tempfile("lyxvcout");
1954 FileName tmpf = tempfile.name();
1956 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1957 log = N_("Error: Could not generate logfile.");
1958 return LyXVC::ErrorBefore;
1962 os << "git commit -m \"" << msg << '"';
1963 for (size_t i = 0; i < f.size(); ++i)
1964 os << ' ' << quoteName(f[i].onlyFileName());
1965 os << " > " << quoteName(tmpf.toFilesystemEncoding());
1966 LyXVC::CommandResult ret =
1967 doVCCommand(os.str(), FileName(owner_->filePath())) ?
1968 LyXVC::ErrorCommand : LyXVC::VCSuccess;
1970 string res = scanLogFile(tmpf, log);
1972 frontend::Alert::error(_("Revision control error."),
1973 _("Error when committing to repository.\n"
1974 "You have to manually resolve the problem.\n"
1975 "LyX will reopen the document after you press OK."));
1976 ret = LyXVC::ErrorCommand;
1980 log.insert(0, "GIT: ");
1981 if (ret == LyXVC::VCSuccess && log.empty())
1982 log = "GIT: Proceeded";
1987 bool GIT::checkInEnabled()
1993 bool GIT::isCheckInWithConfirmation()
1995 // FIXME one day common getDiff and perhaps OpMode for all backends
1997 TempFile tempfile("lyxvcout");
1998 FileName tmpf = tempfile.name();
2000 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
2004 doVCCommandCall("git diff " + quoteName(owner_->absFileName())
2005 + " > " + quoteName(tmpf.toFilesystemEncoding()),
2006 FileName(owner_->filePath()));
2008 docstring diff = tmpf.fileContents("UTF-8");
2017 // FIXME Correctly return code should be checked instead of this.
2018 // This would need another solution than just plain startscript.
2019 // Hint from Andre': QProcess::readAllStandardError()...
2020 string GIT::scanLogFile(FileName const & f, string & status)
2022 ifstream ifs(f.toFilesystemEncoding().c_str());
2027 LYXERR(Debug::LYXVC, line << "\n");
2029 status += line + "; ";
2030 if (prefixIs(line, "C ") || prefixIs(line, "CU ")
2031 || contains(line, "Commit failed")) {
2041 string GIT::checkOut()
2047 bool GIT::checkOutEnabled()
2053 string GIT::repoUpdate()
2059 bool GIT::repoUpdateEnabled()
2065 string GIT::lockingToggle()
2071 bool GIT::lockingToggleEnabled()
2079 // Reverts to the version in GIT repository and
2080 // gets the updated version from the repository.
2081 string const fil = quoteName(onlyFileName(owner_->absFileName()));
2083 if (doVCCommand("git checkout -q " + fil,
2084 FileName(owner_->filePath())))
2086 owner_->markClean();
2091 bool GIT::isRevertWithConfirmation()
2093 //FIXME owner && diff
2098 void GIT::undoLast()
2100 // merge the current with the previous version
2101 // in a reverse patch kind of way, so that the
2102 // result is to revert the last changes.
2103 lyxerr << "Sorry, not implemented." << endl;
2107 bool GIT::undoLastEnabled()
2113 string GIT::revisionInfo(LyXVC::RevisionInfo const info)
2115 if (info == LyXVC::Tree) {
2116 if (rev_tree_cache_.empty())
2117 if (!getTreeRevisionInfo())
2118 rev_tree_cache_ = "?";
2119 if (rev_tree_cache_ == "?")
2122 return rev_tree_cache_;
2125 // fill the rest of the attributes for a single file
2126 if (rev_file_cache_.empty())
2127 if (!getFileRevisionInfo()) {
2128 rev_file_cache_ = "?";
2129 rev_file_abbrev_cache_ = "?";
2134 if (rev_file_cache_ == "?")
2136 return rev_file_cache_;
2137 case LyXVC::FileAbbrev:
2138 if (rev_file_abbrev_cache_ == "?")
2140 return rev_file_abbrev_cache_;
2142 return rev_author_cache_;
2144 return rev_date_cache_;
2146 return rev_time_cache_;
2155 bool GIT::getFileRevisionInfo()
2157 TempFile tempfile("lyxvcout");
2158 FileName tmpf = tempfile.name();
2160 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
2164 doVCCommand("git log -n 1 --pretty=format:%H%n%h%n%an%n%ai " + quoteName(onlyFileName(owner_->absFileName()))
2165 + " > " + quoteName(tmpf.toFilesystemEncoding()),
2166 FileName(owner_->filePath()));
2171 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
2174 getline(ifs, rev_file_cache_);
2176 getline(ifs, rev_file_abbrev_cache_);
2178 getline(ifs, rev_author_cache_);
2182 rev_time_cache_ = split(line, rev_date_cache_, ' ');
2186 return !rev_file_cache_.empty();
2190 bool GIT::getTreeRevisionInfo()
2192 TempFile tempfile("lyxvcout");
2193 FileName tmpf = tempfile.name();
2195 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
2199 doVCCommand("git describe --abbrev --dirty --long > " + quoteName(tmpf.toFilesystemEncoding()),
2200 FileName(owner_->filePath()),
2201 false); //git describe returns $?=128 when no tag found (but git repo still exists)
2206 // only first line in case something bad happens.
2207 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
2208 getline(ifs, rev_tree_cache_);
2211 return !rev_tree_cache_.empty();
2215 void GIT::getLog(FileName const & tmpf)
2217 doVCCommand("git log " + quoteName(onlyFileName(owner_->absFileName()))
2218 + " > " + quoteName(tmpf.toFilesystemEncoding()),
2219 FileName(owner_->filePath()));
2223 //at this moment we don't accept revision SHA, but just number of revision steps back
2224 //GUI and infrastucture needs to be changed first
2225 bool GIT::prepareFileRevision(string const & revis, string & f)
2227 // anything positive means we got hash, not "0" or minus revision
2230 // hash is rarely number and should be long
2231 if (isStrInt(revis) && revis.length()<20)
2232 rev = convert<int>(revis);
2234 // revision and filename
2237 // go back for "minus" revisions
2239 pointer = "HEAD~" + convert<string>(-rev);
2246 TempFile tempfile("lyxvcrev_" + revis + '_');
2247 tempfile.setAutoRemove(false);
2248 FileName tmpf = tempfile.name();
2250 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
2254 doVCCommand("git show " + pointer + "./"
2255 + quoteName(onlyFileName(owner_->absFileName()))
2256 + " > " + quoteName(tmpf.toFilesystemEncoding()),
2257 FileName(owner_->filePath()));
2259 if (tmpf.isFileEmpty())
2262 f = tmpf.absFileName();
2267 bool GIT::prepareFileRevisionEnabled()
2273 bool GIT::toggleReadOnlyEnabled()