/****************************************************************************
**
** Copyright (C) 2004-2007 Frank Hemer <frank@hemer.org>,
**                         Tilo Riemer <riemer@crossvc.com>
**
**
**----------------------------------------------------------------------------
**
**----------------------------------------------------------------------------
**
** CrossVC is available under two different licenses:
**
** If CrossVC is linked against the GPLed version of Qt 
** CrossVC is released under the terms of GPL also.
**
** If CrossVC is linked against a nonGPLed version of Qt 
** CrossVC is released under the terms of the 
** CrossVC License for non-Unix platforms (CLNU)
**
**
** CrossVC License for non-Unix platforms (CLNU):
**
** Redistribution and use in binary form, without modification, 
** are permitted provided that the following conditions are met:
**
** 1. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 2. It is not permitted to distribute the binary package under a name
**    different than CrossVC.
** 3. The name of the authors may not be used to endorse or promote
**    products derived from this software without specific prior written
**    permission.
** 4. The source code is the creative property of the authors.
**    Extensions and development under the terms of the Gnu Public License
**    are limited to the Unix platform. Any distribution or compilation of 
**    the source code against libraries licensed other than gpl requires 
**    the written permission of the authors.
**
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR 
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
** GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
**
**
** CrossVC License for Unix platforms:
**
** 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, version 2 of the License.
** 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 version 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
*****************************************************************************/

#include "CvsDirectory.h"

#include <qdir.h>
#include <qregexp.h>

//GUI related -> remove later
#include <qmessagebox.h>
#include <qinputdialog.h>
#include <assert.h>

#include "globals.h"
#include "tzconvert.h"
#include "filesubr.h"
#include "TextDecoder.h"
#include "login.h"
#include "pixmapcache.h"
#include "cvslistview.h"
#include "LinCVSLog.h"
#include "cvsignorelistview.h"
#include "noncvslistview.h"
#include "qtdatetime.h"

#define CVSDIRECTORY 123457

//=========================================== public ===============================================

//top level dir
CvsDirectory::CvsDirectory( QStringList subProjectList,
      const QString& fileName)
   : Directory(subProjectList)
{
   s_topView->setUpdatesEnabled(FALSE);
   init(fileName);
   s_topView->setUpdatesEnabled(TRUE);
   s_topView->triggerUpdate();
}

//----------------------------------------------------------------------------

//subdir
CvsDirectory::CvsDirectory( QListViewItem * parent, 
      const QString& fileName, 
      bool virtualDir)
   : Directory( parent, virtualDir)
{
   bool update;
   if (update = s_topView->isUpdatesEnabled()) {
      s_topView->setUpdatesEnabled(FALSE);
   }
   init(fileName);
   if (update) {
      s_topView->setUpdatesEnabled(TRUE);
      s_topView->triggerUpdate();
   }
}

//----------------------------------------------------------------------------

CvsDirectory::~CvsDirectory() {
}

//----------------------------------------------------------------------------

int CvsDirectory::rtti() const {
   return CVSDIRECTORY;
}

//----------------------------------------------------------------------------

CvsDirectory * CvsDirectory::firstChild() {
   QListViewItem * lvi = QListViewItem::firstChild();
   while ( lvi && (lvi->rtti() != CVSDIRECTORY) ) {
      lvi = lvi->nextSibling();
   }
   return static_cast<CvsDirectory *>(lvi);
}

//----------------------------------------------------------------------------

CvsDirectory * CvsDirectory::nextSibling() {
   QListViewItem * lvi = QListViewItem::nextSibling();
   while ( lvi && (lvi->rtti() != CVSDIRECTORY) ) {
      lvi = lvi->nextSibling();
   }
   return static_cast<CvsDirectory *>(lvi);
}

//----------------------------------------------------------------------------

CvsDirectory * CvsDirectory::dyncast_cvsdir(QListViewItem * lvi) {
   if ( lvi && (lvi->rtti() == CVSDIRECTORY) ) return static_cast<CvsDirectory *>(lvi);
   else return NULL;
}

//=========================================== protected ============================================

//----------------------------------------------------------------------------




//=========================================== privat ===============================================

//--------------------------------- inherited virtual methods --------------------------------------

Directory * CvsDirectory::createChild( const QString& fileName, bool virtualDir) {
   return new CvsDirectory(this,
	 fileName,
	 virtualDir);
}

//----------------------------------------------------------------------------

bool CvsDirectory::loginOk(CCvsOutput *pMessages, bool showMessage)
{
   if(!m_isControlledDir) {//fixme move to CvsDirectory
      QString cap = LC_APPNAME;
      cap += " - " + QObject::tr("Warning");
      QMessageBox::warning(qApp->mainWidget(), cap, QObject::tr("Is not a CVS directory."), 0);
      return false;
   }
   
   if((m_connectMethod != "pserver") && (m_connectMethod != "sspi")){
      return true;
   }

   if (!m_passwd.isNull()) {
      if(showMessage) {
         QString cap = LC_APPNAME;
         cap += " - " + QObject::tr("Information");
         QString msg = QObject::tr("No login necessary. Password is set in CVSROOT.");
         msg += "\n" + QObject::tr("Note this is a security risk!");
         QMessageBox::information(qApp->mainWidget(), cap, msg, 0);
      }
      
      return true;
   }
   
   QString cvsRoot = ":" + m_connectMethod + ":" + m_userName + 
      "@" + m_host + ":" + QString::number(m_port) + m_rootDir;
   
   if (!isInCvsPass(cvsRoot)) {

      bool ok = false;

      QString pwd = QInputDialog::getText("Enter CVS password",
	    m_root, QLineEdit::Password, QString::null,
	    &ok, 0/*dynamic_cast<QWidget *>(this)*/, "Password Dialog");

      if (!ok)
	 return false;

      Login *l = new Login( pMessages, m_connectMethod, m_userName, m_host, m_port, m_rootDir);
      l->doLogin(pwd);
      delete l;

      if (!isInCvsPass(cvsRoot)) {
	 QString cap = LC_APPNAME;
	 cap += " - " + QObject::tr("Warning");
	 QMessageBox::warning(qApp->mainWidget(), cap, QObject::tr("Login failed."), 0);
	 
	 return false;
      }

      if (showMessage) {
	 QString cap = LC_APPNAME;
	 cap += " - " + QObject::tr("Information");
	 QMessageBox::information(qApp->mainWidget(), cap, QObject::tr("Login successful."), 0);
      }

      return true;
   }
   
   if(showMessage) {
      QString cap = LC_APPNAME;
      cap += " - " + QObject::tr("Information");
      QMessageBox::information(qApp->mainWidget(), cap, QObject::tr("You are already logged in."), 0);
   }
   
   return true;
}

//----------------------------------------------------------------------------

void CvsDirectory::removeLogin(CCvsOutput *pMessages) {
   if(!m_isControlledDir) {//fixme move to CvsDirectory
      QString cap = LC_APPNAME;
      cap += " - " + QObject::tr("Warning");
      QMessageBox::warning(qApp->mainWidget(), cap, QObject::tr("Is not a CVS directory!"), 0);
      return;
   }
   
   if (!m_passwd.isNull()) {
      QString cap = LC_APPNAME;
      cap += " - " + QObject::tr("Information");
      QString msg = QObject::tr("Logout impossible! Password is set in CVSROOT.");
      msg += "\n" + QObject::tr("Note this is a security risk!");
      QMessageBox::information(qApp->mainWidget(), cap, msg, 0);
      return;
   }
   
   Login *l = new Login( pMessages, m_connectMethod, m_userName, m_host, m_port, m_rootDir);
   if (!(l->removeCvsPassEntry())) {
      QString cap = LC_APPNAME;
      cap += " - " + QObject::tr("Information");
      QMessageBox::information(qApp->mainWidget(), cap,
			       QObject::tr("Logout impossible. You have not been logged in."), 0);
   }
   delete l;
}

//---------------------------------------------------------------------------

bool CvsDirectory::isModified() {
   QString path = m_fullName;
   if(path.length() > 1){//is not root
      path += "/";
   }
  
   QFileInfo fInfo(path);
   if (!fInfo.exists()) return false;
   //   qDebug("dir: "+m_fullName
   // 	 +", lastModified: "+m_lastModTimeOfDir.toString("hh:mm:ss:zzz")
   // 	 +", new: "+fInfo.lastModified().toString("hh:mm:ss:zzz")
   // 	 +", cur: "+QTime::currentTime().toString("hh:mm:ss:zzz"));
   if (m_lastModTimeOfDir == fInfo.lastModified()) {
      return (entriesFileModified() || entriesLogFileModified());
   } else if (fInfo.lastModified() > QDateTime::currentDateTime().addSecs(-1)) {//causes problems on mounted dirs with non-synched timestamp
      //fileInfo doesn't contain msec info, so new changes are not
      //detected if they are within the same second
      //we ignore changes here, so the first run with latest changes
      //being in past more than 1 second will return true
      //     qDebug("dir: "+m_fullName
      // 	   +", old: "+m_lastModTimeOfDir.toString("hh:mm:ss:zzz")
      // 	   +", new: "+fInfo.lastModified().toString("hh:mm:ss:zzz")
      // 	   +", CvsCall: "+m_lastCallTime.toString("hh:mm:ss:zzz"));
      if (fInfo.lastModified() <= m_lastCallTime) {
	 //if fInfo.lastModified() is within the last second from current time
	 //and the last cvs call happened _after_ that, don't ignore changes
	 //to prevent an additional view update
	 m_lastModTimeOfDir = fInfo.lastModified();
	 return true;
      }
      return (entriesFileModified() || entriesLogFileModified());
   } else {
      //     qDebug("Dir modified: "+m_fullName);
      m_lastModTimeOfDir = fInfo.lastModified();
      return true;
   }
}

//----------------------------------------------------------------------------

bool CvsDirectory::isControlledTree() {
   return (m_isControlledDir || m_hasControlledSubDir);
}

//----------------------------------------------------------------------------

bool CvsDirectory::checkDirForControl( ) {
   QString path = m_fullName;

   if(path.length() > 1){//is not root
      path += "/";
   }
      
   QFile f;
   QString line;
   
   QString logMsg;

   //read Root
   f.setName(path + "CVS/Root");
   if(f.open(IO_ReadOnly)) {//file is readable
      QTextStream textStream(&f); 

      line = textStream.readLine();
      f.close();

      m_root = line.stripWhiteSpace();
      if (!extractCVSROOT(line,
		  m_connectMethod,
		  m_userName,
		  m_passwd,
		  m_host,
		  m_port,
		  m_rootDir)) {
         Debug::g_pLog->log (Debug::LL_GOSSIP_MONGER, "extractCVSROOT failed");
	 return false;
      }

      if ((m_connectMethod == "local")
	    || (m_connectMethod == "fork")) { //local
	 m_userName = getenv("USER");
	 m_host = "localhost";
	 m_connectMethod = "local";
      }

      if (Debug::g_logLevel == Debug::LL_GOSSIP_MONGER) {
         logMsg += "extracting CVS/Root in: "+m_fullName+"\n";
         logMsg += "\tconnectMethod: "+m_connectMethod+"\n";
         logMsg += "\tuserName: "+m_userName+"\n";
         if (!m_passwd.isEmpty ()) logMsg += "\tpassword set: xxxxx\n";
         logMsg += "\thost: "+m_host+"\n";
         logMsg += "\tport: "+QString::number (m_port)+"\n";
         logMsg += "\trootDir: "+m_rootDir+"\n";
      }

   } else {
      Debug::g_pLog->log (Debug::LL_THE_OLD_MAN_AND_THE_SEA, m_fullName+": cannot read CVS/Root");
      return false;
   }

   //read Repository
   f.setName(path + "CVS/Repository");
   if(f.open(IO_ReadOnly)) {//file is readable
      QTextStream textStream(&f); 
      m_repository = textStream.readLine().stripWhiteSpace();
      f.close();

      if (Debug::g_logLevel == Debug::LL_GOSSIP_MONGER) {
         logMsg += "extracting CVS/Repository\n";
         logMsg += "\trepository: "+m_repository+"\n";
      }

   } else {
      if (Debug::g_logLevel == Debug::LL_THE_OLD_MAN_AND_THE_SEA) {
         logMsg += m_fullName+": cannot read CVS/Repository";
         Debug::g_pLog->log (Debug::LL_THE_OLD_MAN_AND_THE_SEA, logMsg);
      }
      return false;
   }

   //workaround for old cvs version
   if(m_repository.find(m_rootDir) == 0){//old cvs version detected
      m_repository = m_repository.mid(m_rootDir.length() + 1);
   }
   
   // check/set sub-project properties
   QString name = shortName();
   Directory * p = parent();
   if ( p) {
      bool addSubProject = false;
      if ( p->isControlled()) {
         if ( m_root != p->getRoot() ) addSubProject = true;
	 else if (m_repository != p->repository()+"/"+name) {//maybe sub project
	    //test for existence in parent entries file
	    addSubProject = true;
	    QStringList dirList = parent()->getControledSubdirs();
	    QStringList::Iterator iterator;
	    if ( (iterator = dirList.find(name)) != dirList.end() ) {
	       addSubProject = false;
	    }
	 }
      } else {
	 addSubProject = TRUE;
      }

      if (addSubProject) {
	 m_isSubProjectRoot = true;
	 QStringList subList;
	 QString subProjectName = relativeName();
	 projectSettings->getSubProjects(m_topDir->shortName(),subList);
	 if ( subList.find(subProjectName) == subList.end()) {
	    projectSettings->addSubProject(m_topDir->shortName(),subProjectName);
	    projectSettings->set(subProjectName,WORKDIR,m_fullName);
	    m_topDir->getSubProjectList()->append(subProjectName);
	 }
      }
   }

   Debug::g_pLog->log (Debug::LL_GOSSIP_MONGER, logMsg);
   return true;
}

