]> git.lyx.org Git - lyx.git/blob - src/VCBackend.cpp
664e86c16d40e628e69fd8e490a34d1f91c91875
[lyx.git] / src / VCBackend.cpp
1 /**
2  * \file VCBackend.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "VCBackend.h"
14 #include "Buffer.h"
15
16 #include "frontends/alert.h"
17
18 #include "support/debug.h"
19 #include "support/filetools.h"
20 #include "support/gettext.h"
21 #include "support/lstrings.h"
22 #include "support/Path.h"
23 #include "support/Systemcall.h"
24
25 #include <boost/regex.hpp>
26
27 #include <fstream>
28
29 using namespace std;
30 using namespace lyx::support;
31
32 using boost::regex;
33 using boost::regex_match;
34 using boost::smatch;
35
36 namespace lyx {
37
38
39 int VCS::doVCCommandCall(string const & cmd, FileName const & path){
40         LYXERR(Debug::LYXVC, "doVCCommandCall: " << cmd);
41         Systemcall one;
42         support::PathChanger p(path);
43         return one.startscript(Systemcall::Wait, cmd);
44 }
45
46 int VCS::doVCCommand(string const & cmd, FileName const & path)
47 {
48         owner_->setBusy(true);
49         int const ret = doVCCommandCall(cmd, path);
50         owner_->setBusy(false);
51         if (ret)
52                 frontend::Alert::error(_("Revision control error."),
53                         bformat(_("Some problem occured while running the command:\n"
54                                   "'%1$s'."),
55                         from_utf8(cmd)));
56         return ret;
57 }
58
59
60 /////////////////////////////////////////////////////////////////////
61 //
62 // RCS
63 //
64 /////////////////////////////////////////////////////////////////////
65
66 RCS::RCS(FileName const & m)
67 {
68         master_ = m;
69         scanMaster();
70 }
71
72
73 FileName const RCS::findFile(FileName const & file)
74 {
75         // Check if *,v exists.
76         FileName tmp(file.absFilename() + ",v");
77         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp);
78         if (tmp.isReadableFile()) {
79                 LYXERR(Debug::LYXVC, "Yes " << file << " is under rcs.");
80                 return tmp;
81         }
82
83         // Check if RCS/*,v exists.
84         tmp = FileName(addName(addPath(onlyPath(file.absFilename()), "RCS"), file.absFilename()) + ",v");
85         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under rcs: " << tmp);
86         if (tmp.isReadableFile()) {
87                 LYXERR(Debug::LYXVC, "Yes " << file << " it is under rcs.");
88                 return tmp;
89         }
90
91         return FileName();
92 }
93
94
95 void RCS::retrieve(FileName const & file)
96 {
97         LYXERR(Debug::LYXVC, "LyXVC::RCS: retrieve.\n\t" << file);
98         doVCCommandCall("co -q -r " + quoteName(file.toFilesystemEncoding()),
99                          FileName());
100 }
101
102
103 void RCS::scanMaster()
104 {
105         if (master_.empty())
106                 return;
107
108         LYXERR(Debug::LYXVC, "LyXVC::RCS: scanMaster: " << master_);
109
110         ifstream ifs(master_.toFilesystemEncoding().c_str());
111
112         string token;
113         bool read_enough = false;
114
115         while (!read_enough && ifs >> token) {
116                 LYXERR(Debug::LYXVC, "LyXVC::scanMaster: current lex text: `"
117                         << token << '\'');
118
119                 if (token.empty())
120                         continue;
121                 else if (token == "head") {
122                         // get version here
123                         string tmv;
124                         ifs >> tmv;
125                         tmv = rtrim(tmv, ";");
126                         version_ = tmv;
127                         LYXERR(Debug::LYXVC, "LyXVC: version found to be " << tmv);
128                 } else if (contains(token, "access")
129                            || contains(token, "symbols")
130                            || contains(token, "strict")) {
131                         // nothing
132                 } else if (contains(token, "locks")) {
133                         // get locker here
134                         if (contains(token, ';')) {
135                                 locker_ = "Unlocked";
136                                 vcstatus = UNLOCKED;
137                                 continue;
138                         }
139                         string tmpt;
140                         string s1;
141                         string s2;
142                         do {
143                                 ifs >> tmpt;
144                                 s1 = rtrim(tmpt, ";");
145                                 // tmp is now in the format <user>:<version>
146                                 s1 = split(s1, s2, ':');
147                                 // s2 is user, and s1 is version
148                                 if (s1 == version_) {
149                                         locker_ = s2;
150                                         vcstatus = LOCKED;
151                                         break;
152                                 }
153                         } while (!contains(tmpt, ';'));
154
155                 } else if (token == "comment") {
156                         // we don't need to read any further than this.
157                         read_enough = true;
158                 } else {
159                         // unexpected
160                         LYXERR(Debug::LYXVC, "LyXVC::scanMaster(): unexpected token");
161                 }
162         }
163 }
164
165
166 void RCS::registrer(string const & msg)
167 {
168         string cmd = "ci -q -u -i -t-\"";
169         cmd += msg;
170         cmd += "\" ";
171         cmd += quoteName(onlyFilename(owner_->absFileName()));
172         doVCCommand(cmd, FileName(owner_->filePath()));
173 }
174
175
176 string RCS::checkIn(string const & msg)
177 {
178         int ret = doVCCommand("ci -q -u -m\"" + msg + "\" "
179                     + quoteName(onlyFilename(owner_->absFileName())),
180                     FileName(owner_->filePath()));
181         return ret ? string() : "RCS: Proceeded";
182 }
183
184 bool RCS::checkInEnabled()
185 {
186         return owner_ && !owner_->isReadonly();
187 }
188
189 string RCS::checkOut()
190 {
191         owner_->markClean();
192         int ret = doVCCommand("co -q -l " + quoteName(onlyFilename(owner_->absFileName())),
193                     FileName(owner_->filePath()));
194         return ret ? string() : "RCS: Proceeded";
195 }
196
197
198 bool RCS::checkOutEnabled()
199 {
200         return owner_ && owner_->isReadonly();
201 }
202
203
204 void RCS::revert()
205 {
206         doVCCommand("co -f -u" + version() + " "
207                     + quoteName(onlyFilename(owner_->absFileName())),
208                     FileName(owner_->filePath()));
209         // We ignore changes and just reload!
210         owner_->markClean();
211 }
212
213
214 void RCS::undoLast()
215 {
216         LYXERR(Debug::LYXVC, "LyXVC: undoLast");
217         doVCCommand("rcs -o" + version() + " "
218                     + quoteName(onlyFilename(owner_->absFileName())),
219                     FileName(owner_->filePath()));
220 }
221
222
223 bool RCS::undoLastEnabled()
224 {
225         return true;
226 }
227
228
229 void RCS::getLog(FileName const & tmpf)
230 {
231         doVCCommand("rlog " + quoteName(onlyFilename(owner_->absFileName()))
232                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
233                     FileName(owner_->filePath()));
234 }
235
236
237 bool RCS::toggleReadOnlyEnabled()
238 {
239         return true;
240 }
241
242
243 /////////////////////////////////////////////////////////////////////
244 //
245 // CVS
246 //
247 /////////////////////////////////////////////////////////////////////
248
249 CVS::CVS(FileName const & m, FileName const & f)
250 {
251         master_ = m;
252         file_ = f;
253         scanMaster();
254 }
255
256
257 FileName const CVS::findFile(FileName const & file)
258 {
259         // First we look for the CVS/Entries in the same dir
260         // where we have file.
261         FileName const entries(onlyPath(file.absFilename()) + "/CVS/Entries");
262         string const tmpf = '/' + onlyFilename(file.absFilename()) + '/';
263         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under cvs in `" << entries
264                              << "' for `" << tmpf << '\'');
265         if (entries.isReadableFile()) {
266                 // Ok we are at least in a CVS dir. Parse the CVS/Entries
267                 // and see if we can find this file. We do a fast and
268                 // dirty parse here.
269                 ifstream ifs(entries.toFilesystemEncoding().c_str());
270                 string line;
271                 while (getline(ifs, line)) {
272                         LYXERR(Debug::LYXVC, "\tEntries: " << line);
273                         if (contains(line, tmpf))
274                                 return entries;
275                 }
276         }
277         return FileName();
278 }
279
280
281 void CVS::scanMaster()
282 {
283         LYXERR(Debug::LYXVC, "LyXVC::CVS: scanMaster. \n     Checking: " << master_);
284         // Ok now we do the real scan...
285         ifstream ifs(master_.toFilesystemEncoding().c_str());
286         string tmpf = '/' + onlyFilename(file_.absFilename()) + '/';
287         LYXERR(Debug::LYXVC, "\tlooking for `" << tmpf << '\'');
288         string line;
289         static regex const reg("/(.*)/(.*)/(.*)/(.*)/(.*)");
290         while (getline(ifs, line)) {
291                 LYXERR(Debug::LYXVC, "\t  line: " << line);
292                 if (contains(line, tmpf)) {
293                         // Ok extract the fields.
294                         smatch sm;
295
296                         regex_match(line, sm, reg);
297
298                         //sm[0]; // whole matched string
299                         //sm[1]; // filename
300                         version_ = sm.str(2);
301                         string const file_date = sm.str(3);
302
303                         //sm[4]; // options
304                         //sm[5]; // tag or tagdate
305                         // FIXME: must double check file is stattable/existing
306                         time_t mod = file_.lastModified();
307                         string mod_date = rtrim(asctime(gmtime(&mod)), "\n");
308                         LYXERR(Debug::LYXVC, "Date in Entries: `" << file_date
309                                 << "'\nModification date of file: `" << mod_date << '\'');
310                         //FIXME this whole locking bussiness is not working under cvs and the machinery
311                         // conforms to the ci usage, not cvs.
312                         if (file_date == mod_date) {
313                                 locker_ = "Unlocked";
314                                 vcstatus = UNLOCKED;
315                         } else {
316                                 // Here we should also to some more checking
317                                 // to see if there are conflicts or not.
318                                 locker_ = "Locked";
319                                 vcstatus = LOCKED;
320                         }
321                         break;
322                 }
323         }
324 }
325
326
327 void CVS::registrer(string const & msg)
328 {
329         doVCCommand("cvs -q add -m \"" + msg + "\" "
330                     + quoteName(onlyFilename(owner_->absFileName())),
331                     FileName(owner_->filePath()));
332 }
333
334
335 string CVS::checkIn(string const & msg)
336 {
337         int ret = doVCCommand("cvs -q commit -m \"" + msg + "\" "
338                     + quoteName(onlyFilename(owner_->absFileName())),
339                     FileName(owner_->filePath()));
340         return ret ? string() : "CVS: Proceeded";
341 }
342
343
344 bool CVS::checkInEnabled()
345 {
346         return true;
347 }
348
349
350 string CVS::checkOut()
351 {
352         // cvs update or perhaps for cvs this should be a noop
353         // we need to detect conflict (eg "C" in output)
354         // before we can do this.
355         lyxerr << "Sorry not implemented." << endl;
356         return string();
357 }
358
359
360 bool CVS::checkOutEnabled()
361 {
362         return false;
363 }
364
365
366 void CVS::revert()
367 {
368         // Reverts to the version in CVS repository and
369         // gets the updated version from the repository.
370         string const fil = quoteName(onlyFilename(owner_->absFileName()));
371         // This is sensitive operation, so at lest some check about
372         // existence of cvs program and its file
373         if (doVCCommand("cvs log "+ fil, FileName(owner_->filePath())))
374                 return;
375         FileName f(owner_->absFileName());
376         f.removeFile();
377         doVCCommand("cvs update " + fil,
378                     FileName(owner_->filePath()));
379         owner_->markClean();
380 }
381
382
383 void CVS::undoLast()
384 {
385         // merge the current with the previous version
386         // in a reverse patch kind of way, so that the
387         // result is to revert the last changes.
388         lyxerr << "Sorry not implemented." << endl;
389 }
390
391
392 bool CVS::undoLastEnabled()
393 {
394         return false;
395 }
396
397
398 void CVS::getLog(FileName const & tmpf)
399 {
400         doVCCommand("cvs log " + quoteName(onlyFilename(owner_->absFileName()))
401                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
402                     FileName(owner_->filePath()));
403 }
404
405 bool CVS::toggleReadOnlyEnabled()
406 {
407         return false;
408 }
409
410 /////////////////////////////////////////////////////////////////////
411 //
412 // SVN
413 //
414 /////////////////////////////////////////////////////////////////////
415
416 SVN::SVN(FileName const & m, FileName const & f)
417 {
418         master_ = m;
419         file_ = f;
420         scanMaster();
421 }
422
423
424 FileName const SVN::findFile(FileName const & file)
425 {
426         // First we look for the .svn/entries in the same dir
427         // where we have file.
428         FileName const entries(onlyPath(file.absFilename()) + "/.svn/entries");
429         string const tmpf = onlyFilename(file.absFilename());
430         LYXERR(Debug::LYXVC, "LyXVC: Checking if file is under svn in `" << entries
431                              << "' for `" << tmpf << '\'');
432         if (entries.isReadableFile()) {
433                 // Ok we are at least in a SVN dir. Parse the .svn/entries
434                 // and see if we can find this file. We do a fast and
435                 // dirty parse here.
436                 ifstream ifs(entries.toFilesystemEncoding().c_str());
437                 string line, oldline;
438                 while (getline(ifs, line)) {
439                         if (line == "dir" || line == "file")
440                                 LYXERR(Debug::LYXVC, "\tEntries: " << oldline);
441                         if (oldline == tmpf && line == "file")
442                                 return entries;
443                         oldline = line;
444                 }
445         }
446         return FileName();
447 }
448
449
450 void SVN::scanMaster()
451 {
452         // if we want some locking under svn
453         // we need different infrastructure around
454         locker_ = "Unlocked";
455         vcstatus = UNLOCKED;
456 }
457
458
459 void SVN::registrer(string const & /*msg*/)
460 {
461         doVCCommand("svn add -q " + quoteName(onlyFilename(owner_->absFileName())),
462                     FileName(owner_->filePath()));
463 }
464
465
466 string SVN::checkIn(string const & msg)
467 {
468         FileName tmpf = FileName::tempName("lyxvcout");
469         if (tmpf.empty()){
470                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
471                 return N_("Error: Could not generate logfile.");
472         }
473
474         doVCCommand("svn commit -m \"" + msg + "\" "
475                     + quoteName(onlyFilename(owner_->absFileName()))
476                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
477                     FileName(owner_->filePath()));
478
479         string log;
480         string res = scanLogFile(tmpf, log);
481         if (!res.empty())
482                 frontend::Alert::error(_("Revision control error."),
483                                 _("Error when commiting to repository.\n"
484                                 "You have to manually resolve the problem.\n"
485                                 "After pressing OK, LyX will reopen the document."));
486         tmpf.erase();
487         return "SVN: " + log;
488 }
489
490
491 bool SVN::checkInEnabled()
492 {
493         return true;
494 }
495
496 // FIXME Correctly return code should be checked instead of this.
497 // This would need another solution than just plain startscript.
498 // Hint from Andre': QProcess::readAllStandardError()...
499 string SVN::scanLogFile(FileName const & f, string & status)
500 {
501         ifstream ifs(f.toFilesystemEncoding().c_str());
502         string line;
503
504         while (ifs) {
505                 getline(ifs, line);
506                 lyxerr << line << "\n";
507                 if (!line.empty()) status += line + "; ";
508                 if (prefixIs(line, "C ") || contains(line, "Commit failed")) {
509                         ifs.close();
510                         return line;
511                 }
512         }
513         ifs.close();
514         return string();
515 }
516
517
518 string SVN::checkOut()
519 {
520         FileName tmpf = FileName::tempName("lyxvcout");
521         if (tmpf.empty()) {
522                 LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
523                 return N_("Error: Could not generate logfile.");
524         }
525
526         doVCCommand("svn update " + quoteName(onlyFilename(owner_->absFileName()))
527                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
528                     FileName(owner_->filePath()));
529
530         string log;
531         string res = scanLogFile(tmpf, log);
532         if (!res.empty())
533                 frontend::Alert::error(_("Revision control error."),
534                         bformat(_("Error when updating from repository.\n"
535                                 "You have to manually resolve the conflicts NOW!\n'%1$s'.\n\n"
536                                 "After pressing OK, LyX will try to reopen resolved document."),
537                         from_local8bit(res)));
538         tmpf.erase();
539         return "SVN: " + log;
540 }
541
542
543 bool SVN::checkOutEnabled()
544 {
545         return true;
546 }
547
548
549 void SVN::revert()
550 {
551         // Reverts to the version in CVS repository and
552         // gets the updated version from the repository.
553         string const fil = quoteName(onlyFilename(owner_->absFileName()));
554
555         doVCCommand("svn revert -q " + fil,
556                     FileName(owner_->filePath()));
557         owner_->markClean();
558 }
559
560
561 void SVN::undoLast()
562 {
563         // merge the current with the previous version
564         // in a reverse patch kind of way, so that the
565         // result is to revert the last changes.
566         lyxerr << "Sorry not implemented." << endl;
567 }
568
569
570 bool SVN::undoLastEnabled()
571 {
572         return false;
573 }
574
575
576 void SVN::getLog(FileName const & tmpf)
577 {
578         doVCCommand("svn log " + quoteName(onlyFilename(owner_->absFileName()))
579                     + " > " + quoteName(tmpf.toFilesystemEncoding()),
580                     FileName(owner_->filePath()));
581 }
582
583
584 bool SVN::toggleReadOnlyEnabled()
585 {
586         return false;
587 }
588
589
590 } // namespace lyx