summaryrefslogtreecommitdiffstats
path: root/src/svnfrontend/graphtree/revisiontree.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/svnfrontend/graphtree/revisiontree.cpp')
-rw-r--r--src/svnfrontend/graphtree/revisiontree.cpp544
1 files changed, 544 insertions, 0 deletions
diff --git a/src/svnfrontend/graphtree/revisiontree.cpp b/src/svnfrontend/graphtree/revisiontree.cpp
new file mode 100644
index 0000000..bb64cf7
--- /dev/null
+++ b/src/svnfrontend/graphtree/revisiontree.cpp
@@ -0,0 +1,544 @@
+/***************************************************************************
+ * Copyright (C) 2005-2007 by Rajko Albrecht *
+ * ral@alwins-world.de *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ***************************************************************************/
+#include "revisiontree.h"
+#include "../stopdlg.h"
+#include "src/svnqt/log_entry.hpp"
+#include "src/svnqt/cache/LogCache.hpp"
+#include "src/svnqt/cache/ReposLog.hpp"
+#include "src/svnqt/url.hpp"
+#include "helpers/sub2qt.h"
+#include "revtreewidget.h"
+#include "revgraphview.h"
+#include "elogentry.h"
+#include "src/svnfrontend/fronthelpers/cursorstack.h"
+#include "src/settings/kdesvnsettings.h"
+
+#include <kdebug.h>
+#include <kprogress.h>
+#include <klocale.h>
+#include <kapp.h>
+#include <klistview.h>
+#include <kmdcodec.h>
+#include <kmessagebox.h>
+
+#include <qwidget.h>
+#include <qdatetime.h>
+#include <qlabel.h>
+#include <qfile.h>
+#include <qtextstream.h>
+#include <qregexp.h>
+
+#define INTERNALCOPY 1
+#define INTERNALRENAME 2
+
+class RtreeData
+{
+public:
+ RtreeData();
+ virtual ~RtreeData();
+
+ QMap<long,eLog_Entry> m_History;
+
+ svn::LogEntriesMap m_OldHistory;
+
+ long max_rev,min_rev;
+ KProgressDialog*progress;
+ QTime m_stopTick;
+
+ QWidget*dlgParent;
+ RevTreeWidget*m_TreeDisplay;
+
+ svn::Client*m_Client;
+ QObject* m_Listener;
+
+ bool getLogs(const QString&,const svn::Revision&startr,const svn::Revision&endr,const QString&origin);
+};
+
+RtreeData::RtreeData()
+ : max_rev(-1),min_rev(-1)
+{
+ progress=0;
+ m_TreeDisplay = 0;
+ m_Client = 0;
+ dlgParent = 0;
+ m_Listener = 0;
+}
+
+RtreeData::~RtreeData()
+{
+ delete progress;
+}
+
+bool RtreeData::getLogs(const QString&reposRoot,const svn::Revision&startr,const svn::Revision&endr,const QString&origin)
+{
+ if (!m_Listener||!m_Client) {
+ return false;
+ }
+ try {
+ CursorStack a(Qt::BusyCursor);
+ StopDlg sdlg(m_Listener,dlgParent,
+ 0,"Logs",i18n("Getting logs - hit cancel for abort"));
+ if (svn::Url::isLocal(reposRoot) ) {
+ m_Client->log(reposRoot,endr,startr,m_OldHistory,startr,true,false,0);
+ } else {
+ svn::cache::ReposLog rl(m_Client,reposRoot);
+ if (rl.isValid()) {
+ rl.simpleLog(m_OldHistory,startr,endr,!Kdesvnsettings::network_on());
+ } else if (Kdesvnsettings::network_on()) {
+ m_Client->log(reposRoot,endr,startr,m_OldHistory,startr,true,false,0);
+ } else {
+ KMessageBox::error(0,i18n("Could not retrieve logs, reason:\n%1").arg(i18n("No logcache possible due broken database and networking not allowed.")));
+ return false;
+ }
+ }
+ } catch (const svn::Exception&ce) {
+ kdDebug()<<ce.msg() << endl;
+ KMessageBox::error(0,i18n("Could not retrieve logs, reason:\n%1").arg(ce.msg()));
+ return false;
+ }
+ return true;
+}
+
+RevisionTree::RevisionTree(svn::Client*aClient,
+ QObject*aListener,
+ const QString& reposRoot,
+ const svn::Revision&startr,const svn::Revision&endr,
+ const QString&origin,
+ const svn::Revision& baserevision,
+ QWidget*treeParent,QWidget*parent)
+ :m_InitialRevsion(0),m_Path(origin),m_Valid(false)
+{
+ m_Data = new RtreeData;
+ m_Data->m_Client=aClient;
+ m_Data->m_Listener=aListener;
+ m_Data->dlgParent=parent;
+
+ if (!m_Data->getLogs(reposRoot,startr,endr,origin)) {
+ return;
+ }
+
+ long possible_rev=-1;
+ kdDebug()<<"Origin: "<<origin << endl;
+
+ m_Data->progress=new KProgressDialog(
+ parent,"progressdlg",i18n("Scanning logs"),i18n("Scanning the logs for %1").arg(origin),true);
+ m_Data->progress->setMinimumDuration(100);
+ m_Data->progress->show();
+ m_Data->progress->setAllowCancel(true);
+ m_Data->progress->progressBar()->setTotalSteps(m_Data->m_OldHistory.size());
+ m_Data->progress->setAutoClose(false);
+ m_Data->progress->show();
+ bool cancel=false;
+ svn::LogEntriesMap::Iterator it;
+ unsigned count = 0;
+ for (it=m_Data->m_OldHistory.begin();it!=m_Data->m_OldHistory.end();++it) {
+ m_Data->progress->progressBar()->setProgress(count);
+ kapp->processEvents();
+ if (m_Data->progress->wasCancelled()) {
+ cancel=true;
+ break;
+ }
+ if (it.key()>m_Data->max_rev) {
+ m_Data->max_rev=it.key();
+ }
+ if (it.key()<m_Data->min_rev||m_Data->min_rev==-1) {
+ m_Data->min_rev=it.key();
+ }
+ if (baserevision.kind()==svn_opt_revision_date) {
+ if (baserevision.date()<=it.data().date && possible_rev==-1||possible_rev>it.key()) {
+ possible_rev=it.key();
+ }
+ }
+ ++count;
+ }
+ if (baserevision.kind()==svn_opt_revision_head||baserevision.kind()==svn_opt_revision_working) {
+ m_Baserevision=m_Data->max_rev;
+ } else if (baserevision.kind()==svn_opt_revision_number) {
+ m_Baserevision=baserevision.revnum();
+ } else if (baserevision.kind()==svn_opt_revision_date) {
+ m_Baserevision=possible_rev;
+ }
+ if (!cancel) {
+ kdDebug( )<<" max revision " << m_Data->max_rev
+ << " min revision " << m_Data->min_rev << endl;
+ if (topDownScan()) {
+ kdDebug()<<"topdown end"<<endl;
+ m_Data->progress->setAutoReset(true);
+ m_Data->progress->progressBar()->setTotalSteps(100);
+ m_Data->progress->progressBar()->setPercentageVisible(false);
+ m_Data->m_stopTick.restart();
+ m_Data->m_TreeDisplay=new RevTreeWidget(m_Data->m_Listener,m_Data->m_Client,treeParent);
+ if (bottomUpScan(m_InitialRevsion,0,m_Path,0)) {
+ kdDebug()<<"Bottom up end"<<endl;
+ m_Valid=true;
+ m_Data->m_TreeDisplay->setBasePath(reposRoot);
+ m_Data->m_TreeDisplay->dumpRevtree();
+ } else {
+ delete m_Data->m_TreeDisplay;
+ m_Data->m_TreeDisplay = 0;
+ }
+ }
+ } else {
+ kdDebug()<<"Canceld"<<endl;
+ }
+ m_Data->progress->hide();
+}
+
+RevisionTree::~RevisionTree()
+{
+ delete m_Data;
+}
+
+bool RevisionTree::isDeleted(long revision,const QString&path)
+{
+ for (unsigned i = 0;i<m_Data->m_History[revision].changedPaths.count();++i) {
+ if (isParent(m_Data->m_History[revision].changedPaths[i].path,path) &&
+ m_Data->m_History[revision].changedPaths[i].action=='D') {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool RevisionTree::topDownScan()
+{
+ m_Data->progress->progressBar()->setTotalSteps(m_Data->max_rev-m_Data->min_rev);
+ bool cancel=false;
+ QString label;
+ QString olabel = m_Data->progress->labelText();
+ for (long j=m_Data->max_rev;j>=m_Data->min_rev;--j) {
+ m_Data->progress->progressBar()->setProgress(m_Data->max_rev-j);
+ kapp->processEvents();
+ if (m_Data->progress->wasCancelled()) {
+ cancel=true;
+ break;
+ }
+ for (unsigned i = 0; i<m_Data->m_OldHistory[j].changedPaths.count();++i) {
+ if (i>0 && i%100==0) {
+ if (m_Data->progress->wasCancelled()) {
+ cancel=true;
+ break;
+ }
+ label = i18n("%1<br>Check change entry %2 of %3")
+ .arg(olabel).arg(i).arg(m_Data->m_OldHistory[j].changedPaths.count());
+ m_Data->progress->setLabel(label);
+ kapp->processEvents();
+ }
+ /* find min revision of item */
+ if (m_Data->m_OldHistory[j].changedPaths[i].action=='A'&&
+ isParent(m_Data->m_OldHistory[j].changedPaths[i].path,m_Path))
+ {
+ if (!m_Data->m_OldHistory[j].changedPaths[i].copyFromPath.isEmpty()) {
+ if (m_InitialRevsion<m_Data->m_OldHistory[j].revision) {
+ QString tmpPath = m_Path;
+ QString r = m_Path.mid(m_Data->m_OldHistory[j].changedPaths[i].path.length());
+ m_Path=m_Data->m_OldHistory[j].changedPaths[i].copyFromPath;
+ m_Path+=r;
+ }
+ } else if (m_Data->m_OldHistory[j].changedPaths[i].path==m_Path && m_Data->m_OldHistory[j].changedPaths[i].copyToPath.isEmpty()){
+ // here it is added
+ m_InitialRevsion = m_Data->m_OldHistory[j].revision;
+ }
+ }
+ }
+ }
+ kdDebug()<<"Stage one done"<<endl;
+ if (cancel==true) {
+ return false;
+ }
+ m_Data->progress->setLabel(olabel);
+ /* find forward references and filter them out */
+ for (long j=m_Data->max_rev;j>=m_Data->min_rev;--j) {
+ m_Data->progress->progressBar()->setProgress(m_Data->max_rev-j);
+ kapp->processEvents();
+ if (m_Data->progress->wasCancelled()) {
+ cancel=true;
+ break;
+ }
+ for (unsigned i = 0; i<m_Data->m_OldHistory[j].changedPaths.count();++i) {
+ if (i>0 && i%100==0) {
+ if (m_Data->progress->wasCancelled()) {
+ cancel=true;
+ break;
+ }
+ label = i18n("%1<br>Check change entry %2 of %3").arg(olabel).arg(i).arg(m_Data->m_OldHistory[j].changedPaths.count());
+ m_Data->progress->setLabel(label);
+ kapp->processEvents();
+ }
+ if (!m_Data->m_OldHistory[j].changedPaths[i].copyFromPath.isEmpty()) {
+ long r = m_Data->m_OldHistory[j].changedPaths[i].copyFromRevision;
+ QString sourcepath = m_Data->m_OldHistory[j].changedPaths[i].copyFromPath;
+ char a = m_Data->m_OldHistory[j].changedPaths[i].action;
+ if (m_Data->m_OldHistory[j].changedPaths[i].path.isEmpty()) {
+ kdDebug()<<"Empty entry! rev " << j << " source " << sourcepath << endl;
+ continue;
+ }
+ if (a=='R') {
+ m_Data->m_OldHistory[j].changedPaths[i].action=0;
+ } else if (a=='A'){
+ a=INTERNALCOPY;
+ for (unsigned z = 0;z<m_Data->m_OldHistory[j].changedPaths.count();++z) {
+ if (m_Data->m_OldHistory[j].changedPaths[z].action=='D'
+ && isParent(m_Data->m_OldHistory[j].changedPaths[z].path,sourcepath) ) {
+ a=INTERNALRENAME;
+ m_Data->m_OldHistory[j].changedPaths[z].action=0;
+ break;
+ }
+ }
+ m_Data->m_History[r].addCopyTo(sourcepath,m_Data->m_OldHistory[j].changedPaths[i].path,j,a,r);
+ m_Data->m_OldHistory[j].changedPaths[i].action=0;
+ } else {
+ kdDebug()<<"Action with source path but wrong action \""<<a<<"\" found!"<<endl;
+ }
+ }
+ }
+ }
+ kdDebug()<<"Stage two done"<<endl;
+ if (cancel==true) {
+ return false;
+ }
+ m_Data->progress->setLabel(olabel);
+ for (long j=m_Data->max_rev;j>=m_Data->min_rev;--j) {
+ m_Data->progress->progressBar()->setProgress(m_Data->max_rev-j);
+ kapp->processEvents();
+ if (m_Data->progress->wasCancelled()) {
+ cancel=true;
+ break;
+ }
+ for (unsigned i = 0; i<m_Data->m_OldHistory[j].changedPaths.count();++i) {
+ if (m_Data->m_OldHistory[j].changedPaths[i].action==0) {
+ continue;
+ }
+ if (i>0 && i%100==0) {
+ if (m_Data->progress->wasCancelled()) {
+ cancel=true;
+ break;
+ }
+ label = i18n("%1<br>Check change entry %2 of %3").arg(olabel).arg(i).arg(m_Data->m_OldHistory[j].changedPaths.count());
+ m_Data->progress->setLabel(label);
+ kapp->processEvents();
+ }
+ m_Data->m_History[j].addCopyTo(m_Data->m_OldHistory[j].changedPaths[i].path,QString::null,-1,m_Data->m_OldHistory[j].changedPaths[i].action);
+ }
+ m_Data->m_History[j].author=m_Data->m_OldHistory[j].author;
+ m_Data->m_History[j].date=m_Data->m_OldHistory[j].date;
+ m_Data->m_History[j].revision=m_Data->m_OldHistory[j].revision;
+ m_Data->m_History[j].message=m_Data->m_OldHistory[j].message;
+ }
+ kdDebug()<<"Stage three done"<<endl;
+ return !cancel;
+}
+
+bool RevisionTree::isParent(const QString&_par,const QString&tar)
+{
+ if (_par==tar) return true;
+ QString par = _par+(_par.endsWith("/")?"":"/");
+ return tar.startsWith(par);
+}
+
+bool RevisionTree::isValid()const
+{
+ return m_Valid;
+}
+
+static QString uniqueNodeName(long rev,const QString&path)
+{
+ QString res = KCodecs::base64Encode(path.local8Bit(),false);
+ res.replace("\"","_quot_");
+ res.replace(" ","_space_");
+ QString n; n.sprintf("%05ld",rev);
+ res = "\""+n+QString("_%1\"").arg(res);
+ return res;
+}
+
+bool RevisionTree::bottomUpScan(long startrev,unsigned recurse,const QString&_path,long _last)
+{
+#define REVENTRY m_Data->m_History[j]
+#define FORWARDENTRY m_Data->m_History[j].changedPaths[i]
+
+ QString path = _path;
+ long lastrev = _last;
+ /* this is required if an item will modified AND copied at same revision.*/
+ long trev = -1;
+#ifdef DEBUG_PARSE
+ kdDebug()<<"Searching for "<<path<< " at revision " << startrev
+ << " recursion " << recurse << endl;
+#endif
+ bool cancel = false;
+ for (long j=startrev;j<=m_Data->max_rev;++j) {
+ if (m_Data->m_stopTick.elapsed()>500) {
+ m_Data->progress->progressBar()->advance(1);
+ kapp->processEvents();
+ m_Data->m_stopTick.restart();
+ }
+ if (m_Data->progress->wasCancelled()) {
+ cancel=true;
+ break;
+ }
+ for (unsigned i=0;i<REVENTRY.changedPaths.count();++i) {
+ if (!isParent(FORWARDENTRY.path,path)) {
+ continue;
+ }
+ QString n1,n2;
+ if (isParent(FORWARDENTRY.path,path)) {
+ bool get_out = false;
+ if (FORWARDENTRY.path!=path) {
+#ifdef DEBUG_PARSE
+ kdDebug()<<"Parent rename? "<< FORWARDENTRY.path << " -> " << FORWARDENTRY.copyToPath << " -> " << FORWARDENTRY.copyFromPath << endl;
+#endif
+ }
+ if (FORWARDENTRY.action==INTERNALCOPY ||
+ FORWARDENTRY.action==INTERNALRENAME ) {
+ bool ren = FORWARDENTRY.action==INTERNALRENAME;
+ QString tmpPath = path;
+ QString recPath;
+ if (FORWARDENTRY.copyToPath.length()==0) {
+ continue;
+ }
+ QString r = path.mid(FORWARDENTRY.path.length());
+ recPath= FORWARDENTRY.copyToPath;
+ recPath+=r;
+ n1 = uniqueNodeName(lastrev,tmpPath);
+ n2 = uniqueNodeName(FORWARDENTRY.copyToRevision,recPath);
+ if (lastrev>0) {
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n1].targets.append(RevGraphView::targetData(n2,FORWARDENTRY.action));
+ }
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].name=recPath;
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].rev = FORWARDENTRY.copyToRevision;
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].Action=FORWARDENTRY.action;
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].Author=m_Data->m_History[FORWARDENTRY.copyToRevision].author;
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].Message=m_Data->m_History[FORWARDENTRY.copyToRevision].message;
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].Date=helpers::sub2qt::apr_time2qtString(m_Data->m_History[FORWARDENTRY.copyToRevision].date);
+ if (ren) {
+ lastrev = FORWARDENTRY.copyToRevision;
+ /* skip items between */
+ j=lastrev;
+#ifdef DEBUG_PARSE
+ kdDebug()<<"Renamed to "<< recPath << " at revision " << FORWARDENTRY.copyToRevision << endl;
+#endif
+ path=recPath;
+ } else {
+#ifdef DEBUG_PARSE
+ kdDebug()<<"Copy to "<< recPath << endl;
+#endif
+ if (!bottomUpScan(FORWARDENTRY.copyToRevision,recurse+1,recPath,FORWARDENTRY.copyToRevision)) {
+ return false;
+ }
+ }
+ } else if (FORWARDENTRY.path==path) {
+ switch (FORWARDENTRY.action) {
+ case 'A':
+#ifdef DEBUG_PARSE
+ kdDebug()<<"Inserting adding base item"<<endl;
+#endif
+ n1 = uniqueNodeName(j,FORWARDENTRY.path);
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n1].Action=FORWARDENTRY.action;
+ fillItem(j,i,n1,path);
+ lastrev=j;
+ break;
+ case 'M':
+ case 'R':
+#ifdef DEBUG_PARSE
+ kdDebug()<<"Item modified at revision "<< j << " recurse " << recurse << endl;
+#endif
+ n1 = uniqueNodeName(j,FORWARDENTRY.path);
+ n2 = uniqueNodeName(lastrev,FORWARDENTRY.path);
+ if (lastrev>0) m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].targets.append(RevGraphView::targetData(n1,FORWARDENTRY.action));
+ fillItem(j,i,n1,path);
+ /* modify of same item (in same recurse) should be only once at a revision
+ * so check if lastrev==j must not be done but will cost cpu ticks so I always
+ * set trev and lastrev.
+ */
+ trev = lastrev;
+ lastrev = j;
+ break;
+ case 'D':
+#ifdef DEBUG_PARSE
+ kdDebug()<<"(Sloppy match) Item deleted at revision "<< j << " recurse " << recurse << endl;
+#endif
+ n1 = uniqueNodeName(j,path);
+ n2 = uniqueNodeName(lastrev,path);
+ if (n1==n2) {
+ /* cvs import - copy and deletion at same revision.
+ * CVS sucks.
+ */
+ n1 = uniqueNodeName(j,"D_"+path);
+ }
+ if (lastrev>0) m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].targets.append(RevGraphView::targetData(n1,FORWARDENTRY.action));
+ fillItem(j,i,n1,path);
+ lastrev = j;
+ get_out= true;
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (FORWARDENTRY.action) {
+ case 'D':
+#ifdef DEBUG_PARSE
+ kdDebug()<<"(Exact match) Item deleted at revision "<< j << " recurse " << recurse << endl;
+#endif
+ n1 = uniqueNodeName(j,path);
+ n2 = uniqueNodeName(lastrev,path);
+ if (n1==n2) {
+ /* cvs import - copy and deletion at same revision.
+ * CVS sucks.
+ */
+ n1 = uniqueNodeName(j,"D_"+path);
+ }
+ if (lastrev>0) m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].targets.append(RevGraphView::targetData(n1,FORWARDENTRY.action));
+ fillItem(j,i,n1,path);
+ lastrev = j;
+ get_out = true;
+ break;
+ default:
+ break;
+ }
+ }
+ if (get_out) {
+ return true;
+ }
+ }
+ }
+ }
+ return !cancel;
+}
+
+QWidget*RevisionTree::getView()
+{
+ return m_Data->m_TreeDisplay;
+}
+
+void RevisionTree::fillItem(long rev,int pathIndex,const QString&nodeName,const QString&path)
+{
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].name=path;
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].rev = rev;
+ if (pathIndex>=0) {
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].Action=m_Data->m_History[rev].changedPaths[pathIndex].action;
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].Author=m_Data->m_History[rev].author;
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].Message=m_Data->m_History[rev].message;
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].Date=helpers::sub2qt::apr_time2qtString(m_Data->m_History[rev].date);
+ } else {
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].Action=0;
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].Author="";
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].Message="";
+ m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].Date=helpers::sub2qt::apr_time2qtString(0);
+ }
+}