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

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

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

import os
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
import Utilities

class Cvs(VersionControl):
    """
    Class implementing the version control systems interface to CVS.
    """
    def __init__(self, parent=None, name=None):
        """
        Constructor
        
        Arguments
        
            parent -- parent widget (QWidget)
            
            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.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.
        
        Returns
        
            flag indicating the existance (boolean)
        """
        proc = QProcess('cvs', self)
        proc.addArgument('-v')
        return proc.start()
        
    def vcsInit(self, vcsDir, noDialog=0):
        """
        Public method used to initialize the cvs repository.
        
        Arguments
        
            vcsDir -- name of the VCS directory (string)
            
            noDialog -- flag indicating quiet operations (boolean)
        
        Returns
        
            flag indicating an execution without errors (boolean)
        """
        proc = QProcess('cvs', self)
        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 vcsImport(self, vcsDir, message, project, vendor, release, projectDir, noDialog=0):
        """
        Public method used to import the project into the cvs repository.
        
        Arguments
        
            vcsDir -- name of the VCS directory (string)
            
            message -- message for this operation (string)
            
            project -- name of the archive (string)
            
            vendor -- vendor string (string)
            
            release -- release version string (string)
            
            projectDir -- project directory to create (string)
            
            noDialog -- flag indicating quiet operations
        
        Returns
        
            flag indicating an execution without errors (boolean)
        """
        msg = QString(message)
        if msg.isEmpty():
            msg = QString('***')
            
        proc = QProcess('cvs', self)
        self.addArguments(proc, self.options['global'])
        proc.addArgument('-d')
        proc.addArgument(vcsDir)
        proc.addArgument('import')
        proc.addArgument('-m')
        proc.addArgument(msg)
        proc.addArgument(project)
        proc.addArgument(vendor)
        proc.addArgument(release)
        proc.setWorkingDirectory(QDir(projectDir))
        
        if noDialog:
            return self.startSynchronizedProcess(proc)
        else:
            dia = CvsDialog(self.trUtf8('Importing project into cvs repository'))
            res = dia.startProcess(proc)
            if res:
                dia.exec_loop()
            return dia.normalExit()
        
    def vcsCheckout(self, vcsDir, project, projectDir, noDialog=0, tag=None):
        """
        Public method used to check the project out of the cvs repository.
        
        Arguments
        
            vcsDir -- name of the VCS directory (string)
            
            project -- name of the archive (string)
            
            projectDir -- project directory to create (string)
            
            noDialog -- flag indicating quiet operations
            
            tag -- tag of version to check out (string)
        
        Returns
        
            flag indicating an execution without errors (boolean)
        """
        proc = QProcess('cvs', self)
        self.addArguments(proc, self.options['global'])
        proc.addArgument('-d')
        proc.addArgument(vcsDir)
        proc.addArgument('checkout')
        self.addArguments(proc, self.options['checkout'])
        if tag is not None and tag != "":
            proc.addArgument('-r')
            proc.addArgument(tag)
        proc.addArgument(project)
        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, vcsDir, project, projectDir, tag=None):
        """
        Public method used to export a directory from the cvs repository.
        
        Arguments
        
            vcsDir -- name of the VCS directory (string)
            
            project -- name of the archive (string)
            
            projectDir -- project directory to create (string)
            
            tag -- tag of version to check out (string)
        
        Returns
        
            flag indicating an execution without errors (boolean)
        """
        proc = QProcess('cvs', self)
        self.addArguments(proc, self.options['global'])
        proc.addArgument('-d')
        proc.addArgument(vcsDir)
        proc.addArgument('export')
        if tag is not None and tag != "":
            proc.addArgument('-r')
            proc.addArgument(tag)
        proc.addArgument(project)
        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.
        
        Arguments
        
            name -- file/directory name to be committed (string)
            
            message -- message for this operation (string)
            
            noDialog -- flag indicating quiet operations
        
        Returns
        
            flag indicating an execution without errors (boolean)
        """
        dname, fname = self.splitPath(name)
        
        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)
        self.addArguments(proc, self.options['global'])
        proc.addArgument('commit')
        self.addArguments(proc, self.options['commit'])
        proc.addArgument('-m')
        proc.addArgument(msg)
        proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        if noDialog:
            return self.startSynchronizedProcess(proc)
        else:
            dia = CvsDialog(self.trUtf8('Commiting changes of %1 to cvs repository')
                .arg(name))
            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.
        
        Arguments
        
            name -- file/directory name to be updated (string)
        """
        dname, fname = self.splitPath(name)
        
        proc = QProcess('cvs', self)
        self.addArguments(proc, self.options['global'])
        proc.addArgument('update')
        self.addArguments(proc, self.options['update'])
        proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = CvsDialog(self.trUtf8('Synchronizing %1 with the cvs repository')
            .arg(name))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        
    def vcsAdd(self, name, isDir=0):
        """
        Public method used to add a file/directory to the cvs repository.
        
        Arguments
        
            name -- file/directory name to be added (string)
            
            isDir -- flag indicating name is a directory (boolean)
        """
        if isDir:
            dname, fname = os.path.split(str(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, dummy = os.path.split(dname)
            wdir = dname
            
        proc = QProcess('cvs', self)
        self.addArguments(proc, self.options['global'])
        proc.addArgument('add')
        self.addArguments(proc, self.options['add'])
        self.addArguments(proc, tree)
        proc.addArgument(name)
        proc.setWorkingDirectory(QDir(wdir))
        
        dia = CvsDialog(self.trUtf8('Adding %1 to the cvs repository')
            .arg(name))
        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.
        
        Arguments
        
            name -- file/directory name to be added (string)
            
            isDir -- flag indicating name is a directory (boolean)
        """
        if isDir:
            dname, fname = os.path.split(str(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, dummy = os.path.split(dname)
            wdir = dname
            
        proc = QProcess('cvs', self)
        self.addArguments(proc, self.options['global'])
        proc.addArgument('add')
        self.addArguments(proc, self.options['add'])
        proc.addArgument('-kb')
        self.addArguments(proc, tree)
        proc.addArgument(name)
        proc.setWorkingDirectory(QDir(wdir))
        
        dia = CvsDialog(self.trUtf8('Adding %1 to the cvs repository in binary mode')
            .arg(name))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
        
    def vcsAddTree(self, path):
        """
        Public method to add a directory tree rooted at path to the cvs repository.
        
        Arguments
        
            path -- root directory of the tree to be added (string)
        """
        entries = Utilities.direntries(path)
        dname, fname = os.path.split(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, dummy = os.path.split(dname)
            wdir = dname
            
        proc = QProcess('cvs', self)
        self.addArguments(proc, self.options['global'])
        proc.addArgument('add')
        self.addArguments(proc, self.options['add'])
        self.addArguments(proc, tree)
        self.addArguments(proc, entries)
        proc.setWorkingDirectory(QDir(wdir))
        
        dia = CvsDialog(self.trUtf8('Adding %1 to the cvs repository')
            .arg(path))
        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.
        
        Arguments
        
            name -- file/directory name to be removed (string)
            
            project -- flag indicating deletion of a project tree (boolean)
            
        Returns
        
            flag indicating successfull operation (boolean)
        """
        if (not project) and os.path.isdir(name):
            dname, fname = os.path.split(name)
            entries = Utilities.direntries(name, 1)
            remtree = 1
        else:
            dname, fname = self.splitPath(name)
            remtree = 0
        
        proc = QProcess('cvs', self)
        self.addArguments(proc, self.options['global'])
        proc.addArgument('remove')
        self.addArguments(proc, self.options['remove'])
        if remtree:
            self.addArguments(proc, entries)
        else:
            proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = CvsDialog(self.trUtf8('Removing %1 from the cvs repository')
            .arg(name))
        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.
        
        Arguments
        
            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.
        
        Arguments
        
            name -- file/directory name to be diffed (string)
        """
        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.
        
        Arguments
        
            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.
        
        Arguments
        
            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.
        
        Arguments
        
            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)
        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.
        
        Arguments
        
            name -- file/directory name to be reverted (string)
        """
        dname, fname = self.splitPath(name)
        
        proc = QProcess('cvs', self)
        self.addArguments(proc, self.options['global'])
        proc.addArgument('update')
        self.addArguments(proc, self.options['update'])
        proc.addArgument('-C')
        proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = CvsDialog(self.trUtf8('Reverting changes to %1')
            .arg(name))
        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.
        
        Arguments
        
            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)
        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.
        
        Arguments
        
            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)
        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 vcsLogin(self, user, passwd, host, repository):
        """
        Public method used to login to the remote repository.
        
        Arguments
        
            user -- user name (string)
            
            passwd -- password (string)
            
            host -- hostname (string)
            
            repository -- repository name (string)
        """
        repdir = ':pserver:%s:%s@%s:%s' % (user, passwd, host, repository)
        proc = QProcess('cvs', self)
        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 vcsLogout(self, user, host, repository):
        """
        Public method used to logout from the remote repository.
        
        Arguments
        
            user -- user name (string)
            
            host -- hostname (string)
            
            repository -- repository name (string)
        """
        repdir = ':pserver:%s@%s:%s' % (user, host, repository)
        proc = QProcess('cvs', self)
        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()
        
    def vcsRegisteredState(self, name):
        """
        Public method used to get the registered state of a file in the vcs.
        
        Arguments
        
            name -- filename to check (string)
            
        Returns
        
            a combination of canBeCommited and canBeAdded or
            0 in order to signal an error
        """
        dname, fname = self.splitPath(name)
        dname = str(dname)
        fname = str(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.
        
        Arguments
        
            names -- dictionary with all filenames to be checked as keys
            
            dname -- directory to check in (string)
            
        Returns
        
            the received dictionary completed with a combination of 
            canBeCommited and canBeAdded or None in order to signal an error
        """
        dname = str(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.
        
        Returns
        
            always 'CVS' (string)
        """
        return "CVS"
        
    def vcsCleanup(self, name):
        """
        Public method used to cleanup the local copy.
        
        Arguments
        
            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.
        
        Arguments
        
            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)
            self.addArguments(proc, commandList)
            proc.setWorkingDirectory(QDir(wd))
            
            dia = CvsDialog(self.trUtf8('CVS command'))
            res = dia.startProcess(proc)
            if res:
                dia.exec_loop()
        
    #################################################################
    ## 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.
        
        Arguments
        
            name -- file name(s) to be edited (string or list of strings)
        """
        proc = QProcess('cvs', self)
        self.addArguments(proc, self.options['global'])
        proc.addArgument('edit')
        proc.addArgument('-a')
        proc.addArgument('none')
        if type(name) is types.ListType:
            for n in name:
                dname, fname = self.splitPath(n)
                proc.addArgument(fname)
        else:
            dname, fname = self.splitPath(name)
            proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = CvsDialog(self.trUtf8('CVS Edit %1')
            .arg(name))
        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.
        
        Arguments
        
            name -- file name(s) to be unedited (string or list of strings)
        """
        proc = QProcess('cvs', self)
        self.addArguments(proc, self.options['global'])
        proc.addArgument('unedit')
        if type(name) is types.ListType:
            for n in name:
                dname, fname = self.splitPath(n)
                proc.addArgument(fname)
        else:
            dname, fname = self.splitPath(name)
            proc.addArgument(fname)
        proc.setWorkingDirectory(QDir(dname))
        
        dia = CvsDialog(self.trUtf8('CVS Unedit %1')
            .arg(name))
        res = dia.startProcess(proc)
        if res:
            dia.exec_loop()
