# -*- coding: utf-8 -*-

# Copyright (c) 2002 - 2005 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the version control systems interface to CVS.
"""

import os
import shutil
import types

from qt import *

from VCS.VersionControl import VersionControl
from CvsDialog import CvsDialog
from CommitDialog import CvsCommitDialog
from LogDialog import CvsLogDialog
from DiffDialog import CvsDiffDialog
from HistoryDialog import CvsHistoryDialog
from StatusDialog import CvsStatusDialog
from TagDialog import CvsTagDialog
from CommandDialog import CvsCommandDialog
from SwitchDialog import CvsSwitchDialog
from MergeDialog import CvsMergeDialog
from OptionsDialog import CvsOptionsDialog
from NewProjectDialog import CvsNewProjectOptionsDialog
from LoginDialog import CvsLoginDialog
from LogoutDialog import CvsLogoutDialog
from ProjectBrowserHelper import CvsProjectBrowserHelper
from ProjectHelper import CvsProjectHelper
import Utilities

class Cvs(VersionControl):
    """
    Class implementing the version control systems interface to CVS.
    """
    def __init__(self, parent=None, name=None):
        """
        Constructor
        
        @param parent parent widget (QWidget)
        @param name name of this object (string or QString)
        """
        VersionControl.__init__(self, parent, name)
        self.defaultOptions = {
            'global' : ['-f'],
            'commit' : [''],
            'checkout' : [''],
            'update' : ['-dP'],
            'add' : [''],
            'remove' : ['-f'],
            'diff' : ['-u3', '-p'],
            'log' : [''],
            'history' : ['-e', '-a'],
            'status' : ['-v'],
            'tag' : ['-c'],
            'export' : ['']
        }
        self.interestingDataKeys = [
        ]
        self.canDetectBinaries = 0
        
        self.options = self.defaultOptions
        self.tagList = QStringList()
        
        self.commandHistory = QStringList()
        self.wdHistory = QStringList()
        
    def vcsExists(self):
        """
        Public method used to test for the presence of the cvs executable.
        
        @return flag indicating the existance (boolean)
        """
        self.versionStr = QString.null
        
        self.proc = QProcess('cvs')
        self.proc.addArgument('-v')
        self.connect(self.proc, SIGNAL('processExited()'), self.vcsExistsProcExited)
        self.connect(self.proc, SIGNAL('readyReadStdout()'), self.vcsExistsReadyReadStdout)
        return self.proc.start()
        
    def vcsInit(self, vcsDir, noDialog=0):
        """
        Public method used to initialize the cvs repository.
        
        @param vcsDir name of the VCS directory (string)
        @param noDialog flag indicating quiet operations (boolean)
        @return flag indicating an execution without errors (boolean)
        """
        proc = QProcess('cvs')
        self.addArguments(proc, self.options['global'])
        proc.addArgument('-d')
        proc.addArgument(vcsDir)
        proc.addArgument('init')
        
        if noDialog:
            return self.startSynchronizedProcess(proc)
        else:
            dia = CvsDialog(self.trUtf8('Initializing cvs repository'))
            res = dia.startProcess(proc)
            if res:
                dia.exec_loop()
            return dia.normalExit()
        
    def vcsConvertProject(self, vcsDataDict, project):
        """
        Public method to convert an uncontrolled project to a version controlled project.
        
        @param vcsDataDict dictionary of data required for the conversion
        @param project reference to the project object
        """
        success = self.vcsImport(vcsDataDict, project.ppath)[0]
        if not success:
            KQMessageBox.critical(None,
                self.trUtf8("Create project in repository"),
                self.trUtf8("""The project could not be created in the repository."""
                            """ Maybe the given repository doesn't exist or the"""
                            """ repository server is down."""),
                self.trUtf8("&OK"),
                QString.null,
                QString.null,
                0, -1)
        else:
            cwdIsPpath = 0
            if os.getcwd() == project.ppath:
                os.chdir(os.path.dirname(project.ppath))
                cwdIsPpath = 1
            tmpProjectDir = "%s_tmp" % project.ppath
            os.rename(project.ppath, tmpProjectDir)
            os.makedirs(project.ppath)
            self.vcsCheckout(vcsDataDict, project.ppath)
            if cwdIsPpath:
                os.chdir(project.ppath)
            self.vcsCommit(os.path.join(project.ppath, vcsDataDict["archive"]), 
                           vcsDataDict["message"], 1)
            pfn = os.path.join(project.ppath, vcsDataDict["archive"], 
                               os.path.basename(project.pfile))
            if not os.path.isfile(pfn):
                pfn += "z"
            if not os.path.isfile(pfn):
                KQMessageBox.critical(None,
                    self.trUtf8("New project"),
                    self.trUtf8("""The project could not be checked out of the repository.<br />"""
                                """Restoring the original contents."""),
                    self.trUtf8("&OK"),
                    QString.null,
                    QString.null,
                    0, -1)
                if os.getcwd() == project.ppath:
                    os.chdir(os.path.dirname(project.ppath))
                    cwdIsPpath = 1
                else:
                    cwdIsPpath = 0
                shutil.rmtree(project.ppath, 1)
                os.rename(tmpProjectDir, project.ppath)
                project.pdata["VCS"] = ['None']
                project.vcs = None
                project.setDirty(1)
                project.saveProject()
                return
            shutil.rmtree(tmpProjectDir, 1)
            project.openProject(pfn)
        
    def vcsImport(self, vcsDataDict, projectDir, noDialog=0):
        """
        Public method used to import the project into the cvs repository.
        
        @param vcsDataDict dictionary of data required for the import
        @param projectDir project directory to create (string)
        @param noDialog flag indicating quiet operations
        @return flag indicating an execution without errors (boolean)
        """
        self.vcsInit(vcsDataDict["repository"], 1)
        msg = QString(vcsDataDict["message"])
        if msg.isEmpty():
            msg = QString('***')
            
        proc = QProcess('cvs')
        self.addArguments(proc, self.options['global'])
        proc.addArgument('-d')
        proc.addArgument(vcsDataDict["repository"])
        proc.addArgument('import')
        proc.addArgument('-m')
        proc.addArgument(vcsDataDict["message"])
        proc.addArgument(vcsDataDict["archive"])
        proc.addArgument(vcsDataDict["vendor"])
        proc.addArgument(vcsDataDict["release"])
        proc.setWorkingDirectory(QDir(projectDir))
        
        if noDialog:
            return self.startSynchronizedProcess(proc), 0
        else:
            dia = CvsDialog(self.trUtf8('Importing project into cvs repository'))
            res = dia.startProcess(proc)
            if res:
                dia.exec_loop()
            return dia.normalExit(), 0
        
    def vcsCheckout(self, vcsDataDict, projectDir, noDialog=0):
        """
        Public method used to check the project out of the cvs repository.
        
        @param vcsDataDict dictionary of data required for the checkout
        @param projectDir project directory to create (string)
        @param noDialog flag indicating quiet operations
        @return flag indicating an execution without errors (boolean)
        """
        try:
            tag = vcsDataDict["tag"]
        except KeyError:
            tag = None
        proc = QProcess('cvs')
        self.addArguments(proc, self.options['global'])
        proc.addArgument('-d')
        proc.addArgument(vcsDataDict["repository"])
        proc.addArgument('checkout')
        self.addArguments(proc, self.options['checkout'])
        if tag is not None and tag != "":
            proc.addArgument('-r')
            proc.addArgument(tag)
        proc.addArgument('-d')
        proc.addArgument(projectDir)
        proc.addArgument(vcsDataDict["archive"])
        proc.setWorkingDirectory(QDir(projectDir))
        
        if noDialog:
            return self.startSynchronizedProcess(proc)
        else:
            dia = CvsDialog(self.trUtf8('Checking project out of cvs repository'))
            res = dia.startProcess(proc)
            if res:
                dia.exec_loop()
            return dia.normalExit()
        
    def vcsExport(self, vcsDataDict, projectDir):
        """
        Public method used to export a directory from the cvs repository.
        
        @param vcsDataDict dictionary of data required for the checkout
        @param project name of the archive (string)
        @param projectDir project directory to create (string)
        @return flag indicating an execution without errors (boolean)
        """
        try:
            tag = vcsDataDict["tag"]
        except KeyError:
            tag = None
        proc = QProcess('cvs')
        self.addArguments(proc, self.options['global'])
        proc.addArgument('-d')
        proc.addArgument(vcsDataDict["repository"])
        proc.addArgument('export')
        if tag is not None and tag != "":
            proc.addArgument('-r')
            proc.addArgument(tag)
        proc.addArgument('-d')
        proc.addArgument(projectDir)
        proc.addArgument(vcsDataDict["archive"])
        proc.setWorkingDirectory(QDir(projectDir))
        
        dia = CvsDialog(self.trUtf8('Exporting project from cvs repository'))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        return dia.normalExit()
        
    def vcsCommit(self, name, message, noDialog=0):
        """
        Public method used to make the change of a file/directory permanent in the cvs repository.
        
        @param name file/directory name to be committed (string)
        @param message message for this operation (string)
        @param noDialog flag indicating quiet operations
        @return flag indicating an execution without errors (boolean)
        """
        msg = QString(message)
        
        if not noDialog and msg.isEmpty():
            # call CommitDialog and get message from there
            dlg = CvsCommitDialog()
            if dlg.exec_loop() == QDialog.Accepted:
                msg = dlg.logMessage()
            else:
                return 0
                
        if msg.isEmpty():
            msg = QString('***')
            
        proc = QProcess('cvs')
        self.addArguments(proc, self.options['global'])
        proc.addArgument('commit')
        self.addArguments(proc, self.options['commit'])
        proc.addArgument('-m')
        proc.addArgument(msg)
        if type(name) is types.ListType:
            dname, fnames = self.splitPathList(name)
            self.addArguments(proc, fnames)
        else:
            dname, fname = self.splitPath(name)
            proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        if noDialog:
            return self.startSynchronizedProcess(proc)
        else:
            dia = CvsDialog(self.trUtf8('Commiting changes to cvs repository'))
            res = dia.startProcess(proc)
            if res:
                dia.exec_loop()
            return dia.normalExit()
        
    def vcsUpdate(self, name):
        """
        Public method used to update a file/directory with the cvs repository.
        
        @param name file/directory name to be updated (string)
        """
        proc = QProcess('cvs')
        self.addArguments(proc, self.options['global'])
        proc.addArgument('update')
        self.addArguments(proc, self.options['update'])
        if type(name) is types.ListType:
            dname, fnames = self.splitPathList(name)
            self.addArguments(proc, fnames)
        else:
            dname, fname = self.splitPath(name)
            proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = CvsDialog(self.trUtf8('Synchronizing with the cvs repository'))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        
    def __stripped(self, item, wdir):
        """
        Private method to give item without the common prefix.
        
        @param item the path to make relative (string)
        @param wdir the working directory (string)
        @return a path relative to wdir (string)
        """
        item = item[len(wdir):]
        while item.startswith( os.path.sep ) or (os.path.altsep and item.startswith(os.path.altsep)):
            item = item[1:]
        return item
        
    def vcsAdd(self, name, isDir=0, binary=0):
        """
        Public method used to add a file/directory to the cvs repository.
        
        @param name file/directory name to be added (string)
        @param isDir flag indicating name is a directory (boolean)
        @param binary flag indicating a binary add (boolean)
        """
        proc = QProcess('cvs')
        self.addArguments(proc, self.options['global'])
        proc.addArgument('add')
        self.addArguments(proc, self.options['add'])
        if binary:
            proc.addArgument('-kb')
        
        if type(name) is types.ListType:
            if isDir:
                dname, fname = os.path.split(unicode(name[0]))
            else:
                dname, fnames = self.splitPathList(name)
        else:
            if isDir:
                dname, fname = os.path.split(unicode(name))
            else:
                dname, fname = self.splitPath(name)
        tree = []
        wdir = dname
        while not os.path.exists(os.path.join(dname, 'CVS')):
            # add directories recursively, if they aren't in the repository already
            tree.insert(-1, dname)
            dname = os.path.split(dname)[0]
            wdir = dname
        # have to split off the working directory so we get relative paths
        tree = [self.__stripped(item, wdir) for item in tree]
        self.addArguments(proc, tree)
        
        if type(name) is types.ListType:
            tree2 = []
            for n in name:
                d = os.path.split(n)[0]
                while not os.path.exists(os.path.join(d, 'CVS')):
                    if d in tree2 + tree:
                        break
                    tree2.append(d)
                    d = os.path.split(d)[0]
            tree2.reverse()
            # have to split off the working directory so we get relative paths
            tree2 = [self.__stripped(item, wdir) for item in tree2]
            name = [self.__stripped(item, wdir) for item in name]
            self.addArguments(proc, tree2)
            self.addArguments(proc, name)
        else:
            proc.addArgument(self.__stripped(name, wdir))
        proc.setWorkingDirectory(QDir(wdir))
        
        if binary:
            msg = self.trUtf8('Adding to the cvs repository in binary mode')
        else:
            msg = self.trUtf8('Adding to the cvs repository')
        dia = CvsDialog(msg)
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        
    def vcsAddBinary(self, name, isDir=0):
        """
        Public method used to add a file/directory in binary mode to the cvs repository.
        
        @param name file/directory name to be added (string)
        @param isDir flag indicating name is a directory (boolean)
        """
        self.vcsAdd(name, isDir=isDir, binary=1)
        
    def vcsAddTree(self, path):
        """
        Public method to add a directory tree rooted at path to the cvs repository.
        
        @param path root directory of the tree to be added (string)
        """
        proc = QProcess('cvs')
        self.addArguments(proc, self.options['global'])
        proc.addArgument('add')
        self.addArguments(proc, self.options['add'])
        
        if type(path) is types.ListType:
            dname, fnames = self.splitPathList(path)
        else:
            dname, fname = self.splitPath(path)
        tree = []
        wdir = dname
        while not os.path.exists(os.path.join(dname, 'CVS')):
            # add directories recursively, if they aren't in the repository already
            tree.insert(-1, dname)
            dname = os.path.split(dname)[0]
            wdir = dname
        # have to split off the working directory so we get relative paths
        tree = [self.__stripped(item, wdir) for item in tree]
        self.addArguments(proc, tree)
        
        if type(path) is types.ListType:
            tree2 = []
            for n in path:
                d = os.path.split(n)[0]
                while not os.path.exists(os.path.join(d, 'CVS')):
                    if d in tree2 + tree:
                        break
                    tree2.append(d)
                    d = os.path.split(d)[0]
            tree2.reverse()
            # have to split off the working directory so we get relative paths
            tree2 = [self.__stripped(item, wdir) for item in tree2]
            self.addArguments(proc, tree2)
            
            for n in path:
                entries = Utilities.direntries(n)
                entries = [self.__stripped(item, wdir) for item in entries]
                self.addArguments(proc, entries)
        else:
            entries = Utilities.direntries(path)
            entries = [self.__stripped(item, wdir) for item in entries]
            self.addArguments(proc, entries)
        
        proc.setWorkingDirectory(QDir(wdir))
        
        dia = CvsDialog(self.trUtf8('Adding to the cvs repository'))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        
    def vcsRemove(self, name, project=0):
        """
        Public method used to remove a file/directory from the cvs repository.
        
        The default operation is to remove the local copy as well.
        
        @param name file/directory name to be removed (string)
        @param project flag indicating deletion of a project tree (boolean)
        @return flag indicating successfull operation (boolean)
        """
        proc = QProcess('cvs')
        self.addArguments(proc, self.options['global'])
        proc.addArgument('remove')
        self.addArguments(proc, self.options['remove'])
        if type(name) is types.ListType:
            dname = self.splitPathList(name)[0]
            for n in name:
                if os.path.isdir(n):
                    entries = Utilities.direntries(n, 1)
                    self.addArguments(proc, entries)
                else:
                    proc.addArgument(n)
        else:
            if (not project) and os.path.isdir(name):
                dname, fname = os.path.split(name)
                entries = Utilities.direntries(name, 1)
                self.addArguments(proc, entries)
            else:
                dname, fname = self.splitPath(name)
                proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = CvsDialog(self.trUtf8('Removing from the cvs repository'))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
            res = dia.normalExit()
            
        return res
        
    def vcsLog(self, name):
        """
        Public method used to view the log of a file/directory from the cvs repository.
        
        @param name file/directory name to show the log for (string)
        """
        self.log = CvsLogDialog(self)
        self.log.show()
        self.log.start(name)
        
    def vcsDiff(self, name):
        """
        Public method used to view the difference of a file/directory to the cvs repository.
        
        If name is a directory and is the project directory, all project files
        are save first. If name is a file, which is being edited and has unsaved
        modification, they can be saved or the operation may be aborted.
        
        @param name file/directory name to be diffed (string)
        """
        if type(name) is types.ListType:
            names = name[:]
        else:
            names = [name]
        for nam in names:
            if os.path.isfile(nam):
                editor = qApp.mainWidget().getViewManager().getOpenEditor(nam)
                if editor and not editor.checkDirty() :
                    return
            else:
                project = qApp.mainWidget().getProject()
                if name == project.ppath and not project.saveAllScripts():
                    return
        self.diff = CvsDiffDialog(self)
        self.diff.show()
        self.diff.start(name)
        
    def vcsHistory(self, name):
        """
        Public method used to view the history of a file/directory in the cvs repository.
        
        @param name file/directory name to show the history for (string)
        """
        self.history = CvsHistoryDialog(self)
        self.history.show()
        self.history.start(name)
        
    def vcsStatus(self, name):
        """
        Public method used to view the status of a file in the cvs repository.
        
        @param name file/directory name to show the status for (string)
        """
        self.status = CvsStatusDialog(self)
        self.status.show()
        self.status.start(name)
        
    def vcsTag(self, name):
        """
        Public method used to set the tag of a file/directory in the cvs repository.
        
        @param name file/directory name to be tagged (string)
        """
        dname, fname = self.splitPath(name)
        
        dlg = CvsTagDialog(self.tagList)
        if dlg.exec_loop() == QDialog.Accepted:
            tag, tagOp = dlg.getParameters()
            self.tagList.remove(tag)
            self.tagList.prepend(tag)
        else:
            return
            
        proc = QProcess('cvs')
        self.addArguments(proc, self.options['global'])
        proc.addArgument('tag')
        self.addArguments(proc, self.options['tag'])
        if tagOp == 2:
            proc.addArgument('-b')
        elif tagOp == 4:
            proc.addArgument('-d')
        proc.addArgument(tag)
        proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = CvsDialog(self.trUtf8('Tagging %1 in the cvs repository')
            .arg(name))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        
    def vcsRevert(self, name):
        """
        Public method used to revert changes made to a file/directory.
        
        @param name file/directory name to be reverted (string)
        """
        proc = QProcess('cvs')
        self.addArguments(proc, self.options['global'])
        proc.addArgument('update')
        self.addArguments(proc, self.options['update'])
        proc.addArgument('-C')
        if type(name) is types.ListType:
            dname = self.splitPathList(name)[0]
            self.addArguments(proc, name)
        else:
            dname = self.splitPath(name)[0]
            proc.addArgument(name)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = CvsDialog(self.trUtf8('Reverting changes'))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        
    def vcsSwitch(self, name):
        """
        Public method used to switch a directory to a different tag/branch.
        
        @param name directory name to be switched (string)
        """
        dname, fname = self.splitPath(name)
        
        dlg = CvsSwitchDialog(self.tagList)
        if dlg.exec_loop() == QDialog.Accepted:
            tag = dlg.getParameters()
            self.tagList.remove(tag)
            self.tagList.prepend(tag)
        else:
            return
            
        proc = QProcess('cvs')
        self.addArguments(proc, self.options['global'])
        proc.addArgument('update')
        self.addArguments(proc, self.options['update'])
        if tag.isEmpty():
            proc.addArgument('-A')
            tn = QString('head revision')
        else:
            proc.addArgument('-r')
            proc.addArgument(tag)
            tn = tag
        proc.setWorkingDirectory(QDir(dname))
        
        dia = CvsDialog(self.trUtf8('Switching to %1')
            .arg(tn))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        
    def vcsMerge(self, name):
        """
        Public method used to merge a tag/branch into the local project.
        
        @param name file/directory name to be merged (string)
        """
        dname, fname = self.splitPath(name)
        
        dlg = CvsMergeDialog(self.tagList)
        if dlg.exec_loop() == QDialog.Accepted:
            tag1, tag2 = dlg.getParameters()
        else:
            return
            
        proc = QProcess('cvs')
        self.addArguments(proc, self.options['global'])
        proc.addArgument('update')
        self.addArguments(proc, self.options['update'])
        proc.addArgument('-j')
        proc.addArgument(tag1)
        if not tag2.isEmpty():
            proc.addArgument('-j')
            proc.addArgument(tag2)
        proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = CvsDialog(self.trUtf8('Merging %1')
            .arg(fname))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        
    def vcsRegisteredState(self, name):
        """
        Public method used to get the registered state of a file in the vcs.
        
        @param name filename to check (string)
        @return a combination of canBeCommited and canBeAdded or
            0 in order to signal an error
        """
        dname, fname = self.splitPath(name)
        dname = unicode(dname)
        fname = unicode(fname)
        if fname == '.':
            dname, fname = os.path.split(dname)
        ename = os.path.join(dname, 'CVS', 'Entries')
        if not os.path.exists(ename):
            ename = os.path.join(dname, fname, 'CVS', 'Entries')
        try:
            f = open(ename, 'rb')
            lines = f.readlines()
            f.close()
        except:
            return 0
            
        state = self.canBeAdded
        for line in lines:
            fields = line.split('/')
            if fields[0] == '' and fields[1] == fname:
                state = self.canBeCommitted
                if fields[2] == '-':
                    state |= self.canBeAdded
                break
            elif fields[0] == 'D' and fields[1] == fname:
                state = self.canBeCommitted
                break
                
        return state
        
    def vcsAllRegisteredStates(self, names, dname):
        """
        Public method used to get the registered states of a number of files in the vcs.
        
        @param names dictionary with all filenames to be checked as keys
        @param dname directory to check in (string)
        @return the received dictionary completed with a combination of 
            canBeCommited and canBeAdded or None in order to signal an error
        """
        dname = unicode(dname)
        ename = os.path.join(dname, 'CVS', 'Entries')
        try:
            f = open(ename, 'rb')
            lines = f.readlines()
            f.close()
        except:
            return names
            
        for line in lines:
            fields = line.split('/')
            if fields[0] == '' or fields[0] == 'D':
                if fields[2] == '-':
                    state = self.canBeCommitted | self.canBeAdded
                else:
                    state = self.canBeCommitted
                name = os.path.normcase(os.path.join(dname, fields[1]))
                if names.has_key(name):
                    names[name] = state
                
        return names
            
    def vcsName(self):
        """
        Public method returning the name of the vcs.
        
        @return always 'CVS' (string)
        """
        return "CVS"
        
    def vcsCleanup(self, name):
        """
        Public method used to cleanup the local copy.
        
        @param name directory name to be cleaned up (string)
        """
        entries = Utilities.direntries(name, 1, '.#*')
        for entry in entries:
            try:
                os.remove(entry)
            except OSError:
                pass
        
    def vcsCommandLine(self, name):
        """
        Public method used to execute arbitrary cvs commands.
        
        @param name directory name of the working directory (string)
        """
        dlg = CvsCommandDialog(self.commandHistory, self.wdHistory, name)
        if dlg.exec_loop() == QDialog.Accepted:
            command, wd = dlg.getData()
            commandList = Utilities.parseOptionString(command)
            
            # This moves any previous occurence of these arguments to the head
            # of the list.
            self.commandHistory.remove(command)
            self.commandHistory.prepend(command)
            self.wdHistory.remove(wd)
            self.wdHistory.prepend(wd)
            
            proc = QProcess('cvs')
            self.addArguments(proc, commandList)
            proc.setWorkingDirectory(QDir(wd))
            
            dia = CvsDialog(self.trUtf8('CVS command'))
            res = dia.startProcess(proc)
            if res:
                dia.exec_loop()
        
    def vcsOptionsDialog(self, project, archive, editable=0, parent=None):
        """
        Public method to get a dialog to enter repository info.
        
        @param project reference to the project object
        @param archive name of the project in the repository (string)
        @param editable flag indicating that the project name is editable (boolean)
        @param parent parent widget (QWidget)
        """
        return CvsOptionsDialog(project, archive, editable, parent)
        
    def vcsNewProjectOptionsDialog(self, parent = None):
        """
        Public method to get a dialog to enter repository info for getting a new project.
        
        @param parent parent widget (QWidget)
        """
        return CvsNewProjectOptionsDialog(parent)
        
    def vcsRepositoryInfos(self, ppath):
        """
        Public method to retrieve information about the repository.
        
        @param ppath local path to get the repository infos (string)
        @return string with ready formated info for display (QString)
        """
        info = {\
            'cvsroot' : ''
        }
        
        rname = os.path.join(ppath, 'CVS', 'Root')
        try:
            f = open(rname, 'rb')
            lines = f.readlines()
            f.close()
        except:
            return QString(qApp.translate('cvs',
                'Could not read the Root file.'))
            
        if len(lines) != 1:
            return QString(qApp.translate('cvs',
                'The Root file has an unknown format.'))
        else:
            info['cvsroot'] = lines[0]
            
        return QString(qApp.translate('cvs',
            """<h3>Repository information</h3>"""
            """<table>"""
            """<tr><td><b>CVS V.</b></td><td>%1</td></tr>"""
            """<tr><td><b>CVS Root</b></td><td>%2</td></tr>"""
            """</table>"""
            ))\
            .arg(self.versionStr)\
            .arg(info['cvsroot'])\
    
    #################################################################
    ## Below are CVS specific methods
    #################################################################
    
    def cvsEdit(self, name):
        """
        Public method used to edit a file under cvs control.
        
        A list of filenames can be passed in as well. In this case
        it is assumed, that all files are located in the same directory.
        
        @param name file name(s) to be edited (string or list of strings)
        """
        proc = QProcess('cvs')
        self.addArguments(proc, self.options['global'])
        proc.addArgument('edit')
        proc.addArgument('-a')
        proc.addArgument('none')
        if type(name) is types.ListType:
            dname, fnames = self.splitPathList(name)
            self.addArguments(proc, fnames)
        else:
            dname, fname = self.splitPath(name)
            proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = CvsDialog(self.trUtf8('CVS Edit'))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
            
    def cvsUnedit(self, name):
        """
        Public method used to unedit a file under cvs control.
        
        A list of filenames can be passed in as well. In this case
        it is assumed, that all files are located in the same directory.
        
        @param name file name(s) to be unedited (string or list of strings)
        """
        proc = QProcess('cvs')
        self.addArguments(proc, self.options['global'])
        proc.addArgument('unedit')
        if type(name) is types.ListType:
            dname, fnames = self.splitPathList(name)
            self.addArguments(proc, fnames)
        else:
            dname, fname = self.splitPath(name)
            proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = CvsDialog(self.trUtf8('CVS Unedit'))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()

    def cvsLogin(self):
        """
        Public method used to login to the CVS repository.
        """
        dlg = CvsLoginDialog()
        if dlg.exec_loop() == QDialog.Accepted:
            protocol, user, passwd, host, port, repository = dlg.getData()
            
            if passwd:
                passwd = ":%s" % passwd
            if protocol in ['ext', 'server']:
                repdir = ':%s:%s@%s:%s' % (protocol, user, host, repository)
            else:
                repdir = ':%s:%s%s@%s:%s%s' % (protocol, user, passwd, host, port, repository)
            proc = QProcess('cvs')
            self.addArguments(proc, self.options['global'])
            proc.addArgument('-d')
            proc.addArgument(repdir)
            proc.addArgument('login')
            
            dia = CvsDialog(self.trUtf8('Logging in to the CVS repository'))
            res = dia.startProcess(proc)
            if res:
                dia.exec_loop()
        
    def cvsLogout(self):
        """
        Public method used to logout from the remote repository.
        """
        dlg = CvsLogoutDialog()
        if dlg.exec_loop() == QDialog.Accepted:
            protocol, user, host, port, repository = dlg.getData()
            
            repdir = ':%s:%s@%s:%s%s' % (protocol, user, host, port, repository)
            proc = QProcess('cvs')
            self.addArguments(proc, self.options['global'])
            proc.addArgument('-d')
            proc.addArgument(repdir)
            proc.addArgument('logout')
            
            dia = CvsDialog(self.trUtf8('Logging out from the CVS repository'))
            res = dia.startProcess(proc)
            if res:
                dia.exec_loop()
        
    ############################################################################
    ## Private CVS specific methods are below.
    ############################################################################
    
    def vcsExistsReadyReadStdout(self):
        """
        Private slot to handle the readyReadStdout signal. 
        
        It reads the output of the process and buffers it..
        """
        if self.versionStr is QString.null:
            s = unicode(self.proc.readStdout())
            self.versionStr = s

    def vcsExistsProcExited(self):
        """
        Private slot connected to the processExited signal.
        """
        self.proc = None
        self.versionStr = self.versionStr.strip().split()[4]

    ############################################################################
    ## Methods to get the helper objects are below.
    ############################################################################
    
    def vcsGetProjectBrowserHelper(self, browser, project, isTranslationsBrowser=0):
        """
        Public method to instanciate a helper object for the different project browsers.
        
        @param browser reference to the project browser object
        @param project reference to the project object
        @param isTranslationsBrowser flag indicating, the helper is requested for the
            translations browser (this needs some special treatment)
        @return the project browser helper object
        """
        return CvsProjectBrowserHelper(self, browser, project, isTranslationsBrowser)
        
    def vcsGetProjectHelper(self, project):
        """
        Public method to instanciate a helper object for the project.
        
        @param project reference to the project object
        @return the project helper object
        """
        return CvsProjectHelper(self, project)
