You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdesvn/src/svnfrontend/graphtree/revisiontree.cpp

545 lines
22 KiB

/***************************************************************************
* 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 "log_entry.h"
#include "LogCache.h"
#include "ReposLog.h"
#include "url.h"
#include "sub2qt.h"
#include "revtreewidget.h"
#include "revgraphview.h"
#include "elogentry.h"
#include "cursorstack.h"
#include "tdesvnsettings.h"
#include <kdebug.h>
#include <kprogress.h>
#include <tdelocale.h>
#include <kapp.h>
#include <tdelistview.h>
#include <kmdcodec.h>
#include <tdemessagebox.h>
#include <tqwidget.h>
#include <tqdatetime.h>
#include <tqlabel.h>
#include <tqfile.h>
#include <tqtextstream.h>
#include <tqregexp.h>
#define INTERNALCOPY 1
#define INTERNALRENAME 2
class RtreeData
{
public:
RtreeData();
virtual ~RtreeData();
TQMap<long,eLog_Entry> m_History;
svn::LogEntriesMap m_OldHistory;
long max_rev,min_rev;
KProgressDialog*progress;
TQTime m_stopTick;
TQWidget*dlgParent;
RevTreeWidget*m_TreeDisplay;
svn::Client*m_Client;
TQObject* m_Listener;
bool getLogs(const TQString&,const svn::Revision&startr,const svn::Revision&endr,const TQString&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 TQString&reposRoot,const svn::Revision&startr,const svn::Revision&endr,const TQString&origin)
{
if (!m_Listener||!m_Client) {
return false;
}
try {
CursorStack a(TQt::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,
TQObject*aListener,
const TQString& reposRoot,
const svn::Revision&startr,const svn::Revision&endr,
const TQString&origin,
const svn::Revision& baserevision,
TQWidget*treeParent,TQWidget*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 TQString&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;
TQString label;
TQString 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) {
TQString tmpPath = m_Path;
TQString 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;
TQString 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,TQString(),-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 TQString&_par,const TQString&tar)
{
if (_par==tar) return true;
TQString par = _par+(_par.endsWith("/")?"":"/");
return tar.startsWith(par);
}
bool RevisionTree::isValid()const
{
return m_Valid;
}
static TQString uniqueNodeName(long rev,const TQString&path)
{
TQString res = KCodecs::base64Encode(path.local8Bit(),false);
res.replace("\"","_quot_");
res.replace(" ","_space_");
TQString n; n.sprintf("%05ld",rev);
res = "\""+n+TQString("_%1\"").arg(res);
return res;
}
bool RevisionTree::bottomUpScan(long startrev,unsigned recurse,const TQString&_path,long _last)
{
#define REVENTRY m_Data->m_History[j]
#define FORWARDENTRY m_Data->m_History[j].changedPaths[i]
TQString 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;
}
TQString 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;
TQString tmpPath = path;
TQString recPath;
if (FORWARDENTRY.copyToPath.length()==0) {
continue;
}
TQString 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;
}
TQWidget*RevisionTree::getView()
{
return m_Data->m_TreeDisplay;
}
void RevisionTree::fillItem(long rev,int pathIndex,const TQString&nodeName,const TQString&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);
}
}