|
|
|
/***************************************************************************
|
|
|
|
* Copyright (C) 2003 by Mario Scalas *
|
|
|
|
* mario.scalas@libero.it *
|
|
|
|
* *
|
|
|
|
* 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. *
|
|
|
|
* *
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
#include <tqregexp.h>
|
|
|
|
#include <tqtimer.h>
|
|
|
|
#include <kurl.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
|
|
|
|
#include <urlutil.h>
|
|
|
|
#include <kdevproject.h>
|
|
|
|
|
|
|
|
#include <dcopref.h>
|
|
|
|
#include <cvsjob_stub.h>
|
|
|
|
#include <cvsservice_stub.h>
|
|
|
|
|
|
|
|
#include "cvspart.h"
|
|
|
|
#include "cvsdir.h"
|
|
|
|
#include "cvsentry.h"
|
|
|
|
#include "cvsfileinfoprovider.h"
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// class CVSFileInfoProvider
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
CVSFileInfoProvider::CVSFileInfoProvider( CvsServicePart *parent, CvsService_stub *cvsService )
|
|
|
|
: KDevVCSFileInfoProvider( parent, "cvsfileinfoprovider" ),
|
|
|
|
m_requestStatusJob( 0 ), m_cvsService( cvsService ), m_cachedDirEntries( 0 )
|
|
|
|
{
|
|
|
|
connect( this, TQ_SIGNAL(needStatusUpdate(const CVSDir&)), this, TQ_SLOT(updateStatusFor(const CVSDir&)));
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
CVSFileInfoProvider::~CVSFileInfoProvider()
|
|
|
|
{
|
|
|
|
if (m_requestStatusJob && m_requestStatusJob->isRunning())
|
|
|
|
m_requestStatusJob->cancel();
|
|
|
|
delete m_requestStatusJob;
|
|
|
|
delete m_cachedDirEntries;
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
const VCSFileInfoMap *CVSFileInfoProvider::status( const TQString &dirPath )
|
|
|
|
{
|
|
|
|
// Same dir: we can do with cache ...
|
|
|
|
if (dirPath != m_previousDirPath)
|
|
|
|
{
|
|
|
|
// ... different dir: flush old cache and cache new dir
|
|
|
|
delete m_cachedDirEntries;
|
|
|
|
CVSDir cvsdir( projectDirectory() + TQDir::separator() + dirPath );
|
|
|
|
m_previousDirPath = dirPath;
|
|
|
|
m_cachedDirEntries = cvsdir.cacheableDirStatus();
|
|
|
|
}
|
|
|
|
return m_cachedDirEntries;
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
bool CVSFileInfoProvider::requestStatus( const TQString &dirPath, void *callerData, bool recursive, bool checkRepos )
|
|
|
|
{
|
|
|
|
m_savedCallerData = callerData;
|
|
|
|
if (m_requestStatusJob)
|
|
|
|
{
|
|
|
|
delete m_requestStatusJob;
|
|
|
|
m_requestStatusJob = 0;
|
|
|
|
}
|
|
|
|
// Flush old cache
|
|
|
|
if (m_cachedDirEntries)
|
|
|
|
{
|
|
|
|
delete m_cachedDirEntries;
|
|
|
|
m_cachedDirEntries = 0;
|
|
|
|
m_previousDirPath = dirPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!checkRepos) {
|
|
|
|
kdDebug(9006) << "No repo check reqested; Just read CVS/Entries from: " << dirPath << endl;
|
|
|
|
TQDir qd(projectDirectory()+TQDir::separator()+dirPath);
|
|
|
|
CVSDir cdir(qd);
|
|
|
|
if (cdir.isValid())
|
|
|
|
{
|
|
|
|
emit needStatusUpdate(cdir);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
kdDebug(9006) << dirPath << " is not a valid cvs directory" << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fix a possible bug in cvs client:
|
|
|
|
// When "cvs status" get's called nonrecursiv for a directory, it will
|
|
|
|
// not print anything if the path ends with a slash. So we need to ensure
|
|
|
|
// this here.
|
|
|
|
TQString newPath = dirPath;
|
|
|
|
if (newPath.endsWith("/"))
|
|
|
|
newPath.truncate( newPath.length()-1 );
|
|
|
|
|
|
|
|
|
|
|
|
// path, recursive, tagInfo: hmmm ... we may use tagInfo for collecting file tags ...
|
|
|
|
DCOPRef job = m_cvsService->status( newPath, recursive, false );
|
|
|
|
m_requestStatusJob = new CvsJob_stub( job.app(), job.obj() );
|
|
|
|
|
|
|
|
kdDebug(9006) << "Running command : " << m_requestStatusJob->cvsCommand() << endl;
|
|
|
|
connectDCOPSignal( job.app(), job.obj(), "jobExited(bool, int)", "slotJobExited(bool, int)", true );
|
|
|
|
connectDCOPSignal( job.app(), job.obj(), "receivedStdout(TQString)", "slotReceivedOutput(TQString)", true );
|
|
|
|
return m_requestStatusJob->execute();
|
|
|
|
/*
|
|
|
|
kdDebug(9006) << k_funcinfo << "Attempting to parse " << dirPath << " using CVS/Entries" << endl;
|
|
|
|
TQDir qd(dirPath);
|
|
|
|
CVSDir cdir(qd);
|
|
|
|
if (cdir.isValid())
|
|
|
|
{
|
|
|
|
emit needStatusUpdate(cdir);
|
|
|
|
return true;
|
|
|
|
}*/
|
|
|
|
}
|
|
|
|
|
|
|
|
void CVSFileInfoProvider::propagateUpdate()
|
|
|
|
{
|
|
|
|
emit statusReady( *m_cachedDirEntries, m_savedCallerData );
|
|
|
|
}
|
|
|
|
|
|
|
|
void CVSFileInfoProvider::updateStatusFor(const CVSDir& dir)
|
|
|
|
{
|
|
|
|
m_cachedDirEntries = dir.cacheableDirStatus();
|
|
|
|
printOutFileInfoMap( *m_cachedDirEntries );
|
|
|
|
|
|
|
|
/* FileTree will call requestStatus() everytime the user expands a directory
|
|
|
|
* Unfortunatly requestStatus() will be called before the
|
|
|
|
* VCSFileTreeViewItem of the directory will be filled with the files
|
|
|
|
* it contains. Meaning, m_savedCallerData contains no childs at that
|
|
|
|
* time. When a dcop call is made to run "cvs status" this is no problem.
|
|
|
|
* The dcop call takes quit long, and so FileTree has enough time the fill
|
|
|
|
* in the childs before we report the status back.
|
|
|
|
* As far as the reading of the CVS/Entries file is very fast,
|
|
|
|
* it will happen that we emit statusReady() here before the directory
|
|
|
|
* item conains any childs. Therefor we need to give FileTree some time
|
|
|
|
* to update the directory item before we give the status infos.
|
|
|
|
*/
|
|
|
|
TQTimer::singleShot( 1000, this, TQ_SLOT(propagateUpdate()) );
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
void CVSFileInfoProvider::slotJobExited( bool normalExit, int /*exitStatus*/ )
|
|
|
|
{
|
|
|
|
kdDebug(9006) << "CVSFileInfoProvider::slotJobExited(bool,int)" << endl;
|
|
|
|
if (!normalExit)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// m_cachedDirEntries = parse( m_requestStatusJob->output() );
|
|
|
|
m_cachedDirEntries = parse( m_statusLines );
|
|
|
|
// Remove me when not debugging
|
|
|
|
printOutFileInfoMap( *m_cachedDirEntries );
|
|
|
|
|
|
|
|
emit statusReady( *m_cachedDirEntries, m_savedCallerData );
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
void CVSFileInfoProvider::slotReceivedOutput( TQString someOutput )
|
|
|
|
{
|
|
|
|
TQStringList strings = m_bufferedReader.process( someOutput );
|
|
|
|
if (strings.count() > 0)
|
|
|
|
{
|
|
|
|
m_statusLines += strings;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
void CVSFileInfoProvider::slotReceivedErrors( TQString /*someErrors*/ )
|
|
|
|
{
|
|
|
|
/* Nothing to do */
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
TQString CVSFileInfoProvider::projectDirectory() const
|
|
|
|
{
|
|
|
|
return owner()->project()->projectDirectory();
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
VCSFileInfoMap *CVSFileInfoProvider::parse( TQStringList stringStream )
|
|
|
|
{
|
|
|
|
TQRegExp rx_recordStart( "^=+$" );
|
|
|
|
TQRegExp rx_fileName( "^File: (\\.|\\-|\\w)+" );
|
|
|
|
TQRegExp rx_fileStatus( "Status: (\\.|-|\\s|\\w)+" );
|
|
|
|
TQRegExp rx_fileWorkRev( "\\bWorking revision:" );
|
|
|
|
TQRegExp rx_fileRepoRev( "\\bRepository revision:" );
|
|
|
|
//TQRegExp rx_stickyTag( "\\s+(Sticky Tag:\\W+(w+|\\(none\\)))" );
|
|
|
|
//TQRegExp rx_stickyDate( "" ); // @todo but are they useful?? :-/
|
|
|
|
//TQRegExp rx_stickyOptions( "" ); //@todo
|
|
|
|
|
|
|
|
TQString fileName,
|
|
|
|
fileStatus,
|
|
|
|
workingRevision,
|
|
|
|
repositoryRevision,
|
|
|
|
stickyTag,
|
|
|
|
stickyDate,
|
|
|
|
stickyOptions;
|
|
|
|
|
|
|
|
VCSFileInfoMap *vcsStates = new VCSFileInfoMap;
|
|
|
|
|
|
|
|
int state = 0;
|
|
|
|
const int lastAcceptableState = 4;
|
|
|
|
// This is where the dirty parsing is done: from a string stream representing the
|
|
|
|
// 'cvs log' output we build a map with more useful strunctured data ;-)
|
|
|
|
for (TQStringList::const_iterator it=stringStream.begin(); it != stringStream.end(); ++it)
|
|
|
|
{
|
|
|
|
TQString s = (*it).stripWhiteSpace();
|
|
|
|
kdDebug(9006) << ">> Parsing: " << s << endl;
|
|
|
|
|
|
|
|
if (rx_recordStart.exactMatch( s ))
|
|
|
|
state = 1;
|
|
|
|
else if (state == 1 && rx_fileName.search( s ) >= 0 && rx_fileStatus.search( s ) >= 0) // FileName
|
|
|
|
{
|
|
|
|
fileName = rx_fileName.cap().replace( "File:", "" ).stripWhiteSpace();
|
|
|
|
fileStatus = rx_fileStatus.cap().replace( "Status:", "" ).stripWhiteSpace();
|
|
|
|
++state; // Next state
|
|
|
|
kdDebug(9006) << ">> " << fileName << ", " << fileStatus << endl;
|
|
|
|
}
|
|
|
|
else if (state == 2 && rx_fileWorkRev.search( s ) >= 0)
|
|
|
|
{
|
|
|
|
workingRevision = s.replace( "Working revision:", "" ).stripWhiteSpace();
|
|
|
|
|
|
|
|
TQRegExp rx_revision( "\\b(((\\d)+\\.?)*|New file!)" );
|
|
|
|
if (rx_revision.search( workingRevision ) >= 0)
|
|
|
|
{
|
|
|
|
workingRevision = rx_revision.cap();
|
|
|
|
kdDebug(9006) << ">> WorkRev: " << workingRevision << endl;
|
|
|
|
++state;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (state == 3 && rx_fileRepoRev.search( s ) >= 0)
|
|
|
|
{
|
|
|
|
repositoryRevision = s.replace( "Repository revision:", "" ).stripWhiteSpace();
|
|
|
|
|
|
|
|
TQRegExp rx_revision( "\\b(((\\d)+\\.?)*|No revision control file)" );
|
|
|
|
if (rx_revision.search( s ) >= 0)
|
|
|
|
{
|
|
|
|
repositoryRevision = rx_revision.cap();
|
|
|
|
kdDebug(9006) << ">> RepoRev: " << repositoryRevision << endl;
|
|
|
|
++state;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
else if (state == 4 && rx_stickyTag.search( s ) >= 0)
|
|
|
|
{
|
|
|
|
stickyTag = rx_stickyTag.cap();
|
|
|
|
++state;
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
else if (state >= lastAcceptableState) // OK, parsed all useful info?
|
|
|
|
{
|
|
|
|
// Package stuff, put into map and get ready for a new record
|
|
|
|
VCSFileInfo vcsInfo( fileName, workingRevision, repositoryRevision,
|
|
|
|
String2EnumState( fileStatus ) );
|
|
|
|
kdDebug(9006) << "== Inserting: " << vcsInfo.toString() << endl;
|
|
|
|
vcsStates->insert( fileName, vcsInfo );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return vcsStates;
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
VCSFileInfo::FileState CVSFileInfoProvider::String2EnumState( TQString stateAsString )
|
|
|
|
{
|
|
|
|
// @todo add more status as "Conflict" and "Sticky" (but I dunno how CVS writes it so I'm going
|
|
|
|
// to await until I have a conflict or somebody else fix it ;-)
|
|
|
|
// @todo use TQRegExp for better matching since it seems strings have changed between CVS releases :-(
|
|
|
|
// @todo a new state for 'Needs patch'
|
|
|
|
if (stateAsString == "Up-to-date")
|
|
|
|
return VCSFileInfo::Uptodate;
|
|
|
|
else if (stateAsString == "Locally Modified")
|
|
|
|
return VCSFileInfo::Modified;
|
|
|
|
else if (stateAsString == "Locally Added")
|
|
|
|
return VCSFileInfo::Added;
|
|
|
|
else if (stateAsString == "Unresolved Conflict")
|
|
|
|
return VCSFileInfo::Conflict;
|
|
|
|
else if (stateAsString == "Needs Patch")
|
|
|
|
return VCSFileInfo::NeedsPatch;
|
|
|
|
else if (stateAsString == "Needs Checkout")
|
|
|
|
return VCSFileInfo::NeedsCheckout;
|
|
|
|
else
|
|
|
|
return VCSFileInfo::Unknown; /// \FIXME exhaust all the previous cases first ;-)
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
void CVSFileInfoProvider::printOutFileInfoMap( const VCSFileInfoMap &map )
|
|
|
|
{
|
|
|
|
kdDebug(9006) << "Files parsed:" << endl;
|
|
|
|
for (VCSFileInfoMap::const_iterator it = map.begin(); it != map.end(); ++it)
|
|
|
|
{
|
|
|
|
const VCSFileInfo &vcsInfo = *it;
|
|
|
|
kdDebug(9006) << vcsInfo.toString() << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "cvsfileinfoprovider.moc"
|