1 /***************************************************************************
2  *   Copyright (C) 2005-2009 by Rajko Albrecht  ral@alwins-world.de        *
3  *   http://kdesvn.alwins-world.de/                                        *
4  *                                                                         *
5  * This program is free software; you can redistribute it and/or           *
6  * modify it under the terms of the GNU Lesser General Public              *
7  * License as published by the Free Software Foundation; either            *
8  * version 2.1 of the License, or (at your option) any later version.      *
9  *                                                                         *
10  * This program is distributed in the hope that it will be useful,         *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       *
13  * Lesser General Public License for more details.                         *
14  *                                                                         *
15  * You should have received a copy of the GNU Lesser General Public        *
16  * License along with this program (in the file LGPL.txt); if not,         *
17  * write to the Free Software Foundation, Inc., 51 Franklin St,            *
18  * Fifth Floor, Boston, MA  02110-1301  USA                                *
19  *                                                                         *
20  * This software consists of voluntary contributions made by many          *
21  * individuals.  For exact contribution history, see the revision          *
22  * history and logs, available at http://kdesvn.alwins-world.de.           *
23  ***************************************************************************/
24 #include "ReposLog.h"
25 
26 #include "LogCache.h"
27 #include "svnqt/info_entry.h"
28 #include "svnqt/svnqttypes.h"
29 #include "svnqt/client.h"
30 #include "svnqt/context_listener.h"
31 #include "svnqt/cache/DatabaseException.h"
32 #include "svnqt/client_parameter.h"
33 
34 #include <QDataStream>
35 #include <QSqlDatabase>
36 #include <QSqlError>
37 #include <QSqlQuery>
38 #include <QVariant>
39 #include <QFileInfo>
40 #include <QBuffer>
41 #define Q_LLONG qlonglong
42 
43 class DatabaseLocker
44 {
45 public:
DatabaseLocker(QSqlDatabase * db)46     DatabaseLocker(QSqlDatabase *db)
47         : m_commited(false)
48         , m_db(db)
49     {
50         m_db->transaction();
51     }
~DatabaseLocker()52     ~DatabaseLocker()
53     {
54         if (!m_commited) {
55             m_db->rollback();
56         }
57     }
58 
commit()59     void commit()
60     {
61         if (m_commited) {
62             return;
63         }
64         m_db->commit();
65         m_commited = true;
66     }
67 
68 protected:
69     bool m_commited;
70     QSqlDatabase *m_db;
71 };
72 
73 /*!
74     \fn svn::cache::ReposLog::ReposLog(svn::Client*aClient,const QString&)
75  */
ReposLog(const svn::ClientP & aClient,const QString & aRepository)76 svn::cache::ReposLog::ReposLog(const svn::ClientP &aClient, const QString &aRepository)
77     : m_Client(aClient)
78     , m_Database()
79     , m_ReposRoot(aRepository)
80     , m_latestHead(svn::Revision::UNDEFINED)
81 {
82     if (!aRepository.isEmpty()) {
83         m_Database = LogCache::self()->reposDb(aRepository);
84     }
85 }
86 
87 /*!
88     \fn svn::cache::ReposLog::latestHeadRev()
89  */
latestHeadRev()90 svn::Revision svn::cache::ReposLog::latestHeadRev()
91 {
92     if (!m_Client || m_ReposRoot.isEmpty()) {
93         return svn::Revision::UNDEFINED;
94     }
95     if (!m_Database.isValid()) {
96         m_Database = LogCache::self()->reposDb(m_ReposRoot);
97         if (!m_Database.isValid()) {
98             return svn::Revision::UNDEFINED;
99         }
100     }
101     /// no catch - exception has go trough...
102     //qDebug("Getting headrev");
103     svn::InfoEntries e = m_Client->info(m_ReposRoot, svn::DepthEmpty, svn::Revision::HEAD, svn::Revision::HEAD);
104     if (e.count() < 1 || e[0].reposRoot().isEmpty()) {
105         return svn::Revision::UNDEFINED;
106     }
107     //qDebug("Getting headrev done");
108     return e[0].revision();
109 }
110 
111 /*!
112     \fn svn::cache::ReposLog::latestCachedRev()
113  */
latestCachedRev()114 svn::Revision svn::cache::ReposLog::latestCachedRev()
115 {
116     if (m_ReposRoot.isEmpty()) {
117         return svn::Revision::UNDEFINED;
118     }
119     if (!m_Database.isValid()) {
120         m_Database = LogCache::self()->reposDb(m_ReposRoot);
121         if (!m_Database.isValid()) {
122             return svn::Revision::UNDEFINED;
123         }
124     }
125     static const QLatin1String q("select revision from 'logentries' order by revision DESC limit 1");
126     QSqlQuery _q(QString(), m_Database);
127     if (!_q.exec(q)) {
128         //qDebug() << _q.lastError().text();
129         return svn::Revision::UNDEFINED;
130     }
131     int _r;
132     if (_q.isActive() && _q.next()) {
133         //qDebug("Sel result: %s",_q.value(0).toString().toUtf8().data());
134         _r = _q.value(0).toInt();
135     } else {
136         //qDebug() << _q.lastError().text();
137         return svn::Revision::UNDEFINED;
138     }
139     return _r;
140 }
141 
count() const142 qlonglong svn::cache::ReposLog::count()const
143 {
144     if (!m_Database.isValid()) {
145         m_Database = LogCache::self()->reposDb(m_ReposRoot);
146         if (!m_Database.isValid()) {
147             return svn::Revision::UNDEFINED;
148         }
149     }
150     static const QLatin1String q("select count(*) from 'logentries'");
151     QSqlQuery _q(QString(), m_Database);
152     if (!_q.exec(q)) {
153         //qDebug() << _q.lastError().text();
154         return -1;
155     }
156     qlonglong _r;
157     QVariant v;
158     if (_q.isActive() && _q.next()) {
159         //qDebug("Sel result: %s",_q.value(0).toString().toUtf8().data());
160         v = _q.value(0);
161         if (v.canConvert(QVariant::LongLong)) {
162             bool ok = false;
163             _r = v.toLongLong(&ok);
164             if (ok) {
165                 return _r;
166             }
167         }
168     }
169     return -1;
170 }
171 
fileSize() const172 qlonglong svn::cache::ReposLog::fileSize()const
173 {
174     if (!m_Database.isValid()) {
175         m_Database = LogCache::self()->reposDb(m_ReposRoot);
176         if (!m_Database.isValid()) {
177             return -1;
178         }
179     }
180     QFileInfo fi(m_Database.databaseName());
181     if (fi.exists()) {
182         return fi.size();
183     }
184     return -1;
185 }
186 
itemCount() const187 qlonglong svn::cache::ReposLog::itemCount()const
188 {
189     if (!m_Database.isValid()) {
190         m_Database = LogCache::self()->reposDb(m_ReposRoot);
191         if (!m_Database.isValid()) {
192             return -1;
193         }
194     }
195     static const QLatin1String q("select count(*) from 'changeditems'");
196     QSqlQuery _q(QString(), m_Database);
197     if (!_q.exec(q)) {
198         //qDebug() << _q.lastError().text();
199         return -1;
200     }
201     qlonglong _r;
202     QVariant v;
203     if (_q.isActive() && _q.next()) {
204         //qDebug("Sel result: %s",_q.value(0).toString().toUtf8().data());
205         v = _q.value(0);
206         if (v.canConvert(QVariant::LongLong)) {
207             bool ok = false;
208             _r = v.toLongLong(&ok);
209             if (ok) {
210                 return _r;
211             }
212         }
213     }
214     return -1;
215 }
216 
checkFill(svn::Revision & start,svn::Revision & end,bool checkHead)217 bool svn::cache::ReposLog::checkFill(svn::Revision &start, svn::Revision &end, bool checkHead)
218 {
219     if (!m_Database.isValid()) {
220         m_Database = LogCache::self()->reposDb(m_ReposRoot);
221         if (!m_Database.isValid()) {
222             return false;
223         }
224     }
225     ContextP cp = m_Client->getContext();
226 //     long long icount=0;
227 
228     svn::Revision _latest = latestCachedRev();
229 //    //qDebug("Latest cached rev: %i",_latest.revnum());
230 //    //qDebug("End revision is: %s",end.toString().toUtf8().data());
231 
232     if (checkHead && _latest.revnum() >= latestHeadRev().revnum()) {
233         return true;
234     }
235 
236     start = date2numberRev(start);
237     end = date2numberRev(end);
238 
239     // both should now one of START, HEAD or NUMBER
240     if (start == svn::Revision::HEAD || (end == svn::Revision::NUMBER && start == svn::Revision::NUMBER && start.revnum() > end.revnum())) {
241         svn::Revision tmp = start;
242         start = end;
243         end = tmp;
244     }
245     svn::Revision _rstart = _latest.revnum() + 1;
246     svn::Revision _rend = end;
247     if (_rend == svn::Revision::UNDEFINED) {
248 //        //qDebug("Setting end to Head");
249         _rend = svn::Revision::HEAD;
250     }
251     // no catch - exception should go outside.
252     if (_rstart == 0) {
253         _rstart = 1;
254     }
255 //    //qDebug("Getting log %s -> %s",_rstart.toString().toUtf8().data(),_rend.toString().toUtf8().data());
256     if (_rend == svn::Revision::HEAD) {
257         _rend = latestHeadRev();
258     }
259 
260     if (_rend == svn::Revision::HEAD || _rend.revnum() > _latest.revnum()) {
261         LogEntriesMap _internal;
262 //        //qDebug("Retrieving from network.");
263         LogParameter params;
264 
265         if (!m_Client->log(params.targets(m_ReposRoot).revisionRange(_rstart, _rend).peg(svn::Revision::UNDEFINED).discoverChangedPathes(true).strictNodeHistory(false), _internal)) {
266             return false;
267         }
268         LogEntriesMap::ConstIterator it = _internal.constBegin();
269 
270         DatabaseLocker l(&m_Database);
271         for (; it != _internal.constEnd(); ++it) {
272             _insertLogEntry((*it));
273             if (cp && cp->getListener()) {
274                 //cp->getListener()->contextProgress(++icount,_internal.size());
275                 if (cp->getListener()->contextCancel()) {
276                     throw DatabaseException(QStringLiteral("Could not retrieve values: User cancel."));
277                 }
278             }
279         }
280         l.commit();
281     }
282     return true;
283 }
284 
fillCache(const svn::Revision & _end)285 bool svn::cache::ReposLog::fillCache(const svn::Revision &_end)
286 {
287     svn::Revision end = _end;
288     svn::Revision start = latestCachedRev().revnum() + 1;
289     return checkFill(start, end, false);
290 }
291 
292 /*!
293     \fn svn::cache::ReposLog::simpleLog(const svn::Revision&start,const svn::Revision&end,LogEntriesMap&target)
294  */
simpleLog(LogEntriesMap & target,const svn::Revision & _start,const svn::Revision & _end,bool noNetwork,const StringArray & exclude)295 bool svn::cache::ReposLog::simpleLog(LogEntriesMap &target, const svn::Revision &_start, const svn::Revision &_end, bool noNetwork, const StringArray &exclude)
296 {
297     if (!m_Client || m_ReposRoot.isEmpty()) {
298         return false;
299     }
300     target.clear();
301     ContextP cp = m_Client->getContext();
302 
303     svn::Revision end = _end;
304     svn::Revision start = _start;
305     if (!noNetwork) {
306         if (!checkFill(start, end, true)) {
307             return false;
308         }
309     } else {
310         end = date2numberRev(end, noNetwork);
311         start = date2numberRev(start, noNetwork);
312     }
313 
314     if (end == svn::Revision::HEAD) {
315         end = latestCachedRev();
316     }
317     if (start == svn::Revision::HEAD) {
318         start = latestCachedRev();
319     }
320 
321     QSqlQuery bcount(m_Database);
322     bcount.prepare(QStringLiteral("select count(*) from logentries where revision<=? and revision>=?"));
323     bcount.bindValue(0, Q_LLONG(end.revnum()));
324     bcount.bindValue(1, Q_LLONG(start.revnum()));
325     if (!bcount.exec()) {
326         //qDebug() << bcount.lastError().text();
327         throw svn::cache::DatabaseException(QLatin1String("Could not retrieve count: ") + bcount.lastError().text());
328         return false;
329     }
330     if (!bcount.next() || bcount.value(0).toLongLong() < 1) {
331         // we didn't found logs with this parameters
332         return false;
333     }
334 
335     QSqlQuery bcur(m_Database);
336     bcur.setForwardOnly(true);
337     bcur.prepare(QStringLiteral("select revision,author,date,message from logentries where revision<=? and revision>=?"));
338     bcur.bindValue(0, Q_LLONG(end.revnum()));
339     bcur.bindValue(1, Q_LLONG(start.revnum()));
340 
341     if (!bcur.exec()) {
342         throw svn::cache::DatabaseException(QLatin1String("Could not retrieve values: ") + bcur.lastError().text());
343         return false;
344     }
345 
346     QString sItems(QStringLiteral("select changeditem,action,copyfrom,copyfromrev from changeditems where revision=?"));
347     for (int i = 0; i < exclude.size(); ++i) {
348         sItems += QLatin1String(" and changeditem not like '") + exclude[i] + QLatin1String("%'");
349     }
350     QSqlQuery cur(m_Database);
351     cur.setForwardOnly(true);
352     cur.prepare(sItems);
353 
354     while (bcur.next()) {
355         const Q_LLONG revision = bcur.value(0).toLongLong();
356         cur.bindValue(0, revision);
357 
358         if (!cur.exec()) {
359           //qDebug() << cur.lastError().text();
360           throw svn::cache::DatabaseException(QStringLiteral("Could not retrieve revision values: %1, %2")
361                                               .arg(cur.lastError().text(),
362                                                    cur.lastError().nativeErrorCode()));
363           return false;
364         }
365         target[revision].revision = revision;
366         target[revision].author = bcur.value(1).toString();
367         target[revision].date = bcur.value(2).toLongLong();
368         target[revision].message = bcur.value(3).toString();
369         while (cur.next()) {
370             LogChangePathEntry lcp;
371             QString ac = cur.value(1).toString();
372             lcp.action = ac[0].toLatin1();
373             lcp.copyFromPath = cur.value(2).toString();
374             lcp.path = cur.value(0).toString();
375             lcp.copyFromRevision = cur.value(3).toLongLong();
376             target[revision].changedPaths.push_back(lcp);
377         }
378         if (cp && cp->getListener()) {
379             if (cp->getListener()->contextCancel()) {
380                 throw svn::cache::DatabaseException(QStringLiteral("Could not retrieve values: User cancel."));
381                 return false;
382             }
383         }
384     }
385     return true;
386 }
387 
388 /*!
389     \fn svn::cache::ReposLog::date2numberRev(const svn::Revision&)
390  */
date2numberRev(const svn::Revision & aRev,bool noNetwork)391 svn::Revision svn::cache::ReposLog::date2numberRev(const svn::Revision &aRev, bool noNetwork)
392 {
393     if (aRev != svn::Revision::DATE) {
394         return aRev;
395     }
396     if (!m_Database.isValid()) {
397         return svn::Revision::UNDEFINED;
398     }
399     QSqlQuery query(QStringLiteral("select revision,date from logentries order by revision desc limit 1"), m_Database);
400 
401     if (query.lastError().type() != QSqlError::NoError) {
402         //qDebug() << query.lastError().text();
403     }
404     bool must_remote = !noNetwork;
405     if (query.next()) {
406         if (query.value(1).toLongLong() >= aRev.date()) {
407             must_remote = false;
408         }
409     }
410     if (must_remote) {
411         svn::InfoEntries e = (m_Client->info(m_ReposRoot, svn::DepthEmpty, aRev, aRev));;
412         if (e.count() < 1 || e[0].reposRoot().isEmpty()) {
413             return aRev;
414         }
415         return e[0].revision();
416     }
417     query.prepare(QStringLiteral("select revision from logentries where date<? order by revision desc"));
418     query.bindValue(0, Q_LLONG(aRev.date()));
419     if (query.exec() && query.next()) {
420         return query.value(0).toInt();
421     }
422     // not found...
423     if (noNetwork) {
424         return svn::Revision::UNDEFINED;
425     }
426     svn::InfoEntries e = (m_Client->info(m_ReposRoot, svn::DepthEmpty, svn::Revision::HEAD, svn::Revision::HEAD));;
427     if (e.count() < 1 || e[0].reposRoot().isEmpty()) {
428         return svn::Revision::UNDEFINED;
429     }
430     return e[0].revision();
431 }
432 
433 /*!
434     \fn svn::cache::ReposLog::insertLogEntry(const svn::LogEntry&)
435  */
_insertLogEntry(const svn::LogEntry & aEntry)436 bool svn::cache::ReposLog::_insertLogEntry(const svn::LogEntry &aEntry)
437 {
438     qlonglong j = aEntry.revision;
439     static const QLatin1String qEntry("insert into logentries (revision,date,author,message) values (?,?,?,?)");
440     static const QLatin1String qPathes("insert into changeditems (revision,changeditem,action,copyfrom,copyfromrev) values (?,?,?,?,?)");
441     QSqlQuery _q(QString(), m_Database);
442     _q.prepare(qEntry);
443     _q.bindValue(0, j);
444     _q.bindValue(1, aEntry.date);
445     _q.bindValue(2, aEntry.author);
446     _q.bindValue(3, aEntry.message);
447     if (!_q.exec()) {
448         //qDebug("Could not insert values: %s",_q.lastError().text().toUtf8().data());
449         //qDebug() << _q.lastQuery();
450         throw svn::cache::DatabaseException(QStringLiteral("_insertLogEntry_0: Could not insert values: %1, %2").arg(_q.lastError().text(), _q.lastError().nativeErrorCode()));
451     }
452     _q.prepare(qPathes);
453     svn::LogChangePathEntries::ConstIterator cpit = aEntry.changedPaths.begin();
454     for (; cpit != aEntry.changedPaths.end(); ++cpit) {
455         _q.bindValue(0, j);
456         _q.bindValue(1, (*cpit).path);
457         _q.bindValue(2, QString(QLatin1Char((*cpit).action)));
458         _q.bindValue(3, (*cpit).copyFromPath);
459         _q.bindValue(4, Q_LLONG((*cpit).copyFromRevision));
460         if (!_q.exec()) {
461             //qDebug("Could not insert values: %s",_q.lastError().text().toUtf8().data());
462             //qDebug() << _q.lastQuery();
463             throw svn::cache::DatabaseException(QStringLiteral("Could not insert values: %1, %2").arg(_q.lastError().text(), _q.lastError().nativeErrorCode()));
464         }
465     }
466     if (!aEntry.m_MergedInRevisions.isEmpty()) {
467         static const QLatin1String qMerges("insert into mergeditems(revision,mergeditems) values(?,?)");
468         _q.prepare(qMerges);
469         QByteArray _merges;
470         QBuffer buffer(&_merges);
471         buffer.open(QIODevice::ReadWrite);
472         QDataStream af(&buffer);
473         af << aEntry.m_MergedInRevisions;
474         buffer.close();
475         _q.bindValue(0, j);
476         _q.bindValue(1, _merges);
477         if (!_q.exec()) {
478             //qDebug("Could not insert values: %s",_q.lastError().text().toUtf8().data());
479             //qDebug() << _q.lastQuery();
480             throw svn::cache::DatabaseException(QStringLiteral("Could not insert values: %1, %2").arg(_q.lastError().text(), _q.lastError().nativeErrorCode()));
481         }
482     }
483     return true;
484 }
485 
insertLogEntry(const svn::LogEntry & aEntry)486 bool svn::cache::ReposLog::insertLogEntry(const svn::LogEntry &aEntry)
487 {
488     DatabaseLocker l(&m_Database);
489     if (!_insertLogEntry(aEntry)) {
490         return false;
491     }
492     l.commit();
493     return true;
494 }
495 
496 /*!
497     \fn svn::cache::ReposLog::log(const svn::Path&,const svn::Revision&start, const svn::Revision&end,const svn::Revision&peg,svn::LogEntriesMap&target, bool strictNodeHistory,int limit))
498  */
log(const svn::Path & what,const svn::Revision & _start,const svn::Revision & _end,const svn::Revision & _peg,svn::LogEntriesMap & target,bool strictNodeHistory,int limit)499 bool svn::cache::ReposLog::log(const svn::Path &what, const svn::Revision &_start, const svn::Revision &_end, const svn::Revision &_peg, svn::LogEntriesMap &target, bool strictNodeHistory, int limit)
500 {
501     Q_UNUSED(strictNodeHistory);
502     static const QLatin1String s_q("select logentries.revision,logentries.author,logentries.date,logentries.message from logentries where logentries.revision in (select changeditems.revision from changeditems where (changeditems.changeditem='%1' or changeditems.changeditem GLOB '%2/*') %3 GROUP BY changeditems.revision) ORDER BY logentries.revision DESC");
503 
504     static const QLatin1String s_e("select changeditem,action,copyfrom,copyfromrev from changeditems where changeditems.revision='%1'");
505     static const QLatin1String s_m("select mergeditems from mergeditems where mergeditems.revision='%1'");
506 
507     svn::Revision peg = date2numberRev(_peg, true);
508     QString query_string = QString(s_q).arg(what.native(), what.native(), (peg == svn::Revision::UNDEFINED ? QString() : QStringLiteral(" AND revision<=%1").arg(peg.revnum())));
509     if (peg == svn::Revision::UNDEFINED) {
510         peg = latestCachedRev();
511     }
512     if (!itemExists(peg, what)) {
513         throw svn::cache::DatabaseException(QStringLiteral("Entry '%1' does not exists at revision %2").arg(what.native(), peg.toString()));
514     }
515     if (limit > 0) {
516         query_string += QStringLiteral(" LIMIT %1").arg(limit);
517     }
518     QSqlQuery _q(m_Database);
519     QSqlQuery _q2(m_Database);
520     _q.setForwardOnly(true);
521     _q.prepare(query_string);
522     if (!_q.exec()) {
523         //qDebug("Could not select values: %s",_q.lastError().text().toUtf8().data());
524         //qDebug() << _q.lastQuery();
525         throw svn::cache::DatabaseException(QStringLiteral("Could not select values: %1, %2").arg(_q.lastError().text(), _q.lastError().nativeErrorCode()));
526     }
527     while (_q.next()) {
528         Q_LLONG revision = _q.value(0).toLongLong();
529         target[revision].revision = revision;
530         target[revision].author = _q.value(1).toString();
531         target[revision].date = _q.value(2).toLongLong();
532         target[revision].message = _q.value(3).toString();
533         query_string = QString(s_e).arg(revision);
534         _q2.setForwardOnly(true);
535         _q2.prepare(query_string);
536         if (!_q2.exec()) {
537             //qDebug("Could not select values: %s",_q2.lastError().text().toUtf8().data());
538         } else {
539             while (_q2.next()) {
540                 target[revision].changedPaths.push_back(
541                     LogChangePathEntry(_q2.value(0).toString(),
542                                        _q2.value(1).toChar().toLatin1(),
543                                        _q2.value(2).toString(),
544                                        _q2.value(3).toLongLong()
545                                       )
546                 );
547             }
548         }
549         query_string = QString(s_m).arg(revision);
550         _q2.prepare(query_string);
551         if (!_q2.exec()) {
552             //qDebug("Could not select values: %s",_q2.lastError().text().toUtf8().data());
553         } else {
554             if (_q2.next()) {
555                 QByteArray byteArray = _q2.value(0).toByteArray();
556                 QBuffer buffer(&byteArray);
557                 QDataStream in(&buffer);
558                 in >> target[revision].m_MergedInRevisions;
559             }
560         }
561     }
562     return true;
563 }
564 
565 /*!
566     \fn svn::cache::ReposLog::itemExists(const svn::Revision&,const QString&)
567  */
itemExists(const svn::Revision & peg,const svn::Path & path)568 bool svn::cache::ReposLog::itemExists(const svn::Revision &peg, const svn::Path &path)
569 {
570     /// @todo this moment I have no idea how to check real  with all moves and deletes of parent folders without a hell of sql statements so we make it quite simple: it exists if we found it.
571 
572     Q_UNUSED(peg);
573     Q_UNUSED(path);
574 
575 #if 0
576     static QString _s1("select revision from changeditems where changeditem='%1' and action='A' and revision<=%2 order by revision desc limit 1");
577     QSqlQuery _q(QString(), m_Database);
578     QString query_string = QString(_s1).arg(path.native()).arg(peg.revnum());
579     if (!_q.exec(query_string)) {
580         //qDebug("Could not select values: %s",_q.lastError().text().toUtf8().data());
581         //qDebug(_q.lastQuery().toUtf8().data());
582         throw svn::cache::DatabaseException(QString("Could not select values: ") + _q.lastError().text(), _q.lastError().number());
583     }
584     //qDebug(_q.lastQuery().toUtf8().data());
585 
586     svn::Path _p = path;
587     static QString _s2("select revision from changeditem where changeditem in (%1) and action='D' and revision>%2 and revision<=%3 order by revision desc limit 1");
588     QStringList p_list;
589     while (_p.length() > 0) {
590         p_list.append(QString("'%1'").arg(_p.native()));
591         _p.removeLast();
592     }
593     query_string = QString(_s2).arg(p_list.join(",")).arg();
594 #endif
595     return true;
596 }
597 
isValid() const598 bool svn::cache::ReposLog::isValid()const
599 {
600     if (!m_Database.isValid()) {
601         m_Database = LogCache::self()->reposDb(m_ReposRoot);
602         if (!m_Database.isValid()) {
603             return false;
604         }
605     }
606     return true;
607 }
608 
cleanLogEntries()609 void svn::cache::ReposLog::cleanLogEntries()
610 {
611 
612     if (!isValid()) {
613         return;
614     }
615     DatabaseLocker l(&m_Database);
616     QSqlQuery _q(QString(), m_Database);
617     if (!_q.exec(QStringLiteral("delete from logentries"))) {
618         return;
619     }
620     if (!_q.exec(QStringLiteral("delete from changeditems"))) {
621         return;
622     }
623     if (!_q.exec(QStringLiteral("delete from mergeditems"))) {
624         return;
625     }
626 
627     l.commit();
628     _q.exec(QStringLiteral("vacuum"));
629 }
630