//---------------------------------------------------------------------------

QStringList CvsDirectory::getControledSubdirs() {
   QStringList list;
   QStringList::Iterator iterator;
   QString line;
   QTextStream textStream; 
   textStream.setCodec(I18n::g_pTextDecoder->pTextCodecPool()->pCodecForName(I18n::g_nameOfDecoder));
   QFile f(m_fullName+"/CVS/Entries");
   if (f.open(IO_ReadOnly)) {
      textStream.setDevice(&f);
      while (!textStream.eof()) {
	 line = textStream.readLine();
	 if (line.at(0) == 'D') {//entry is a directory
	    list.append(line.section("/",1,1));
	 }
      }
      f.close();
      f.setName(m_fullName+"/CVS/Entries.Log");
      if (f.open(IO_ReadOnly)) {
	 textStream.setDevice(&f);
	 while (!textStream.eof()) {
	    line = textStream.readLine();
	    if (line.at(2) != 'D') continue;
	    if (line.at(0) == 'A') {//added
	       line = line.section('/',1,1);
	       if (list.findIndex(line) == -1) list.append(line);
	    } else if (line.at(0) == 'R') {//removed
	       line = line.section('/',1,1);
	       iterator = list.find(line);
	       if (iterator != list.end()) {
		  list.remove(iterator);
	       }
	    }
	 }
	 f.close();
      }
   }
   return list;
}

//---------------------------------------------------------------------------

DirBase * CvsDirectory::searchDirOfPath (QString path, bool findVirtual /* = false */) {
   if (!findVirtual && m_virtual) {
      return NULL;
   }
   if (path.compare (m_fullName) == 0) {
      return this;   //itīs my path :-)
   }
  
   if (path.find (m_fullName+"/") == 0) {//is subdir
      CvsDirectory * myChild = firstChild();
      CvsDirectory * result;
      while (myChild) {
	 if ( (result = dyncast_cvsdir (myChild->searchDirOfPath (path, findVirtual))) ) {
	    return result;
	 }
	 else {
	    myChild = myChild->nextSibling ();
	 }
      }
   }
   return NULL;
}

//----------------------------------------------------------------------------

//return the deepest valid (not disabled, not virtual) dir in hierarchy
DirBase * CvsDirectory::searchLastValidDirOfPath (QString path) {
   if (m_disabled || m_virtual) {
      return NULL;
   }
   if (path.compare(m_fullName) == 0) {
      return this;   //its my path :-)
   }
   if (path.find(m_fullName+"/") == 0) {//is subdir
      CvsDirectory * myChild = firstChild();
      CvsDirectory * result;
      while (myChild) {
	 if ( (result = dyncast_cvsdir (myChild->searchLastValidDirOfPath (path))) ) {
	    return result;
	 }
	 else {
	    myChild = myChild->nextSibling ();
	 }
      }
      return this;
   }
   return NULL;
}

//---------------------------------------------------------------------------

//recursive check for modifications, only detected by a timestamp
//change of the dir or its CVS/Entries file
//if checkCvsFiles is true, also check for a change in each files timestamp
//if modifications are detected, check and set the new dir state
void CvsDirectory::recCheckForModifications(bool checkCvsFiles) {

   if (m_disabled || m_virtual) return;

   qApp->processEvents(1);
   if (globalStopAction) return;

   Directory * C = Directory::firstChild();
   while( C ) {
      C->recCheckForModifications(checkCvsFiles);
      C = C->nextSibling();
   }
   if ( isModified() || (checkCvsFiles && isFilesModified()) ) {
      checkAndShowStatus(NULL,true,Controled|NonControled|Ignored,TRUE);
      if ( Debug::g_logLevel == Debug::LL_GOSSIP_MONGER) {
	 Debug::g_pLog->log(Debug::LL_GOSSIP_MONGER, "Detected changes: "+m_fullName);
      }
   }
}

//---------------------------------------------------------------------------

//recursive check for modifications, only detected by a timestamp
//change of the CVS/Entries / CVS/Entries.log file
//if modifications are detected, activateItem
bool CvsDirectory::postCallCheck(int type /*=Controled|NonControled|Ignored*/) {

   if (m_disabled || m_virtual) return false;

   qApp->processEvents(1);
   if (globalStopAction) return false;

//    qDebug(QString(isSelected() ? "selected" : "not selected")+", "
// 	 +QString((type & Controled) ? "controled" : "not controled")+", "
// 	 +QString(entriesFileModified() ? "entriesFileModified" : "")
// 	 +QString(entriesLogFileModified() ? "entriesLogFileModified" : "")
// 	 +QString((type & Force) ? "Force" : ""));

   if (isSelected() && (type & Controled) && (entriesFileModified() || entriesLogFileModified() || (type & Force)) ) {
      if (Polling::NONE) activateItem(true);
      else activateItem( (type & (NonControled|Ignored)), type );
      return true;
   }

   if (type & Tree) {
      CvsDirectory * dir = firstChild();
      while (dir) {
	 if (dir->postCallCheck(type)) return true;
	 dir = dir->nextSibling();
      }
   }

   return false;
}

//----------------------------------------------------------------------------

void CvsDirectory::validateControlledFilesStatus( bool recursive /* = FALSE*/,
      bool forceCacheReset /* = FALSE*/,
      bool forceEntriesReset /* = FALSE*/) {

   if (forceEntriesReset) {
      m_entries.clear();
   }

   // now check and set state
   CvsEntriesIterator it(*getCvsEntries(forceCacheReset));
   while(it.current()) {
      setStatusInFileListView(NULL, it.current(),true);
      ++it;
   }

   if (recursive) {
      Directory * C = Directory::firstChild();
      while( C ) {
	 C->validateControlledFilesStatus(recursive,forceCacheReset,forceEntriesReset);
	 C = C->nextSibling();
      }
   }
}

//----------------------------------------------------------------------------

//Main function for checking file state and displaying, used by DirWatch,AnalyzeDir,cvscontrol::checkStatus ...
void CvsDirectory::checkAndShowStatus( FileListViewItem *item /* = NULL*/,
      bool checkOnly /* =FALSE*/,
      int tab /* =Controled|NonControled|Ignored*/,
      bool resetCache /* = FALSE*/) {

   CvsEntries * cvsEntries = getCvsEntries();

   if(item) {//only one item
      QString fileName = item->text(0);
      CvsEntry * cvsEntry = cvsEntries->find(fileName);
      if (cvsEntry) {
	 setStatusInFileListView(item, cvsEntry);
      }
   } else { // read entries in directory
      QStringList AllFilesInDirectory;
      QFile f;
      QString line;
      QString path = m_fullName;
      if(path.length() > 1){//is not root
	 path += "/";
      }

      QDir D( path);
      D.setFilter( QDir::Files | QDir::Dirs | QDir::Hidden | QDir::System);
      AllFilesInDirectory = D.entryList();

      // remove . and ..
      AllFilesInDirectory.remove( "." );
      AllFilesInDirectory.remove( ".." );
      AllFilesInDirectory.remove( "CVS" );

      // now check and set state
      CvsEntriesIterator it(*cvsEntries);
      while(it.current()) {
	 if (tab & Controled) item = setStatusInFileListView(0, it.current(), checkOnly);
         if (!it.current ()->isTmp) {
            AllFilesInDirectory.remove(it.current()->name);
         }
         ++it;
      }

      // show 'non-cvs' and 'ignore files' entries
      if ( tab & (NonControled|Ignored) ) {

	 // first read .cvsignore
	 bool homeIgnoresChanged;
	 bool dirIgnoresChanged;
	 const QString homeFilesToIgnore = getHomeFilesToIgnore(m_lastTimeHomeIgnoresChanged,homeIgnoresChanged);
	 const QString filesToIgnore = getDirFilesToIgnore(homeFilesToIgnore,dirIgnoresChanged);
	 
	 //reset cache if changes occured
	 bool ignoreCacheReseted = false;
	 if (resetCache || dirIgnoresChanged || homeIgnoresChanged) {
	    m_ignoreFiles.clear();
	    m_nonFiles.clear();
	    ignoreCacheReseted = true;
	 }
	 
	 // now check the files
	 for (QStringList::Iterator fileit = AllFilesInDirectory.begin(); fileit != AllFilesInDirectory.end(); fileit++) {
	    
	    QString name = path + (*fileit);
	    QFileInfo info(name);
	    
	    bool IsDir = info.isDir();
	    if ( IsDir && (checkType(name+"/") == Cvs) ) {// CVS directory -> do NOT show
	       continue;
	    }
	    bool isWritable = info.isWritable();
	    bool isExecutable = info.isExecutable();
	    
	    //check wether file belongs to .cvsignore or is not registered
	    bool doIgnore = false;
	    bool found;
	    doIgnore = updateFileCacheAndCheckForIgnores(filesToIgnore,*fileit,ignoreCacheReseted, found);
	    
	    if (doIgnore) {
	       // continue here if checkOnly, don't show
	       if (checkOnly || !(tab & Ignored)) continue;
	       // show cvsignore entries
	       item = new FileListViewItem(m_pIgnoreFileListView,IsDir,FileListViewItem::TextNoCase,FileListViewItem::ModDate);
	       item->setPixmap(0, findEmbeddedPixmap((IsDir) ?
				     (isExecutable) ?  "FolderClosed16x16" : "FolderClosedLocked16x16" :
				     (isWritable) ? "FileStatus16x16" : "FileLocked16x16"));
	    } else {
	       // stop here if checkOnly because now we know the non-cvs state (dir state was allready set in updateFileCacheAndCheckForIgnores
	       if (checkOnly) return;
	       if ( !(tab & NonControled) ) continue;
	       //show non-cvs entries
	       item = new FileListViewItem(m_pNonFileListView,IsDir,FileListViewItem::TextNoCase,FileListViewItem::ModDate);
	       item->setPixmap(0, findEmbeddedPixmap((IsDir) ?
				     (isExecutable) ?  "FolderStatus16x16" : "FolderClosedLocked16x16" :
				     (isWritable) ? "FileUnknown16x16" : "FileLocked16x16"));
	    }
	    
	    item->setText(0, *fileit);
	    QDateTime localDate = info.lastModified();
	    item->setModifiedDate(1, &localDate, localDate.toString(LookAndFeel::g_dateTimeFormat));
	 }
	 checkNonControlledFilesDirState();
      }
   }
}

//----------------------------------------------------------------------------

//Add a single file, only known by its name, and display it
void CvsDirectory::checkAndUpdateFileCache(QString& file) {

   // first read .cvsignore
   bool homeIgnoresChanged;
   bool dirIgnoresChanged;
   const QString homeFilesToIgnore = getHomeFilesToIgnore(m_lastTimeHomeIgnoresChanged,homeIgnoresChanged);
   const QString filesToIgnore = getDirFilesToIgnore(homeFilesToIgnore,dirIgnoresChanged);
  
   bool ignoreCacheReseted = false;
   if (dirIgnoresChanged || homeIgnoresChanged) {
      if (isSelected()) {
	 activateItem(TRUE);
	 return;
      } else {
	 m_ignoreFiles.clear();
	 m_nonFiles.clear();
	 ignoreCacheReseted = true;
      }
   }

   CvsEntries * cvsEntries = getCvsEntries();
   CvsEntry *cvsEntry = cvsEntries->find(file);
   if (cvsEntry) {
      QListViewItem * item = NULL;
      if (isSelected()) {
	 item = m_pFileListView->firstChild();
	 while (item) {
	    if (item->text(0) == file) {
	       break;
	    } else item = item->nextSibling();
	 }
      }
      setStatusInFileListView( static_cast<FileListViewItem*>(item), cvsEntry, !isSelected());
   } else {
      bool found;
      bool doIgnore = updateFileCacheAndCheckForIgnores(filesToIgnore,file,ignoreCacheReseted,found);
      if (isSelected() && (!found) ) {
	 FileListViewItem * item;

	 bool IsDir = false;
	 bool isWritable = false;
	 bool isExecutable = false;
      
	 QFileInfo info(m_fullName+"/"+file);
	 IsDir = info.isDir();
	 isWritable = info.isWritable();
	 isExecutable = info.isExecutable();
      
	 if (doIgnore) {
	    // show cvsignore entries
	    item = new FileListViewItem(m_pIgnoreFileListView,IsDir,FileListViewItem::TextNoCase,FileListViewItem::ModDate);
	    item->setPixmap(0, findEmbeddedPixmap((IsDir) ?
			(isExecutable) ?  "FolderClosed16x16" : "FolderClosedLocked16x16" :
			(isWritable) ? "FileStatus16x16" : "FileLocked16x16"));
	 } else {
	    //show non-cvs entries
	    item = new FileListViewItem(m_pNonFileListView,IsDir,FileListViewItem::TextNoCase,FileListViewItem::ModDate);
	    item->setPixmap(0, findEmbeddedPixmap((IsDir) ?
			(isExecutable) ?  "FolderStatus16x16" : "FolderClosedLocked16x16" :
			(isWritable) ? "FileUnknown16x16" : "FileLocked16x16"));
	    m_pNonFileListView->signalState();
	 }
      
	 item->setText(0, file);
	 QDateTime localDate = info.lastModified();
	 item->setModifiedDate(1, &localDate, localDate.toString(LookAndFeel::g_dateTimeFormat));
      }
   }
}

//----------------------------------------------------------------------------

