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"
16 #include "DispatchResult.h"
18 #include "FuncRequest.h"
20 #include "frontends/alert.h"
21 #include "frontends/Application.h"
23 #include "support/convert.h"
24 #include "support/debug.h"
25 #include "support/filetools.h"
26 #include "support/gettext.h"
27 #include "support/lstrings.h"
28 #include "support/PathChanger.h"
29 #include "support/Systemcall.h"
30 #include "support/regex.h"
31 #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()
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_;
439 bool RCS::getRevisionInfo()
441 TempFile tempfile("lyxvcout");
442 FileName tmpf = tempfile.name();
444 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
447 doVCCommand("rlog -r " + quoteName(onlyFileName(owner_->absFileName()))
448 + " > " + quoteName(tmpf.toFilesystemEncoding()),
449 FileName(owner_->filePath()));
454 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
457 // we reached to the entry, i.e. after initial log message
459 // line with critical info, e.g:
460 //"date: 2011/07/02 11:02:54; author: sanda; state: Exp; lines: +17 -2"
465 LYXERR(Debug::LYXVC, line);
466 if (entry && prefixIs(line, "date:")) {
470 if (prefixIs(line, "revision"))
476 rev_date_cache_ = token(result, ' ', 1);
477 rev_time_cache_ = rtrim(token(result, ' ', 2), ";");
478 rev_author_cache_ = trim(token(token(result, ';', 1), ':', 1));
480 return !rev_author_cache_.empty();
483 bool RCS::prepareFileRevision(string const &revis, string & f)
486 if (!VCS::makeRCSRevision(version_, rev))
489 TempFile tempfile("lyxvcrev_" + rev + '_');
490 tempfile.setAutoRemove(false);
491 FileName tmpf = tempfile.name();
493 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
497 doVCCommand("co -p" + rev + ' '
498 + quoteName(onlyFileName(owner_->absFileName()))
499 + " > " + quoteName(tmpf.toFilesystemEncoding()),
500 FileName(owner_->filePath()));
502 if (tmpf.isFileEmpty())
505 f = tmpf.absFileName();
510 bool RCS::prepareFileRevisionEnabled()
516 /////////////////////////////////////////////////////////////////////
520 /////////////////////////////////////////////////////////////////////
522 CVS::CVS(FileName const & m, Buffer * b) : VCS(b)
524 // Here we know that the buffer file is either already in CVS or
525 // about to be registered
527 have_rev_info_ = false;
532 FileName const CVS::findFile(FileName const & file)
534 // First we look for the CVS/Entries in the same dir
535 // where we have file.
536 FileName const entries(onlyPath(file.absFileName()) + "/CVS/Entries");
537 string const tmpf = '/' + onlyFileName(file.absFileName()) + '/';
538 LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under cvs in `" << entries
539 << "' for `" << tmpf << '\'');
540 if (entries.isReadableFile()) {
541 // Ok we are at least in a CVS dir. Parse the CVS/Entries
542 // and see if we can find this file. We do a fast and
544 ifstream ifs(entries.toFilesystemEncoding().c_str());
546 while (getline(ifs, line)) {
547 LYXERR(Debug::LYXVC, "\tEntries: " << line);
548 if (contains(line, tmpf))
556 void CVS::scanMaster()
558 LYXERR(Debug::LYXVC, "LyXVC::CVS: scanMaster. \n Checking: " << master_);
559 // Ok now we do the real scan...
560 ifstream ifs(master_.toFilesystemEncoding().c_str());
561 string name = onlyFileName(owner_->absFileName());
562 string tmpf = '/' + name + '/';
563 LYXERR(Debug::LYXVC, "\tlooking for `" << tmpf << '\'');
565 static regex const reg("/(.*)/(.*)/(.*)/(.*)/(.*)");
566 while (getline(ifs, line)) {
567 LYXERR(Debug::LYXVC, "\t line: " << line);
568 if (contains(line, tmpf)) {
569 // Ok extract the fields.
572 // false positive from coverity
573 // coverity[CHECKED_RETURN]
574 regex_match(line, sm, reg);
576 //sm[0]; // whole matched string
578 version_ = sm.str(2);
579 string const file_date = sm.str(3);
582 //sm[5]; // tag or tagdate
583 FileName file(owner_->absFileName());
584 if (file.isReadableFile()) {
585 time_t mod = file.lastModified();
586 string mod_date = rtrim(asctime(gmtime(&mod)), "\n");
587 LYXERR(Debug::LYXVC, "Date in Entries: `" << file_date
588 << "'\nModification date of file: `" << mod_date << '\'');
589 if (file.isReadOnly()) {
590 // readonly checkout is unlocked
593 FileName bdir(addPath(master_.onlyPath().absFileName(),"Base"));
594 FileName base(addName(bdir.absFileName(),name));
595 // if base version is existent "cvs edit" was used to lock
596 vcstatus = base.isReadableFile() ? LOCKED : NOLOCKING;
599 vcstatus = NOLOCKING;
607 bool CVS::retrieve(FileName const & file)
609 LYXERR(Debug::LYXVC, "LyXVC::CVS: retrieve.\n\t" << file);
610 // The caller ensures that file does not exist, so no need to check that.
611 return doVCCommandCall("cvs -q update " + quoteName(file.toFilesystemEncoding()),
612 file.onlyPath()) == 0;
616 string const CVS::getTarget(OperationMode opmode) const
620 // in client server mode CVS does not like full path operand for directory operation
621 // since LyX switches to the repo dir "." is good enough as target
624 return quoteName(onlyFileName(owner_->absFileName()));
630 docstring CVS::toString(CvsStatus status) const
634 return _("Up-to-date");
635 case LocallyModified:
636 return _("Locally Modified");
638 return _("Locally Added");
640 return _("Needs Merge");
642 return _("Needs Checkout");
644 return _("No CVS file");
646 return _("Cannot retrieve CVS status");
652 int CVS::doVCCommandWithOutput(string const & cmd, FileName const & path,
653 FileName const & output, bool reportError)
655 string redirection = output.empty() ? "" : " > " + quoteName(output.toFilesystemEncoding());
656 return doVCCommand(cmd + redirection, path, reportError);
660 int CVS::doVCCommandCallWithOutput(std::string const & cmd,
661 support::FileName const & path,
662 support::FileName const & output)
664 string redirection = output.empty() ? "" : " > " + quoteName(output.toFilesystemEncoding());
665 return doVCCommandCall(cmd + redirection, path);
669 CVS::CvsStatus CVS::getStatus()
671 TempFile tempfile("lyxvout");
672 FileName tmpf = tempfile.name();
674 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
678 if (doVCCommandCallWithOutput("cvs status " + getTarget(File),
679 FileName(owner_->filePath()), tmpf)) {
683 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
684 CvsStatus status = NoCvsFile;
689 LYXERR(Debug::LYXVC, line << '\n');
690 if (prefixIs(line, "File:")) {
691 if (contains(line, "Up-to-date"))
693 else if (contains(line, "Locally Modified"))
694 status = LocallyModified;
695 else if (contains(line, "Locally Added"))
696 status = LocallyAdded;
697 else if (contains(line, "Needs Merge"))
699 else if (contains(line, "Needs Checkout"))
700 status = NeedsCheckout;
706 void CVS::getRevisionInfo()
710 have_rev_info_ = true;
711 TempFile tempfile("lyxvout");
712 FileName tmpf = tempfile.name();
714 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
718 int rc = doVCCommandCallWithOutput("cvs log -r" + version_
719 + ' ' + getTarget(File),
720 FileName(owner_->filePath()), tmpf);
722 LYXERR(Debug::LYXVC, "cvs log failed with exit code " << rc);
726 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
727 static regex const reg("date: (.*) (.*) (.*); author: (.*); state: (.*);(.*)");
732 LYXERR(Debug::LYXVC, line << '\n');
733 if (prefixIs(line, "date:")) {
735 regex_match(line, sm, reg);
736 //sm[0]; // whole matched string
737 rev_date_cache_ = sm[1];
738 rev_time_cache_ = sm[2];
739 //sm[3]; // GMT offset
740 rev_author_cache_ = sm[4];
744 if (rev_author_cache_.empty())
746 "Could not retrieve revision info for " << version_ <<
747 " of " << getTarget(File));
751 void CVS::registrer(string const & msg)
753 doVCCommand("cvs -q add -m \"" + msg + "\" "
755 FileName(owner_->filePath()));
759 bool CVS::renameEnabled()
765 string CVS::rename(support::FileName const & newFile, string const & msg)
767 // CVS has no real rename command, so we create a poor mans version
768 support::FileName const oldFile(owner_->absFileName());
769 string ret = copy(newFile, msg);
772 string cmd = "cvs -q remove -m \"" + msg + "\" " +
773 quoteName(oldFile.onlyFileName());
774 FileName path(oldFile.onlyPath());
775 return doVCCommand(cmd, path) ? string() : ret;
779 bool CVS::copyEnabled()
785 string CVS::copy(support::FileName const & newFile, string const & msg)
787 // CVS has no real copy command, so we create a poor mans version
788 support::FileName const oldFile(owner_->absFileName());
789 if (!oldFile.copyTo(newFile))
791 FileName path(oldFile.onlyPath());
792 string relFile(to_utf8(newFile.relPath(path.absFileName())));
793 string cmd("cvs -q add -m \"" + msg + "\" " + quoteName(relFile));
794 return doVCCommand(cmd, path) ? string() : "CVS: Proceeded";
798 void CVS::getDiff(OperationMode opmode, FileName const & tmpf)
800 doVCCommandWithOutput("cvs diff " + getTarget(opmode),
801 FileName(owner_->filePath()), tmpf, false);
808 return doVCCommand("cvs -q edit " + getTarget(File),
809 FileName(owner_->filePath()));
816 return doVCCommand("cvs -q unedit " + getTarget(File),
817 FileName(owner_->filePath()));
821 int CVS::update(OperationMode opmode, FileName const & tmpf)
823 return doVCCommandWithOutput("cvs -q update "
825 FileName(owner_->filePath()), tmpf, false);
829 string CVS::scanLogFile(FileName const & f, string & status)
831 ifstream ifs(f.toFilesystemEncoding().c_str());
836 LYXERR(Debug::LYXVC, line << '\n');
838 status += line + "; ";
839 if (prefixIs(line, "C ")) {
849 LyXVC::CommandResult CVS::checkIn(string const & msg, string & log)
851 CvsStatus status = getStatus();
854 if (vcstatus != NOLOCKING)
856 return LyXVC::ErrorCommand;
857 log = "CVS: Proceeded";
858 return LyXVC::VCSuccess;
859 case LocallyModified:
861 int rc = doVCCommand("cvs -q commit -m \"" + msg + "\" "
863 FileName(owner_->filePath()));
865 return LyXVC::ErrorCommand;
866 log = "CVS: Proceeded";
867 return LyXVC::VCSuccess;
871 frontend::Alert::error(_("Revision control error."),
872 _("The repository version is newer then the current check out.\n"
873 "You have to update from repository first or revert your changes.")) ;
876 frontend::Alert::error(_("Revision control error."),
877 bformat(_("Bad status when checking in changes.\n"
882 return LyXVC::ErrorBefore;
886 bool CVS::isLocked() const
888 FileName fn(owner_->absFileName());
890 return !fn.isReadOnly();
894 bool CVS::checkInEnabled()
896 if (vcstatus != NOLOCKING)
903 bool CVS::isCheckInWithConfirmation()
905 CvsStatus status = getStatus();
906 return status == LocallyModified || status == LocallyAdded;
910 string CVS::checkOut()
912 if (vcstatus != NOLOCKING && edit())
914 TempFile tempfile("lyxvout");
915 FileName tmpf = tempfile.name();
917 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
921 int rc = update(File, tmpf);
923 string const res = scanLogFile(tmpf, log);
925 frontend::Alert::error(_("Revision control error."),
926 bformat(_("Error when updating from repository.\n"
927 "You have to manually resolve the conflicts NOW!\n'%1$s'.\n\n"
928 "After pressing OK, LyX will try to reopen the resolved document."),
929 from_local8bit(res)));
933 return rc ? string() : log.empty() ? "CVS: Proceeded" : "CVS: " + log;
937 bool CVS::checkOutEnabled()
939 if (vcstatus != NOLOCKING)
946 string CVS::repoUpdate()
948 TempFile tempfile("lyxvout");
949 FileName tmpf = tempfile.name();
951 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
955 getDiff(Directory, tmpf);
956 docstring res = tmpf.fileContents("UTF-8");
958 LYXERR(Debug::LYXVC, "Diff detected:\n" << res);
959 docstring const file = from_utf8(owner_->filePath());
960 docstring text = bformat(_("There were detected changes "
961 "in the working directory:\n%1$s\n\n"
962 "Possible file conflicts must be then resolved manually "
963 "or you will need to revert back to the repository version."), file);
964 int ret = frontend::Alert::prompt(_("Changes detected"),
965 text, 0, 1, _("&Continue"), _("&Abort"), _("View &Log ..."));
967 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "file " + tmpf.absFileName()));
968 ret = frontend::Alert::prompt(_("Changes detected"),
969 text, 0, 1, _("&Continue"), _("&Abort"));
970 hideDialogs("file", 0);
976 int rc = update(Directory, tmpf);
977 res += "Update log:\n" + tmpf.fileContents("UTF-8");
978 LYXERR(Debug::LYXVC, res);
981 string sres = scanLogFile(tmpf, log);
983 docstring const file = owner_->fileName().displayName(20);
984 frontend::Alert::error(_("Revision control error."),
985 bformat(_("Error when updating document %1$s from repository.\n"
986 "You have to manually resolve the conflicts NOW!\n'%2$s'.\n\n"
987 "After pressing OK, LyX will try to reopen the resolved document."),
988 file, from_local8bit(sres)));
992 return rc ? string() : log.empty() ? "CVS: Proceeded" : "CVS: " + log;
996 bool CVS::repoUpdateEnabled()
1002 string CVS::lockingToggle()
1004 lyxerr << "Sorry, not implemented." << endl;
1009 bool CVS::lockingToggleEnabled()
1015 bool CVS::isRevertWithConfirmation()
1017 CvsStatus status = getStatus();
1018 return !owner_->isClean() || status == LocallyModified || status == NeedsMerge;
1024 // Reverts to the version in CVS repository and
1025 // gets the updated version from the repository.
1026 CvsStatus status = getStatus();
1029 if (vcstatus != NOLOCKING)
1030 return 0 == unedit();
1034 case LocallyModified: {
1035 FileName f(owner_->absFileName());
1037 update(File, FileName());
1038 owner_->markClean();
1041 case LocallyAdded: {
1042 docstring const file = owner_->fileName().displayName(20);
1043 frontend::Alert::error(_("Revision control error."),
1044 bformat(_("The document %1$s is not in repository.\n"
1045 "You have to check in the first revision before you can revert."),
1050 docstring const file = owner_->fileName().displayName(20);
1051 frontend::Alert::error(_("Revision control error."),
1052 bformat(_("Cannot revert document %1$s to repository version.\n"
1053 "The status '%2$s' is unexpected."),
1054 file, toString(status)));
1062 void CVS::undoLast()
1064 // merge the current with the previous version
1065 // in a reverse patch kind of way, so that the
1066 // result is to revert the last changes.
1067 lyxerr << "Sorry, not implemented." << endl;
1071 bool CVS::undoLastEnabled()
1077 void CVS::getLog(FileName const & tmpf)
1079 doVCCommandWithOutput("cvs log " + getTarget(File),
1080 FileName(owner_->filePath()),
1085 bool CVS::toggleReadOnlyEnabled()
1091 string CVS::revisionInfo(LyXVC::RevisionInfo const info)
1093 if (!version_.empty()) {
1099 return rev_author_cache_;
1101 return rev_date_cache_;
1103 return rev_time_cache_;
1111 bool CVS::prepareFileRevision(string const & revis, string & f)
1114 if (!VCS::makeRCSRevision(version_, rev))
1117 TempFile tempfile("lyxvcrev_" + rev + '_');
1118 tempfile.setAutoRemove(false);
1119 FileName tmpf = tempfile.name();
1121 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1125 doVCCommandWithOutput("cvs update -p -r" + rev + ' '
1127 FileName(owner_->filePath()), tmpf);
1129 if (tmpf.isFileEmpty())
1132 f = tmpf.absFileName();
1137 bool CVS::prepareFileRevisionEnabled()
1143 /////////////////////////////////////////////////////////////////////
1147 /////////////////////////////////////////////////////////////////////
1149 SVN::SVN(FileName const & m, Buffer * b) : VCS(b)
1151 // Here we know that the buffer file is either already in SVN or
1152 // about to be registered
1159 FileName const SVN::findFile(FileName const & file)
1161 // First we check the existence of repository meta data.
1162 if (!VCS::checkparentdirs(file, ".svn")) {
1163 LYXERR(Debug::LYXVC, "Cannot find SVN meta data for " << file);
1167 // Now we check the status of the file.
1168 TempFile tempfile("lyxvcout");
1169 FileName tmpf = tempfile.name();
1171 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1175 string const fname = onlyFileName(file.absFileName());
1176 LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under svn control for `" << fname << '\'');
1177 bool found = 0 == doVCCommandCall("svn info " + quoteName(fname)
1178 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1180 LYXERR(Debug::LYXVC, "SVN control: " << (found ? "enabled" : "disabled"));
1181 return found ? file : FileName();
1185 void SVN::scanMaster()
1187 // vcstatus code is somewhat superflous,
1188 // until we want to implement read-only toggle for svn.
1189 vcstatus = NOLOCKING;
1190 if (checkLockMode()) {
1194 vcstatus = UNLOCKED;
1199 bool SVN::checkLockMode()
1201 TempFile tempfile("lyxvcout");
1202 FileName tmpf = tempfile.name();
1204 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1208 LYXERR(Debug::LYXVC, "Detecting locking mode...");
1209 if (doVCCommandCall("svn proplist " + quoteName(onlyFileName(owner_->absFileName()))
1210 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1211 FileName(owner_->filePath())))
1214 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1218 while (ifs && !ret) {
1220 LYXERR(Debug::LYXVC, line);
1221 if (contains(line, "svn:needs-lock"))
1224 LYXERR(Debug::LYXVC, "Locking enabled: " << ret);
1232 bool SVN::isLocked() const
1234 FileName file(owner_->absFileName());
1236 return !file.isReadOnly();
1240 bool SVN::retrieve(FileName const & file)
1242 LYXERR(Debug::LYXVC, "LyXVC::SVN: retrieve.\n\t" << file);
1243 // The caller ensures that file does not exist, so no need to check that.
1244 return doVCCommandCall("svn update -q --non-interactive " + quoteName(file.onlyFileName()),
1245 file.onlyPath()) == 0;
1249 void SVN::registrer(string const & /*msg*/)
1251 doVCCommand("svn add -q " + quoteName(onlyFileName(owner_->absFileName())),
1252 FileName(owner_->filePath()));
1256 bool SVN::renameEnabled()
1262 string SVN::rename(support::FileName const & newFile, string const & msg)
1264 // svn move does not require a log message, since it does not commit.
1265 // In LyX we commit immediately afterwards, otherwise it could be
1266 // confusing to the user to have two uncommitted files.
1267 FileName path(owner_->filePath());
1268 string relFile(to_utf8(newFile.relPath(path.absFileName())));
1269 string cmd("svn move -q " + quoteName(onlyFileName(owner_->absFileName())) +
1270 ' ' + quoteName(relFile));
1271 if (doVCCommand(cmd, path)) {
1272 cmd = "svn revert -q " +
1273 quoteName(onlyFileName(owner_->absFileName())) + ' ' +
1275 doVCCommand(cmd, path);
1276 if (newFile.exists())
1277 newFile.removeFile();
1280 vector<support::FileName> f;
1281 f.push_back(owner_->fileName());
1282 f.push_back(newFile);
1284 if (checkIn(f, msg, log) != LyXVC::VCSuccess) {
1285 cmd = "svn revert -q " +
1286 quoteName(onlyFileName(owner_->absFileName())) + ' ' +
1288 doVCCommand(cmd, path);
1289 if (newFile.exists())
1290 newFile.removeFile();
1297 bool SVN::copyEnabled()
1303 string SVN::copy(support::FileName const & newFile, string const & msg)
1305 // svn copy does not require a log message, since it does not commit.
1306 // In LyX we commit immediately afterwards, otherwise it could be
1307 // confusing to the user to have an uncommitted file.
1308 FileName path(owner_->filePath());
1309 string relFile(to_utf8(newFile.relPath(path.absFileName())));
1310 string cmd("svn copy -q " + quoteName(onlyFileName(owner_->absFileName())) +
1311 ' ' + quoteName(relFile));
1312 if (doVCCommand(cmd, path))
1314 vector<support::FileName> f(1, newFile);
1316 if (checkIn(f, msg, log) == LyXVC::VCSuccess)
1322 LyXVC::CommandResult SVN::checkIn(string const & msg, string & log)
1324 vector<support::FileName> f(1, owner_->fileName());
1325 return checkIn(f, msg, log);
1329 LyXVC::CommandResult
1330 SVN::checkIn(vector<support::FileName> const & f, string const & msg, string & log)
1332 TempFile tempfile("lyxvcout");
1333 FileName tmpf = tempfile.name();
1335 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1336 log = N_("Error: Could not generate logfile.");
1337 return LyXVC::ErrorBefore;
1341 os << "svn commit -m \"" << msg << '"';
1342 for (size_t i = 0; i < f.size(); ++i)
1343 os << ' ' << quoteName(f[i].onlyFileName());
1344 os << " > " << quoteName(tmpf.toFilesystemEncoding());
1345 LyXVC::CommandResult ret =
1346 doVCCommand(os.str(), FileName(owner_->filePath())) ?
1347 LyXVC::ErrorCommand : LyXVC::VCSuccess;
1349 string res = scanLogFile(tmpf, log);
1351 frontend::Alert::error(_("Revision control error."),
1352 _("Error when committing to repository.\n"
1353 "You have to manually resolve the problem.\n"
1354 "LyX will reopen the document after you press OK."));
1355 ret = LyXVC::ErrorCommand;
1358 if (!fileLock(false, tmpf, log))
1359 ret = LyXVC::ErrorCommand;
1362 log.insert(0, "SVN: ");
1363 if (ret == LyXVC::VCSuccess && log.empty())
1364 log = "SVN: Proceeded";
1369 bool SVN::checkInEnabled()
1378 bool SVN::isCheckInWithConfirmation()
1380 // FIXME one day common getDiff and perhaps OpMode for all backends
1382 TempFile tempfile("lyxvcout");
1383 FileName tmpf = tempfile.name();
1385 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1389 doVCCommandCall("svn diff " + quoteName(owner_->absFileName())
1390 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1391 FileName(owner_->filePath()));
1393 docstring diff = tmpf.fileContents("UTF-8");
1402 // FIXME Correctly return code should be checked instead of this.
1403 // This would need another solution than just plain startscript.
1404 // Hint from Andre': QProcess::readAllStandardError()...
1405 string SVN::scanLogFile(FileName const & f, string & status)
1407 ifstream ifs(f.toFilesystemEncoding().c_str());
1412 LYXERR(Debug::LYXVC, line << '\n');
1414 status += line + "; ";
1415 if (prefixIs(line, "C ") || prefixIs(line, "CU ")
1416 || contains(line, "Commit failed")) {
1420 if (contains(line, "svn:needs-lock")) {
1430 bool SVN::fileLock(bool lock, FileName const & tmpf, string &status)
1432 if (!locked_mode_ || (isLocked() == lock))
1435 string const arg = lock ? "lock " : "unlock ";
1436 doVCCommand("svn "+ arg + quoteName(onlyFileName(owner_->absFileName()))
1437 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1438 FileName(owner_->filePath()));
1440 // Lock error messages go unfortunately on stderr and are unreachable this way.
1441 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1445 if (!line.empty()) status += line + "; ";
1449 if (isLocked() == lock)
1453 frontend::Alert::error(_("Revision control error."),
1454 _("Error while acquiring write lock.\n"
1455 "Another user is most probably editing\n"
1456 "the current document now!\n"
1457 "Also check the access to the repository."));
1459 frontend::Alert::error(_("Revision control error."),
1460 _("Error while releasing write lock.\n"
1461 "Check the access to the repository."));
1466 string SVN::checkOut()
1468 TempFile tempfile("lyxvcout");
1469 FileName tmpf = tempfile.name();
1471 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1472 return N_("Error: Could not generate logfile.");
1475 doVCCommand("svn update --non-interactive " + quoteName(onlyFileName(owner_->absFileName()))
1476 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1477 FileName(owner_->filePath()));
1480 string const res = scanLogFile(tmpf, log);
1482 frontend::Alert::error(_("Revision control error."),
1483 bformat(_("Error when updating from repository.\n"
1484 "You have to manually resolve the conflicts NOW!\n'%1$s'.\n\n"
1485 "After pressing OK, LyX will try to reopen the resolved document."),
1486 from_local8bit(res)));
1488 fileLock(true, tmpf, log);
1490 return log.empty() ? string() : "SVN: " + log;
1494 bool SVN::checkOutEnabled()
1503 string SVN::repoUpdate()
1505 TempFile tempfile("lyxvcout");
1506 FileName tmpf = tempfile.name();
1508 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1509 return N_("Error: Could not generate logfile.");
1512 doVCCommand("svn diff " + quoteName(owner_->filePath())
1513 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1514 FileName(owner_->filePath()));
1515 docstring res = tmpf.fileContents("UTF-8");
1517 LYXERR(Debug::LYXVC, "Diff detected:\n" << res);
1518 docstring const file = from_utf8(owner_->filePath());
1519 docstring text = bformat(_("There were detected changes "
1520 "in the working directory:\n%1$s\n\n"
1521 "In case of file conflict version of the local directory files "
1522 "will be preferred."
1523 "\n\nContinue?"), file);
1524 int ret = frontend::Alert::prompt(_("Changes detected"),
1525 text, 0, 1, _("&Yes"), _("&No"), _("View &Log ..."));
1527 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "file " + tmpf.absFileName()));
1528 ret = frontend::Alert::prompt(_("Changes detected"),
1529 text, 0, 1, _("&Yes"), _("&No"));
1530 hideDialogs("file", 0);
1536 // Reverting looks too harsh, see bug #6255.
1537 // doVCCommand("svn revert -R " + quoteName(owner_->filePath())
1538 // + " > " + quoteName(tmpf.toFilesystemEncoding()),
1539 // FileName(owner_->filePath()));
1540 // res = "Revert log:\n" + tmpf.fileContents("UTF-8");
1541 doVCCommand("svn update --accept mine-full " + quoteName(owner_->filePath())
1542 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1543 FileName(owner_->filePath()));
1544 res += "Update log:\n" + tmpf.fileContents("UTF-8");
1546 LYXERR(Debug::LYXVC, res);
1547 return to_utf8(res);
1551 bool SVN::repoUpdateEnabled()
1557 string SVN::lockingToggle()
1559 TempFile tempfile("lyxvcout");
1560 FileName tmpf = tempfile.name();
1562 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1563 return N_("Error: Could not generate logfile.");
1566 int ret = doVCCommand("svn proplist " + quoteName(onlyFileName(owner_->absFileName()))
1567 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1568 FileName(owner_->filePath()));
1573 string res = scanLogFile(tmpf, log);
1574 bool locking = contains(res, "svn:needs-lock");
1576 ret = doVCCommand("svn propset svn:needs-lock ON "
1577 + quoteName(onlyFileName(owner_->absFileName()))
1578 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1579 FileName(owner_->filePath()));
1581 ret = doVCCommand("svn propdel svn:needs-lock "
1582 + quoteName(onlyFileName(owner_->absFileName()))
1583 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1584 FileName(owner_->filePath()));
1588 frontend::Alert::warning(_("SVN File Locking"),
1589 (locking ? _("Locking property unset.") : _("Locking property set.")) + '\n'
1590 + _("Do not forget to commit the locking property into the repository."),
1593 return string("SVN: ") + (locking ?
1594 N_("Locking property unset.") : N_("Locking property set."));
1598 bool SVN::lockingToggleEnabled()
1606 // Reverts to the version in SVN repository and
1607 // gets the updated version from the repository.
1608 string const fil = quoteName(onlyFileName(owner_->absFileName()));
1610 if (doVCCommand("svn revert -q " + fil,
1611 FileName(owner_->filePath())))
1613 owner_->markClean();
1618 bool SVN::isRevertWithConfirmation()
1620 //FIXME owner && diff
1625 void SVN::undoLast()
1627 // merge the current with the previous version
1628 // in a reverse patch kind of way, so that the
1629 // result is to revert the last changes.
1630 lyxerr << "Sorry, not implemented." << endl;
1634 bool SVN::undoLastEnabled()
1640 string SVN::revisionInfo(LyXVC::RevisionInfo const info)
1642 if (info == LyXVC::Tree) {
1643 if (rev_tree_cache_.empty())
1644 if (!getTreeRevisionInfo())
1645 rev_tree_cache_ = "?";
1646 if (rev_tree_cache_ == "?")
1649 return rev_tree_cache_;
1652 // fill the rest of the attributes for a single file
1653 if (rev_file_cache_.empty())
1654 if (!getFileRevisionInfo())
1655 rev_file_cache_ = "?";
1659 if (rev_file_cache_ == "?")
1661 return rev_file_cache_;
1663 return rev_author_cache_;
1665 return rev_date_cache_;
1667 return rev_time_cache_;
1676 bool SVN::getFileRevisionInfo()
1678 TempFile tempfile("lyxvcout");
1679 FileName tmpf = tempfile.name();
1681 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1685 doVCCommand("svn info --xml " + quoteName(onlyFileName(owner_->absFileName()))
1686 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1687 FileName(owner_->filePath()));
1692 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1700 LYXERR(Debug::LYXVC, line);
1701 if (prefixIs(line, "<commit"))
1703 if (c && prefixIs(line, " revision=\"") && suffixIs(line, "\">")) {
1704 string l1 = subst(line, "revision=\"", "");
1705 string l2 = trim(subst(l1, "\">", ""));
1707 rev_file_cache_ = rev = l2;
1709 if (c && prefixIs(line, "<author>") && suffixIs(line, "</author>")) {
1710 string l1 = subst(line, "<author>", "");
1711 string l2 = subst(l1, "</author>", "");
1712 rev_author_cache_ = l2;
1714 if (c && prefixIs(line, "<date>") && suffixIs(line, "</date>")) {
1715 string l1 = subst(line, "<date>", "");
1716 string l2 = subst(l1, "</date>", "");
1717 l2 = split(l2, l1, 'T');
1718 rev_date_cache_ = l1;
1719 l2 = split(l2, l1, '.');
1720 rev_time_cache_ = l1;
1725 return !rev.empty();
1729 bool SVN::getTreeRevisionInfo()
1731 TempFile tempfile("lyxvcout");
1732 FileName tmpf = tempfile.name();
1734 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1738 doVCCommand("svnversion -n . > " + quoteName(tmpf.toFilesystemEncoding()),
1739 FileName(owner_->filePath()));
1744 // only first line in case something bad happens.
1745 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
1750 rev_tree_cache_ = line;
1751 return !line.empty();
1755 void SVN::getLog(FileName const & tmpf)
1757 doVCCommand("svn log " + quoteName(onlyFileName(owner_->absFileName()))
1758 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1759 FileName(owner_->filePath()));
1763 bool SVN::prepareFileRevision(string const & revis, string & f)
1765 if (!isStrInt(revis))
1768 int rev = convert<int>(revis);
1770 if (!getFileRevisionInfo())
1773 rev = convert<int>(rev_file_cache_);
1774 // go back for minus rev
1776 rev = rev + convert<int>(rev_file_cache_);
1781 string revname = convert<string>(rev);
1782 TempFile tempfile("lyxvcrev_" + revname + '_');
1783 tempfile.setAutoRemove(false);
1784 FileName tmpf = tempfile.name();
1786 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1790 doVCCommand("svn cat -r " + revname + ' '
1791 + quoteName(onlyFileName(owner_->absFileName()))
1792 + " > " + quoteName(tmpf.toFilesystemEncoding()),
1793 FileName(owner_->filePath()));
1795 if (tmpf.isFileEmpty())
1798 f = tmpf.absFileName();
1803 bool SVN::prepareFileRevisionEnabled()
1810 bool SVN::toggleReadOnlyEnabled()
1816 /////////////////////////////////////////////////////////////////////
1820 /////////////////////////////////////////////////////////////////////
1822 GIT::GIT(FileName const & m, Buffer * b) : VCS(b)
1824 // Here we know that the buffer file is either already in GIT or
1825 // about to be registered
1831 FileName const GIT::findFile(FileName const & file)
1833 // First we check the existence of repository meta data.
1834 if (!VCS::checkparentdirs(file, ".git")) {
1835 LYXERR(Debug::LYXVC, "Cannot find GIT meta data for " << file);
1839 // Now we check the status of the file.
1840 TempFile tempfile("lyxvcout");
1841 FileName tmpf = tempfile.name();
1843 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1847 string const fname = onlyFileName(file.absFileName());
1848 LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under git control for `"
1850 doVCCommandCall("git ls-files " +
1851 quoteName(fname) + " > " +
1852 quoteName(tmpf.toFilesystemEncoding()),
1855 bool found = !tmpf.isFileEmpty();
1856 LYXERR(Debug::LYXVC, "GIT control: " << (found ? "enabled" : "disabled"));
1857 return found ? file : FileName();
1861 void GIT::scanMaster()
1863 // vcstatus code is somewhat superflous,
1864 // until we want to implement read-only toggle for git.
1865 vcstatus = NOLOCKING;
1869 bool GIT::retrieve(FileName const & file)
1871 LYXERR(Debug::LYXVC, "LyXVC::GIT: retrieve.\n\t" << file);
1872 // The caller ensures that file does not exist, so no need to check that.
1873 return doVCCommandCall("git checkout -q " + quoteName(file.onlyFileName()),
1874 file.onlyPath()) == 0;
1878 void GIT::registrer(string const & /*msg*/)
1880 doVCCommand("git add " + quoteName(onlyFileName(owner_->absFileName())),
1881 FileName(owner_->filePath()));
1885 bool GIT::renameEnabled()
1891 string GIT::rename(support::FileName const & newFile, string const & msg)
1893 // git mv does not require a log message, since it does not commit.
1894 // In LyX we commit immediately afterwards, otherwise it could be
1895 // confusing to the user to have two uncommitted files.
1896 FileName path(owner_->filePath());
1897 string relFile(to_utf8(newFile.relPath(path.absFileName())));
1898 string cmd("git mv " + quoteName(onlyFileName(owner_->absFileName())) +
1899 ' ' + quoteName(relFile));
1900 if (doVCCommand(cmd, path)) {
1901 cmd = "git checkout -q " +
1902 quoteName(onlyFileName(owner_->absFileName())) + ' ' +
1904 doVCCommand(cmd, path);
1905 if (newFile.exists())
1906 newFile.removeFile();
1909 vector<support::FileName> f;
1910 f.push_back(owner_->fileName());
1911 f.push_back(newFile);
1913 if (checkIn(f, msg, log) != LyXVC::VCSuccess) {
1914 cmd = "git checkout -q " +
1915 quoteName(onlyFileName(owner_->absFileName())) + ' ' +
1917 doVCCommand(cmd, path);
1918 if (newFile.exists())
1919 newFile.removeFile();
1926 bool GIT::copyEnabled()
1932 string GIT::copy(support::FileName const & /*newFile*/, string const & /*msg*/)
1934 // git does not support copy with history preservation
1939 LyXVC::CommandResult GIT::checkIn(string const & msg, string & log)
1941 vector<support::FileName> f(1, owner_->fileName());
1942 return checkIn(f, msg, log);
1946 LyXVC::CommandResult
1947 GIT::checkIn(vector<support::FileName> const & f, string const & msg, string & log)
1949 TempFile tempfile("lyxvcout");
1950 FileName tmpf = tempfile.name();
1952 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
1953 log = N_("Error: Could not generate logfile.");
1954 return LyXVC::ErrorBefore;
1958 os << "git commit -m \"" << msg << '"';
1959 for (size_t i = 0; i < f.size(); ++i)
1960 os << ' ' << quoteName(f[i].onlyFileName());
1961 os << " > " << quoteName(tmpf.toFilesystemEncoding());
1962 LyXVC::CommandResult ret =
1963 doVCCommand(os.str(), FileName(owner_->filePath())) ?
1964 LyXVC::ErrorCommand : LyXVC::VCSuccess;
1966 string res = scanLogFile(tmpf, log);
1968 frontend::Alert::error(_("Revision control error."),
1969 _("Error when committing to repository.\n"
1970 "You have to manually resolve the problem.\n"
1971 "LyX will reopen the document after you press OK."));
1972 ret = LyXVC::ErrorCommand;
1976 log.insert(0, "GIT: ");
1977 if (ret == LyXVC::VCSuccess && log.empty())
1978 log = "GIT: Proceeded";
1983 bool GIT::checkInEnabled()
1989 bool GIT::isCheckInWithConfirmation()
1991 // FIXME one day common getDiff and perhaps OpMode for all backends
1993 TempFile tempfile("lyxvcout");
1994 FileName tmpf = tempfile.name();
1996 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
2000 doVCCommandCall("git diff " + quoteName(owner_->absFileName())
2001 + " > " + quoteName(tmpf.toFilesystemEncoding()),
2002 FileName(owner_->filePath()));
2004 docstring diff = tmpf.fileContents("UTF-8");
2013 // FIXME Correctly return code should be checked instead of this.
2014 // This would need another solution than just plain startscript.
2015 // Hint from Andre': QProcess::readAllStandardError()...
2016 string GIT::scanLogFile(FileName const & f, string & status)
2018 ifstream ifs(f.toFilesystemEncoding().c_str());
2023 LYXERR(Debug::LYXVC, line << "\n");
2025 status += line + "; ";
2026 if (prefixIs(line, "C ") || prefixIs(line, "CU ")
2027 || contains(line, "Commit failed")) {
2037 string GIT::checkOut()
2043 bool GIT::checkOutEnabled()
2049 string GIT::repoUpdate()
2055 bool GIT::repoUpdateEnabled()
2061 string GIT::lockingToggle()
2067 bool GIT::lockingToggleEnabled()
2075 // Reverts to the version in GIT repository and
2076 // gets the updated version from the repository.
2077 string const fil = quoteName(onlyFileName(owner_->absFileName()));
2079 if (doVCCommand("git checkout -q " + fil,
2080 FileName(owner_->filePath())))
2082 owner_->markClean();
2087 bool GIT::isRevertWithConfirmation()
2089 //FIXME owner && diff
2094 void GIT::undoLast()
2096 // merge the current with the previous version
2097 // in a reverse patch kind of way, so that the
2098 // result is to revert the last changes.
2099 lyxerr << "Sorry, not implemented." << endl;
2103 bool GIT::undoLastEnabled()
2109 string GIT::revisionInfo(LyXVC::RevisionInfo const info)
2111 if (info == LyXVC::Tree) {
2112 if (rev_tree_cache_.empty())
2113 if (!getTreeRevisionInfo())
2114 rev_tree_cache_ = "?";
2115 if (rev_tree_cache_ == "?")
2118 return rev_tree_cache_;
2121 // fill the rest of the attributes for a single file
2122 if (rev_file_cache_.empty())
2123 if (!getFileRevisionInfo())
2124 rev_file_cache_ = "?";
2128 if (rev_file_cache_ == "?")
2130 return rev_file_cache_;
2132 return rev_author_cache_;
2134 return rev_date_cache_;
2136 return rev_time_cache_;
2145 bool GIT::getFileRevisionInfo()
2147 TempFile tempfile("lyxvcout");
2148 FileName tmpf = tempfile.name();
2150 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
2154 doVCCommand("git log -n 1 --pretty=format:%H%n%an%n%ai " + quoteName(onlyFileName(owner_->absFileName()))
2155 + " > " + quoteName(tmpf.toFilesystemEncoding()),
2156 FileName(owner_->filePath()));
2161 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
2164 getline(ifs, rev_file_cache_);
2166 getline(ifs, rev_author_cache_);
2170 rev_time_cache_ = split(line, rev_date_cache_, ' ');
2174 return !rev_file_cache_.empty();
2178 bool GIT::getTreeRevisionInfo()
2180 TempFile tempfile("lyxvcout");
2181 FileName tmpf = tempfile.name();
2183 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
2187 doVCCommand("git describe --abbrev --dirty --long > " + quoteName(tmpf.toFilesystemEncoding()),
2188 FileName(owner_->filePath()));
2193 // only first line in case something bad happens.
2194 ifstream ifs(tmpf.toFilesystemEncoding().c_str());
2195 getline(ifs, rev_tree_cache_);
2198 return !rev_tree_cache_.empty();
2202 void GIT::getLog(FileName const & tmpf)
2204 doVCCommand("git log " + quoteName(onlyFileName(owner_->absFileName()))
2205 + " > " + quoteName(tmpf.toFilesystemEncoding()),
2206 FileName(owner_->filePath()));
2210 //at this moment we don't accept revision SHA, but just number of revision steps back
2211 //GUI and infrastucture needs to be changed first
2212 bool GIT::prepareFileRevision(string const & revis, string & f)
2214 // anything positive means we got hash, not "0" or minus revision
2217 // hash is rarely number and should be long
2218 if (isStrInt(revis) && revis.length()<20)
2219 rev = convert<int>(revis);
2221 // revision and filename
2224 // go back for "minus" revisions
2226 pointer = "HEAD~" + convert<string>(-rev);
2233 TempFile tempfile("lyxvcrev_" + revis + '_');
2234 tempfile.setAutoRemove(false);
2235 FileName tmpf = tempfile.name();
2237 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
2241 doVCCommand("git show " + pointer + "./"
2242 + quoteName(onlyFileName(owner_->absFileName()))
2243 + " > " + quoteName(tmpf.toFilesystemEncoding()),
2244 FileName(owner_->filePath()));
2246 if (tmpf.isFileEmpty())
2249 f = tmpf.absFileName();
2254 bool GIT::prepareFileRevisionEnabled()
2260 bool GIT::toggleReadOnlyEnabled()