void CvsDirectory::removeTmpEntries(QString name) {
   QStringList::Iterator it;
   if (name.isNull()) {
      for ( it = m_tmpEntries.begin(); it != m_tmpEntries.end(); it++ ) {
	 setEntryState(*it,ES_unknown);//adapt the warn level before removal
	 m_CvsEntries.remove(*it);
	 removeEntry(*it);
      }
      m_tmpEntries.clear();
   } else {
      it = m_tmpEntries.find (name);
      if (it != m_tmpEntries.end ()) {
         setEntryState(name,ES_unknown);//adapt the warn level before removal
         m_CvsEntries.remove(name);
         removeEntry(name);
         m_tmpEntries.remove(it);
      }
   }
   //remove virtual dirs
   Directory *myChild = Directory::firstChild();
   while (myChild) {
      if (myChild->isVirtual()) {
	 Directory * myTmpChild = myChild;
	 myChild = myChild->nextSibling();
	 if (name.isNull() || (name == myTmpChild->shortName()) ) {
	    delete (myTmpChild);
	    setDirState(ES_unknown,true);//reset state
	    if (!name.isNull()) break;
	 }
      } else {
	 myChild = myChild->nextSibling();
      }
   }
}

//----------------------------------------------------------------------------

void CvsDirectory::recRemoveTmpEntries(bool allProjects) {
   if (m_disabled) return;
   CvsDirectory * C = firstChild();
   while( C ) {
      if (allProjects || (C->topControlledDir() == topControlledDir()) ) C->recRemoveTmpEntries(allProjects);
      C = C->nextSibling();
   }
   removeTmpEntries();
}

//----------------------------------------------------------------------------

bool CvsDirectory::recCopyTree(QString src,QString dst, bool deleteSource /*=TRUE*/) {

   QDir newDir;
   QString dstDir = dst+"/"+src.mid(src.findRev("/"));
   if (!newDir.mkdir(dstDir) ) {
      qDebug("can't create dir: "+dstDir );
      return FALSE;
   }

   //dirs first
   QStringList AllSubDirs;
   QDir D( src);
   D.setFilter(QDir::Dirs);
   AllSubDirs = D.entryList();

   // remove . and ..
   AllSubDirs.remove( "." );
   AllSubDirs.remove( ".." );
   AllSubDirs.remove( "CVS" );
  
   QStringList::Iterator fileit;  //VC6 does _not_ like it...
   for (fileit = AllSubDirs.begin(); fileit != AllSubDirs.end(); fileit++) {
      recCopyTree(src+"/"+(*fileit),dstDir,deleteSource);
   }
  
   //now the files
   QStringList AllFilesInDirectory;
   D.setFilter( QDir::Files | QDir::Hidden);
   AllFilesInDirectory = D.entryList();

   for (fileit = AllFilesInDirectory.begin(); fileit != AllFilesInDirectory.end(); fileit++) {
      QString srcName = src+"/"+(*fileit);
      QString dstName = dstDir+"/"+(*fileit);
      QFileInfo fInfo(srcName);
      int permission = 0;
      if (fInfo.isReadable()) permission = (permission | READABLE);
      if (fInfo.isWritable()) permission = (permission | WRITEABLE);
      if (fInfo.isExecutable()) permission = (permission | EXECUTABLE);
      if (!copyFile(srcName, dstName, permission, deleteSource) ) {
	 qDebug("can't copy file: "+srcName+" -> "+dstName);
	 return FALSE;
      }
   }
   return TRUE;
}

//----------------------------------------------------------------------------

//----------------------------------------------------------------------------

bool CvsDirectory::parseCallResult(CvsBuffer *output, int icmd, QStringList *stringList) {

   bool retVal = TRUE;
   m_lastErrorMsg = "";

   CvsDirectory *pCurCvsDir = NULL;
   CvsDirectory *pCurCvsLastUpdateDir = NULL;

   QStringList curCvsUpdateFileList;
   QStringList addedDirList;
   QString curCvsFileName;
   EntryStates locCurCvsState = ES_unknown ;
   unsigned int len = (*output).numLines();
   int pos = 0;
   EntryStates_E stateToSet = ES_unknown;//init to prevent compiler warning
   QString tmp, fullPath, prevFullPath, path, repository;
   prevFullPath = "";
   fullPath = "";
   
   //some presets
   if (stringList && !stringList->isEmpty ()) {
      switch (icmd) {
         case CVS_UPDATE_FILES_CMD: {
            curCvsUpdateFileList = *stringList;
         }
         default:
            break;
      }
   }

   QString line;
   for (unsigned int i = 0; i<len;i++) {
      line = (*output).textLine(i);
      if (line.isEmpty()) continue;

      switch(icmd) {
	 case CVS_COMMIT_CMD:
	    if (line.startsWith("Removing ")) {// commit after remove
	       curCvsFileName = line.mid(9, line.length() - 10);   //truncate ";"
	       stateToSet=ES_unknown; // we have to set a state to adapt the warnlevel
	    } else if(line.startsWith("Checking in ")) {//followed of filename
	       curCvsFileName = line.mid(12, line.length() - 13);   //truncate ";"
	       stateToSet=ES_up_to_date;
	    } else if(line.compare("done") == 0){//commit completed successfully
	       if(pCurCvsDir && curCvsFileName.length()) {
		  pCurCvsDir->setEntryState(curCvsFileName, stateToSet);
		  if (stateToSet==ES_unknown) pCurCvsDir->removeEntry(curCvsFileName); // must not appear in entries any more
	       }
	       curCvsFileName="";
	       stateToSet=ES_unknown;
	       break;
	    } else if ( (pos = line.find(",v  <--  ")) > 0 ) {//cvs 1.12.xxx series
	       curCvsFileName = rcsToFileName (line.left (pos));
	    } else if (line.startsWith("new revision: ") || line.startsWith("initial revision: ") ) {//cvs 1.12.xxx series
	       if (line.startsWith("new revision: delete")) stateToSet=ES_unknown;
	       else stateToSet=ES_up_to_date;
	       if(pCurCvsDir && curCvsFileName.length()) {
		  pCurCvsDir->setEntryState(curCvsFileName, stateToSet);
		  if (stateToSet==ES_unknown) pCurCvsDir->removeEntry(curCvsFileName); // must not appear in entries any more
	       }
	       curCvsFileName="";
	       stateToSet=ES_unknown;
	       break;
	    } else {
	       continue; //nothing found
	    }
	    pos = curCvsFileName.findRev("/");
	    if (pos > -1) { //located in subdir
	       fullPath = m_fullName + "/" + curCvsFileName.mid(0, pos);
	       removeDoubleSlashes(fullPath);
	       curCvsFileName = curCvsFileName.mid(pos + 1);
	       if(fullPath.compare(prevFullPath)) { // change subdir
		  pCurCvsDir = dyncast_cvsdir(searchDirOfPath(fullPath));
		  prevFullPath = fullPath;
	       }
	    } else {
	       pCurCvsDir = this;   //no subdir
	       fullPath = "";
	       prevFullPath = "";
	    }
	    break;
	 case CVS_STATUS_CMD:
	    if((pos = line.find("Examining ")) > -1) {
	       path = line.mid(pos + 10);
	       path = path.stripWhiteSpace();
	   
	       //is a subdir?
	       if (path.compare(".") == 0) pCurCvsDir = this;
	       else {
		  fullPath = m_fullName + "/" + path;
		  removeDoubleSlashes(fullPath);
	   
		  //locate dir
		  pCurCvsDir = dyncast_cvsdir(searchDirOfPath(fullPath));//search only for non-virtual dirs
	       }
	    } else if(line.find("File:") == 0){//status output of next file begins
	       curCvsFileName = line.mid(6, line.find("\t", 6) - 6);
	       curCvsFileName = curCvsFileName.stripWhiteSpace();
	       if(curCvsFileName.find("no file") == 0) {
		  curCvsFileName = curCvsFileName.mid(8);
	       }
	   
	       if(line.find("Up-to-date") > -1){
		  locCurCvsState = ES_up_to_date;
	       } else if(line.find("Locally Modified") > -1){
		  locCurCvsState = ES_modified;
	       } else if(line.find("Needs Patch") > -1){
		  locCurCvsState = ES_needs_patch;
	       } else if(line.find("Needs Merge") > -1){
		  locCurCvsState = ES_needs_merge;
	       } else if(line.find("Needs Checkout") > -1){
		  locCurCvsState = ES_needs_checkout;
	       } else if(line.find("File had conflicts on merge") > -1){
		  locCurCvsState = ES_conflict;
	       } else if(line.find("Locally Added") > -1){
		  locCurCvsState = ES_added;
	       } else if(line.find("Locally Removed") > -1){
		  locCurCvsState = ES_removed;
	       } else if (line.find ("Entry Invalid") > -1) {
		  locCurCvsState = ES_needs_remove;
	       }

	       //set state
	       if(pCurCvsDir) {
		  pCurCvsDir->setAndAppendEntry(curCvsFileName, locCurCvsState);
	       }

	    }
	    break;
	 case CVS_STATUS_FILES_CMD:
	    if(line.find("File:") == 0){//status output of next file begins
	       curCvsFileName = line.mid(6, line.find("\t", 6) - 6);
	       curCvsFileName = curCvsFileName.stripWhiteSpace();
	       if(curCvsFileName.find("no file") == 0) {
		  curCvsFileName = curCvsFileName.mid(8);
	       }
	   
	       if(line.find("Up-to-date") > -1){
		  locCurCvsState = ES_up_to_date;
	       } else if(line.find("Locally Modified") > -1){
		  locCurCvsState = ES_modified;
	       } else if(line.find("Needs Patch") > -1){
		  locCurCvsState = ES_needs_patch;
	       } else if(line.find("Needs Merge") > -1){
		  locCurCvsState = ES_needs_merge;
	       } else if(line.find("Needs Checkout") > -1){
		  locCurCvsState = ES_needs_checkout;
	       } else if(line.find("File had conflicts on merge") > -1){
		  locCurCvsState = ES_conflict;
	       } else if(line.find("Locally Added") > -1){
		  locCurCvsState = ES_added;
	       } else if(line.find("Locally Removed") > -1){
		  locCurCvsState = ES_removed;
	       } else if (line.find ("Entry Invalid") > -1) {
		  locCurCvsState = ES_needs_remove;
	       }
	    } else if ( (line.find("Repository revision:") > -1) && ((pos = line.find ("\t/")) > -1) ) {//repository of fileName
               QString file = line.mid (pos + 1);
               file = rcsToFileName (file.left (file.length ()-2));

               if ((pos = file.findRev ("/")) > -1) {
                  path = file.left (pos);
                  path = path.stripWhiteSpace ();
                  file = file.mid (pos + 1);
                  //is a subdir?
                  fullPath = m_fullName + "/" + path;
                  removeDoubleSlashes (fullPath);
                  //locate dir
                  pCurCvsDir = dyncast_cvsdir (searchDirOfPath(fullPath));
               } else {
                  pCurCvsDir = this;
               }
               assert (file == curCvsFileName);

               //set state
               if(pCurCvsDir) {
                  pCurCvsDir->setAndAppendEntry(curCvsFileName, locCurCvsState);
               }
	    }
	    break; 
	 case CVS_GET_TAG_INFO_PROJECT_CMD:
	 case CVS_GET_TAG_INFO_FILES_CMD:
	    if (pos == 0) {
	       if (line.find("Existing Tags:") > -1) pos = 1;
	    }
	    else if ( (line.find("No Tags Exist") > -1)
		  || (line.startsWith("cvs "))
		  || (line.startsWith("===================================================================")) ) {
	       pos = 0;
	    } else {
	       QRegExp rx( "(\\S+)(?:\\s*\\()(\\w+):" );
	       rx.search(line);
	       //qDebug("cap(0): ->"+rx.cap(0)+"<- cap(1): ->"+rx.cap(1)+"<- cap(2): ->"+rx.cap(2)+"<-");
	       QString txt;
	       if (rx.cap(2).startsWith("branch")) {
		  txt = "B: "+rx.cap(1);
	       } else if (rx.cap(2).startsWith("revision")) {
		  txt = "T: "+rx.cap(1);
	       } else {
		  qDebug("unknown line on GET_TAG_INFO: "+line);
		  continue;
	       }
	       if (stringList->find(txt) == stringList->end()) {
		  stringList->append(txt);
	       }
	    }
	    break;
	 case CVS_UPDATE_DIR_CMD:

	    //updating of dir ... welches verzeichnis?
	    if ((pos = line.find("Updating ")) > -1) {
	       //cvs changed dir, put last scanned dir to up-to-date or remove if not physically available
	       if (pCurCvsLastUpdateDir) {
		  QDir dir(pCurCvsLastUpdateDir->fullName());
		  if (dir.exists()) {
		     pCurCvsLastUpdateDir->setFilesToUpToDate(curCvsUpdateFileList);
		  } else {
		     ((CvsDirectory*)pCurCvsLastUpdateDir->parent())->removeChild(pCurCvsLastUpdateDir->fullName());
		  }
	       }

	       path = line.mid(pos + 9);
	       path = path.stripWhiteSpace();
	   
	       //is a subdir?
	       if (path.compare(".") == 0) fullPath = m_fullName;
	       else fullPath = m_fullName + "/" + path;
	       removeDoubleSlashes(fullPath);
	   
	       //locate dir
	       pCurCvsLastUpdateDir = dyncast_cvsdir(searchDirOfPath(fullPath));//search only for non-virtual dirs

	       if (!pCurCvsLastUpdateDir) {//not scanned or just newly added?
		  addedDirList.append(fullPath);
	       }
	   
	       //fill filelist with entries of located dir
	       curCvsUpdateFileList.clear();   
	       if (pCurCvsLastUpdateDir) pCurCvsLastUpdateDir->fillFileListWithEntries(curCvsUpdateFileList);
	    }

	    locCurCvsState = ES_unknown;
	    //filestate...
	    if((line.find("P ") == 0) 
		  || (line.find("U ") == 0)){
	       curCvsFileName = line.mid(2);
	       locCurCvsState = ES_up_to_date;
	    } else if(line.find("M ") == 0){
	       curCvsFileName = line.mid(2);
	       locCurCvsState = ES_modified;
	    } else if(line.find("C ") == 0){
	       curCvsFileName = line.mid(2);
	       locCurCvsState = ES_conflict;
	    } else if(line.find("A ") == 0){
	       curCvsFileName = line.mid(2);
	       locCurCvsState = ES_added;
	    } else if(line.find("R ") == 0){
	       curCvsFileName = line.mid(2);
	       locCurCvsState = ES_removed;
	    } else if(line.find("RCS file: ") == 0) {
	       curCvsFileName = rcsToFileName (line.left (line.length ()-2).mid (10));
	       locCurCvsState = ES_modified;
	    } else if (line.find(": scheduling ") > -1) {
	       curCvsFileName = line.mid(line.find(": scheduling ")+13);
	       curCvsFileName.truncate(curCvsFileName.find(" for removal"));
	       locCurCvsState = ES_removed;
	    } else if (line.find("warning: conflicts during merge") > -1) {//filename allready extracted at <Merging differences between ...>
	       locCurCvsState = ES_conflict;
	    } else {
	       bool remove = true;
	       int pos1,pos2;
               if (((pos1 = line.find (": ")) > -1) && ((pos2 = line.find (" is no longer in the repository")) > -1)) {
		  curCvsFileName = line.mid (pos1 + 2, pos2 - pos1 - 2);
               } else if (((pos1 = line.find (": ")) > -1) && ((pos2 = line.find (" should be removed and is still there")) > -1)) {
		  curCvsFileName = line.mid (pos1 + 2, pos2 - pos1 - 2);
		  m_lastErrorMsg += line+"\n";
		  retVal = FALSE;
                  locCurCvsState = ES_conflict;//fixme: untested
                  remove = false;
	       } else if (((pos1=line.find("warning: ")) > -1) && ((pos2=line.find(" is not (any longer) pertinent")) > -1)) {
		  curCvsFileName = line.mid(pos1+9,pos2-pos1-9);
	       } else if (((pos1=line.find("warning: new-born ")) > -1) && ((pos2=line.find(" has disappeared")) > -1)) {
		  curCvsFileName = line.mid(pos1+18,pos2-pos1-18);
	       } else if (((pos1=line.find("conflict: ")) > -1) && ((pos2=line.find(" is modified but no longer in the repository")) > -1)) {
		  curCvsFileName = line.mid (pos1 + 10, pos2 - pos1 - 10);
		  m_lastErrorMsg += line+"\n";
		  retVal = FALSE;
		  i++;//this is dirty but easier than using a separate check for skipping the next line.
		  //the next line needs to be skipped to not parse the conflict info given by cvs since it differs from CVS/Entries
                  locCurCvsState = ES_modified;
                  remove = false;
	       } else if ( (((pos1=line.find("conflict: ")) > -1) && ((pos2=line.find(" has been added, but already exists")) > -1))
                     || (((pos1=line.find("conflict: ")) > -1) && ((pos2=line.find(" created independently by second party")) > -1)) ) {
		  curCvsFileName = line.mid (pos1 + 10, pos2 - pos1 - 10);
		  m_lastErrorMsg += line+"\n";
		  retVal = FALSE;
		  i++;//this is dirty but easier than using a separate check for skipping the next line.
		  //the next line needs to be skipped to not parse the conflict info given by cvs since it differs from CVS/Entries
                  locCurCvsState = ES_conflict;//fixme: untested
                  remove = false;
	       } else if (((pos1 = line.find ("conflict: removed ")) > -1) && ((pos2 = line.find (" was modified by second party")) > -1)) {
                  curCvsFileName = line.mid (pos1 + 18, pos2 - pos1 - 18);
		  m_lastErrorMsg += line + "\n";
		  retVal = FALSE;
		  i++;//this is dirty but easier than using a separate check for skipping the next line.
		  //the next line needs to be skipped to not parse the conflict info given by cvs since it differs from CVS/Entries
                  locCurCvsState = ES_conflict;//fixme: untested
                  remove = false;
	       } else if (((pos1 = line.find ("move away ")) > -1) && ((pos2 = line.find ("; it is in the way")) > -1)) {
                  curCvsFileName = line.mid (pos1 + 10, pos2 - pos1 - 10);
		  m_lastErrorMsg += line + "\n";
		  retVal = FALSE;
		  i++;//this is dirty but easier than using a separate check for skipping the next line.
		  //the next line needs to be skipped to not parse the conflict info given by cvs since it differs from CVS/Entries
                  locCurCvsState = ES_needs_patch;
                  remove = false;
	       } else {
		  break;
	       }
	       removeFileNameInconsistencies(curCvsFileName);
	       if((pos1 = curCvsFileName.findRev("/")) > -1) //is a subdir?
		  curCvsFileName = curCvsFileName.mid(pos1 + 1);
	       if(pCurCvsLastUpdateDir) {
		  pCurCvsLastUpdateDir->setAndAppendEntry(curCvsFileName, locCurCvsState);//we have to set a state to adapt the warnlevel
		  if (remove) pCurCvsLastUpdateDir->removeEntry(curCvsFileName);
		  curCvsUpdateFileList.remove(curCvsFileName);
	       }
	       break;
	    }
	 
	    if(locCurCvsState != ES_unknown) {
	       removeFileNameInconsistencies(curCvsFileName);
	   
	       //is file located in subdir?
	       if((pos = curCvsFileName.findRev("/")) > -1) 
		  curCvsFileName = curCvsFileName.mid(pos + 1);
	   
	       if (pCurCvsLastUpdateDir && curCvsFileName.length ()) {
		  pCurCvsLastUpdateDir->setAndAppendEntry(curCvsFileName, locCurCvsState);
		  curCvsUpdateFileList.remove(curCvsFileName);
	       }
	    }      
	    break;
	 case CVS_UPDATE_FILES_CMD:

	    locCurCvsState = ES_unknown;
	    //filestate...
	    if((line.find("P ") == 0) 
		  || (line.find("U ") == 0)){
	       curCvsFileName = line.mid(2);
	       locCurCvsState = ES_up_to_date;
	    } else if(line.find("M ") == 0){
	       curCvsFileName = line.mid(2);
	       locCurCvsState = ES_modified;
	    } else if(line.find("C ") == 0){
	       curCvsFileName = line.mid(2);
	       locCurCvsState = ES_conflict;
	    } else if(line.find("A ") == 0){
	       curCvsFileName = line.mid(2);
	       locCurCvsState = ES_added;
	    } else if(line.find("R ") == 0){
	       curCvsFileName = line.mid(2);
	       locCurCvsState = ES_removed;
	    } else if(line.find("RCS file: ") == 0) {
	       curCvsFileName = rcsToFileName (line.left (line.length ()-2).mid (10));
	       locCurCvsState = ES_modified;
	    } else if (line.find("warning: conflicts during merge") > -1) {//filename allready extracted at <Merging differences between ...>
	       locCurCvsState = ES_conflict;
	    } else if (line.find(": scheduling ") > -1) {
	       curCvsFileName = line.mid(line.find(": scheduling ")+13);
	       curCvsFileName.truncate(curCvsFileName.find(" for removal"));
	       locCurCvsState = ES_removed;
	    } else {
	       bool remove = true;
	       int pos1,pos2;
               if (((pos1 = line.find (": ")) > -1) && ((pos2 = line.find (" is no longer in the repository")) > -1)) {
                  curCvsFileName = line.mid (pos1 + 2, pos2 - pos1 - 2);
               } else if (((pos1 = line.find (": ")) > -1) && ((pos2 = line.find (" should be removed and is still there")) > -1)) {
                  curCvsFileName = line.mid (pos1 + 2, pos2 - pos1 - 2);
                  m_lastErrorMsg += line+"\n";
                  retVal = FALSE;
                  locCurCvsState = ES_conflict;//fixme: untested
                  remove = false;
	       } else if (((pos1=line.find("warning: ")) > -1) && ((pos2=line.find(" is not (any longer) pertinent")) > -1)) {
                  curCvsFileName = line.mid(pos1+9,pos2-pos1-9);
	       } else if (((pos1=line.find("warning: new-born ")) > -1) && ((pos2=line.find(" has disappeared")) > -1)) {
                  curCvsFileName = line.mid(pos1+18,pos2-pos1-18);
	       } else if (((pos1=line.find("conflict: ")) > -1) && ((pos2=line.find(" is modified but no longer in the repository")) > -1)) {
                  curCvsFileName = line.mid (pos1 + 10, pos2 - pos1 - 10);
                  m_lastErrorMsg += line+"\n";
                  retVal = FALSE;
                  i++;//this is dirty but easier than using a separate check for skipping the next line.
                  //the next line needs to be skipped to not parse the conflict info given by cvs since it differs from CVS/Entries
                  locCurCvsState = ES_modified;
                  remove = false;
	       } else if ( (((pos1=line.find("conflict: ")) > -1) && ((pos2=line.find(" has been added, but already exists")) > -1))
                     || (((pos1=line.find("conflict: ")) > -1) && ((pos2=line.find(" created independently by second party")) > -1)) ) {
                  curCvsFileName = line.mid (pos1 + 10, pos2 - pos1 - 10);
                  m_lastErrorMsg += line+"\n";
                  retVal = FALSE;
                  i++;//this is dirty but easier than using a separate check for skipping the next line.
                  //the next line needs to be skipped to not parse the conflict info given by cvs since it differs from CVS/Entries
                  locCurCvsState = ES_conflict;//fixme: untested
                  remove = false;
	       } else if (((pos1 = line.find ("conflict: removed ")) > -1) && ((pos2 = line.find (" was modified by second party")) > -1)) {
                  curCvsFileName = line.mid (pos1 + 18, pos2 - pos1 - 18);
                  m_lastErrorMsg += line + "\n";
                  retVal = FALSE;
                  i++;//this is dirty but easier than using a separate check for skipping the next line.
                  //the next line needs to be skipped to not parse the conflict info given by cvs since it differs from CVS/Entries
                  locCurCvsState = ES_conflict;//fixme: untested
                  remove = false;
	       } else if (((pos1 = line.find ("move away ")) > -1) && ((pos2 = line.find ("; it is in the way")) > -1)) {
                  curCvsFileName = line.mid (pos1 + 10, pos2 - pos1 - 10);
                  m_lastErrorMsg += line + "\n";
                  retVal = FALSE;
                  i++;//this is dirty but easier than using a separate check for skipping the next line.
                  //the next line needs to be skipped to not parse the conflict info given by cvs since it differs from CVS/Entries
                  locCurCvsState = ES_needs_patch;
                  remove = false;
	       } else {
                  break;
	       }
          
               removeFileNameInconsistencies(curCvsFileName);
               setAndAppendEntry(curCvsFileName, locCurCvsState);//we have to set a state to adapt the warnlevel
               if (remove) removeEntry(curCvsFileName);
               break;
            }
       
            curCvsFileName = curCvsFileName.stripWhiteSpace();
            if(locCurCvsState != ES_unknown ) {
               removeFileNameInconsistencies(curCvsFileName);
               curCvsUpdateFileList.remove (curCvsFileName);
               if (curCvsFileName.length ()) {
                  //remove tmp entries in case the file was there but not under cvs control
                  //the tmp entrie is the result of query update command
                  removeTmpEntries (curCvsFileName);
                  setAndAppendEntry(curCvsFileName, locCurCvsState);
               }
            }

	    break;
	 case CVS_QUERY_UPDATE_ALL_CMD:
	 case CVS_QUERY_UPDATE_CMD:

	    locCurCvsState = ES_unknown;
	    if((pos = line.find("Updating ")) > -1) {
	       path = line.mid(pos + 9);
	       path = path.stripWhiteSpace();
	       //is a subdir?
	       if (path.compare(".") == 0) fullPath = m_fullName;
	       else fullPath = m_fullName + "/" + path;
	       removeDoubleSlashes(fullPath);
	       //locate dir
	       pCurCvsDir = dyncast_cvsdir(searchDirOfPath(fullPath,VIRTUAL));

	       if (!pCurCvsDir) {
		  Directory * tmpDir = dyncast_dir(searchLastValidDirOfPath(fullPath));
		  if (tmpDir && !CvsOptions::g_bShowVirtualInFiletab) addDir(fullPath, VIRTUAL);//add as virtual
		  pCurCvsDir = dyncast_cvsdir(searchDirOfPath(fullPath,VIRTUAL));//let's see if it's there
	       }
	       //put all files of dir into list
	       //and don't clear list because buggy cvs output returns filenames in wrong order
	       //and so we set untouched files up-to-date after we parsed the whole output
	       if (pCurCvsDir) {
		  pCurCvsDir->fillFileListWithFullNameEntries(curCvsUpdateFileList);
	       }
	       break;
	    }
	 
	    //filestate... differs from CVS_UPDATE_DIR_CMD 'cause it's just a query
	    else if((line.find("M ") == 0)){
	       if (curCvsFileName == line.mid(2)) break;//skip modified info after 'needs merge'
	       curCvsFileName = line.mid(2);
	       locCurCvsState = ES_modified;
	    } else if((line.find("U ") == 0)){
	       curCvsFileName = line.mid(2);
	       locCurCvsState = ES_needs_patch;
	    } else if((line.find("P ") == 0)){
	       curCvsFileName = line.mid(2);
	       locCurCvsState = ES_needs_patch;
	    } else if(line.find("C ") == 0){
	       curCvsFileName = line.mid(2);
	       locCurCvsState = ES_needs_merge;
	    } else if(line.find("A ") == 0){
	       curCvsFileName = line.mid(2);
	       locCurCvsState = ES_added;
	    } else if(line.find("R ") == 0){
	       curCvsFileName = line.mid(2);
	       locCurCvsState = ES_removed;
	    } else if(line.find("RCS file: ") == 0) {
               curCvsFileName = rcsToFileName (line.left (line.length ()-2).mid (10));
	       locCurCvsState = ES_needs_merge;
// 	    } else if (line.find("warning: conflicts during merge") > -1) {//filename allready extracted at <Merging differences between ...>
// 	       locCurCvsState = ES_conflict;
	    } else {
	       int pos1,pos2;
               if (((pos1 = line.find (": ")) > -1) && ((pos2 = line.find (" is no longer in the repository")) > -1)) {
		  curCvsFileName = line.mid (pos1 + 2, pos2 - pos1 - 2);
		  locCurCvsState = ES_needs_remove;
	       } else if (((pos1 = line.find ("warning: ")) > -1) && ((pos2 = line.find (" is not (any longer) pertinent")) > -1)) {
                  curCvsFileName = line.mid (pos1 + 9, pos2 - pos1 - 9);
		  locCurCvsState = ES_needs_checkout;
	       } else if ( ((pos1 = line.find ("New directory ")) > -1) && ((pos2 = line.find(" -- ignored" )) > -1) ) {
		  curCvsFileName = line.mid (pos1 + 14, pos2 - pos1 - 14);
		  locCurCvsState = ES_missing_dir;
               } else if (((pos1 = line.find (": ")) > -1) && ((pos2 = line.find (" should be removed and is still there")) > -1)) {
		  curCvsFileName = line.mid (pos1 + 2, pos2 - pos1 - 2);
                  locCurCvsState = ES_conflict;//fixme: untested
	       } else if (((pos1 = line.find ("warning: new-born ")) > -1) && ((pos2 = line.find (" has disappeared")) > -1)) {
		  curCvsFileName = line.mid (pos1 + 18, pos2 - pos1 - 18);
                  locCurCvsState = ES_removed;
	       } else if (((pos1=line.find("conflict: ")) > -1) && ((pos2=line.find(" is modified but no longer in the repository")) > -1)) {
		  curCvsFileName = line.mid (pos1 + 10, pos2 - pos1 - 10);
		  m_lastErrorMsg += line+"\n";
		  retVal = FALSE;
		  i++;//this is dirty but easier than using a separate check for skipping the next line.
		  //the next line needs to be skipped to not parse the conflict info given by cvs since it differs from CVS/Entries
                  locCurCvsState = ES_conflict;//fixme: untested
	       } else if ( (((pos1=line.find("conflict: ")) > -1) && ((pos2=line.find(" has been added, but already exists")) > -1))
                     || (((pos1=line.find("conflict: ")) > -1) && ((pos2=line.find(" created independently by second party")) > -1)) ) {
		  curCvsFileName = line.mid (pos1 + 10, pos2 - pos1 - 10);
		  m_lastErrorMsg += line+"\n";
		  retVal = FALSE;
		  i++;//this is dirty but easier than using a separate check for skipping the next line.
		  //the next line needs to be skipped to not parse the conflict info given by cvs since it differs from CVS/Entries
                  locCurCvsState = ES_conflict;
	       }
	    }
	 
	    if (locCurCvsState != ES_unknown) {
	       removeFileNameInconsistencies(curCvsFileName);

	       curCvsUpdateFileList.remove(m_fullName+"/"+curCvsFileName); //don't set to up-to-date

	       //now set new state
	       //is file located in subdir?
	       if((pos = curCvsFileName.findRev("/")) > -1) {
             path = curCvsFileName.left(pos);
             path = path.stripWhiteSpace();
             QString fn = curCvsFileName.mid(pos + 1);
             //is a subdir?
             fullPath = m_fullName + "/" + path;
             removeDoubleSlashes(fullPath);
             //locate dir
             pCurCvsDir = dyncast_cvsdir(searchDirOfPath(fullPath,VIRTUAL));
             if (pCurCvsDir) { //subdir
                if (locCurCvsState == ES_needs_checkout && pCurCvsDir->m_entries.find(fn)) {
                   //state is bivalent in this case
                   locCurCvsState = ES_needs_patch;
                }
                switch (locCurCvsState) {
                   case ES_needs_checkout: {
                      //if file doesn't exists in entries, we have to add it to be displayable in cvs-views
                      pCurCvsDir->setAndAppendTmpEntryQueryUpdate(fn);
                      QFileInfo fInfo (m_fullName + "/" + fn);
                      if (fInfo.exists ()) {
                         locCurCvsState = ES_needs_patch;
                      }
                      pCurCvsDir->setAndAppendEntry(fn, locCurCvsState);
                      break;
                   }
                   case ES_missing_dir: {
                      if (CvsOptions::g_bShowVirtualInFiletab) {
                         //if file doesn't exists in entries, we have to add it to be displayable in cvs-views
                         if (pCurCvsDir->setAndAppendTmpEntryQueryUpdate(fn)) {
                            pCurCvsDir->setAndAppendEntry(fn, locCurCvsState);
                         }
                      } else {
                         if (!searchDirOfPath(fullPath+"/"+fn, VIRTUAL)) {//don't ever add twice
                            addDir(fullPath+"/"+fn, VIRTUAL);//add as virtual
                         }
                      }
                      break;
                   }
                   default: {
                      pCurCvsDir->setAndAppendEntry(fn, locCurCvsState);
                      break;
                   }
                }
             }
	       } else if (curCvsFileName.length ()) {
             if (locCurCvsState == ES_needs_checkout && m_entries.find(curCvsFileName)) {
                //state is bivalent in this case
                locCurCvsState = ES_needs_patch;
             }
		  switch (locCurCvsState) {
		     case ES_needs_checkout: {
			//if file doesn't exists in entries, we have to add it to be displayable in cvs-views
			setAndAppendTmpEntryQueryUpdate(curCvsFileName);
                        QFileInfo fInfo (fullPath + "/" + curCvsFileName);
                        if (fInfo.exists ()) {
                           locCurCvsState = ES_needs_patch;
                        }

			setAndAppendEntry(curCvsFileName, locCurCvsState); // local dir
			break;
		     }
		     case ES_missing_dir: {
			if (CvsOptions::g_bShowVirtualInFiletab) {
			   //if file doesn't exists in entries, we have to add it to be displayable in cvs-views
			   if (setAndAppendTmpEntryQueryUpdate(curCvsFileName)) {
			      setAndAppendEntry(curCvsFileName, locCurCvsState); // local dir
			   }
			} else {
			   if (!searchDirOfPath(m_fullName+"/"+curCvsFileName, VIRTUAL)) {//don't ever add twice
			      addDir(m_fullName+"/"+curCvsFileName, VIRTUAL);//add as virtual
			   }
			}
			break;
		     }
		     default: {
			setAndAppendEntry(curCvsFileName, locCurCvsState); // local dir
			break;
		     }
		  }
	       }
	    }
	    break;
	 case CVS_ADD_CMD:
	    if ( (pos=line.find("scheduling file ")) > -1) {
	       pos += 16;
	       int endPos;
	       if ( (endPos=line.find(" for addition",pos)) > -1) {
		  curCvsFileName = line.mid(pos,endPos-pos);
		  removeFileNameInconsistencies(curCvsFileName);
		  setAndAppendEntry(curCvsFileName, ES_added);
	       }
	    } else if ( (pos=line.find("Re-adding file ")) > -1) {
	       pos += 15;
	       int endPos;
	       if ( (endPos=line.find(" after dead revision",pos)) > -1) {
		  curCvsFileName = line.mid(pos,endPos-pos);
		  removeFileNameInconsistencies(curCvsFileName);
		  setAndAppendEntry(curCvsFileName, ES_added);
	       }
	    } else if(line.find("U ")==0){//get the filename
	       curCvsFileName = line.mid(2);
	       curCvsFileName = curCvsFileName.stripWhiteSpace();
	       setAndAppendEntry(curCvsFileName, ES_probably_up_to_date);
	    }
	    break;
	 case CVS_REMOVE_CMD:
	    if ( (pos=line.find("scheduling ")) > -1) {
	       pos += 11;
	       int endPos;
	       if ( (endPos=line.find(" for removal",pos)) > -1) {
		  curCvsFileName = line.mid(pos,endPos-pos);
		  removeFileNameInconsistencies(curCvsFileName);
		  setAndAppendEntry(curCvsFileName, ES_removed);
	       }
	    }
	    break;
	 case CVS_REMOVEDIR_CMD:
	    //removing of dir ...
	    if((pos = line.find("Removing ")) > -1) {
	       path = line.mid(pos + 9);
	       path = path.stripWhiteSpace();

	       //is a subdir?
	       if (path.compare(".") == 0) fullPath = m_fullName;
	       else fullPath = m_fullName + "/" + path;
	       removeDoubleSlashes(fullPath);
	       //locate dir
	       pCurCvsLastUpdateDir = dyncast_cvsdir(searchDirOfPath(fullPath));
	    } else if ( (pos=line.find("scheduling ")) > -1) {
	       pos += 11;
	       int endPos;
	       if ( (endPos=line.find(" for removal",pos)) > -1) {
		  QString curCvsFileName = line.mid(pos,endPos-pos);
		  removeFileNameInconsistencies(curCvsFileName);
		  //is file located in subdir?
		  if((pos = curCvsFileName.findRev("/")) > -1) {
		     curCvsFileName = curCvsFileName.mid(pos + 1);
		  }
		  if(pCurCvsLastUpdateDir) {
		     pCurCvsLastUpdateDir->setAndAppendEntry(curCvsFileName, ES_removed);
		  }
	       }
	    }
	    break;
	 case CVS_MERGE_REV_INTO_CMD:
	    if (line.find("RCS file: ") == 0) {
	       curCvsFileName = rcsToFileName (line.left (line.length ()-2).mid (10));
 	       setAndAppendEntry(curCvsFileName, ES_modified);
	    } else if (curCvsFileName.length () && (line.find("warning: conflicts during merge") > -1)) {//filename allready extracted at <Merging differences between ...>
	       locCurCvsState = ES_conflict;
 	       setAndAppendEntry(curCvsFileName, ES_conflict);
	    }
	    break;
	 case CVS_RESURRECT_CMD:
	    if(line.find("U ")==0){//get the filename
	       curCvsFileName = line.mid(2);
	       curCvsFileName = curCvsFileName.stripWhiteSpace();
	    } else if (line.findRev("resurrected")>=0) {//finished, set the state
	       setAndAppendEntry(curCvsFileName, ES_up_to_date);
	    }
	    break;
      }
   }
   //some things have to be done when parsing has finished
   switch(icmd) {
      case CVS_QUERY_UPDATE_ALL_CMD:
      case CVS_QUERY_UPDATE_CMD: { //set all file not mentioned to up-to-date
	 QString tmp,file;
	 while (!curCvsUpdateFileList.isEmpty()) {
	    file = tmp = curCvsUpdateFileList.first();
	    //is file located in subdir?
	    if((pos = tmp.findRev("/")) > -1) {
	       path = tmp.left(pos);
	       path = path.stripWhiteSpace();
	       tmp = tmp.mid(pos + 1);
	       removeDoubleSlashes(path);
	       //locate dir
	       pCurCvsDir = dyncast_cvsdir(searchDirOfPath(path));
	       if(pCurCvsDir) { //subdir
		  pCurCvsDir->setAndAppendEntry(tmp, ES_up_to_date);
	       }
	    } else if (tmp.length ()) {
	       setAndAppendEntry(tmp, ES_up_to_date); // local dir
	    }
	    curCvsUpdateFileList.remove(file);
	 }
	 break;
      }
      case CVS_UPDATE_FILES_CMD: {//set files not mentioned by cvs output to up-to-date
         while (!curCvsUpdateFileList.isEmpty()) {
            QString tmp = curCvsUpdateFileList.first();
            curCvsUpdateFileList.remove (tmp);
            pCurCvsDir = NULL;
            QString fileName = tmp;
	    if (fileName.length ()) {
               pCurCvsDir = this;
	    }
            if (!pCurCvsDir || !pCurCvsDir->setEntryState (fileName, ES_up_to_date)) {
               Debug::g_pLog->log (Debug::LL_THE_OLD_MAN_AND_THE_SEA, "Filename not found in entries: "+tmp);
            }
         }
         break;
      }
      case CVS_UPDATE_DIR_CMD: { //we didn't set states to up-to-date for the finally scanned dir yet
	 if (pCurCvsLastUpdateDir) {
	    QDir dir(pCurCvsLastUpdateDir->fullName());
	    if (dir.exists()) {
	       pCurCvsLastUpdateDir->setFilesToUpToDate(curCvsUpdateFileList);
	    } else {
	       ((CvsDirectory*)pCurCvsLastUpdateDir->parent())->removeChild(pCurCvsLastUpdateDir->fullName());
	    }
	 }

	 while (!addedDirList.isEmpty()) {//add new dirs, all in up to date state since they are just updated

	    QString dirName = addedDirList.first();
	    CvsDirectory * tmpDir = dyncast_cvsdir(searchLastValidDirOfPath(dirName));
            if (tmpDir) {
               QString newDir = dirName.left(dirName.find("/",tmpDir->fullName().length()+1));

               if ( !getDisabled(newDir) && tmpDir->fullName().compare(newDir)) {//only add once
                  //check if exists, don't know wether cvs runns in 'prune empty dirs' mode
                  QFileInfo info(newDir);
                  if (info.exists()) {

                     if (tmpDir->m_isAnalyzedDir) {//don't continue if not analyzed in on-the-fly-mode
                        DirBase * vDir = searchDirOfPath(dirName,true);
                        if (vDir && vDir->isVirtual()) {//remove virtual dir before adding the real dir
                           delete vDir;
                           setDirState(ES_unknown,true);
                        }
                        CvsDirectory * unanalyzedDir = dyncast_cvsdir(tmpDir->addDir(newDir));//add in case it was just updated
                        if (unanalyzedDir && tmpDir->isOpen()) {//will not be analyzed on open() because it is allready open
                           unanalyzedDir->analyzeDirs();
                        }
                     }
                  }
               }
            } else {
               Debug::g_pLog->log (Debug::LL_INFO, "No valid dir path found for: "+dirName);
            }
	    addedDirList.remove(dirName);
	 }
	 break;
      }
      case CVS_UNEDIT_CMD: {
	 if (stringList) {
	    while (!stringList->isEmpty()) {
	       QString fileName = stringList->first();
	       stringList->remove(fileName);
	       setEntryState(fileName,ES_unknown);
	       removeEntry(fileName);
	    }
	 } else {
	    m_entries.clear();
	    QString fileName = "dummy";
	    setAndAppendEntry(fileName,ES_unknown);
	    removeEntry(fileName);
	 }
	 break;
      }
      default: {
	 break;
      }
   }
   return retVal;
}

//--------------------------------------------------------------------------------------------

QString CvsDirectory::rcsToFileName (QString s, bool checkForAttic /* = true */) {
   assert (!s.isEmpty ());
   int pos = s.findRev ("/");
   assert (pos > -1);

   if (checkForAttic && (pos > 6) && (s.mid (pos - 6, 7) == "/Attic/")) {
      s = s.remove (pos - 6, 6);
      pos -= 6;
   }

   QString file = s.mid (pos + 1);
   QString rcs = m_rootDir + "/" + m_repository + "/";

   if (s.startsWith (rcs)) {
      QString subPath = s.left (pos).mid (rcs.length ());
      if (!subPath.isEmpty ()) {
         CvsDirectory * dir = dyncast_cvsdir (searchDirOfPath (m_fullName + "/" + subPath));//search only for non-virtual dirs
         if (dir) {
            QString retVal = dir->rcsToFileName (s, false);
            if (!retVal.isEmpty ()) {
               return subPath + "/" + retVal;
            }
         }
      } else {
         return file;
      }
   } else {//iter
      CvsDirectory * C = firstChild();
      while( C ) {
         QString retVal = C->rcsToFileName (s, false);
         if (!retVal.isEmpty ()) {
            return C->shortName () + "/" + retVal;
         }
         C = C->nextSibling();
      }
   }
   return "";
}

//--------------------------------------------------------------------------------------------

void CvsDirectory::removeFileNameInconsistencies(QString & fileName) {
   fileName = fileName.stripWhiteSpace();
   if (fileName.startsWith("`")) fileName=fileName.mid(1);
   if (fileName.endsWith("'")) fileName.truncate(fileName.length()-1);
}

// ------------------------ functions telling about cvs file state -------------------------------

bool CvsDirectory::isControlled(QString fileName) {
   CvsEntries * cvsEntries = getCvsEntries();
   CvsEntry * cvsEntry = cvsEntries->find(fileName);
   if (cvsEntry) {
      return TRUE;
   } else return FALSE;
}

//--------------------------------------------------------------------------------------------

bool CvsDirectory::isBinary(QString fileName) {
   CvsEntries * cvsEntries = getCvsEntries();
   CvsEntry * cvsEntry = cvsEntries->find(fileName);
   if (cvsEntry) {
      return cvsEntry->option.b;
   } else return FALSE;
}

//--------------------------------------------------------------------------------------------

bool CvsDirectory::analyzeDirs() {
   if (m_isAnalyzedDir) {//don't analyze twice on setOpen()
      return m_isControlledDir || m_hasControlledSubDir;
   }

   //    qDebug("analyzing: "+m_fullName+", depth: "+QString::number(depth()));
   QDir myDir(m_fullName);
   myDir.setFilter( QDir::Dirs | QDir::Hidden);
   QStringList subDirList = myDir.entryList();
   QString path = m_fullName;
   QString curSubDirName;
   QFileInfo fileInfo;
   Directory *item;

   //is the current dir or the parent dir -- ignore it
   subDirList.remove(".");
   subDirList.remove("..");
   subDirList.remove("CVS");

   if(!subDirList.isEmpty()) {//has subdirs
      if(m_fullName.length() > 1){//is not root
         path += "/";
      }
   
      for(unsigned int i = 0; i < subDirList.count(); i++) {
         if(globalStopAction) break;   //cancel this action

         curSubDirName = subDirList[i];
         fileInfo.setFile(path + curSubDirName);

	 //ignore symlinks
         if( fileInfo.isSymLink()) continue;

	 //ignore non-cvs dirs
	 if ( (!bSCANNONCVS) && (!QFileInfo(path+curSubDirName+"/CVS").exists()) ) continue;

         if(fileInfo.isReadable() && fileInfo.isExecutable()) {//is a dir and readable

	    //at the moment lacks a test for enough mem   
	    m_isAnalyzedDir = true;//no need to analyze again in onthefly mode

	    item = Directory::createChildInstance(this, path + curSubDirName);

	    if(item->isControlledTree()) { // fails if dir is disabled and returns true even if
	       // there are dirs in between that have no CVS dir
	       m_hasControlledSubDir = true;
	    }
	    else if (item->isDisabled()) continue;
	    else if (ONTHEFLYSCANNING && !item->isAnalyzed()) continue;//don't know jet if there are cvs dirs somewhere in depth
	    else {
	       delete item;
	       item = 0; // paranoia
	    }
	 }
      }

      calcDirChilds_ES_Max();

   } else {//has no subdirs
      m_isAnalyzedDir = true;//no need to analyze again in onthefly mode
   }

   if (m_isControlledDir || m_hasControlledSubDir) {
      // check and set FolderState and therewith initialize caches
      checkAndShowStatus(NULL,true);
      return true;
   } else {
      return false;
   }
}

//--------------------------------------------------------------------------------------------

Entries * CvsDirectory::getEntries(bool resetCache/*=false*/) {
   return getCvsEntries(resetCache);
}

//----------------------------------------------------------------------------

//return QString* of files to ignore, taken from hardcoded presets plus:
//$HOME/.cvsignore plus $CVSIGNORE
//lastChecked: when this dir's ignores were last checked for changes
//changed: set to true if there was a change detected (so the dir's file cache needs a refresh)
const QString CvsDirectory::getHomeFilesToIgnore(QDateTime& lastChecked, bool& changed) {

   static QString ignoreString;
   static QDateTime modTimeOfIgnores;
   static bool noIgnores = FALSE;

   changed = TRUE;
   QFile cvsignore;
   cvsignore.setName(QDir::homeDirPath() + "/.cvsignore");
   if (cvsignore.exists()) {
      QDateTime lastModTimeOfIgnores = QFileInfo(cvsignore).lastModified();
      if ( (!modTimeOfIgnores.isValid()) || (modTimeOfIgnores != lastModTimeOfIgnores)) {
	 ignoreString = "";
	 QString ign_line;
	 if(cvsignore.open(IO_ReadOnly)) {
	    QTextStream ignoreStream(&cvsignore); 
	    while(!ignoreStream.eof()) {
	       ign_line = ignoreStream.readLine();
	       ignoreString += " " + ign_line.stripWhiteSpace();
	    }
	    cvsignore.close();
	 }
	 ignoreString = ignoreString.stripWhiteSpace();
	 modTimeOfIgnores = lastModTimeOfIgnores;
	 lastChecked = lastModTimeOfIgnores;
      } else if ( lastChecked.isValid() && (lastChecked >= lastModTimeOfIgnores) ) {
	 changed = FALSE;
	 return ignoreString;
      } else {
	 lastChecked = lastModTimeOfIgnores;
	 return ignoreString;
      }
      noIgnores = FALSE;
   } else if (!noIgnores) {
      noIgnores = TRUE;
   } else {
      changed = FALSE;
      return ignoreString;
   }

   QString retval = CVSIGNOREFILES + " " + ignoreString;
   retval = retval.stripWhiteSpace();
   retval += " " + QString(getenv( "CVSIGNORE")).simplifyWhiteSpace();
   int pos;
   if ( (pos = retval.findRev("!")) > -1) {
      ignoreString = retval.mid(pos + 1);
      ignoreString = ignoreString.stripWhiteSpace();
      return ignoreString;
   }

   //use reverse order to encrease parsing speed
   ignoreString += " " + QString(getenv( "CVSIGNORE")).stripWhiteSpace() + " " + CVSIGNOREFILES;
   ignoreString = ignoreString.stripWhiteSpace();
   return ignoreString;
}

//--------------------------------------------------------------------------------------------

//return QString* of files to ignore, taken from per dir .cvsignore
//homeIgnores: QString* holding the files to ignore from getHomeFilesToIgnore(QDateTime& lastChecked, bool& changed)
//changed: set to true if there was a change detected (so the dir's file cache needs a refresh)
const QString CvsDirectory::getDirFilesToIgnore(const QString homeIgnores, bool& changed) {

   changed = FALSE;
   QFile cvsignore;
   cvsignore.setName(m_fullName + "/.cvsignore");
   if (cvsignore.exists()) {
      QDateTime lastModTimeOfIgnores = QFileInfo(cvsignore).lastModified();
      m_checkedForIgnoresFile = FALSE;
      if ( (!m_lastTimeDirIgnoresChanged.isValid()) || (m_lastTimeDirIgnoresChanged != lastModTimeOfIgnores) ) {
	 m_pDirIgnores = "";
	 if(cvsignore.open(IO_ReadOnly)) {//read per dir .cvsignore
	    QString ign_line;
	    QTextStream ignoreStream(&cvsignore); 
	    while(!ignoreStream.eof()) {
	       ign_line = ignoreStream.readLine();
	       m_pDirIgnores = m_pDirIgnores + " " + ign_line.stripWhiteSpace();
	    }
	    cvsignore.close();
	 }
	 m_pDirIgnores = m_pDirIgnores.stripWhiteSpace();
	 m_lastTimeDirIgnoresChanged = lastModTimeOfIgnores;
	 changed = TRUE;
      } else {
	 return m_pDirIgnores;
      }
   } else if (!m_checkedForIgnoresFile) {
      m_checkedForIgnoresFile = TRUE;
      changed = TRUE;
   } else {
      return m_pDirIgnores;
   }
  
   int pos;
   if ( (pos = m_pDirIgnores.findRev("!")) > -1) {
      m_pDirIgnores = m_pDirIgnores.mid(pos + 1);
   } else {
      //reverse order to increase parsing speed
      m_pDirIgnores = m_pDirIgnores + " " + homeIgnores;
   }
   m_pDirIgnores = m_pDirIgnores.stripWhiteSpace();
   return m_pDirIgnores;
}

//------------------------- private local methods ---------------------------------------------------

void CvsDirectory::init(const QString& fileName)
{
   m_checkedForIgnoresFile = false;
   m_CvsEntries.setAutoDelete(true);
   m_entriesCached = false;
   Directory::init(fileName);
}

//--------------------------------------------------------------------------------------------

void CvsDirectory::fillFileListWithEntries(QStringList& fileList) {
   CvsEntriesIterator it(m_CvsEntries);
   while(it.current()) {
      if (getEntryState(it.current()->name) != ES_missing_dir) fileList.append(it.current()->name);
      ++it;
   }
}

//----------------------------------------------------------------------------

void CvsDirectory::fillFileListWithFullNameEntries(QStringList& fileList) {
   CvsEntriesIterator it(m_CvsEntries);
   while(it.current()) {
      if (getEntryState(it.current()->name) != ES_missing_dir) fileList.append(m_fullName+"/"+it.current()->name);
      ++it;
   }
}

//----------------------------------------------------------------------------

CvsEntries * CvsDirectory::getCvsEntries(bool resetCache/*=false*/) {

   bool modified = FALSE;
   if ( !m_entriesCached ||
	 (modified = entriesFileModified()) ||
	 (modified = entriesLogFileModified()) ||
	 resetCache) { // not initialized or cached jet

      QFile f;
      QString line;
      QString logMsg;

      QString path = m_fullName;
      if(path.length() > 1){//is not root
	 path += "/";
      }

      m_CvsEntries.clear();
      QStringList oldTmpEntries = m_tmpEntries;
      m_tmpEntries.clear();

      if (Debug::g_logLevel == Debug::LL_GOSSIP_MONGER) {
	 logMsg = m_fullName+" reading CVS dir:\n";
      }

      //read Tag
      f.setName( path + "CVS/Tag");
      if(f.open(IO_ReadOnly)) {//file is readable
	 QTextStream textStream(&f); 
	 while(!textStream.atEnd()) {
	    line = textStream.readLine();
	    if ( (line.at(0)=='T') || (line.at(0)=='N') || (line.at(0)=='D') ) {
	       m_dirTag = line.stripWhiteSpace();
	       break;
	    } else {
	       qDebug(m_fullName+"/CVS/Tag contains unknown entrie: "+line);
	    }
	 }
	 if (Debug::g_logLevel == Debug::LL_GOSSIP_MONGER) {
	    f.at(0);
	    QTextStream s(&f);
	    logMsg += "CVS/Tag:\n"+s.read();
	 }
	 f.close();
      } else {
	 m_dirTag = "";
      }

      //read Entries
      QStringList entrieList;
      f.setName(path + "CVS/Entries");
      if(f.open(IO_ReadOnly)) {//file is readable
	 QTextStream textStream(&f); 
	 textStream.setCodec(I18n::g_pTextDecoder->pTextCodecPool()->pCodecForName(I18n::g_nameOfDecoder));
	 QFileInfo fInfo(f);
	 m_modTimeOfEntries = fInfo.lastModified();

	 if (modified && (m_lastCallTime < m_modTimeOfEntries) ) {//clear align-cache, only in case of an external modification of CVS dir
            if (Debug::g_logLevel == Debug::LL_GOSSIP_MONGER) {
               logMsg += "Clearing entries cache, callTime: " + m_lastCallTime.toString ("yyyy/MM/dd hh:mm:ss:zzz")
                  + ", modTimeOfEntries: " + m_modTimeOfEntries.toString ("yyyy/MM/dd hh:mm:ss:zzz");
            }
	    m_entries.clear();
	    m_lastCallTime = m_modTimeOfEntries;
	 }
      
	 // read CVS/Entries and initialize cache
	 while(!textStream.eof()) {
	    line = textStream.readLine();
	    if(line.at(0) == 'D') {
	       continue;   //entry is a directory
	    }
	    if(line.at(0)=='/') {//entry seems to be correct
	       entrieList.append(line);
	    }
	 }
	 if (Debug::g_logLevel == Debug::LL_GOSSIP_MONGER) {
	    f.at(0);
	    QTextStream s(&f);
	    logMsg += "CVS/Entries:\n"+s.read();
	 }
	 f.close();
      } else {
	 //qDebug("file: "+path+"CVS/Entries not readable");//will give output on each checkAndShowStatus if no CVS/Entries file
      }

      //read Entries.log
      f.setName(path + "CVS/Entries.Log");
      if(f.open(IO_ReadOnly)) {//file is readable
	 QTextStream textStream(&f); 
	 QFileInfo fInfo(f);
	 m_modTimeOfEntriesLog = fInfo.lastModified();
      
	 if (modified && (m_lastCallTime < m_modTimeOfEntriesLog) ) {//clear align-cache, only in case of an external modification of CVS dir
            if (Debug::g_logLevel == Debug::LL_GOSSIP_MONGER) {
               logMsg += "Clearing entries cache, callTime: " + m_lastCallTime.toString ("yyyy/MM/dd hh:mm:ss:zzz")
                  + ", modTimeOfEntriesLog: " + m_modTimeOfEntriesLog.toString ("yyyy/MM/dd hh:mm:ss:zzz");
            }
	    m_entries.clear();
	    m_lastCallTime = m_modTimeOfEntriesLog;
	 }

	 // read CVS/Entries and initialize cache
	 while(!textStream.eof()) {
	    line = textStream.readLine();
	    if(line.at(2) == 'D') {
	       continue;   //entry is a directory
	    }
	    if(line.at(0)=='A') {//add entry
	       line = line.mid(2);
	       QString removeEntry = line.left(line.find("/",1)+1);
	       QStringList::Iterator test = entrieList.begin();
	       while (test != entrieList.end()) {
		  if ((*test).startsWith(removeEntry)) {
		     entrieList.remove(test);
		     break;
		  }
		  ++test;
	       }
	       entrieList.append(line);
	    } else if (line.at(0)=='R') {//remove entry
	       line = line.mid(2);
	       QString removeEntry = line.left(line.find("/",1)+1);
	       QStringList::Iterator test = entrieList.begin();
	       while (test != entrieList.end()) {
		  if ((*test).startsWith(removeEntry)) {
		     entrieList.remove(test);
		     break;
		  }
		  ++test;
	       }
	    } else {
	       qDebug("unknown line in: "+m_fullName+"/CVS/Entries.log: "+line);
	    }
	 }
	 if (Debug::g_logLevel == Debug::LL_GOSSIP_MONGER) {
	    f.at(0);
	    QTextStream s(&f);
	    logMsg += "CVS/Entries.log:\n"+s.read();
	 }
	 f.close();
      } else {
	 //qDebug("file: "+path+"CVS/Entries.Log not readable");//will give output on each checkAndShowStatus if no CVS/Entries.Log file
      }

      QStringList::Iterator it;
      for (it = entrieList.begin(); it != entrieList.end(); it++) {

	 int posLeft,posRight;
	 line = (*it);
	  
	 if((posLeft = line.find("/", 1)) > 1) {
	    //cvsEntry->name = line.mid(1, posLeft - 1);
	    CvsEntry * cvsEntry = new CvsEntry(line.section("/",1,1));
	    if((posRight = line.find("/", posLeft + 1)) > 1) {
	       if (line.find("-",posLeft + 1) == (posLeft + 1)) {
		  cvsEntry->rev.missing = true;
		  posLeft++;
	       } else {
		  cvsEntry->rev.missing = false;
	       }
	       cvsEntry->rev.string  = line.mid(posLeft + 1, posRight - 1 - posLeft);
	       posLeft = posRight;
	       if((posRight = line.find("/", posLeft + 1)) > 1) {
		  cvsEntry->date.string = line.mid(posLeft + 1, posRight - 1 - posLeft).simplifyWhiteSpace();
		  if (cvsEntry->date.string.find("dummy timestamp") > -1) {
		     cvsEntry->date.dummy = true;
		     if(cvsEntry->date.string.find("from new-entry",16) > -1) {//modified file after switching to a different branch where this file doesn't exist ...
			cvsEntry->date.newDummy = true;
		     } else {
			cvsEntry->date.newDummy = false;
		     }
		  } else if(cvsEntry->date.string.find("Initial ") > -1) {//same as befor but in :local: mode
		     cvsEntry->date.dummy = true;
		     cvsEntry->date.newDummy = false;
		  } else if (cvsEntry->rev.string == "0") {//only in :local: mode?
		     cvsEntry->date.dummy = true;
		     cvsEntry->date.newDummy = false;
		     cvsEntry->date.string = "";
		  } else {
		     cvsEntry->date.dummy = false;
		     cvsEntry->date.newDummy = false;
		  }
		  QDateTime tmpTime = *((QDateTime *) &QtDateTime::fromString(cvsEntry->date.string));   // bugfix for qt3.3.8 datetime parsing bug
		  cvsEntry->date.datetime = tmpTime;
		  bool invalid = false;
		  if (!tmpTime.isValid()) {//no date in entries but "Result of merge" or alike
		     cvsEntry->date.localdatetime = QFileInfo(path+cvsEntry->name).lastModified().addSecs(-1);//has to be marked as modified
		     cvsEntry->date.datetime = getAsUTC(cvsEntry->date.localdatetime,m_hasDSTBug);
		     invalid = true;
		  }
		  if (WINVERSION > 0) {//fix for cvs windows version that doesn't consider daylight settings
		     //seems to be a w2k bug: create a file, note timestamp and change system date from
		     //winter to summer. Surprised?
		     if (CVSVERSION == "cvs") {
			if (winOnly_isDayLight) {
			   cvsEntry->date.datetime = cvsEntry->date.datetime.addSecs(3600);
			}
			if (!invalid) {//date.string might hold merge or dummy timestamp marker
			   cvsEntry->date.localdatetime = getAsLocal(cvsEntry->date.datetime,m_hasDSTBug);
			   cvsEntry->date.string = cvsEntry->date.localdatetime.toString(LookAndFeel::g_dateTimeFormat);
			}
		     } else if (!invalid) {
			cvsEntry->date.localdatetime = getAsLocal(tmpTime,m_hasDSTBug);//convert to local time
			cvsEntry->date.string = cvsEntry->date.localdatetime.toString(LookAndFeel::g_dateTimeFormat);
		     }
		  } else if (!invalid) {
		     cvsEntry->date.localdatetime = getAsLocal(tmpTime,m_hasDSTBug);//convert to local time
		     cvsEntry->date.string = cvsEntry->date.localdatetime.toString(LookAndFeel::g_dateTimeFormat);
		  }
		  cvsEntry->date.date = cvsEntry->date.datetime.date();
		  cvsEntry->date.time = cvsEntry->date.datetime.time();
		  if (cvsEntry->date.string.find("Result of merge+")> -1) {
		     cvsEntry->date.conflict = true;
		  } else {
		     cvsEntry->date.conflict = false;
		  }
	       } else {
		  cvsEntry->date.string = "";
	       }
	       posLeft = posRight;
	       if((posRight = line.find("/", posLeft + 1)) > 1) {
		  if ( (posRight-posLeft)>1) {
		     cvsEntry->option.string = line.mid(posLeft + 1, posRight - 1 - posLeft).stripWhiteSpace();
		     switch (cvsEntry->option.string.at(2).latin1()) {
			case 'b': {
			   cvsEntry->option.b = true;
			   cvsEntry->option.o = false;
			   cvsEntry->option.v = false;
			   break;
			}
			case 'o': {
			   cvsEntry->option.b = false;
			   cvsEntry->option.o = true;
			   cvsEntry->option.v = false;
			   break;
			}
			case 'v': {
			   cvsEntry->option.b = false;
			   cvsEntry->option.o = false;
			   cvsEntry->option.v = true;
			   break;
			}
			default: {
			   cvsEntry->option.b = false;
			   cvsEntry->option.o = false;
			   cvsEntry->option.v = false;
			}
		     }
		  } else {
		     cvsEntry->option.string = "";
		     cvsEntry->option.b = false;
		     cvsEntry->option.o = false;
		     cvsEntry->option.v = false;
		  }
	       }
	       posLeft = line.findRev("/");
	       cvsEntry->sticky.string = line.mid(posLeft+2);
	       if( line.find("T",posLeft) > 0) {   //sticky tag
		  cvsEntry->sticky.T = true;
	       } else {
		  cvsEntry->sticky.T = false;
	       }
	       if( line.find("D",posLeft) > 0) {   //sticky date
		  cvsEntry->sticky.D = true;
	       } else {
		  cvsEntry->sticky.D = false;
	       }
	    } else {
	       delete cvsEntry;
	       cvsEntry = 0;
	       continue;
	    }
	    if (m_CvsEntries.size() <= m_CvsEntries.count()) m_CvsEntries.resize( nextPrime(m_CvsEntries.size()));
	    if (cvsEntry) m_CvsEntries.insert(cvsEntry->name,cvsEntry);
	 } else {
	    continue;
	 }
      }      

      //read previous tmp entries
      QStringList::Iterator iter;
      for ( iter = oldTmpEntries.begin(); iter != oldTmpEntries.end(); iter++ ) {
	 setAndAppendTmpEntryQueryUpdate(*iter);
      }
      if ( Debug::g_logLevel == Debug::LL_GOSSIP_MONGER) {
	 if ( !m_tmpEntries.isEmpty()) {
	    logMsg += "tmp entries:\n\""+m_tmpEntries.join(" ")+"\"";
	 }
	 Debug::g_pLog->log(Debug::LL_GOSSIP_MONGER, logMsg);
      }

      //and set cache flag
      m_entriesCached = TRUE;

      //     QDictIterator<CvsEntries>iterator(m_CvsEntries);
      //     while(iterator.current()) {
      //       qDebug( "checking name: " + iterator.current()->name );
      //       qDebug( "rev: " + iterator.current()->rev.string );
      //       qDebug( "rev-missing: " + QString(iterator.current()->rev.missing ? "true" : "false") );
      //       qDebug( "datestring: " + iterator.current()->date.string );
      //       qDebug( "datetime: " + iterator.current()->date.datetime.toString() );
      //       qDebug( "date: " + iterator.current()->date.date.toString() );
      //       qDebug( "date-dummy: " + QString(iterator.current()->date.dummy ? "true" : "false") );
      //       qDebug( "date-missing: " + QString(iterator.current()->date.conflict ? "true" : "false") );
      //       qDebug( "sticky-T: " + QString(iterator.current()->sticky.T ? "true" : "false") );
      //       qDebug( "sticky-D: " + QString(iterator.current()->sticky.D ? "true" : "false") );
      //       qDebug( "==============================================================");
      //       ++iterator;
      //     }

   }
   return &m_CvsEntries;
}

//----------------------------------------------------------------------------

EntryStates CvsDirectory::alignWithEntries(QString name, EntryStates stateId) {

   EntryState *entry = m_entries.find(name);

   if(entry) {//file allready registered
      //get and set state
      switch(stateId)
	 {
	    case ES_modified:
	       if((entry->state() == ES_needs_patch)
		     || (entry->state() == ES_needs_merge)
		     || (entry->state() == ES_needs_remove)
		     || (entry->state() == ES_conflict)) {
		  entry->setState(entry->state());
		  stateId = entry->state();
	       } else if(entry->state() == ES_added) {
		  stateId = ES_added;
	       } else {
		  entry->setState(ES_modified);
	       }
	       break;
	    case ES_missing:
	       if(entry->state() == ES_needs_checkout) {
		  stateId = ES_needs_checkout;
	       } else if (entry->state() == ES_missing_dir) {
		  stateId = ES_missing_dir;
		  break;
	       } else if (entry->state() == ES_needs_remove) {
		  stateId = ES_needs_remove;
		  break;
	       } else if (entry->state() != ES_missing) {
		  entry->setState(ES_needs_checkout);
		  stateId = ES_needs_checkout;
	       }
	       break;
	    case ES_added://special for merge, added files are displayed as U
	       if(entry->state() == ES_up_to_date) {
		  entry->setState(ES_added);
	       }
	       break;
	    case ES_probably_up_to_date:
	       if((entry->state() == ES_added) || (entry->state() == ES_modified)) {
		  entry->setState(ES_probably_up_to_date);
	       } else {
		  stateId = entry->state();
	       }
	       break;
	    case ES_removed://special if files are removed from outside lincvs
	       entry->setState(ES_removed);
	       break;
	    case ES_probably_up_to_date_and_timezone_incorrect:
	       if((entry->state() == ES_added) || (entry->state() == ES_modified)) {
		  entry->setState(ES_probably_up_to_date);
	       }
	       stateId = entry->state();
	       break;
	    default:
	       stateId = entry->state();
	       break;
	 }            
   }
   else {
      appendEntry(name, new EntryState(name, stateId, this));
   }
   return stateId;
}

//----------------------------------------------------------------------------

// This method is a fix for showing dirs and files detected to be missing by QUERY_UPDATE_CMD
// For displaying, these have to be added to Entries. We don't modify CVS/Entries, but
// add temporarily until next modification of CVS/Entries.
bool CvsDirectory::setAndAppendTmpEntryQueryUpdate(QString m_curCvsFileName) {
   if (getCvsEntries()->find(m_curCvsFileName)) return false; //entry found
   QFileInfo fInfo(m_fullName+"/"+m_curCvsFileName);
   if (fInfo.exists() && fInfo.isDir ()) return false;//don't add existing dirs as tmp, they might allready be in one of the other tabs

   CvsEntry * cvsEntry = new CvsEntry(m_curCvsFileName);
   cvsEntry->rev.string = "locally missing";
   cvsEntry->rev.missing = false;
   cvsEntry->date.string = "unknown";
   cvsEntry->date.dummy = false;
   cvsEntry->date.newDummy = false;
   cvsEntry->date.conflict = false;
   cvsEntry->date.datetime = QDateTime::currentDateTime();
   cvsEntry->date.date = cvsEntry->date.datetime.date();
   cvsEntry->date.time = cvsEntry->date.datetime.time();
   cvsEntry->option.string = "";
   cvsEntry->option.b = false;
   cvsEntry->option.o = false;
   cvsEntry->option.v = false;
   cvsEntry->sticky.string = "";
   cvsEntry->sticky.D = false;
   cvsEntry->sticky.T = false;
   cvsEntry->isTmp = true;

   m_CvsEntries.insert(cvsEntry->name,cvsEntry);
   m_tmpEntries.append(m_curCvsFileName);
   return true;
}


//----------------------------------------------------------------------------

inline FileListViewItem * CvsDirectory::setStatusInFileListView(FileListViewItem *item, Entry * entry, bool checkOnly /* = FALSE*/) {

   CvsEntry * cvsEntry = static_cast<CvsEntry*>(entry);
   assert(cvsEntry);
   EntryStates stateId = ES_unknown;
   QString modDateLocal;
   int type = FileState::S_i;
   bool sticky = false;
   QDateTime localDate, localDateUtc;
   QString path = m_fullName;
   if(path.length() > 1){//is not root
      path += "/";
   }

   if (cvsEntry->sticky.T || cvsEntry->sticky.D) sticky = true;

   //status?
   if (!cvsEntry->isTmp) {
      if ( getFileModTime(path+cvsEntry->name,localDate,&type) ) {
         localDateUtc = getAsUTC(localDate,m_hasDSTBug);
         if(localDateUtc == cvsEntry->date.datetime){
            stateId = ES_probably_up_to_date;
         } else if(cvsEntry->date.dummy) {
            if (cvsEntry->rev.missing) {
               stateId = ES_removed;
            } else if (cvsEntry->date.newDummy) {
               stateId = ES_modified;
            } else {
               stateId = ES_added;
            }
         } else if (cvsEntry->date.conflict) {
            stateId = ES_conflict;
         /*} else if ( (localDateUtc.time().second() == cvsEntry->date.time.second()) &&
               (localDateUtc.time().minute() == cvsEntry->date.time.minute())) {
            //   	  cout << "timezone: " << cvsEntry->name.ascii() <<
            // 	    ", utc: ->" << modDateUtc << "<- entry: ->" <<
            // 	    cvsEntry->date.string << "<-!!!\n";
            stateId = ES_probably_up_to_date_and_timezone_incorrect;*/
         } else {
            stateId = ES_modified;
         }
      } else if (cvsEntry->rev.missing) {
         stateId = ES_removed;
      } else {
         stateId = ES_missing;
      }
   }

   stateId = alignWithEntries(cvsEntry->name, stateId);

   if (checkOnly) return NULL; // we only check and set the states, displaying is timeconsuming

   switch (stateId) {
      case ES_probably_up_to_date_and_timezone_incorrect:
      case ES_modified:
      case ES_added:
	 if (localDate.isValid()) modDateLocal = localDate.toString(LookAndFeel::g_dateTimeFormat);
	 break;
      case ES_needs_patch:
      case ES_needs_merge:
      case ES_needs_checkout:
      case ES_conflict:
	 if ( localDate.isValid() && (localDateUtc != cvsEntry->date.datetime) ) {
	    modDateLocal = localDate.toString(LookAndFeel::g_dateTimeFormat);
	 }
	 break;
      default:
	 break;
   }
      
            
   if( item == 0 ) {
      item = new FileListViewItem(m_pFileListView,
	    FileListViewItem::TextNoCase,
	    FileListViewItem::Rev,
	    FileListViewItem::TextNoCase,
	    FileListViewItem::TextNoCase,
	    FileListViewItem::Text,
	    FileListViewItem::Date,
	    FileListViewItem::ModDate);
   }

   item->setText(0, cvsEntry->name );
   item->setText(1, cvsEntry->rev.string );
   item->setText(2, cvsEntry->sticky.string );
   item->setText(3, cvsEntry->option.string ); 
   item->setText(4, getState(stateId) ); 
   item->setDate(5, &cvsEntry->date.localdatetime, cvsEntry->date.string);
   item->setModifiedDate(6, &localDate, modDateLocal);
   item->setPixmap(0, findEmbeddedPixmap (getPix(stateId,type)));

   if (sticky) {
      item->setPixmap(2, findEmbeddedPixmap ("Tag16x16"));
   }  else {
      item->setPixmap(2, 0);
   }
   if (cvsEntry->option.b) {
      item->setPixmap(3, findEmbeddedPixmap ("FileBinary16x16"));
   } else {
      item->setPixmap(3, 0);
   }
   return item;
}

//----------------------------------------------------------------------------

inline QString CvsDirectory::getState(const EntryStates & stateId) {
   switch(stateId) {
      case ES_probably_up_to_date:
	 return tr("seems up to date");
      case ES_probably_up_to_date_and_timezone_incorrect:
	 return tr("!!check timezone: incorrect!!");
      case ES_up_to_date:
	 return tr("up to date");
      case ES_modified:
	 return tr("modified");
      case ES_needs_patch:
	 return tr("needs patch");
      case ES_needs_merge:
	 return tr("needs merge");
      case ES_needs_checkout:
	 return tr("needs checkout");
      case ES_missing:
	 return tr("missing");
      case ES_missing_dir:
	 return tr("needs checkout");
      case ES_needs_remove:
	 return tr("update to remove");
      case ES_conflict:
	 return tr("conflict");
      case ES_added:
	 return tr("commit to add");
      case ES_removed:
	 return tr("commit to remove");
      default:
	 return tr("unknown");
   }
}

//----------------------------------------------------------------------------

inline char * CvsDirectory::getPix(const EntryStates & stateId, const int & type) {
   switch(stateId) {
      case ES_modified:
	 return "FileModified16x16";
      case ES_needs_patch:
	 return "FileNeedsPatch16x16";
      case ES_needs_merge:
	 return "FileNeedsMerge16x16";
      case ES_needs_checkout:
	 return "FileNeedsCheckout16x16";
      case ES_needs_remove:
	 return "FileRemoved16x16";
      case ES_missing:
	 return "FileRemoved16x16";
      case ES_missing_dir:
	 return "FolderMissing16x16";
      case ES_conflict:
	 return "FileConflict16x16";
      case ES_added:
	 return "FileAdded16x16";
      case ES_removed:
	 return "FileRemoved16x16";
      case ES_probably_up_to_date:
      case ES_probably_up_to_date_and_timezone_incorrect:
      case ES_up_to_date:
      default:
	 switch(type) {
	    case FileState::S_Ro:
	       return "FileUnchanged16x16";
	    case FileState::S_Rw:
	       return "FileWriteable16x16";
	    default:
	       return "lincvs-missing";
	 }
   }
}

//----------------------------------------------------------------------------

//check all files for differences between timestamp of CVS/Entries
//returns true if modified files are detected that are not jet in modified state
bool CvsDirectory::isFilesModified() {

   CvsEntriesIterator it(*getCvsEntries());
   QString path = m_fullName+"/";
   while(it.current()) {

      EntryStates es = getEntryState(it.current()->name);
      if (es <= ES_up_to_date) {
	 QDateTime localTime;
	 if (!getFileModTime(path + it.current()->name,localTime)) return true;//file missing
	 localTime = getAsUTC(localTime,m_hasDSTBug);
	 if ( (it.current()->date.datetime != localTime)
	       && (es != ES_probably_up_to_date_and_timezone_incorrect) ) {
	    //       qDebug("File modified: "+path+it.current()->name);
	    return true;
	 }
      }
      ++it;
   }
   return false;
}

//----------------------------------------------------------------------------

bool CvsDirectory::entriesFileModified() { // returns true if modified or m_modTimeOfEntries not set( so, nothing cached yet)
   QString path = m_fullName;
   if(path.length() > 1){//is not root
      path += "/";
   }

   QFileInfo fInfo(path + "CVS/Entries");
   if (!fInfo.exists()) return false;

   if (m_modTimeOfEntries == fInfo.lastModified()) {
//       qDebug("CVS not modified: "+m_fullName
//        	    +", old: "+m_modTimeOfEntries.toString()
//        	    +", cur: "+QTime::currentTime().toString());
      return false;
   } else if (m_disabled) {
      return false;
   } else {
//       qDebug("CVS modified: "+m_fullName
//        	    +", old: "+m_modTimeOfEntries.toString()
//        	    +", new: "+fInfo.lastModified().toString()
//        	    +", cur: "+QTime::currentTime().toString());
      return true;
   }
}

//----------------------------------------------------------------------------

bool CvsDirectory::entriesLogFileModified() { // returns true if modified or m_modTimeOfEntriesLog not set( so, nothing cached yet)
   QString path = m_fullName;
   if(path.length() > 1){//is not root
      path += "/";
   }

   QFileInfo fInfo(path + "CVS/Entries.Log");
   if (!fInfo.exists()) return false;
      
   if (m_modTimeOfEntriesLog == fInfo.lastModified()) {
      return false;
   } else if (m_disabled) {
      return false;
   } else {
      //      qDebug("entriesLogFileModified");
      return true;
   }
}

//----------------------------------------------------------------------------

void CvsDirectory::getNameAndRevOfFirstSelectedFile(QString *name, QString *rev) {

   QListViewItem *myChild = m_pFileListView->firstChild();
   while(myChild) {
      if(myChild->isSelected()) {
	 *name = myChild->text(0).stripWhiteSpace();
	 *rev = myChild->text(1).stripWhiteSpace();
	 break;
      }
      myChild = myChild->nextSibling();
   }
}

//----------------------------------------------------------------------------

QString CvsDirectory::getRev(QString *fileToFind) {

   QListViewItem *myChild = m_pFileListView->firstChild();
   while(myChild) {
      if (myChild->text(0).stripWhiteSpace() == (*fileToFind)) {
	 return myChild->text(1).stripWhiteSpace();
      }
      myChild = myChild->nextSibling();
   }
   return QString::null;
}

//----------------------------------------------------------------------------

bool CvsDirectory::backupFiles(QStringList list) {
   QString srcPath = m_fullName+"/";
   QString dstDir = srcPath+"CVS/LinCVS-tmp";
   QDir d;
   if (d.mkdir(dstDir)) {
      QString dstPath = dstDir+"/";
      QStringList copyList;
      QStringList::Iterator it;
      for (it = list.begin(); it != list.end(); ++it) {
         QString srcName = srcPath+*it;
         QString dstName = dstPath+*it;
         QFileInfo fInfo(srcName);
         int permission = 0;
         if (fInfo.isReadable()) permission = (permission | READABLE);
         if (fInfo.isWritable()) permission = (permission | WRITEABLE);
         if (fInfo.isExecutable()) permission = (permission | EXECUTABLE);
         if (!copyFile(srcName, dstName, permission, false) ) {
            qDebug("can't copy file: "+srcName+" -> "+dstName+", aborting");
            for (it = copyList.begin(); it != copyList.end(); ++it) {
               QFile file(*it);
               setPermission(file,READABLE | WRITEABLE);
               file.remove();
            }
            d.rmdir(dstDir);
            return false;
         } else {
            copyList.append(dstName);
         }
      }
   }
   return true;
}

//----------------------------------------------------------------------------

bool CvsDirectory::restoreFiles(QStringList list) {
   QString dstPath = m_fullName+"/";
   QString srcPath = dstPath+"CVS/LinCVS-tmp/";
   QDir d;
   
   QStringList::Iterator it;
   for (it = list.begin(); it != list.end(); ++it) {
      QString srcName = srcPath+*it;
      QString dstName = dstPath+*it;
      QFileInfo fInfo(srcName);
      int permission = 0;
      if (fInfo.isReadable()) permission = (permission | READABLE);
      if (fInfo.isWritable()) permission = (permission | WRITEABLE);
      if (fInfo.isExecutable()) permission = (permission | EXECUTABLE);
      if (!copyFile(srcName, dstName, permission, true) ) {
         qDebug("can't copy file: "+srcName+" -> "+dstName);
      }
   }
   d.rmdir(dstPath+"CVS/LinCVS-tmp");//won't be removed if not empty
   return true;
}

//----------------------------------------------------------------------------

